Delta parceling, valid types bugfix, INSERT parsing.
Made EntityDelta and ValuesDelta directly Parcelable to
pass across configuration changes. Moved the re-parenting
code to separate EntityDelta.mergeAfter() method. Fixed
getValidTypes() bug that didn't handle typeOverallMax
in "unlimited" cases. Wrote INSERT parsing code to merge
incoming extras bundle into a new or existing EntityDelta,
also handles any source constraints.
Initial hook-up of edit UI to persist changed data, only
shows first Entity because not connected to tabs yet.
diff --git a/res/layout/act_edit.xml b/res/layout/act_edit.xml
index fe79160..ab04e25 100644
--- a/res/layout/act_edit.xml
+++ b/res/layout/act_edit.xml
@@ -14,40 +14,50 @@
limitations under the License.
-->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<ScrollView
+ xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
- android:orientation="vertical"
android:fillViewport="true">
- <!-- TODO: insert aggregate summary widget -->
- <!-- TODO: insert contact tab widget -->
-
- <ScrollView
- android:id="@android:id/tabcontent"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:layout_weight="1"
- android:fillViewport="true" />
-
<LinearLayout
android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:orientation="horizontal"
- style="@android:style/ButtonBar">
+ android:layout_height="fill_parent"
+ android:orientation="vertical"
+ android:fillViewport="true">
- <Button android:id="@+id/btn_done"
- android:layout_width="0dip"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:text="@string/menu_done" />
+ <!-- TODO: insert aggregate summary widget -->
+ <!-- TODO: insert contact tab widget -->
- <Button android:id="@+id/btn_discard"
- android:layout_width="0dip"
- android:layout_height="wrap_content"
+ <FrameLayout
+ android:id="@android:id/tabcontent"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
android:layout_weight="1"
- android:text="@string/menu_doNotSave" />
+ android:fillViewport="true" />
+
+ <LinearLayout
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ style="@android:style/ButtonBar">
+
+ <Button
+ android:id="@+id/btn_done"
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:text="@string/menu_done" />
+
+ <Button
+ android:id="@+id/btn_discard"
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:text="@string/menu_doNotSave" />
+
+ </LinearLayout>
</LinearLayout>
-</LinearLayout>
+</ScrollView>
diff --git a/src/com/android/contacts/model/EntityDelta.java b/src/com/android/contacts/model/EntityDelta.java
index 09e2e98..540cd0b 100644
--- a/src/com/android/contacts/model/EntityDelta.java
+++ b/src/com/android/contacts/model/EntityDelta.java
@@ -23,6 +23,7 @@
import android.content.Entity.NamedContentValues;
import android.net.Uri;
import android.os.Parcel;
+import android.os.Parcelable;
import android.provider.BaseColumns;
import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.RawContacts;
@@ -46,7 +47,7 @@
* rows are missing from the new {@link Entity}, we know the original data must
* be deleted, but to preserve the user modifications we treat as an insert.
*/
-public class EntityDelta {
+public class EntityDelta implements Parcelable {
// TODO: optimize by using contentvalues pool, since we allocate so many of them
/**
@@ -83,6 +84,34 @@
return entity;
}
+ /**
+ * Merge the "after" values from the given {@link EntityDelta} onto the
+ * "before" state represented by this {@link EntityDelta}, discarding any
+ * existing "after" states. This is typically used when re-parenting changes
+ * onto an updated {@link Entity}.
+ */
+ public void mergeAfter(EntityDelta remote) {
+ // Always take after values from new state
+ this.mValues.mAfter = remote.mValues.mAfter;
+
+ // Find matching local entry for each remote values, or create
+ for (ArrayList<ValuesDelta> mimeEntries : remote.mEntries.values()) {
+ for (ValuesDelta remoteEntry : mimeEntries) {
+ final Long childId = remoteEntry.getId();
+
+ ValuesDelta localEntry = this.getEntry(childId);
+ if (localEntry == null) {
+ // Is "insert", or "before" record is missing, so now "insert"
+ localEntry = ValuesDelta.fromAfter(remoteEntry.mAfter);
+ this.addEntry(localEntry);
+ } else {
+ // Existing entry "update"
+ localEntry.mAfter = remoteEntry.mAfter;
+ }
+ }
+ }
+ }
+
public ValuesDelta getValues() {
return mValues;
}
@@ -91,9 +120,12 @@
* Get the {@link ValuesDelta} child marked as {@link Data#IS_PRIMARY}.
*/
public ValuesDelta getPrimaryEntry(String mimeType) {
+ final ArrayList<ValuesDelta> mimeEntries = getMimeEntries(mimeType, false);
+
// TODO: handle the case where the caller must have a non-null value,
// for example inserting a displayname automatically
- final ArrayList<ValuesDelta> mimeEntries = getMimeEntries(mimeType, false);
+ if (mimeEntries == null) return null;
+
for (ValuesDelta entry : mimeEntries) {
if (entry.isPrimary()) {
return entry;
@@ -129,9 +161,7 @@
}
/**
- * Find the {@link ValuesDelta} that has a specific
- * {@link BaseColumns#_ID} value, used when {@link #augmentFrom(Parcel)} is
- * inflating a modified state.
+ * Find entry with the given {@link BaseColumns#_ID} value.
*/
public ValuesDelta getEntry(Long childId) {
if (childId == null) {
@@ -165,71 +195,6 @@
return count;
}
- private static final int MODE_CONTINUE = 1;
- private static final int MODE_DONE = 2;
-
- /**
- * Read a set of modifying actions from the given {@link Parcel}, which
- * expects the format written by {@link #augmentTo(Parcel)}. This expects
- * that we already have a base {@link Entity} that we are applying over.
- */
- public void augmentFrom(Parcel parcel) {
- {
- final ContentValues after = (ContentValues)parcel.readValue(null);
- if (mValues == null) {
- // Entity didn't exist before, so "insert"
- mValues = ValuesDelta.fromAfter(after);
- } else {
- // Existing entity "update"
- mValues.mAfter = after;
- }
- }
-
- // Read in packaged children until finished
- int mode = parcel.readInt();
- while (mode == MODE_CONTINUE) {
- final Long childId = readLong(parcel);
- final ContentValues after = (ContentValues)parcel.readValue(null);
-
- ValuesDelta entry = getEntry(childId);
- if (entry == null) {
- // Is "insert", or "before" record is missing, so now "insert"
- entry = ValuesDelta.fromAfter(after);
- addEntry(entry);
- } else {
- // Existing entry "update"
- entry.mAfter = after;
- }
-
- mode = parcel.readInt();
- }
- }
-
- /**
- * Store all modifying actions into the given {@link Parcel}.
- */
- public void augmentTo(Parcel parcel) {
- parcel.writeValue(mValues.mAfter);
-
- for (ArrayList<ValuesDelta> mimeEntries : mEntries.values()) {
- for (ValuesDelta child : mimeEntries) {
- parcel.writeInt(MODE_CONTINUE);
- writeLong(parcel, child.getId());
- parcel.writeValue(child.mAfter);
- }
- }
- parcel.writeInt(MODE_DONE);
- }
-
- private void writeLong(Parcel parcel, Long value) {
- parcel.writeLong(value == null ? -1 : value);
- }
-
- private Long readLong(Parcel parcel) {
- final long value = parcel.readLong();
- return value == -1 ? null : value;
- }
-
@Override
public boolean equals(Object object) {
if (object instanceof EntityDelta) {
@@ -326,23 +291,63 @@
// If any operations, assert that version is identical so we bail if changed
if (diff.size() > 0 && beforeVersion != null && beforeId != null) {
- builder = ContentProviderOperation.newCountQuery(RawContacts.CONTENT_URI);
- builder.withSelection(RawContacts._ID + "=" + beforeId + " AND " + RawContacts.VERSION
- + "=" + beforeVersion, null);
- builder.withExpectedCount(1);
- // Sneak version check at beginning of list
- diff.add(0, builder.build());
+ // TODO: re-enable version enforcement once we have COUNT(*) or ASSERT
+// builder = ContentProviderOperation.newCountQuery(RawContacts.CONTENT_URI);
+// builder.withSelection(RawContacts._ID + "=" + beforeId + " AND " + RawContacts.VERSION
+// + "=" + beforeVersion, null);
+// builder.withExpectedCount(1);
+// // Sneak version check at beginning of list
+// diff.add(0, builder.build());
}
return diff;
}
+ /** {@inheritDoc} */
+ public int describeContents() {
+ // Nothing special about this parcel
+ return 0;
+ }
+
+ /** {@inheritDoc} */
+ public void writeToParcel(Parcel dest, int flags) {
+ final int size = this.getEntryCount(false);
+ dest.writeInt(size);
+ dest.writeParcelable(mValues, flags);
+ for (ArrayList<ValuesDelta> mimeEntries : mEntries.values()) {
+ for (ValuesDelta child : mimeEntries) {
+ dest.writeParcelable(child, flags);
+ }
+ }
+ }
+
+ public void readFromParcel(Parcel source) {
+ final int size = source.readInt();
+ mValues = source.<ValuesDelta> readParcelable(null);
+ for (int i = 0; i < size; i++) {
+ final ValuesDelta child = source.<ValuesDelta> readParcelable(null);
+ this.addEntry(child);
+ }
+ }
+
+ public static final Parcelable.Creator<EntityDelta> CREATOR = new Parcelable.Creator<EntityDelta>() {
+ public EntityDelta createFromParcel(Parcel in) {
+ final EntityDelta state = new EntityDelta();
+ state.readFromParcel(in);
+ return state;
+ }
+
+ public EntityDelta[] newArray(int size) {
+ return new EntityDelta[size];
+ }
+ };
+
/**
* Type of {@link ContentValues} that maintains both an original state and a
* modified version of that state. This allows us to build insert, update,
* or delete operations based on a "before" {@link Entity} snapshot.
*/
- public static class ValuesDelta {
+ public static class ValuesDelta implements Parcelable {
private ContentValues mBefore;
private ContentValues mAfter;
private String mIdTable = "data";
@@ -402,7 +407,7 @@
}
public void setIdColumn(String idTable, String idColumn) {
- // TODO: remove idTable when we've fixed contentprovider
+ // TODO: remove idTable when contentprovider _id collisions fixed
mIdTable = idTable;
mIdColumn = idColumn;
}
@@ -554,5 +559,38 @@
}
return builder;
}
+
+ /** {@inheritDoc} */
+ public int describeContents() {
+ // Nothing special about this parcel
+ return 0;
+ }
+
+ /** {@inheritDoc} */
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeParcelable(mBefore, flags);
+ dest.writeParcelable(mAfter, flags);
+ dest.writeString(mIdTable);
+ dest.writeString(mIdColumn);
+ }
+
+ public void readFromParcel(Parcel source) {
+ mBefore = source.<ContentValues> readParcelable(null);
+ mAfter = source.<ContentValues> readParcelable(null);
+ mIdTable = source.readString();
+ mIdColumn = source.readString();
+ }
+
+ public static final Parcelable.Creator<ValuesDelta> CREATOR = new Parcelable.Creator<ValuesDelta>() {
+ public ValuesDelta createFromParcel(Parcel in) {
+ final ValuesDelta values = new ValuesDelta();
+ values.readFromParcel(in);
+ return values;
+ }
+
+ public ValuesDelta[] newArray(int size) {
+ return new ValuesDelta[size];
+ }
+ };
}
}
diff --git a/src/com/android/contacts/model/EntityModifier.java b/src/com/android/contacts/model/EntityModifier.java
index 5dd2880..f0d0017 100644
--- a/src/com/android/contacts/model/EntityModifier.java
+++ b/src/com/android/contacts/model/EntityModifier.java
@@ -16,12 +16,23 @@
package com.android.contacts.model;
-import com.android.contacts.model.EntityDelta.ValuesDelta;
import com.android.contacts.model.ContactsSource.DataKind;
import com.android.contacts.model.ContactsSource.EditType;
+import com.android.contacts.model.EntityDelta.ValuesDelta;
import android.content.ContentValues;
+import android.os.Bundle;
import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.Intents;
+import android.provider.ContactsContract.RawContacts;
+import android.provider.ContactsContract.CommonDataKinds.Email;
+import android.provider.ContactsContract.CommonDataKinds.Im;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
+import android.provider.ContactsContract.Intents.Insert;
+import android.text.TextUtils;
+import android.util.Log;
import android.util.SparseIntArray;
import java.util.ArrayList;
@@ -47,7 +58,11 @@
}
public static boolean hasValidTypes(EntityDelta state, DataKind kind) {
- return (getValidTypes(state, kind).size() > 0);
+ if (EntityModifier.hasEditTypes(kind)) {
+ return (getValidTypes(state, kind).size() > 0);
+ } else {
+ return true;
+ }
}
/**
@@ -102,11 +117,13 @@
// Build list of valid types
final int overallCount = typeCount.get(FREQUENCY_TOTAL);
for (EditType type : kind.typeList) {
- final boolean validUnlimited = (type.specificMax == -1 && overallCount < kind.typeOverallMax);
- final boolean validSpecific = (typeCount.get(type.rawValue) < type.specificMax);
+ final boolean validOverall = (kind.typeOverallMax == -1 ? true
+ : overallCount < kind.typeOverallMax);
+ final boolean validSpecific = (type.specificMax == -1 ? true : typeCount
+ .get(type.rawValue) < type.specificMax);
final boolean validSecondary = (includeSecondary ? true : !type.secondary);
final boolean forcedInclude = type.equals(forceInclude);
- if (forcedInclude || (validSecondary && (validUnlimited || validSpecific))) {
+ if (forcedInclude || (validOverall && validSpecific && validSecondary)) {
// Type is valid when no limit, under limit, or forced include
validTypes.add(type);
}
@@ -183,7 +200,7 @@
* exist, we pick the last valid option.
*/
public static EditType getBestValidType(EntityDelta state, DataKind kind,
- boolean includeSecondary) {
+ boolean includeSecondary, int exactValue) {
// Shortcut when no types
if (kind.typeColumn == null) return null;
@@ -202,6 +219,11 @@
final EditType type = iterator.next();
final int count = typeCount.get(type.rawValue);
+ if (count == exactValue) {
+ // Found exact value match
+ return type;
+ }
+
if (count > 0) {
// Type already appears, so don't consider
iterator.remove();
@@ -218,24 +240,27 @@
/**
* Insert a new child of kind {@link DataKind} into the given
- * {@link EntityDelta}. Tries using the best {@link EditType} found
- * using {@link #getBestValidType(EntityDelta, DataKind, boolean)}.
+ * {@link EntityDelta}. Tries using the best {@link EditType} found using
+ * {@link #getBestValidType(EntityDelta, DataKind, boolean, int)}.
*/
- public static void insertChild(EntityDelta state, DataKind kind) {
+ public static ValuesDelta insertChild(EntityDelta state, DataKind kind) {
// First try finding a valid primary
- EditType bestType = getBestValidType(state, kind, false);
+ EditType bestType = getBestValidType(state, kind, false, Integer.MIN_VALUE);
if (bestType == null) {
// No valid primary found, so expand search to secondary
- bestType = getBestValidType(state, kind, true);
+ bestType = getBestValidType(state, kind, true, Integer.MIN_VALUE);
}
- insertChild(state, kind, bestType);
+ return insertChild(state, kind, bestType);
}
/**
* Insert a new child of kind {@link DataKind} into the given
* {@link EntityDelta}, marked with the given {@link EditType}.
*/
- public static void insertChild(EntityDelta state, DataKind kind, EditType type) {
+ public static ValuesDelta insertChild(EntityDelta state, DataKind kind, EditType type) {
+ // Bail early if invalid kind
+ if (kind == null) return null;
+
final ContentValues after = new ContentValues();
// Our parent CONTACT_ID is provided later
@@ -251,7 +276,98 @@
after.put(kind.typeColumn, type.rawValue);
}
- state.addEntry(ValuesDelta.fromAfter(after));
+ final ValuesDelta child = ValuesDelta.fromAfter(after);
+ state.addEntry(child);
+ return child;
}
+ /**
+ * Parse the given {@link Bundle} into the given {@link EntityDelta} state,
+ * assuming the extras defined through {@link Intents}.
+ */
+ public static void parseExtras(EntityDelta state, Bundle extras) {
+ final String accountType = state.getValues().getAsString(RawContacts.ACCOUNT_TYPE);
+ final ContactsSource source = Sources.getInstance().getSourceForType(accountType);
+
+ {
+ // StructuredName
+ final DataKind kind = source.getKindForMimetype(StructuredName.CONTENT_ITEM_TYPE);
+ final String name = extras.getString(Insert.NAME);
+ final String phoneticName = extras.getString(Insert.PHONETIC_NAME);
+ if (kind != null && (TextUtils.isGraphic(name) || TextUtils.isGraphic(phoneticName))) {
+ // TODO: handle the case where name already exists and limited to one
+ // TODO: represent phonetic name as structured fields
+ final ValuesDelta child = EntityModifier.insertChild(state, kind, null);
+ child.put(StructuredName.DISPLAY_NAME, name);
+ child.put(StructuredName.PHONETIC_GIVEN_NAME, phoneticName);
+ }
+ }
+
+ {
+ // StructuredPostal
+ final DataKind kind = source.getKindForMimetype(StructuredPostal.CONTENT_ITEM_TYPE);
+ parseExtras(state, kind, extras, Insert.POSTAL_TYPE, Insert.POSTAL,
+ StructuredPostal.FORMATTED_ADDRESS);
+ }
+
+ {
+ // Phone
+ final DataKind kind = source.getKindForMimetype(Phone.CONTENT_ITEM_TYPE);
+ parseExtras(state, kind, extras, Insert.PHONE_TYPE, Insert.PHONE, Phone.NUMBER);
+ parseExtras(state, kind, extras, Insert.SECONDARY_PHONE_TYPE, Insert.SECONDARY_PHONE,
+ Phone.NUMBER);
+ parseExtras(state, kind, extras, Insert.TERTIARY_PHONE_TYPE, Insert.TERTIARY_PHONE,
+ Phone.NUMBER);
+ }
+
+ {
+ // Email
+ final DataKind kind = source.getKindForMimetype(Email.CONTENT_ITEM_TYPE);
+ parseExtras(state, kind, extras, Insert.EMAIL_TYPE, Insert.PHONE, Email.DATA);
+ parseExtras(state, kind, extras, Insert.SECONDARY_EMAIL_TYPE, Insert.SECONDARY_EMAIL,
+ Email.DATA);
+ parseExtras(state, kind, extras, Insert.TERTIARY_EMAIL_TYPE, Insert.TERTIARY_EMAIL,
+ Email.DATA);
+ }
+
+ {
+ // Im
+ // TODO: handle decodeImProtocol for legacy reasons
+ final DataKind kind = source.getKindForMimetype(Im.CONTENT_ITEM_TYPE);
+ parseExtras(state, kind, extras, Insert.IM_PROTOCOL, Insert.IM_HANDLE, Im.DATA);
+ }
+ }
+
+ /**
+ * Parse a specific entry from the given {@link Bundle} and insert into the
+ * given {@link EntityDelta}. Silently skips the insert when missing value
+ * or no valid {@link EditType} found.
+ *
+ * @param typeExtra {@link Bundle} key that holds the incoming
+ * {@link EditType#rawValue} value.
+ * @param valueExtra {@link Bundle} key that holds the incoming value.
+ * @param valueColumn Column to write value into {@link ValuesDelta}.
+ */
+ public static void parseExtras(EntityDelta state, DataKind kind, Bundle extras,
+ String typeExtra, String valueExtra, String valueColumn) {
+ final String value = extras.getString(valueExtra);
+
+ // Bail when can't insert type, or value missing
+ final boolean canInsert = EntityModifier.canInsert(state, kind);
+ final boolean validValue = TextUtils.isGraphic(value);
+ if (!validValue || !canInsert) return;
+
+ // Find exact type, or otherwise best type
+ final int typeValue = extras.getInt(typeExtra, Integer.MIN_VALUE);
+ final EditType editType = EntityModifier.getBestValidType(state, kind, true, typeValue);
+
+ // Create data row and fill with value
+ final ValuesDelta child = EntityModifier.insertChild(state, kind, editType);
+ child.put(valueColumn, value);
+
+ if (editType != null && editType.customColumn != null) {
+ // Write down label when custom type picked
+ child.put(editType.customColumn, extras.getString(typeExtra));
+ }
+ }
}
diff --git a/src/com/android/contacts/model/Sources.java b/src/com/android/contacts/model/Sources.java
index e77a7dd..f0a2840 100644
--- a/src/com/android/contacts/model/Sources.java
+++ b/src/com/android/contacts/model/Sources.java
@@ -63,7 +63,7 @@
}
public static final String ACCOUNT_TYPE_GOOGLE = "com.google.GAIA";
- public static final String ACCOUNT_TYPE_EXCHANGE = "vnd.exchange";
+ public static final String ACCOUNT_TYPE_EXCHANGE = "com.android.exchange";
private HashMap<String, ContactsSource> mSources = new HashMap<String, ContactsSource>();
@@ -76,7 +76,7 @@
* Find the {@link ContactsSource} for the given
* {@link Contacts#ACCOUNT_TYPE}.
*/
- public ContactsSource getKindsForAccountType(String accountType) {
+ public ContactsSource getSourceForType(String accountType) {
return mSources.get(accountType);
}
diff --git a/src/com/android/contacts/ui/EditContactActivity.java b/src/com/android/contacts/ui/EditContactActivity.java
index 94a67bf..f173172 100644
--- a/src/com/android/contacts/ui/EditContactActivity.java
+++ b/src/com/android/contacts/ui/EditContactActivity.java
@@ -17,9 +17,10 @@
package com.android.contacts.ui;
import com.android.contacts.R;
-import com.android.contacts.model.EntityDelta;
import com.android.contacts.model.ContactsSource;
+import com.android.contacts.model.EntityDelta;
import com.android.contacts.model.Sources;
+import com.android.contacts.model.EntityDelta.ValuesDelta;
import com.android.contacts.ui.widget.ContactEditorView;
import android.app.Activity;
@@ -27,6 +28,7 @@
import android.content.ContentProviderOperation;
import android.content.ContentResolver;
import android.content.ContentUris;
+import android.content.ContentValues;
import android.content.Context;
import android.content.Entity;
import android.content.EntityIterator;
@@ -36,13 +38,13 @@
import android.os.Bundle;
import android.os.RemoteException;
import android.provider.ContactsContract;
+import android.provider.ContactsContract.RawContacts;
import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
-import android.view.ViewGroup;
import java.util.ArrayList;
@@ -52,8 +54,6 @@
public final class EditContactActivity extends Activity implements View.OnClickListener,
View.OnFocusChangeListener {
- // TODO: with new augmentedentity approach, turn insert and update cases into same action
-
private static final String TAG = "EditContactActivity";
/** The launch code when picking a photo and the raw data is returned */
@@ -67,26 +67,13 @@
public static final int MENU_ITEM_REVERT = 2;
public static final int MENU_ITEM_PHOTO = 6;
-
- private LayoutInflater mInflater;
- private ViewGroup mContentView;
-
-// private MenuItem mPhotoMenuItem;
-// private boolean mPhotoPresent = false;
-
- /** Flag marking this contact as changed, meaning we should write changes back. */
-// private boolean mContactChanged = false;
-
- private Uri mUri;
- private ArrayList<EntityDelta> mEntities = new ArrayList<EntityDelta>();
-
- private ContentResolver mResolver;
+ private View mTabContent;
private ContactEditorView mEditor;
- private ViewGroup mTabContent;
+ private Uri mUri;
+ private Sources mSources;
- // we edit an aggregate, which has several entities
- // we query and build AugmentedEntities, which is what ContactEditorView expects
+ private ArrayList<EntityDelta> mEntities = new ArrayList<EntityDelta>();
@@ -95,233 +82,135 @@
super.onCreate(icicle);
final Context context = this;
+ final LayoutInflater inflater = this.getLayoutInflater();
- mInflater = getLayoutInflater();
- mResolver = getContentResolver();
+ setContentView(R.layout.act_edit);
- mContentView = (ViewGroup)mInflater.inflate(R.layout.act_edit, null);
- mTabContent = (ViewGroup)mContentView.findViewById(android.R.id.tabcontent);
+ mTabContent = this.findViewById(android.R.id.tabcontent);
- setContentView(mContentView);
+ mEditor = new ContactEditorView(context);
+ mEditor.swapWith(mTabContent);
- // Setup floating buttons
findViewById(R.id.btn_done).setOnClickListener(this);
findViewById(R.id.btn_discard).setOnClickListener(this);
-
-
final Intent intent = getIntent();
final String action = intent.getAction();
final Bundle extras = intent.getExtras();
mUri = intent.getData();
+ mSources = Sources.getInstance();
- // TODO: read all contacts part of this aggregate and hook into tabs
+ if (Intent.ACTION_EDIT.equals(action) && icicle == null) {
+ // Read initial state from database
+ readEntities();
+ rebuildTabs();
+ }
+ }
- // Resolve the intent
- if (Intent.ACTION_EDIT.equals(action) && mUri != null) {
+ private static final String KEY_DELTAS = "deltas";
- try {
- final long aggId = ContentUris.parseId(mUri);
- final EntityIterator iterator = mResolver.queryEntities(
- ContactsContract.RawContacts.CONTENT_URI,
- ContactsContract.RawContacts.CONTACT_ID + "=" + aggId, null, null);
- while (iterator.hasNext()) {
- final Entity before = iterator.next();
- final EntityDelta entity = EntityDelta.fromBefore(before);
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ // Store entities with modifications
+ outState.putParcelableArrayList(KEY_DELTAS, mEntities);
- mEntities.add(entity);
+ super.onSaveInstanceState(outState);
+ }
- Log.d(TAG, "Loaded entity...");
- }
- iterator.close();
- } catch (RemoteException e) {
- Log.d(TAG, "Problem reading aggregate", e);
- }
+ @Override
+ protected void onRestoreInstanceState(Bundle savedInstanceState) {
-// if (icicle == null) {
-// // Build the entries & views
-// buildEntriesForEdit(extras);
-// buildViews();
-// }
- setTitle(R.string.editContact_title_edit);
- } else if (Intent.ACTION_INSERT.equals(action)) {
-// if (icicle == null) {
-// // Build the entries & views
-// buildEntriesForInsert(extras);
-// buildViews();
-// }
-// setTitle(R.string.editContact_title_insert);
-// mState = STATE_INSERT;
+ // Read and apply modifications from instance
+ mEntities = savedInstanceState.<EntityDelta> getParcelableArrayList(KEY_DELTAS);
+ rebuildTabs();
+
+ // Restore selected tab and any focus
+ super.onRestoreInstanceState(savedInstanceState);
+ }
+
+ @Override
+ protected void onPostCreate(Bundle savedInstanceState) {
+ super.onPostCreate(savedInstanceState);
+
+ final String action = getIntent().getAction();
+ if (Intent.ACTION_INSERT.equals(action)) {
+ // TODO: show account disambig dialog before creating
+
+ final ContentValues values = new ContentValues();
+ values.put(RawContacts.ACCOUNT_TYPE, Sources.ACCOUNT_TYPE_GOOGLE);
+
+ final EntityDelta insert = new EntityDelta(ValuesDelta.fromAfter(values));
+ mEntities.add(insert);
+
}
-// if (mState == STATE_UNKNOWN) {
-// Log.e(TAG, "Cannot resolve intent: " + intent);
-// finish();
-// return;
-// }
+ // TODO: if insert, handle account disambig if not already done
+ }
- mEditor = new ContactEditorView(context);
+ protected void readEntities() {
+ // TODO: handle saving the previous values before replacing, in some cases
+ try {
+ final ContentResolver resolver = this.getContentResolver();
+ final long aggId = ContentUris.parseId(mUri);
+ final EntityIterator iterator = resolver.queryEntities(
+ ContactsContract.RawContacts.CONTENT_URI,
+ ContactsContract.RawContacts.CONTACT_ID + "=" + aggId, null, null);
+ while (iterator.hasNext()) {
+ final Entity before = iterator.next();
+ final EntityDelta entity = EntityDelta.fromBefore(before);
+ mEntities.add(entity);
+ }
+ iterator.close();
+ } catch (RemoteException e) {
+ Log.d(TAG, "Problem reading aggregate", e);
+ }
+ }
- mTabContent.removeAllViews();
- mTabContent.addView(mEditor.getView());
+ protected void rebuildTabs() {
+ // TODO: hook up to tabs
+ showEntity(0);
+ }
-// final ContactsSource source = Sources.getInstance().getKindsForAccountType(
-// Sources.ACCOUNT_TYPE_GOOGLE);
- final ContactsSource source = Sources.getInstance().getKindsForAccountType(
- Sources.ACCOUNT_TYPE_EXCHANGE);
- mEditor.setState(mEntities.get(0), source);
+ protected void showEntity(int index) {
+ // Find entity and source for selected tab
+ final EntityDelta entity = mEntities.get(index);
+ final String accountType = entity.getValues().getAsString(RawContacts.ACCOUNT_TYPE);
+ ContactsSource source = mSources.getSourceForType(accountType);
+ if (source == null) {
+ // TODO: remove and place "read only" placard when missing
+ source = mSources.getSourceForType(Sources.ACCOUNT_TYPE_GOOGLE);
+ }
+ // Assign editor state based on entity and source
+ mEditor.setState(entity, source);
+ }
+
+ public void onTabChanged(String tabId) {
+ // Tag is really an array index
+ final int index = Integer.parseInt(tabId);
+ showEntity(index);
}
- // TODO: build entity from incoming intent
- // TODO: build entity from "new" action
-
-
-// private void addFromExtras(Bundle extras, Uri phonesUri, Uri methodsUri) {
-// EditEntry entry;
-//
-// // Read the name from the bundle
-// CharSequence name = extras.getCharSequence(Insert.NAME);
-// if (name != null && TextUtils.isGraphic(name)) {
-// mNameView.setText(name);
-// }
-//
-// // Read the phonetic name from the bundle
-// CharSequence phoneticName = extras.getCharSequence(Insert.PHONETIC_NAME);
-// if (!TextUtils.isEmpty(phoneticName)) {
-// mPhoneticNameView.setText(phoneticName);
-// }
-//
-// // StructuredPostal entries from extras
-// CharSequence postal = extras.getCharSequence(Insert.POSTAL);
-// int postalType = extras.getInt(Insert.POSTAL_TYPE, INVALID_TYPE);
-// if (!TextUtils.isEmpty(postal) && postalType == INVALID_TYPE) {
-// postalType = DEFAULT_POSTAL_TYPE;
-// }
-//
-// if (postalType != INVALID_TYPE) {
-// entry = EditEntry.newPostalEntry(this, null, postalType, postal.toString(),
-// methodsUri, 0);
-// entry.isPrimary = extras.getBoolean(Insert.POSTAL_ISPRIMARY);
-// mPostalEntries.add(entry);
-// }
-//
-// // Email entries from extras
-// addEmailFromExtras(extras, methodsUri, Insert.EMAIL, Insert.EMAIL_TYPE,
-// Insert.EMAIL_ISPRIMARY);
-// addEmailFromExtras(extras, methodsUri, Insert.SECONDARY_EMAIL, Insert.SECONDARY_EMAIL_TYPE,
-// null);
-// addEmailFromExtras(extras, methodsUri, Insert.TERTIARY_EMAIL, Insert.TERTIARY_EMAIL_TYPE,
-// null);
-//
-// // Phone entries from extras
-// addPhoneFromExtras(extras, phonesUri, Insert.PHONE, Insert.PHONE_TYPE,
-// Insert.PHONE_ISPRIMARY);
-// addPhoneFromExtras(extras, phonesUri, Insert.SECONDARY_PHONE, Insert.SECONDARY_PHONE_TYPE,
-// null);
-// addPhoneFromExtras(extras, phonesUri, Insert.TERTIARY_PHONE, Insert.TERTIARY_PHONE_TYPE,
-// null);
-//
-// // IM entries from extras
-// CharSequence imHandle = extras.getCharSequence(Insert.IM_HANDLE);
-// CharSequence imProtocol = extras.getCharSequence(Insert.IM_PROTOCOL);
-//
-// if (imHandle != null && imProtocol != null) {
-// Object protocolObj = ContactMethods.decodeImProtocol(imProtocol.toString());
-// if (protocolObj instanceof Number) {
-// int protocol = ((Number)F protocolObj).intValue();
-// entry = EditEntry.newImEntry(this,
-// getLabelsForKind(this, Contacts.KIND_IM)[protocol], protocol,
-// imHandle.toString(), methodsUri, 0);
-// } else {
-// entry = EditEntry.newImEntry(this, protocolObj.toString(), -1, imHandle.toString(),
-// methodsUri, 0);
-// }
-// entry.isPrimary = extras.getBoolean(Insert.IM_ISPRIMARY);
-// mImEntries.add(entry);
-// }
-// }
-//
-// private void addEmailFromExtras(Bundle extras, Uri methodsUri, String emailField,
-// String typeField, String primaryField) {
-// CharSequence email = extras.getCharSequence(emailField);
-//
-// // Correctly handle String in typeField as TYPE_CUSTOM
-// int emailType = INVALID_TYPE;
-// String customLabel = null;
-// if(extras.get(typeField) instanceof String) {
-// emailType = ContactsContract.TYPE_CUSTOM;
-// customLabel = extras.getString(typeField);
-// } else {
-// emailType = extras.getInt(typeField, INVALID_TYPE);
-// }
-//
-// if (!TextUtils.isEmpty(email) && emailType == INVALID_TYPE) {
-// emailType = DEFAULT_EMAIL_TYPE;
-// mPrimaryEmailAdded = true;
-// }
-//
-// if (emailType != INVALID_TYPE) {
-// EditEntry entry = EditEntry.newEmailEntry(this, customLabel, emailType, email.toString(),
-// methodsUri, 0);
-// entry.isPrimary = (primaryField == null) ? false : extras.getBoolean(primaryField);
-// mEmailEntries.add(entry);
-//
-// // Keep track of which primary types have been added
-// if (entry.isPrimary) {
-// mPrimaryEmailAdded = true;
-// }
-// }
-// }
-//
-// private void addPhoneFromExtras(Bundle extras, Uri phonesUri, String phoneField,
-// String typeField, String primaryField) {
-// CharSequence phoneNumber = extras.getCharSequence(phoneField);
-//
-// // Correctly handle String in typeField as TYPE_CUSTOM
-// int phoneType = INVALID_TYPE;
-// String customLabel = null;
-// if(extras.get(typeField) instanceof String) {
-// phoneType = Phone.TYPE_CUSTOM;
-// customLabel = extras.getString(typeField);
-// } else {
-// phoneType = extras.getInt(typeField, INVALID_TYPE);
-// }
-//
-// if (!TextUtils.isEmpty(phoneNumber) && phoneType == INVALID_TYPE) {
-// phoneType = DEFAULT_PHONE_TYPE;
-// }
-//
-// if (phoneType != INVALID_TYPE) {
-// EditEntry entry = EditEntry.newPhoneEntry(this, customLabel, phoneType,
-// phoneNumber.toString(), phonesUri, 0);
-// entry.isPrimary = (primaryField == null) ? false : extras.getBoolean(primaryField);
-// mPhoneEntries.add(entry);
-//
-// // Keep track of which primary types have been added
-// if (phoneType == Phone.TYPE_MOBILE) {
-// mMobilePhoneAdded = true;
-// }
-// }
-// }
-// */
+ public View createTabContent(String tag) {
+ // Content is identical for all tabs
+ return mEditor.getView();
+ }
- public void onClick(View v) {
- switch (v.getId()) {
-
- case R.id.btn_done:
+ public void onClick(View view) {
+ switch (view.getId()) {
+ case R.id.btn_done: {
doSaveAction();
break;
-
- case R.id.btn_discard:
+ }
+ case R.id.btn_discard: {
doRevertAction();
break;
-
+ }
}
}
@@ -338,119 +227,6 @@
- @Override
- protected void onSaveInstanceState(Bundle outState) {
-
- // TODO: store down uri, selected contactid, and pile of augmentedstates
-
- // store down the focused tab and child data _id (what about storing as index?)
-
- // tell sections to store their state, which also picks correct field and cursor location
-
-
-
-//
-// // To store current focus between config changes, follow focus down the
-// // view tree, keeping track of any parents with EditEntry tags
-// View focusedChild = mContentView.getFocusedChild();
-// EditEntry focusedEntry = null;
-// while (focusedChild != null) {
-// Object tag = focusedChild.getTag();
-// if (tag instanceof EditEntry) {
-// focusedEntry = (EditEntry) tag;
-// }
-//
-// // Keep going deeper until child isn't a group
-// if (focusedChild instanceof ViewGroup) {
-// View deeperFocus = ((ViewGroup) focusedChild).getFocusedChild();
-// if (deeperFocus != null) {
-// focusedChild = deeperFocus;
-// } else {
-// break;
-// }
-// } else {
-// break;
-// }
-// }
-//
-// if (focusedChild != null) {
-// int requestFocusId = focusedChild.getId();
-// int requestCursor = 0;
-// if (focusedChild instanceof EditText) {
-// requestCursor = ((EditText) focusedChild).getSelectionStart();
-// }
-//
-// // Store focus values in EditEntry if found, otherwise store as
-// // generic values
-// if (focusedEntry != null) {
-// focusedEntry.requestFocusId = requestFocusId;
-// focusedEntry.requestCursor = requestCursor;
-// } else {
-// outState.putInt("requestFocusId", requestFocusId);
-// outState.putInt("requestCursor", requestCursor);
-// }
-// }
-//
-// outState.putParcelableArrayList("phoneEntries", mPhoneEntries);
-// outState.putParcelableArrayList("emailEntries", mEmailEntries);
-// outState.putParcelableArrayList("imEntries", mImEntries);
-// outState.putParcelableArrayList("postalEntries", mPostalEntries);
-// outState.putParcelableArrayList("orgEntries", mOrgEntries);
-// outState.putParcelableArrayList("noteEntries", mNoteEntries);
-// outState.putParcelableArrayList("otherEntries", mOtherEntries);
-// outState.putInt("state", mState);
-// outState.putBoolean("insert", mInsert);
-// outState.putParcelable("uri", mUri);
-// outState.putString("name", mNameView.getText().toString());
-// outState.putParcelable("photo", mPhoto);
-// outState.putBoolean("photoChanged", mPhotoChanged);
-// outState.putString("phoneticName", mPhoneticNameView.getText().toString());
-// outState.putBoolean("contactChanged", mContactChanged);
- }
-
- @Override
- protected void onRestoreInstanceState(Bundle inState) {
-// mPhoneEntries = inState.getParcelableArrayList("phoneEntries");
-// mEmailEntries = inState.getParcelableArrayList("emailEntries");
-// mImEntries = inState.getParcelableArrayList("imEntries");
-// mPostalEntries = inState.getParcelableArrayList("postalEntries");
-// mOrgEntries = inState.getParcelableArrayList("orgEntries");
-// mNoteEntries = inState.getParcelableArrayList("noteEntries");
-// mOtherEntries = inState.getParcelableArrayList("otherEntries");
-// setupSections();
-//
-// mState = inState.getInt("state");
-// mInsert = inState.getBoolean("insert");
-// mUri = inState.getParcelable("uri");
-// mNameView.setText(inState.getString("name"));
-// mPhoto = inState.getParcelable("photo");
-// if (mPhoto != null) {
-// mPhotoImageView.setImageBitmap(mPhoto);
-// setPhotoPresent(true);
-// } else {
-// mPhotoImageView.setImageResource(R.drawable.ic_contact_picture);
-// setPhotoPresent(false);
-// }
-// mPhotoChanged = inState.getBoolean("photoChanged");
-// mPhoneticNameView.setText(inState.getString("phoneticName"));
-// mContactChanged = inState.getBoolean("contactChanged");
-//
-// // Now that everything is restored, build the view
-// buildViews();
-//
-// // Try restoring any generally requested focus
-// int requestFocusId = inState.getInt("requestFocusId", View.NO_ID);
-// View focusedChild = mContentView.findViewById(requestFocusId);
-// if (focusedChild != null) {
-// focusedChild.requestFocus();
-// if (focusedChild instanceof EditText) {
-// int requestCursor = inState.getInt("requestCursor", 0);
-// ((EditText) focusedChild).setSelection(requestCursor);
-// }
-// }
- }
-
-
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
@@ -575,300 +351,9 @@
}
this.finish();
-
-
-
- // Save or create the contact if needed
-// switch (mState) {
-// case STATE_EDIT:
-// save();
-// break;
-//
-// /*
-// case STATE_INSERT:
-// create();
-// break;
-// */
-//
-// default:
-// Log.e(TAG, "Unknown state in doSaveOrCreate: " + mState);
-// break;
-// }
- finish();
}
- /**
- * Save the various fields to the existing contact.
- */
- private void save() {
-// ContentValues values = new ContentValues();
-// String data;
-// int numValues = 0;
-//
-// // Handle the name and send to voicemail specially
-// final String name = mNameView.getText().toString();
-// if (name != null && TextUtils.isGraphic(name)) {
-// numValues++;
-// }
-//
-// values.put(StructuredName.DISPLAY_NAME, name);
-// /*
-// values.put(People.PHONETIC_NAME, mPhoneticNameView.getText().toString());
-// */
-// mResolver.update(mStructuredNameUri, values, null, null);
-//
-// // This will go down in for loop somewhere
-// if (mPhotoChanged) {
-// // Only write the photo if it's changed, since we don't initially load mPhoto
-// values.clear();
-// if (mPhoto != null) {
-// ByteArrayOutputStream stream = new ByteArrayOutputStream();
-// mPhoto.compress(Bitmap.CompressFormat.JPEG, 75, stream);
-// values.put(Photo.PHOTO, stream.toByteArray());
-// mResolver.update(mPhotoDataUri, values, null, null);
-// } else {
-// values.putNull(Photo.PHOTO);
-// mResolver.update(mPhotoDataUri, values, null, null);
-// }
-// }
-//
-// int entryCount = ContactEntryAdapter.countEntries(mSections, false);
-// for (int i = 0; i < entryCount; i++) {
-// EditEntry entry = ContactEntryAdapter.getEntry(mSections, i, false);
-// data = entry.getData();
-// boolean empty = data == null || !TextUtils.isGraphic(data);
-// /*
-// if (kind == EditEntry.KIND_GROUP) {
-// if (entry.id != 0) {
-// for (int g = 0; g < mGroups.length; g++) {
-// long groupId = getGroupId(mResolver, mGroups[g].toString());
-// if (mInTheGroup[g]) {
-// Contacts.People.addToGroup(mResolver, entry.id, groupId);
-// numValues++;
-// } else {
-// deleteGroupMembership(entry.id, groupId);
-// }
-// }
-// }
-// }
-// */
-// if (!empty) {
-// values.clear();
-// entry.toValues(values);
-// if (entry.id != 0) {
-// mResolver.update(entry.uri, values, null, null);
-// } else {
-// /* mResolver.insert(entry.uri, values); */
-// }
-// } else if (entry.id != 0) {
-// mResolver.delete(entry.uri, null, null);
-// }
-// }
-//
-// /*
-// if (numValues == 0) {
-// // The contact is completely empty, delete it
-// mResolver.delete(mUri, null, null);
-// mUri = null;
-// setResult(RESULT_CANCELED);
-// } else {
-// // Add the entry to the my contacts group if it isn't there already
-// People.addToMyContactsGroup(mResolver, ContentUris.parseId(mUri));
-// setResult(RESULT_OK, new Intent().setData(mUri));
-//
-// // Only notify user if we actually changed contact
-// if (mContactChanged || mPhotoChanged) {
-// Toast.makeText(this, R.string.contactSavedToast, Toast.LENGTH_SHORT).show();
-// }
-// }
-// */
- }
-
- /**
- * Takes the entered data and saves it to a new contact.
- */
- /*
- private void create() {
- ContentValues values = new ContentValues();
- String data;
- int numValues = 0;
-
- // Create the contact itself
- final String name = mNameView.getText().toString();
- if (name != null && TextUtils.isGraphic(name)) {
- numValues++;
- }
- values.put(People.NAME, name);
- values.put(People.PHONETIC_NAME, mPhoneticNameView.getText().toString());
-
- // Add the contact to the My Contacts group
- Uri contactUri = People.createPersonInMyContactsGroup(mResolver, values);
-
- // Add the contact to the group that is being displayed in the contact list
- SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
- int displayType = prefs.getInt(ContactsListActivity.PREF_DISPLAY_TYPE,
- ContactsListActivity.DISPLAY_TYPE_UNKNOWN);
- if (displayType == ContactsListActivity.DISPLAY_TYPE_USER_GROUP) {
- String displayGroup = prefs.getString(ContactsListActivity.PREF_DISPLAY_INFO,
- null);
- if (!TextUtils.isEmpty(displayGroup)) {
- People.addToGroup(mResolver, ContentUris.parseId(contactUri), displayGroup);
- }
- } else {
- // Check to see if we're not syncing everything and if so if My Contacts is synced.
- // If it isn't then the created contact can end up not in any groups that are
- // currently synced and end up getting removed from the phone, which is really bad.
- boolean syncingEverything = !"0".equals(Contacts.Settings.getSetting(mResolver, null,
- Contacts.Settings.SYNC_EVERYTHING));
- if (!syncingEverything) {
- boolean syncingMyContacts = false;
- Cursor c = mResolver.query(Groups.CONTENT_URI, new String[] { Groups.SHOULD_SYNC },
- Groups.SYSTEM_ID + "=?", new String[] { Groups.GROUP_MY_CONTACTS }, null);
- if (c != null) {
- try {
- if (c.moveToFirst()) {
- syncingMyContacts = !"0".equals(c.getString(0));
- }
- } finally {
- c.close();
- }
- }
-
- if (!syncingMyContacts) {
- // Not syncing My Contacts, so find a group that is being synced and stick
- // the contact in there. We sort the list so at least all contacts
- // will appear in the same group.
- c = mResolver.query(Groups.CONTENT_URI, new String[] { Groups._ID },
- Groups.SHOULD_SYNC + "!=0", null, Groups.DEFAULT_SORT_ORDER);
- if (c != null) {
- try {
- if (c.moveToFirst()) {
- People.addToGroup(mResolver, ContentUris.parseId(contactUri),
- c.getLong(0));
- }
- } finally {
- c.close();
- }
- }
- }
- }
- }
-
- // Handle the photo
- if (mPhoto != null) {
- ByteArrayOutputStream stream = new ByteArrayOutputStream();
- mPhoto.compress(Bitmap.CompressFormat.JPEG, 75, stream);
- Contacts.People.setPhotoData(getContentResolver(), contactUri, stream.toByteArray());
- }
-
- // Create the contact methods
- int entryCount = ContactEntryAdapter.countEntries(mSections, false);
- for (int i = 0; i < entryCount; i++) {
- EditEntry entry = ContactEntryAdapter.getEntry(mSections, i, false);
- if (entry.kind == EditEntry.KIND_GROUP) {
- long contactId = ContentUris.parseId(contactUri);
- for (int g = 0; g < mGroups.length; g++) {
- if (mInTheGroup[g]) {
- long groupId = getGroupId(mResolver, mGroups[g].toString());
- People.addToGroup(mResolver, contactId, groupId);
- numValues++;
- }
- }
- } else if (entry.kind != EditEntry.KIND_CONTACT) {
- values.clear();
- if (entry.toValues(values)) {
- // Only create the entry if there is data
- entry.uri = mResolver.insert(
- Uri.withAppendedPath(contactUri, entry.contentDirectory), values);
- entry.id = ContentUris.parseId(entry.uri);
- }
- } else {
- // Update the contact with any straggling data, like notes
- data = entry.getData();
- values.clear();
- if (data != null && TextUtils.isGraphic(data)) {
- values.put(entry.column, data);
- mResolver.update(contactUri, values, null, null);
- }
- }
- }
-
- if (numValues == 0) {
- mResolver.delete(contactUri, null, null);
- setResult(RESULT_CANCELED);
- } else {
- mUri = contactUri;
- Intent resultIntent = new Intent()
- .setData(mUri)
- .putExtra(Intent.EXTRA_SHORTCUT_NAME, name);
- setResult(RESULT_OK, resultIntent);
- Toast.makeText(this, R.string.contactCreatedToast, Toast.LENGTH_SHORT).show();
- }
- }
- */
-
-
-
-// /**
-// * Builds the views for a specific section.
-// *
-// * @param layout the container
-// * @param section the section to build the views for
-// */
-// private void buildViewsForSection(final LinearLayout layout, ArrayList<EditEntry> section,
-// int separatorResource, int sectionType) {
-//
-// View divider = mInflater.inflate(R.layout.edit_divider, layout, false);
-// layout.addView(divider);
-//
-// // Count up undeleted children
-// int activeChildren = 0;
-// for (int i = section.size() - 1; i >= 0; i--) {
-// EditEntry entry = section.get(i);
-// if (!entry.isDeleted) {
-// activeChildren++;
-// }
-// }
-//
-// // Build the correct group header based on undeleted children
-// ViewGroup header;
-// if (activeChildren == 0) {
-// header = (ViewGroup) mInflater.inflate(R.layout.edit_separator_alone, layout, false);
-// } else {
-// header = (ViewGroup) mInflater.inflate(R.layout.edit_separator, layout, false);
-// }
-//
-// // Because we're emulating a ListView, we need to handle focus changes
-// // with some additional logic.
-// header.setOnFocusChangeListener(this);
-//
-// TextView text = (TextView) header.findViewById(R.id.text);
-// text.setText(getText(separatorResource));
-//
-// // Force TextView to always default color if we have children. This makes sure
-// // we don't change color when parent is pressed.
-// if (activeChildren > 0) {
-// ColorStateList stateList = text.getTextColors();
-// text.setTextColor(stateList.getDefaultColor());
-// }
-//
-// View addView = header.findViewById(R.id.separator);
-// addView.setTag(Integer.valueOf(sectionType));
-// addView.setOnClickListener(this);
-//
-// // Build views for the current section
-// for (EditEntry entry : section) {
-// entry.activity = this; // this could be null from when the state is restored
-// if (!entry.isDeleted) {
-// View view = buildViewForEntry(entry);
-// header.addView(view);
-// }
-// }
-//
-// layout.addView(header);
-// }
-
@@ -877,4 +362,6 @@
// views as they are focused.
v.setSelected(hasFocus);
}
+
+
}
diff --git a/src/com/android/contacts/ui/widget/ContactEditorView.java b/src/com/android/contacts/ui/widget/ContactEditorView.java
index 5e7785a..6e67dd6 100644
--- a/src/com/android/contacts/ui/widget/ContactEditorView.java
+++ b/src/com/android/contacts/ui/widget/ContactEditorView.java
@@ -75,7 +75,10 @@
mSecondary = (ViewGroup)mContent.findViewById(R.id.sect_secondary);
mPhoto = new PhotoEditor(context);
+ mPhoto.swapWith(mContent, R.id.hook_photo);
+
mDisplayName = new DisplayNameEditor(context);
+ mDisplayName.swapWith(mContent, R.id.hook_displayname);
}
/**
@@ -88,6 +91,9 @@
mGeneral.removeAllViews();
mSecondary.removeAllViews();
+ // Bail if invalid state or source
+ if (state == null || source == null) return;
+
// Create editor sections for each possible data kind
for (DataKind kind : source.getSortedDataKinds()) {
// Skip kind of not editable
@@ -304,7 +310,9 @@
for (EditField field : kind.fieldList) {
// Inflate field from definition
EditText fieldView = (EditText)mInflater.inflate(RES_FIELD, mFields, false);
- fieldView.setHint(field.titleRes);
+ if (field.titleRes != -1) {
+ fieldView.setHint(field.titleRes);
+ }
fieldView.setInputType(field.inputType);
fieldView.setMinLines(field.minLines);
diff --git a/src/com/android/contacts/ui/widget/ViewHolder.java b/src/com/android/contacts/ui/widget/ViewHolder.java
index 2dec441..223dbb1 100644
--- a/src/com/android/contacts/ui/widget/ViewHolder.java
+++ b/src/com/android/contacts/ui/widget/ViewHolder.java
@@ -19,6 +19,9 @@
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+import android.widget.FrameLayout;
/**
* Helper to inflate a given layout and produce the {@link View} when requested.
@@ -34,6 +37,30 @@
mContent = mInflater.inflate(layoutRes, null);
}
+ public void swapWith(View target) {
+ // Borrow layout params and id for ourselves
+ this.mContent.setLayoutParams(target.getLayoutParams());
+ this.mContent.setId(target.getId());
+
+ // Find the direct parent of this view
+ final ViewParent parent = target.getParent();
+ if (parent == null || !(parent instanceof ViewGroup)) return;
+
+ // Swap out existing view with ourselves
+ final ViewGroup parentGroup = (ViewGroup)parent;
+ final int index = parentGroup.indexOfChild(target);
+ parentGroup.removeViewAt(index);
+ parentGroup.addView(this.mContent, index);
+ }
+
+ public void swapWith(View ancestor, int id) {
+ // Try finding the target view to replace
+ final View target = ancestor.findViewById(id);
+ if (target != null) {
+ swapWith(target);
+ }
+ }
+
public View getView() {
return mContent;
}
diff --git a/tests/src/com/android/contacts/EntityModifierTests.java b/tests/src/com/android/contacts/EntityModifierTests.java
index 9d41f67..7b8580c 100644
--- a/tests/src/com/android/contacts/EntityModifierTests.java
+++ b/tests/src/com/android/contacts/EntityModifierTests.java
@@ -171,7 +171,8 @@
}
/**
- * Test {@link EntityModifier#getBestValidType(EntityDelta, DataKind)}
+ * Test
+ * {@link EntityModifier#getBestValidType(EntityDelta, DataKind, boolean, int)}
* by asserting expected best options in various states.
*/
public void testBestValidType() {
@@ -187,27 +188,27 @@
// Default suggestion should be home
final EntityDelta state = getEntity();
- suggested = EntityModifier.getBestValidType(state, kindPhone, false);
+ suggested = EntityModifier.getBestValidType(state, kindPhone, false, Integer.MIN_VALUE);
assertEquals("Unexpected suggestion", typeHome, suggested);
// Add first home, should now suggest work
EntityModifier.insertChild(state, kindPhone, typeHome);
- suggested = EntityModifier.getBestValidType(state, kindPhone, false);
+ suggested = EntityModifier.getBestValidType(state, kindPhone, false, Integer.MIN_VALUE);
assertEquals("Unexpected suggestion", typeWork, suggested);
// Add work fax, should still suggest work
EntityModifier.insertChild(state, kindPhone, typeFaxWork);
- suggested = EntityModifier.getBestValidType(state, kindPhone, false);
+ suggested = EntityModifier.getBestValidType(state, kindPhone, false, Integer.MIN_VALUE);
assertEquals("Unexpected suggestion", typeWork, suggested);
// Add other, should still suggest work
EntityModifier.insertChild(state, kindPhone, typeOther);
- suggested = EntityModifier.getBestValidType(state, kindPhone, false);
+ suggested = EntityModifier.getBestValidType(state, kindPhone, false, Integer.MIN_VALUE);
assertEquals("Unexpected suggestion", typeWork, suggested);
// Add work, now should suggest other
EntityModifier.insertChild(state, kindPhone, typeWork);
- suggested = EntityModifier.getBestValidType(state, kindPhone, false);
+ suggested = EntityModifier.getBestValidType(state, kindPhone, false, Integer.MIN_VALUE);
assertEquals("Unexpected suggestion", typeOther, suggested);
}
}