blob: b7d06657c0bb11d180f6883190137bf2ac6b6d95 [file] [log] [blame]
Yorke Lee2644d942013-10-28 11:05:43 -07001/*
2 * Copyright (C) 2009 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
Gary Mai69c182a2016-12-05 13:07:03 -080017package com.android.contacts.model;
Yorke Lee2644d942013-10-28 11:05:43 -070018
19import android.content.ContentProviderOperation;
20import android.content.ContentProviderOperation.Builder;
21import android.content.ContentValues;
22import android.content.Context;
23import android.net.Uri;
24import android.os.Parcel;
25import android.os.Parcelable;
26import android.provider.BaseColumns;
27import android.provider.ContactsContract.Data;
28import android.provider.ContactsContract.Profile;
29import android.provider.ContactsContract.RawContacts;
30import android.util.Log;
31
Gary Mai69c182a2016-12-05 13:07:03 -080032import com.android.contacts.compat.CompatUtils;
33import com.android.contacts.model.account.AccountType;
34import com.android.contacts.model.account.AccountWithDataSet;
Gary Mai4ceabed2016-09-16 12:14:13 -070035
Yorke Lee2644d942013-10-28 11:05:43 -070036import com.google.common.collect.Lists;
37import com.google.common.collect.Maps;
38
39import java.util.ArrayList;
40import java.util.HashMap;
41
42/**
43 * Contains a {@link RawContact} and records any modifications separately so the
44 * original {@link RawContact} can be swapped out with a newer version and the
45 * changes still cleanly applied.
46 * <p>
47 * One benefit of this approach is that we can build changes entirely on an
48 * empty {@link RawContact}, which then becomes an insert {@link RawContacts} case.
49 * <p>
50 * When applying modifications over an {@link RawContact}, we try finding the
51 * original {@link Data#_ID} rows where the modifications took place. If those
52 * rows are missing from the new {@link RawContact}, we know the original data must
53 * be deleted, but to preserve the user modifications we treat as an insert.
54 */
55public class RawContactDelta implements Parcelable {
56 // TODO: optimize by using contentvalues pool, since we allocate so many of them
57
58 private static final String TAG = "EntityDelta";
59 private static final boolean LOGV = false;
60
61 /**
62 * Direct values from {@link Entity#getEntityValues()}.
63 */
64 private ValuesDelta mValues;
65
66 /**
67 * URI used for contacts queries, by default it is set to query raw contacts.
68 * It can be set to query the profile's raw contact(s).
69 */
70 private Uri mContactsQueryUri = RawContacts.CONTENT_URI;
71
72 /**
73 * Internal map of children values from {@link Entity#getSubValues()}, which
74 * we store here sorted into {@link Data#MIMETYPE} bins.
75 */
76 private final HashMap<String, ArrayList<ValuesDelta>> mEntries = Maps.newHashMap();
77
78 public RawContactDelta() {
79 }
80
81 public RawContactDelta(ValuesDelta values) {
82 mValues = values;
83 }
84
85 /**
86 * Build an {@link RawContactDelta} using the given {@link RawContact} as a
87 * starting point; the "before" snapshot.
88 */
89 public static RawContactDelta fromBefore(RawContact before) {
90 final RawContactDelta rawContactDelta = new RawContactDelta();
91 rawContactDelta.mValues = ValuesDelta.fromBefore(before.getValues());
92 rawContactDelta.mValues.setIdColumn(RawContacts._ID);
93 for (final ContentValues values : before.getContentValues()) {
94 rawContactDelta.addEntry(ValuesDelta.fromBefore(values));
95 }
96 return rawContactDelta;
97 }
98
99 /**
100 * Merge the "after" values from the given {@link RawContactDelta} onto the
101 * "before" state represented by this {@link RawContactDelta}, discarding any
102 * existing "after" states. This is typically used when re-parenting changes
103 * onto an updated {@link Entity}.
104 */
105 public static RawContactDelta mergeAfter(RawContactDelta local, RawContactDelta remote) {
106 // Bail early if trying to merge delete with missing local
107 final ValuesDelta remoteValues = remote.mValues;
108 if (local == null && (remoteValues.isDelete() || remoteValues.isTransient())) return null;
109
110 // Create local version if none exists yet
111 if (local == null) local = new RawContactDelta();
112
113 if (LOGV) {
114 final Long localVersion = (local.mValues == null) ? null : local.mValues
115 .getAsLong(RawContacts.VERSION);
116 final Long remoteVersion = remote.mValues.getAsLong(RawContacts.VERSION);
117 Log.d(TAG, "Re-parenting from original version " + remoteVersion + " to "
118 + localVersion);
119 }
120
121 // Create values if needed, and merge "after" changes
122 local.mValues = ValuesDelta.mergeAfter(local.mValues, remote.mValues);
123
124 // Find matching local entry for each remote values, or create
125 for (ArrayList<ValuesDelta> mimeEntries : remote.mEntries.values()) {
126 for (ValuesDelta remoteEntry : mimeEntries) {
127 final Long childId = remoteEntry.getId();
128
129 // Find or create local match and merge
130 final ValuesDelta localEntry = local.getEntry(childId);
131 final ValuesDelta merged = ValuesDelta.mergeAfter(localEntry, remoteEntry);
132
133 if (localEntry == null && merged != null) {
134 // No local entry before, so insert
135 local.addEntry(merged);
136 }
137 }
138 }
139
140 return local;
141 }
142
143 public ValuesDelta getValues() {
144 return mValues;
145 }
146
147 public boolean isContactInsert() {
148 return mValues.isInsert();
149 }
150
151 /**
152 * Get the {@link ValuesDelta} child marked as {@link Data#IS_PRIMARY},
153 * which may return null when no entry exists.
154 */
155 public ValuesDelta getPrimaryEntry(String mimeType) {
156 final ArrayList<ValuesDelta> mimeEntries = getMimeEntries(mimeType, false);
157 if (mimeEntries == null) return null;
158
159 for (ValuesDelta entry : mimeEntries) {
160 if (entry.isPrimary()) {
161 return entry;
162 }
163 }
164
165 // When no direct primary, return something
166 return mimeEntries.size() > 0 ? mimeEntries.get(0) : null;
167 }
168
169 /**
170 * calls {@link #getSuperPrimaryEntry(String, boolean)} with true
171 * @see #getSuperPrimaryEntry(String, boolean)
172 */
173 public ValuesDelta getSuperPrimaryEntry(String mimeType) {
174 return getSuperPrimaryEntry(mimeType, true);
175 }
176
177 /**
178 * Returns the super-primary entry for the given mime type
179 * @param forceSelection if true, will try to return some value even if a super-primary
180 * doesn't exist (may be a primary, or just a random item
181 * @return
182 */
Yorke Lee2644d942013-10-28 11:05:43 -0700183 public ValuesDelta getSuperPrimaryEntry(String mimeType, boolean forceSelection) {
184 final ArrayList<ValuesDelta> mimeEntries = getMimeEntries(mimeType, false);
185 if (mimeEntries == null) return null;
186
187 ValuesDelta primary = null;
188 for (ValuesDelta entry : mimeEntries) {
189 if (entry.isSuperPrimary()) {
190 return entry;
191 } else if (entry.isPrimary()) {
192 primary = entry;
193 }
194 }
195
196 if (!forceSelection) {
197 return null;
198 }
199
200 // When no direct super primary, return something
201 if (primary != null) {
202 return primary;
203 }
204 return mimeEntries.size() > 0 ? mimeEntries.get(0) : null;
205 }
206
207 /**
208 * Return the AccountType that this raw-contact belongs to.
209 */
210 public AccountType getRawContactAccountType(Context context) {
211 ContentValues entityValues = getValues().getCompleteValues();
212 String type = entityValues.getAsString(RawContacts.ACCOUNT_TYPE);
213 String dataSet = entityValues.getAsString(RawContacts.DATA_SET);
214 return AccountTypeManager.getInstance(context).getAccountType(type, dataSet);
215 }
216
217 public Long getRawContactId() {
218 return getValues().getAsLong(RawContacts._ID);
219 }
220
221 public String getAccountName() {
222 return getValues().getAsString(RawContacts.ACCOUNT_NAME);
223 }
224
225 public String getAccountType() {
226 return getValues().getAsString(RawContacts.ACCOUNT_TYPE);
227 }
228
229 public String getDataSet() {
230 return getValues().getAsString(RawContacts.DATA_SET);
231 }
232
233 public AccountType getAccountType(AccountTypeManager manager) {
234 return manager.getAccountType(getAccountType(), getDataSet());
235 }
236
Gary Mai4ceabed2016-09-16 12:14:13 -0700237 public AccountWithDataSet getAccountWithDataSet() {
238 return new AccountWithDataSet(getAccountName(), getAccountType(), getDataSet());
239 }
240
Yorke Lee2644d942013-10-28 11:05:43 -0700241 public boolean isVisible() {
242 return getValues().isVisible();
243 }
244
245 /**
246 * Return the list of child {@link ValuesDelta} from our optimized map,
247 * creating the list if requested.
248 */
Walter Jangde90f442015-11-13 18:29:06 +0000249 private ArrayList<ValuesDelta> getMimeEntries(String mimeType, boolean lazyCreate) {
Yorke Lee2644d942013-10-28 11:05:43 -0700250 ArrayList<ValuesDelta> mimeEntries = mEntries.get(mimeType);
251 if (mimeEntries == null && lazyCreate) {
252 mimeEntries = Lists.newArrayList();
253 mEntries.put(mimeType, mimeEntries);
254 }
255 return mimeEntries;
256 }
257
258 public ArrayList<ValuesDelta> getMimeEntries(String mimeType) {
259 return getMimeEntries(mimeType, false);
260 }
261
262 public int getMimeEntriesCount(String mimeType, boolean onlyVisible) {
263 final ArrayList<ValuesDelta> mimeEntries = getMimeEntries(mimeType);
264 if (mimeEntries == null) return 0;
265
266 int count = 0;
267 for (ValuesDelta child : mimeEntries) {
268 // Skip deleted items when requesting only visible
269 if (onlyVisible && !child.isVisible()) continue;
270 count++;
271 }
272 return count;
273 }
274
275 public boolean hasMimeEntries(String mimeType) {
276 return mEntries.containsKey(mimeType);
277 }
278
279 public ValuesDelta addEntry(ValuesDelta entry) {
280 final String mimeType = entry.getMimetype();
281 getMimeEntries(mimeType, true).add(entry);
282 return entry;
283 }
284
285 public ArrayList<ContentValues> getContentValues() {
286 ArrayList<ContentValues> values = Lists.newArrayList();
287 for (ArrayList<ValuesDelta> mimeEntries : mEntries.values()) {
288 for (ValuesDelta entry : mimeEntries) {
289 if (!entry.isDelete()) {
290 values.add(entry.getCompleteValues());
291 }
292 }
293 }
294 return values;
295 }
296
297 /**
298 * Find entry with the given {@link BaseColumns#_ID} value.
299 */
300 public ValuesDelta getEntry(Long childId) {
301 if (childId == null) {
302 // Requesting an "insert" entry, which has no "before"
303 return null;
304 }
305
306 // Search all children for requested entry
307 for (ArrayList<ValuesDelta> mimeEntries : mEntries.values()) {
308 for (ValuesDelta entry : mimeEntries) {
309 if (childId.equals(entry.getId())) {
310 return entry;
311 }
312 }
313 }
314 return null;
315 }
316
317 /**
318 * Return the total number of {@link ValuesDelta} contained.
319 */
320 public int getEntryCount(boolean onlyVisible) {
321 int count = 0;
322 for (String mimeType : mEntries.keySet()) {
323 count += getMimeEntriesCount(mimeType, onlyVisible);
324 }
325 return count;
326 }
327
328 @Override
329 public boolean equals(Object object) {
330 if (object instanceof RawContactDelta) {
331 final RawContactDelta other = (RawContactDelta)object;
332
333 // Equality failed if parent values different
334 if (!other.mValues.equals(mValues)) return false;
335
336 for (ArrayList<ValuesDelta> mimeEntries : mEntries.values()) {
337 for (ValuesDelta child : mimeEntries) {
338 // Equality failed if any children unmatched
339 if (!other.containsEntry(child)) return false;
340 }
341 }
342
343 // Passed all tests, so equal
344 return true;
345 }
346 return false;
347 }
348
349 private boolean containsEntry(ValuesDelta entry) {
350 for (ArrayList<ValuesDelta> mimeEntries : mEntries.values()) {
351 for (ValuesDelta child : mimeEntries) {
352 // Contained if we find any child that matches
353 if (child.equals(entry)) return true;
354 }
355 }
356 return false;
357 }
358
359 /**
360 * Mark this entire object deleted, including any {@link ValuesDelta}.
361 */
362 public void markDeleted() {
363 this.mValues.markDeleted();
364 for (ArrayList<ValuesDelta> mimeEntries : mEntries.values()) {
365 for (ValuesDelta child : mimeEntries) {
366 child.markDeleted();
367 }
368 }
369 }
370
371 @Override
372 public String toString() {
373 final StringBuilder builder = new StringBuilder();
374 builder.append("\n(");
375 builder.append("Uri=");
376 builder.append(mContactsQueryUri);
377 builder.append(", Values=");
378 builder.append(mValues != null ? mValues.toString() : "null");
379 builder.append(", Entries={");
380 for (ArrayList<ValuesDelta> mimeEntries : mEntries.values()) {
381 for (ValuesDelta child : mimeEntries) {
382 builder.append("\n\t");
383 child.toString(builder);
384 }
385 }
386 builder.append("\n})\n");
387 return builder.toString();
388 }
389
390 /**
391 * Consider building the given {@link ContentProviderOperation.Builder} and
392 * appending it to the given list, which only happens if builder is valid.
393 */
394 private void possibleAdd(ArrayList<ContentProviderOperation> diff,
395 ContentProviderOperation.Builder builder) {
396 if (builder != null) {
397 diff.add(builder.build());
398 }
399 }
400
401 /**
Wenyi Wang93fdd482015-12-07 14:26:54 -0800402 * For compatibility purpose, this method is copied from {@link #possibleAdd} and takes
403 * BuilderWrapper and an ArrayList of CPOWrapper as parameters.
404 */
405 private void possibleAddWrapper(ArrayList<CPOWrapper> diff, BuilderWrapper bw) {
406 if (bw != null && bw.getBuilder() != null) {
407 diff.add(new CPOWrapper(bw.getBuilder().build(), bw.getType()));
408 }
409 }
410
411 /**
Yorke Lee2644d942013-10-28 11:05:43 -0700412 * Build a list of {@link ContentProviderOperation} that will assert any
413 * "before" state hasn't changed. This is maintained separately so that all
414 * asserts can take place before any updates occur.
415 */
416 public void buildAssert(ArrayList<ContentProviderOperation> buildInto) {
Wenyi Wang93fdd482015-12-07 14:26:54 -0800417 final Builder builder = buildAssertHelper();
418 if (builder != null) {
419 buildInto.add(builder.build());
420 }
421 }
422
423 /**
424 * For compatibility purpose, this method is copied from {@link #buildAssert} and takes an
425 * ArrayList of CPOWrapper as parameter.
426 */
427 public void buildAssertWrapper(ArrayList<CPOWrapper> buildInto) {
428 final Builder builder = buildAssertHelper();
429 if (builder != null) {
430 buildInto.add(new CPOWrapper(builder.build(), CompatUtils.TYPE_ASSERT));
431 }
432 }
433
434 private Builder buildAssertHelper() {
Yorke Lee2644d942013-10-28 11:05:43 -0700435 final boolean isContactInsert = mValues.isInsert();
Wenyi Wang93fdd482015-12-07 14:26:54 -0800436 ContentProviderOperation.Builder builder = null;
Yorke Lee2644d942013-10-28 11:05:43 -0700437 if (!isContactInsert) {
438 // Assert version is consistent while persisting changes
439 final Long beforeId = mValues.getId();
440 final Long beforeVersion = mValues.getAsLong(RawContacts.VERSION);
Wenyi Wang93fdd482015-12-07 14:26:54 -0800441 if (beforeId == null || beforeVersion == null) return builder;
442 builder = ContentProviderOperation.newAssertQuery(mContactsQueryUri);
Yorke Lee2644d942013-10-28 11:05:43 -0700443 builder.withSelection(RawContacts._ID + "=" + beforeId, null);
444 builder.withValue(RawContacts.VERSION, beforeVersion);
Yorke Lee2644d942013-10-28 11:05:43 -0700445 }
Wenyi Wang93fdd482015-12-07 14:26:54 -0800446 return builder;
Yorke Lee2644d942013-10-28 11:05:43 -0700447 }
448
449 /**
450 * Build a list of {@link ContentProviderOperation} that will transform the
451 * current "before" {@link Entity} state into the modified state which this
452 * {@link RawContactDelta} represents.
453 */
454 public void buildDiff(ArrayList<ContentProviderOperation> buildInto) {
455 final int firstIndex = buildInto.size();
456
457 final boolean isContactInsert = mValues.isInsert();
458 final boolean isContactDelete = mValues.isDelete();
459 final boolean isContactUpdate = !isContactInsert && !isContactDelete;
460
461 final Long beforeId = mValues.getId();
462
463 Builder builder;
464
465 if (isContactInsert) {
466 // TODO: for now simply disabling aggregation when a new contact is
467 // created on the phone. In the future, will show aggregation suggestions
468 // after saving the contact.
469 mValues.put(RawContacts.AGGREGATION_MODE, RawContacts.AGGREGATION_MODE_SUSPENDED);
470 }
471
472 // Build possible operation at Contact level
473 builder = mValues.buildDiff(mContactsQueryUri);
474 possibleAdd(buildInto, builder);
475
476 // Build operations for all children
477 for (ArrayList<ValuesDelta> mimeEntries : mEntries.values()) {
478 for (ValuesDelta child : mimeEntries) {
479 // Ignore children if parent was deleted
480 if (isContactDelete) continue;
481
482 // Use the profile data URI if the contact is the profile.
483 if (mContactsQueryUri.equals(Profile.CONTENT_RAW_CONTACTS_URI)) {
484 builder = child.buildDiff(Uri.withAppendedPath(Profile.CONTENT_URI,
485 RawContacts.Data.CONTENT_DIRECTORY));
486 } else {
487 builder = child.buildDiff(Data.CONTENT_URI);
488 }
489
490 if (child.isInsert()) {
491 if (isContactInsert) {
492 // Parent is brand new insert, so back-reference _id
493 builder.withValueBackReference(Data.RAW_CONTACT_ID, firstIndex);
494 } else {
495 // Inserting under existing, so fill with known _id
496 builder.withValue(Data.RAW_CONTACT_ID, beforeId);
497 }
498 } else if (isContactInsert && builder != null) {
499 // Child must be insert when Contact insert
500 throw new IllegalArgumentException("When parent insert, child must be also");
501 }
502 possibleAdd(buildInto, builder);
503 }
504 }
505
506 final boolean addedOperations = buildInto.size() > firstIndex;
507 if (addedOperations && isContactUpdate) {
508 // Suspend aggregation while persisting updates
509 builder = buildSetAggregationMode(beforeId, RawContacts.AGGREGATION_MODE_SUSPENDED);
510 buildInto.add(firstIndex, builder.build());
511
512 // Restore aggregation mode as last operation
513 builder = buildSetAggregationMode(beforeId, RawContacts.AGGREGATION_MODE_DEFAULT);
514 buildInto.add(builder.build());
515 } else if (isContactInsert) {
516 // Restore aggregation mode as last operation
517 builder = ContentProviderOperation.newUpdate(mContactsQueryUri);
518 builder.withValue(RawContacts.AGGREGATION_MODE, RawContacts.AGGREGATION_MODE_DEFAULT);
519 builder.withSelection(RawContacts._ID + "=?", new String[1]);
520 builder.withSelectionBackReference(0, firstIndex);
521 buildInto.add(builder.build());
522 }
523 }
524
525 /**
Wenyi Wang93fdd482015-12-07 14:26:54 -0800526 * For compatibility purpose, this method is copied from {@link #buildDiff} and takes an
527 * ArrayList of CPOWrapper as parameter.
528 */
529 public void buildDiffWrapper(ArrayList<CPOWrapper> buildInto) {
530 final int firstIndex = buildInto.size();
531
532 final boolean isContactInsert = mValues.isInsert();
533 final boolean isContactDelete = mValues.isDelete();
534 final boolean isContactUpdate = !isContactInsert && !isContactDelete;
535
536 final Long beforeId = mValues.getId();
537
538 if (isContactInsert) {
539 // TODO: for now simply disabling aggregation when a new contact is
540 // created on the phone. In the future, will show aggregation suggestions
541 // after saving the contact.
542 mValues.put(RawContacts.AGGREGATION_MODE, RawContacts.AGGREGATION_MODE_SUSPENDED);
543 }
544
545 // Build possible operation at Contact level
546 BuilderWrapper bw = mValues.buildDiffWrapper(mContactsQueryUri);
547 possibleAddWrapper(buildInto, bw);
548
549 // Build operations for all children
550 for (ArrayList<ValuesDelta> mimeEntries : mEntries.values()) {
551 for (ValuesDelta child : mimeEntries) {
552 // Ignore children if parent was deleted
553 if (isContactDelete) continue;
554
555 // Use the profile data URI if the contact is the profile.
556 if (mContactsQueryUri.equals(Profile.CONTENT_RAW_CONTACTS_URI)) {
557 bw = child.buildDiffWrapper(Uri.withAppendedPath(Profile.CONTENT_URI,
558 RawContacts.Data.CONTENT_DIRECTORY));
559 } else {
560 bw = child.buildDiffWrapper(Data.CONTENT_URI);
561 }
562
563 if (child.isInsert()) {
564 if (isContactInsert) {
565 // Parent is brand new insert, so back-reference _id
566 bw.getBuilder().withValueBackReference(Data.RAW_CONTACT_ID, firstIndex);
567 } else {
568 // Inserting under existing, so fill with known _id
569 bw.getBuilder().withValue(Data.RAW_CONTACT_ID, beforeId);
570 }
571 } else if (isContactInsert && bw != null && bw.getBuilder() != null) {
572 // Child must be insert when Contact insert
573 throw new IllegalArgumentException("When parent insert, child must be also");
574 }
575 possibleAddWrapper(buildInto, bw);
576 }
577 }
578
579 final boolean addedOperations = buildInto.size() > firstIndex;
580 if (addedOperations && isContactUpdate) {
581 // Suspend aggregation while persisting updates
582 Builder builder =
583 buildSetAggregationMode(beforeId, RawContacts.AGGREGATION_MODE_SUSPENDED);
584 buildInto.add(firstIndex, new CPOWrapper(builder.build(), CompatUtils.TYPE_UPDATE));
585
586 // Restore aggregation mode as last operation
587 builder = buildSetAggregationMode(beforeId, RawContacts.AGGREGATION_MODE_DEFAULT);
Wenyi Wang74853192016-02-23 09:46:02 -0800588 buildInto.add(new CPOWrapper(builder.build(), CompatUtils.TYPE_UPDATE));
Wenyi Wang93fdd482015-12-07 14:26:54 -0800589 } else if (isContactInsert) {
590 // Restore aggregation mode as last operation
591 Builder builder = ContentProviderOperation.newUpdate(mContactsQueryUri);
592 builder.withValue(RawContacts.AGGREGATION_MODE, RawContacts.AGGREGATION_MODE_DEFAULT);
593 builder.withSelection(RawContacts._ID + "=?", new String[1]);
594 builder.withSelectionBackReference(0, firstIndex);
595 buildInto.add(new CPOWrapper(builder.build(), CompatUtils.TYPE_UPDATE));
596 }
597 }
598
599 /**
Yorke Lee2644d942013-10-28 11:05:43 -0700600 * Build a {@link ContentProviderOperation} that changes
601 * {@link RawContacts#AGGREGATION_MODE} to the given value.
602 */
603 protected Builder buildSetAggregationMode(Long beforeId, int mode) {
604 Builder builder = ContentProviderOperation.newUpdate(mContactsQueryUri);
605 builder.withValue(RawContacts.AGGREGATION_MODE, mode);
606 builder.withSelection(RawContacts._ID + "=" + beforeId, null);
607 return builder;
608 }
609
610 /** {@inheritDoc} */
611 public int describeContents() {
612 // Nothing special about this parcel
613 return 0;
614 }
615
616 /** {@inheritDoc} */
617 public void writeToParcel(Parcel dest, int flags) {
618 final int size = this.getEntryCount(false);
619 dest.writeInt(size);
620 dest.writeParcelable(mValues, flags);
621 dest.writeParcelable(mContactsQueryUri, flags);
622 for (ArrayList<ValuesDelta> mimeEntries : mEntries.values()) {
623 for (ValuesDelta child : mimeEntries) {
624 dest.writeParcelable(child, flags);
625 }
626 }
627 }
628
629 public void readFromParcel(Parcel source) {
630 final ClassLoader loader = getClass().getClassLoader();
631 final int size = source.readInt();
632 mValues = source.<ValuesDelta> readParcelable(loader);
633 mContactsQueryUri = source.<Uri> readParcelable(loader);
634 for (int i = 0; i < size; i++) {
635 final ValuesDelta child = source.<ValuesDelta> readParcelable(loader);
636 this.addEntry(child);
637 }
638 }
639
640 /**
641 * Used to set the query URI to the profile URI to store profiles.
642 */
643 public void setProfileQueryUri() {
644 mContactsQueryUri = Profile.CONTENT_RAW_CONTACTS_URI;
645 }
646
647 public static final Parcelable.Creator<RawContactDelta> CREATOR =
648 new Parcelable.Creator<RawContactDelta>() {
649 public RawContactDelta createFromParcel(Parcel in) {
650 final RawContactDelta state = new RawContactDelta();
651 state.readFromParcel(in);
652 return state;
653 }
654
655 public RawContactDelta[] newArray(int size) {
656 return new RawContactDelta[size];
657 }
658 };
659
660}