15ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee/*
25ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee * Copyright (C) 2009 The Android Open Source Project
35ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee *
45ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee * Licensed under the Apache License, Version 2.0 (the "License");
55ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee * you may not use this file except in compliance with the License.
65ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee * You may obtain a copy of the License at
75ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee *
85ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee *      http://www.apache.org/licenses/LICENSE-2.0
95ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee *
105ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee * Unless required by applicable law or agreed to in writing, software
115ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee * distributed under the License is distributed on an "AS IS" BASIS,
125ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
135ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee * See the License for the specific language governing permissions and
145ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee * limitations under the License.
155ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee */
165ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee
175ade0bb1757b216ace2f50d2357409bf9876a07aYorke Leepackage com.android.contacts.common.model;
185ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee
195ade0bb1757b216ace2f50d2357409bf9876a07aYorke Leeimport android.content.ContentProviderOperation;
205ade0bb1757b216ace2f50d2357409bf9876a07aYorke Leeimport android.content.ContentProviderOperation.Builder;
215ade0bb1757b216ace2f50d2357409bf9876a07aYorke Leeimport android.content.ContentResolver;
225ade0bb1757b216ace2f50d2357409bf9876a07aYorke Leeimport android.content.Context;
235ade0bb1757b216ace2f50d2357409bf9876a07aYorke Leeimport android.content.Entity;
245ade0bb1757b216ace2f50d2357409bf9876a07aYorke Leeimport android.content.EntityIterator;
255ade0bb1757b216ace2f50d2357409bf9876a07aYorke Leeimport android.net.Uri;
265ade0bb1757b216ace2f50d2357409bf9876a07aYorke Leeimport android.os.Parcel;
275ade0bb1757b216ace2f50d2357409bf9876a07aYorke Leeimport android.os.Parcelable;
285ade0bb1757b216ace2f50d2357409bf9876a07aYorke Leeimport android.provider.ContactsContract.AggregationExceptions;
295ade0bb1757b216ace2f50d2357409bf9876a07aYorke Leeimport android.provider.ContactsContract.Contacts;
305ade0bb1757b216ace2f50d2357409bf9876a07aYorke Leeimport android.provider.ContactsContract.RawContacts;
315ade0bb1757b216ace2f50d2357409bf9876a07aYorke Leeimport android.util.Log;
325ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee
335ade0bb1757b216ace2f50d2357409bf9876a07aYorke Leeimport com.android.contacts.common.model.ValuesDelta;
345ade0bb1757b216ace2f50d2357409bf9876a07aYorke Leeimport com.google.common.collect.Lists;
355ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee
365ade0bb1757b216ace2f50d2357409bf9876a07aYorke Leeimport java.util.ArrayList;
375ade0bb1757b216ace2f50d2357409bf9876a07aYorke Leeimport java.util.Arrays;
385ade0bb1757b216ace2f50d2357409bf9876a07aYorke Leeimport java.util.Iterator;
395ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee
405ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee/**
415ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee * Container for multiple {@link RawContactDelta} objects, usually when editing
425ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee * together as an entire aggregate. Provides convenience methods for parceling
435ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee * and applying another {@link RawContactDeltaList} over it.
445ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee */
455ade0bb1757b216ace2f50d2357409bf9876a07aYorke Leepublic class RawContactDeltaList extends ArrayList<RawContactDelta> implements Parcelable {
465ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee    private static final String TAG = RawContactDeltaList.class.getSimpleName();
475ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee    private static final boolean VERBOSE_LOGGING = Log.isLoggable(TAG, Log.VERBOSE);
485ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee
495ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee    private boolean mSplitRawContacts;
505ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee    private long[] mJoinWithRawContactIds;
515ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee
525ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee    public RawContactDeltaList() {
535ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee    }
545ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee
555ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee    /**
565ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee     * Create an {@link RawContactDeltaList} based on {@link Contacts} specified by the
575ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee     * given query parameters. This closes the {@link EntityIterator} when
585ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee     * finished, so it doesn't subscribe to updates.
595ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee     */
605ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee    public static RawContactDeltaList fromQuery(Uri entityUri, ContentResolver resolver,
615ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee            String selection, String[] selectionArgs, String sortOrder) {
625ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        final EntityIterator iterator = RawContacts.newEntityIterator(
635ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee                resolver.query(entityUri, null, selection, selectionArgs, sortOrder));
645ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        try {
655ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee            return fromIterator(iterator);
665ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        } finally {
675ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee            iterator.close();
685ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        }
695ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee    }
705ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee
715ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee    /**
725ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee     * Create an {@link RawContactDeltaList} that contains the entities of the Iterator as before
735ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee     * values.  This function can be passed an iterator of Entity objects or an iterator of
745ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee     * RawContact objects.
755ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee     */
765ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee    public static RawContactDeltaList fromIterator(Iterator<?> iterator) {
775ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        final RawContactDeltaList state = new RawContactDeltaList();
785ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        state.addAll(iterator);
795ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        return state;
805ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee    }
815ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee
825ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee    public void addAll(Iterator<?> iterator) {
835ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        // Perform background query to pull contact details
845ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        while (iterator.hasNext()) {
855ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee            // Read all contacts into local deltas to prepare for edits
865ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee            Object nextObject = iterator.next();
875ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee            final RawContact before = nextObject instanceof Entity
885ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee                    ? RawContact.createFrom((Entity) nextObject)
895ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee                    : (RawContact) nextObject;
905ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee            final RawContactDelta rawContactDelta = RawContactDelta.fromBefore(before);
915ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee            add(rawContactDelta);
925ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        }
935ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee    }
945ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee
955ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee    /**
965ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee     * Merge the "after" values from the given {@link RawContactDeltaList}, discarding any
975ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee     * previous "after" states. This is typically used when re-parenting user
985ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee     * edits onto an updated {@link RawContactDeltaList}.
995ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee     */
1005ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee    public static RawContactDeltaList mergeAfter(RawContactDeltaList local,
1015ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee            RawContactDeltaList remote) {
1025ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        if (local == null) local = new RawContactDeltaList();
1035ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee
1045ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        // For each entity in the remote set, try matching over existing
1055ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        for (RawContactDelta remoteEntity : remote) {
1065ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee            final Long rawContactId = remoteEntity.getValues().getId();
1075ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee
1085ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee            // Find or create local match and merge
1095ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee            final RawContactDelta localEntity = local.getByRawContactId(rawContactId);
1105ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee            final RawContactDelta merged = RawContactDelta.mergeAfter(localEntity, remoteEntity);
1115ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee
1125ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee            if (localEntity == null && merged != null) {
1135ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee                // No local entry before, so insert
1145ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee                local.add(merged);
1155ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee            }
1165ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        }
1175ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee
1185ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        return local;
1195ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee    }
1205ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee
1215ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee    /**
1225ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee     * Build a list of {@link ContentProviderOperation} that will transform all
1235ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee     * the "before" {@link Entity} states into the modified state which all
1245ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee     * {@link RawContactDelta} objects represent. This method specifically creates
1255ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee     * any {@link AggregationExceptions} rules needed to groups edits together.
1265ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee     */
1275ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee    public ArrayList<ContentProviderOperation> buildDiff() {
1285ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        if (VERBOSE_LOGGING) {
1295ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee            Log.v(TAG, "buildDiff: list=" + toString());
1305ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        }
1315ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        final ArrayList<ContentProviderOperation> diff = Lists.newArrayList();
1325ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee
1335ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        final long rawContactId = this.findRawContactId();
1345ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        int firstInsertRow = -1;
1355ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee
1365ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        // First pass enforces versions remain consistent
1375ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        for (RawContactDelta delta : this) {
1385ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee            delta.buildAssert(diff);
1395ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        }
1405ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee
1415ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        final int assertMark = diff.size();
1425ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        int backRefs[] = new int[size()];
1435ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee
1445ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        int rawContactIndex = 0;
1455ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee
1465ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        // Second pass builds actual operations
1475ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        for (RawContactDelta delta : this) {
1485ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee            final int firstBatch = diff.size();
1495ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee            final boolean isInsert = delta.isContactInsert();
1505ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee            backRefs[rawContactIndex++] = isInsert ? firstBatch : -1;
1515ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee
1525ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee            delta.buildDiff(diff);
1535ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee
1545ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee            // If the user chose to join with some other existing raw contact(s) at save time,
1555ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee            // add aggregation exceptions for all those raw contacts.
1565ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee            if (mJoinWithRawContactIds != null) {
1575ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee                for (Long joinedRawContactId : mJoinWithRawContactIds) {
1585ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee                    final Builder builder = beginKeepTogether();
1595ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee                    builder.withValue(AggregationExceptions.RAW_CONTACT_ID1, joinedRawContactId);
1605ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee                    if (rawContactId != -1) {
1615ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee                        builder.withValue(AggregationExceptions.RAW_CONTACT_ID2, rawContactId);
1625ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee                    } else {
1635ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee                        builder.withValueBackReference(
1645ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee                                AggregationExceptions.RAW_CONTACT_ID2, firstBatch);
1655ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee                    }
1665ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee                    diff.add(builder.build());
1675ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee                }
1685ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee            }
1695ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee
1705ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee            // Only create rules for inserts
1715ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee            if (!isInsert) continue;
1725ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee
1735ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee            // If we are going to split all contacts, there is no point in first combining them
1745ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee            if (mSplitRawContacts) continue;
1755ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee
1765ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee            if (rawContactId != -1) {
1775ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee                // Has existing contact, so bind to it strongly
1785ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee                final Builder builder = beginKeepTogether();
1795ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee                builder.withValue(AggregationExceptions.RAW_CONTACT_ID1, rawContactId);
1805ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee                builder.withValueBackReference(AggregationExceptions.RAW_CONTACT_ID2, firstBatch);
1815ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee                diff.add(builder.build());
1825ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee
1835ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee            } else if (firstInsertRow == -1) {
1845ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee                // First insert case, so record row
1855ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee                firstInsertRow = firstBatch;
1865ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee
1875ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee            } else {
1885ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee                // Additional insert case, so point at first insert
1895ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee                final Builder builder = beginKeepTogether();
1905ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee                builder.withValueBackReference(AggregationExceptions.RAW_CONTACT_ID1,
1915ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee                        firstInsertRow);
1925ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee                builder.withValueBackReference(AggregationExceptions.RAW_CONTACT_ID2, firstBatch);
1935ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee                diff.add(builder.build());
1945ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee            }
1955ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        }
1965ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee
1975ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        if (mSplitRawContacts) {
1985ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee            buildSplitContactDiff(diff, backRefs);
1995ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        }
2005ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee
2015ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        // No real changes if only left with asserts
2025ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        if (diff.size() == assertMark) {
2035ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee            diff.clear();
2045ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        }
2055ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        if (VERBOSE_LOGGING) {
2065ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee            Log.v(TAG, "buildDiff: ops=" + diffToString(diff));
2075ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        }
2085ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        return diff;
2095ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee    }
2105ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee
2115ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee    private static String diffToString(ArrayList<ContentProviderOperation> ops) {
2125ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        StringBuilder sb = new StringBuilder();
2135ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        sb.append("[\n");
2145ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        for (ContentProviderOperation op : ops) {
2155ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee            sb.append(op.toString());
2165ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee            sb.append(",\n");
2175ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        }
2185ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        sb.append("]\n");
2195ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        return sb.toString();
2205ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee    }
2215ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee
2225ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee    /**
2235ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee     * Start building a {@link ContentProviderOperation} that will keep two
2245ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee     * {@link RawContacts} together.
2255ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee     */
2265ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee    protected Builder beginKeepTogether() {
2275ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        final Builder builder = ContentProviderOperation
2285ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee                .newUpdate(AggregationExceptions.CONTENT_URI);
2295ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        builder.withValue(AggregationExceptions.TYPE, AggregationExceptions.TYPE_KEEP_TOGETHER);
2305ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        return builder;
2315ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee    }
2325ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee
2335ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee    /**
2345ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee     * Builds {@link AggregationExceptions} to split all constituent raw contacts into
2355ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee     * separate contacts.
2365ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee     */
2375ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee    private void buildSplitContactDiff(final ArrayList<ContentProviderOperation> diff,
2385ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee            int[] backRefs) {
2395ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        int count = size();
2405ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        for (int i = 0; i < count; i++) {
2415ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee            for (int j = 0; j < count; j++) {
2425ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee                if (i != j) {
2435ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee                    buildSplitContactDiff(diff, i, j, backRefs);
2445ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee                }
2455ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee            }
2465ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        }
2475ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee    }
2485ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee
2495ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee    /**
2505ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee     * Construct a {@link AggregationExceptions#TYPE_KEEP_SEPARATE}.
2515ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee     */
2525ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee    private void buildSplitContactDiff(ArrayList<ContentProviderOperation> diff, int index1,
2535ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee            int index2, int[] backRefs) {
2545ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        Builder builder =
2555ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee                ContentProviderOperation.newUpdate(AggregationExceptions.CONTENT_URI);
2565ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        builder.withValue(AggregationExceptions.TYPE, AggregationExceptions.TYPE_KEEP_SEPARATE);
2575ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee
2585ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        Long rawContactId1 = get(index1).getValues().getAsLong(RawContacts._ID);
2595ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        int backRef1 = backRefs[index1];
2605ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        if (rawContactId1 != null && rawContactId1 >= 0) {
2615ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee            builder.withValue(AggregationExceptions.RAW_CONTACT_ID1, rawContactId1);
2625ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        } else if (backRef1 >= 0) {
2635ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee            builder.withValueBackReference(AggregationExceptions.RAW_CONTACT_ID1, backRef1);
2645ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        } else {
2655ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee            return;
2665ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        }
2675ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee
2685ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        Long rawContactId2 = get(index2).getValues().getAsLong(RawContacts._ID);
2695ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        int backRef2 = backRefs[index2];
2705ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        if (rawContactId2 != null && rawContactId2 >= 0) {
2715ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee            builder.withValue(AggregationExceptions.RAW_CONTACT_ID2, rawContactId2);
2725ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        } else if (backRef2 >= 0) {
2735ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee            builder.withValueBackReference(AggregationExceptions.RAW_CONTACT_ID2, backRef2);
2745ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        } else {
2755ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee            return;
2765ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        }
2775ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee
2785ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        diff.add(builder.build());
2795ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee    }
2805ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee
2815ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee    /**
2825ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee     * Search all contained {@link RawContactDelta} for the first one with an
2835ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee     * existing {@link RawContacts#_ID} value. Usually used when creating
2845ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee     * {@link AggregationExceptions} during an update.
2855ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee     */
2865ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee    public long findRawContactId() {
2875ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        for (RawContactDelta delta : this) {
2885ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee            final Long rawContactId = delta.getValues().getAsLong(RawContacts._ID);
2895ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee            if (rawContactId != null && rawContactId >= 0) {
2905ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee                return rawContactId;
2915ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee            }
2925ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        }
2935ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        return -1;
2945ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee    }
2955ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee
2965ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee    /**
2975ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee     * Find {@link RawContacts#_ID} of the requested {@link RawContactDelta}.
2985ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee     */
2995ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee    public Long getRawContactId(int index) {
3005ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        if (index >= 0 && index < this.size()) {
3015ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee            final RawContactDelta delta = this.get(index);
3025ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee            final ValuesDelta values = delta.getValues();
3035ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee            if (values.isVisible()) {
3045ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee                return values.getAsLong(RawContacts._ID);
3055ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee            }
3065ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        }
3075ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        return null;
3085ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee    }
3095ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee
3105ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee    /**
3115ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee     * Find the raw-contact (an {@link RawContactDelta}) with the specified ID.
3125ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee     */
3135ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee    public RawContactDelta getByRawContactId(Long rawContactId) {
3145ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        final int index = this.indexOfRawContactId(rawContactId);
3155ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        return (index == -1) ? null : this.get(index);
3165ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee    }
3175ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee
3185ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee    /**
3195ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee     * Find index of given {@link RawContacts#_ID} when present.
3205ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee     */
3215ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee    public int indexOfRawContactId(Long rawContactId) {
3225ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        if (rawContactId == null) return -1;
3235ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        final int size = this.size();
3245ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        for (int i = 0; i < size; i++) {
3255ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee            final Long currentId = getRawContactId(i);
3265ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee            if (rawContactId.equals(currentId)) {
3275ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee                return i;
3285ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee            }
3295ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        }
3305ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        return -1;
3315ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee    }
3325ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee
3335ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee    /**
3345ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee     * Return the index of the first RawContactDelta corresponding to a writable raw-contact, or -1.
3355ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee     * */
3365ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee    public int indexOfFirstWritableRawContact(Context context) {
3375ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        // Find the first writable entity.
3385ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        int entityIndex = 0;
3395ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        for (RawContactDelta delta : this) {
3405ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee            if (delta.getRawContactAccountType(context).areContactsWritable()) return entityIndex;
3415ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee            entityIndex++;
3425ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        }
3435ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        return -1;
3445ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee    }
3455ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee
3465ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee    /**  Return the first RawContactDelta corresponding to a writable raw-contact, or null. */
3475ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee    public RawContactDelta getFirstWritableRawContact(Context context) {
3485ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        final int index = indexOfFirstWritableRawContact(context);
3495ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        return (index == -1) ? null : get(index);
3505ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee    }
3515ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee
3525ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee    public ValuesDelta getSuperPrimaryEntry(final String mimeType) {
3535ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        ValuesDelta primary = null;
3545ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        ValuesDelta randomEntry = null;
3555ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        for (RawContactDelta delta : this) {
3565ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee            final ArrayList<ValuesDelta> mimeEntries = delta.getMimeEntries(mimeType);
3575ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee            if (mimeEntries == null) return null;
3585ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee
3595ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee            for (ValuesDelta entry : mimeEntries) {
3605ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee                if (entry.isSuperPrimary()) {
3615ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee                    return entry;
3625ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee                } else if (primary == null && entry.isPrimary()) {
3635ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee                    primary = entry;
3645ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee                } else if (randomEntry == null) {
3655ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee                    randomEntry = entry;
3665ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee                }
3675ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee            }
3685ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        }
3695ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        // When no direct super primary, return something
3705ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        if (primary != null) {
3715ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee            return primary;
3725ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        }
3735ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        return randomEntry;
3745ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee    }
3755ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee
3765ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee    /**
3775ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee     * Sets a flag that will split ("explode") the raw_contacts into seperate contacts
3785ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee     */
3795ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee    public void markRawContactsForSplitting() {
3805ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        mSplitRawContacts = true;
3815ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee    }
3825ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee
3835ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee    public boolean isMarkedForSplitting() {
3845ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        return mSplitRawContacts;
3855ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee    }
3865ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee
3875ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee    public void setJoinWithRawContacts(long[] rawContactIds) {
3885ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        mJoinWithRawContactIds = rawContactIds;
3895ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee    }
3905ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee
3915ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee    public boolean isMarkedForJoining() {
3925ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        return mJoinWithRawContactIds != null && mJoinWithRawContactIds.length > 0;
3935ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee    }
3945ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee
3955ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee    /** {@inheritDoc} */
3965ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee    @Override
3975ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee    public int describeContents() {
3985ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        // Nothing special about this parcel
3995ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        return 0;
4005ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee    }
4015ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee
4025ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee    /** {@inheritDoc} */
4035ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee    @Override
4045ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee    public void writeToParcel(Parcel dest, int flags) {
4055ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        final int size = this.size();
4065ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        dest.writeInt(size);
4075ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        for (RawContactDelta delta : this) {
4085ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee            dest.writeParcelable(delta, flags);
4095ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        }
4105ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        dest.writeLongArray(mJoinWithRawContactIds);
4115ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        dest.writeInt(mSplitRawContacts ? 1 : 0);
4125ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee    }
4135ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee
4145ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee    @SuppressWarnings("unchecked")
4155ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee    public void readFromParcel(Parcel source) {
4165ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        final ClassLoader loader = getClass().getClassLoader();
4175ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        final int size = source.readInt();
4185ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        for (int i = 0; i < size; i++) {
4195ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee            this.add(source.<RawContactDelta> readParcelable(loader));
4205ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        }
4215ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        mJoinWithRawContactIds = source.createLongArray();
4225ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        mSplitRawContacts = source.readInt() != 0;
4235ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee    }
4245ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee
4255ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee    public static final Parcelable.Creator<RawContactDeltaList> CREATOR =
4265ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee            new Parcelable.Creator<RawContactDeltaList>() {
4275ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        @Override
4285ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        public RawContactDeltaList createFromParcel(Parcel in) {
4295ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee            final RawContactDeltaList state = new RawContactDeltaList();
4305ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee            state.readFromParcel(in);
4315ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee            return state;
4325ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        }
4335ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee
4345ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        @Override
4355ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        public RawContactDeltaList[] newArray(int size) {
4365ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee            return new RawContactDeltaList[size];
4375ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        }
4385ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee    };
4395ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee
4405ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee    @Override
4415ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee    public String toString() {
4425ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        StringBuilder sb = new StringBuilder();
4435ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        sb.append("(");
4445ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        sb.append("Split=");
4455ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        sb.append(mSplitRawContacts);
4465ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        sb.append(", Join=[");
4475ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        sb.append(Arrays.toString(mJoinWithRawContactIds));
4485ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        sb.append("], Values=");
4495ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        sb.append(super.toString());
4505ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        sb.append(")");
4515ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee        return sb.toString();
4525ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee    }
4535ade0bb1757b216ace2f50d2357409bf9876a07aYorke Lee}
454