Loading src/com/android/contacts/common/compat/CompatUtils.java +23 −0 Original line number Diff line number Diff line Loading @@ -18,8 +18,31 @@ package com.android.contacts.common.compat; import android.os.Build; import com.android.contacts.common.compat.SdkVersionOverride; import com.android.contacts.common.model.CPOWrapper; public final class CompatUtils { /** * These 4 variables are copied from ContentProviderOperation for compatibility. */ public final static int TYPE_INSERT = 1; public final static int TYPE_UPDATE = 2; public final static int TYPE_DELETE = 3; public final static int TYPE_ASSERT = 4; /** * Returns whether the operation in CPOWrapper is of TYPE_INSERT; */ public static boolean isInsertCompat(CPOWrapper cpoWrapper) { if (SdkVersionOverride.getSdkVersion(Build.VERSION_CODES.M) >= Build.VERSION_CODES.M) { return cpoWrapper.getOperation().isInsert(); } else { return (cpoWrapper.getType() == TYPE_INSERT); } } /** * PrioritizedMimeType is added in API level 23. */ Loading src/com/android/contacts/common/model/BuilderWrapper.java 0 → 100644 +52 −0 Original line number Diff line number Diff line /* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.contacts.common.model; import android.content.ContentProviderOperation.Builder; /** * This class is created for the purpose of compatibility and make the type of * ContentProviderOperation available on pre-M SDKs. Since ContentProviderOperation is * usually created by Builder and we don’t have access to the type via Builder, so we need to * create a wrapper class for Builder first and include type. Then we could use the builder and * the type in this class to create a wrapper of ContentProviderOperation. */ public class BuilderWrapper { private Builder mBuilder; private int mType; public BuilderWrapper(Builder builder, int type) { mBuilder = builder; mType = type; } public int getType() { return mType; } public void setType(int mType) { this.mType = mType; } public Builder getBuilder() { return mBuilder; } public void setBuilder(Builder mBuilder) { this.mBuilder = mBuilder; } } src/com/android/contacts/common/model/CPOWrapper.java 0 → 100644 +49 −0 Original line number Diff line number Diff line /* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.contacts.common.model; import android.content.ContentProviderOperation; /** * This class is created for the purpose of compatibility and make the type of * ContentProviderOperation available on pre-M SDKs. */ public class CPOWrapper { private ContentProviderOperation mOperation; private int mType; public CPOWrapper(ContentProviderOperation builder, int type) { mOperation = builder; mType = type; } public int getType() { return mType; } public void setType(int type) { this.mType = type; } public ContentProviderOperation getOperation() { return mOperation; } public void setOperation(ContentProviderOperation operation) { this.mOperation = operation; } } src/com/android/contacts/common/model/RawContactDelta.java +109 −5 Original line number Diff line number Diff line Loading @@ -29,7 +29,10 @@ import android.provider.ContactsContract.Profile; import android.provider.ContactsContract.RawContacts; import android.util.Log; import com.android.contacts.common.compat.CompatUtils; import com.android.contacts.common.model.AccountTypeManager; import com.android.contacts.common.model.BuilderWrapper; import com.android.contacts.common.model.CPOWrapper; import com.android.contacts.common.model.ValuesDelta; import com.android.contacts.common.model.account.AccountType; import com.android.contacts.common.testing.NeededForTesting; Loading Loading @@ -395,25 +398,52 @@ public class RawContactDelta implements Parcelable { } } /** * For compatibility purpose, this method is copied from {@link #possibleAdd} and takes * BuilderWrapper and an ArrayList of CPOWrapper as parameters. */ private void possibleAddWrapper(ArrayList<CPOWrapper> diff, BuilderWrapper bw) { if (bw != null && bw.getBuilder() != null) { diff.add(new CPOWrapper(bw.getBuilder().build(), bw.getType())); } } /** * Build a list of {@link ContentProviderOperation} that will assert any * "before" state hasn't changed. This is maintained separately so that all * asserts can take place before any updates occur. */ public void buildAssert(ArrayList<ContentProviderOperation> buildInto) { final Builder builder = buildAssertHelper(); if (builder != null) { buildInto.add(builder.build()); } } /** * For compatibility purpose, this method is copied from {@link #buildAssert} and takes an * ArrayList of CPOWrapper as parameter. */ public void buildAssertWrapper(ArrayList<CPOWrapper> buildInto) { final Builder builder = buildAssertHelper(); if (builder != null) { buildInto.add(new CPOWrapper(builder.build(), CompatUtils.TYPE_ASSERT)); } } private Builder buildAssertHelper() { final boolean isContactInsert = mValues.isInsert(); ContentProviderOperation.Builder builder = null; if (!isContactInsert) { // Assert version is consistent while persisting changes final Long beforeId = mValues.getId(); final Long beforeVersion = mValues.getAsLong(RawContacts.VERSION); if (beforeId == null || beforeVersion == null) return; final ContentProviderOperation.Builder builder = ContentProviderOperation .newAssertQuery(mContactsQueryUri); if (beforeId == null || beforeVersion == null) return builder; builder = ContentProviderOperation.newAssertQuery(mContactsQueryUri); builder.withSelection(RawContacts._ID + "=" + beforeId, null); builder.withValue(RawContacts.VERSION, beforeVersion); buildInto.add(builder.build()); } return builder; } /** Loading Loading @@ -492,6 +522,80 @@ public class RawContactDelta implements Parcelable { } } /** * For compatibility purpose, this method is copied from {@link #buildDiff} and takes an * ArrayList of CPOWrapper as parameter. */ public void buildDiffWrapper(ArrayList<CPOWrapper> buildInto) { final int firstIndex = buildInto.size(); final boolean isContactInsert = mValues.isInsert(); final boolean isContactDelete = mValues.isDelete(); final boolean isContactUpdate = !isContactInsert && !isContactDelete; final Long beforeId = mValues.getId(); if (isContactInsert) { // TODO: for now simply disabling aggregation when a new contact is // created on the phone. In the future, will show aggregation suggestions // after saving the contact. mValues.put(RawContacts.AGGREGATION_MODE, RawContacts.AGGREGATION_MODE_SUSPENDED); } // Build possible operation at Contact level BuilderWrapper bw = mValues.buildDiffWrapper(mContactsQueryUri); possibleAddWrapper(buildInto, bw); // Build operations for all children for (ArrayList<ValuesDelta> mimeEntries : mEntries.values()) { for (ValuesDelta child : mimeEntries) { // Ignore children if parent was deleted if (isContactDelete) continue; // Use the profile data URI if the contact is the profile. if (mContactsQueryUri.equals(Profile.CONTENT_RAW_CONTACTS_URI)) { bw = child.buildDiffWrapper(Uri.withAppendedPath(Profile.CONTENT_URI, RawContacts.Data.CONTENT_DIRECTORY)); } else { bw = child.buildDiffWrapper(Data.CONTENT_URI); } if (child.isInsert()) { if (isContactInsert) { // Parent is brand new insert, so back-reference _id bw.getBuilder().withValueBackReference(Data.RAW_CONTACT_ID, firstIndex); } else { // Inserting under existing, so fill with known _id bw.getBuilder().withValue(Data.RAW_CONTACT_ID, beforeId); } } else if (isContactInsert && bw != null && bw.getBuilder() != null) { // Child must be insert when Contact insert throw new IllegalArgumentException("When parent insert, child must be also"); } possibleAddWrapper(buildInto, bw); } } final boolean addedOperations = buildInto.size() > firstIndex; if (addedOperations && isContactUpdate) { // Suspend aggregation while persisting updates Builder builder = buildSetAggregationMode(beforeId, RawContacts.AGGREGATION_MODE_SUSPENDED); buildInto.add(firstIndex, new CPOWrapper(builder.build(), CompatUtils.TYPE_UPDATE)); // Restore aggregation mode as last operation builder = buildSetAggregationMode(beforeId, RawContacts.AGGREGATION_MODE_DEFAULT); buildInto.add(firstIndex, new CPOWrapper(builder.build(), CompatUtils.TYPE_UPDATE)); } else if (isContactInsert) { // Restore aggregation mode as last operation Builder builder = ContentProviderOperation.newUpdate(mContactsQueryUri); builder.withValue(RawContacts.AGGREGATION_MODE, RawContacts.AGGREGATION_MODE_DEFAULT); builder.withSelection(RawContacts._ID + "=?", new String[1]); builder.withSelectionBackReference(0, firstIndex); buildInto.add(new CPOWrapper(builder.build(), CompatUtils.TYPE_UPDATE)); } } /** * Build a {@link ContentProviderOperation} that changes * {@link RawContacts#AGGREGATION_MODE} to the given value. Loading src/com/android/contacts/common/model/RawContactDeltaList.java +132 −12 Original line number Diff line number Diff line Loading @@ -30,7 +30,10 @@ import android.provider.ContactsContract.Contacts; import android.provider.ContactsContract.RawContacts; import android.util.Log; import com.android.contacts.common.compat.CompatUtils; import com.android.contacts.common.model.CPOWrapper; import com.android.contacts.common.model.ValuesDelta; import com.google.common.collect.Lists; import java.util.ArrayList; Loading Loading @@ -208,8 +211,96 @@ public class RawContactDeltaList extends ArrayList<RawContactDelta> implements P return diff; } /** * For compatibility purpose, this method is copied from {@link #buildDiff} and returns an * ArrayList of CPOWrapper. */ public ArrayList<CPOWrapper> buildDiffWrapper() { if (VERBOSE_LOGGING) { Log.v(TAG, "buildDiffWrapper: list=" + toString()); } final ArrayList<CPOWrapper> diffWrapper = Lists.newArrayList(); final long rawContactId = this.findRawContactId(); int firstInsertRow = -1; // First pass enforces versions remain consistent for (RawContactDelta delta : this) { delta.buildAssertWrapper(diffWrapper); } final int assertMark = diffWrapper.size(); int backRefs[] = new int[size()]; int rawContactIndex = 0; // Second pass builds actual operations for (RawContactDelta delta : this) { final int firstBatch = diffWrapper.size(); final boolean isInsert = delta.isContactInsert(); backRefs[rawContactIndex++] = isInsert ? firstBatch : -1; delta.buildDiffWrapper(diffWrapper); // If the user chose to join with some other existing raw contact(s) at save time, // add aggregation exceptions for all those raw contacts. if (mJoinWithRawContactIds != null) { for (Long joinedRawContactId : mJoinWithRawContactIds) { final Builder builder = beginKeepTogether(); builder.withValue(AggregationExceptions.RAW_CONTACT_ID1, joinedRawContactId); if (rawContactId != -1) { builder.withValue(AggregationExceptions.RAW_CONTACT_ID2, rawContactId); } else { builder.withValueBackReference( AggregationExceptions.RAW_CONTACT_ID2, firstBatch); } diffWrapper.add(new CPOWrapper(builder.build(), CompatUtils.TYPE_UPDATE)); } } // Only create rules for inserts if (!isInsert) continue; // If we are going to split all contacts, there is no point in first combining them if (mSplitRawContacts) continue; if (rawContactId != -1) { // Has existing contact, so bind to it strongly final Builder builder = beginKeepTogether(); builder.withValue(AggregationExceptions.RAW_CONTACT_ID1, rawContactId); builder.withValueBackReference(AggregationExceptions.RAW_CONTACT_ID2, firstBatch); diffWrapper.add(new CPOWrapper(builder.build(), CompatUtils.TYPE_UPDATE)); } else if (firstInsertRow == -1) { // First insert case, so record row firstInsertRow = firstBatch; } else { // Additional insert case, so point at first insert final Builder builder = beginKeepTogether(); builder.withValueBackReference(AggregationExceptions.RAW_CONTACT_ID1, firstInsertRow); builder.withValueBackReference(AggregationExceptions.RAW_CONTACT_ID2, firstBatch); diffWrapper.add(new CPOWrapper(builder.build(), CompatUtils.TYPE_UPDATE)); } } if (mSplitRawContacts) { buildSplitContactDiffWrapper(diffWrapper, backRefs); } // No real changes if only left with asserts if (diffWrapper.size() == assertMark) { diffWrapper.clear(); } if (VERBOSE_LOGGING) { Log.v(TAG, "buildDiff: ops=" + diffToStringWrapper(diffWrapper)); } return diffWrapper; } private static String diffToString(ArrayList<ContentProviderOperation> ops) { StringBuilder sb = new StringBuilder(); final StringBuilder sb = new StringBuilder(); sb.append("[\n"); for (ContentProviderOperation op : ops) { sb.append(op.toString()); Loading @@ -219,6 +310,17 @@ public class RawContactDeltaList extends ArrayList<RawContactDelta> implements P return sb.toString(); } /** * For compatibility purpose. */ private static String diffToStringWrapper(ArrayList<CPOWrapper> cpoWrappers) { ArrayList<ContentProviderOperation> ops = Lists.newArrayList(); for (CPOWrapper cpoWrapper : cpoWrappers) { ops.add(cpoWrapper.getOperation()); } return diffToString(ops); } /** * Start building a {@link ContentProviderOperation} that will keep two * {@link RawContacts} together. Loading @@ -236,22 +338,41 @@ public class RawContactDeltaList extends ArrayList<RawContactDelta> implements P */ private void buildSplitContactDiff(final ArrayList<ContentProviderOperation> diff, int[] backRefs) { int count = size(); final int count = size(); for (int i = 0; i < count; i++) { for (int j = 0; j < count; j++) { if (i != j) { buildSplitContactDiff(diff, i, j, backRefs); if (i == j) { continue; } final Builder builder = buildSplitContactDiffHelper(i, j, backRefs); if (builder != null) { diff.add(builder.build()); } } } } /** * Construct a {@link AggregationExceptions#TYPE_KEEP_SEPARATE}. * For compatibility purpose, this method is copied from {@link #buildSplitContactDiff} and * takes an ArrayList of CPOWrapper as parameter. */ private void buildSplitContactDiff(ArrayList<ContentProviderOperation> diff, int index1, int index2, int[] backRefs) { Builder builder = private void buildSplitContactDiffWrapper(final ArrayList<CPOWrapper> diff, int[] backRefs) { final int count = size(); for (int i = 0; i < count; i++) { for (int j = 0; j < count; j++) { if (i == j) { continue; } final Builder builder = buildSplitContactDiffHelper(i, j, backRefs); if (builder != null) { diff.add(new CPOWrapper(builder.build(), CompatUtils.TYPE_UPDATE)); } } } } private Builder buildSplitContactDiffHelper(int index1, int index2, int[] backRefs) { final Builder builder = ContentProviderOperation.newUpdate(AggregationExceptions.CONTENT_URI); builder.withValue(AggregationExceptions.TYPE, AggregationExceptions.TYPE_KEEP_SEPARATE); Loading @@ -262,7 +383,7 @@ public class RawContactDeltaList extends ArrayList<RawContactDelta> implements P } else if (backRef1 >= 0) { builder.withValueBackReference(AggregationExceptions.RAW_CONTACT_ID1, backRef1); } else { return; return null; } Long rawContactId2 = get(index2).getValues().getAsLong(RawContacts._ID); Loading @@ -272,10 +393,9 @@ public class RawContactDeltaList extends ArrayList<RawContactDelta> implements P } else if (backRef2 >= 0) { builder.withValueBackReference(AggregationExceptions.RAW_CONTACT_ID2, backRef2); } else { return; return null; } diff.add(builder.build()); return builder; } /** Loading Loading
src/com/android/contacts/common/compat/CompatUtils.java +23 −0 Original line number Diff line number Diff line Loading @@ -18,8 +18,31 @@ package com.android.contacts.common.compat; import android.os.Build; import com.android.contacts.common.compat.SdkVersionOverride; import com.android.contacts.common.model.CPOWrapper; public final class CompatUtils { /** * These 4 variables are copied from ContentProviderOperation for compatibility. */ public final static int TYPE_INSERT = 1; public final static int TYPE_UPDATE = 2; public final static int TYPE_DELETE = 3; public final static int TYPE_ASSERT = 4; /** * Returns whether the operation in CPOWrapper is of TYPE_INSERT; */ public static boolean isInsertCompat(CPOWrapper cpoWrapper) { if (SdkVersionOverride.getSdkVersion(Build.VERSION_CODES.M) >= Build.VERSION_CODES.M) { return cpoWrapper.getOperation().isInsert(); } else { return (cpoWrapper.getType() == TYPE_INSERT); } } /** * PrioritizedMimeType is added in API level 23. */ Loading
src/com/android/contacts/common/model/BuilderWrapper.java 0 → 100644 +52 −0 Original line number Diff line number Diff line /* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.contacts.common.model; import android.content.ContentProviderOperation.Builder; /** * This class is created for the purpose of compatibility and make the type of * ContentProviderOperation available on pre-M SDKs. Since ContentProviderOperation is * usually created by Builder and we don’t have access to the type via Builder, so we need to * create a wrapper class for Builder first and include type. Then we could use the builder and * the type in this class to create a wrapper of ContentProviderOperation. */ public class BuilderWrapper { private Builder mBuilder; private int mType; public BuilderWrapper(Builder builder, int type) { mBuilder = builder; mType = type; } public int getType() { return mType; } public void setType(int mType) { this.mType = mType; } public Builder getBuilder() { return mBuilder; } public void setBuilder(Builder mBuilder) { this.mBuilder = mBuilder; } }
src/com/android/contacts/common/model/CPOWrapper.java 0 → 100644 +49 −0 Original line number Diff line number Diff line /* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.contacts.common.model; import android.content.ContentProviderOperation; /** * This class is created for the purpose of compatibility and make the type of * ContentProviderOperation available on pre-M SDKs. */ public class CPOWrapper { private ContentProviderOperation mOperation; private int mType; public CPOWrapper(ContentProviderOperation builder, int type) { mOperation = builder; mType = type; } public int getType() { return mType; } public void setType(int type) { this.mType = type; } public ContentProviderOperation getOperation() { return mOperation; } public void setOperation(ContentProviderOperation operation) { this.mOperation = operation; } }
src/com/android/contacts/common/model/RawContactDelta.java +109 −5 Original line number Diff line number Diff line Loading @@ -29,7 +29,10 @@ import android.provider.ContactsContract.Profile; import android.provider.ContactsContract.RawContacts; import android.util.Log; import com.android.contacts.common.compat.CompatUtils; import com.android.contacts.common.model.AccountTypeManager; import com.android.contacts.common.model.BuilderWrapper; import com.android.contacts.common.model.CPOWrapper; import com.android.contacts.common.model.ValuesDelta; import com.android.contacts.common.model.account.AccountType; import com.android.contacts.common.testing.NeededForTesting; Loading Loading @@ -395,25 +398,52 @@ public class RawContactDelta implements Parcelable { } } /** * For compatibility purpose, this method is copied from {@link #possibleAdd} and takes * BuilderWrapper and an ArrayList of CPOWrapper as parameters. */ private void possibleAddWrapper(ArrayList<CPOWrapper> diff, BuilderWrapper bw) { if (bw != null && bw.getBuilder() != null) { diff.add(new CPOWrapper(bw.getBuilder().build(), bw.getType())); } } /** * Build a list of {@link ContentProviderOperation} that will assert any * "before" state hasn't changed. This is maintained separately so that all * asserts can take place before any updates occur. */ public void buildAssert(ArrayList<ContentProviderOperation> buildInto) { final Builder builder = buildAssertHelper(); if (builder != null) { buildInto.add(builder.build()); } } /** * For compatibility purpose, this method is copied from {@link #buildAssert} and takes an * ArrayList of CPOWrapper as parameter. */ public void buildAssertWrapper(ArrayList<CPOWrapper> buildInto) { final Builder builder = buildAssertHelper(); if (builder != null) { buildInto.add(new CPOWrapper(builder.build(), CompatUtils.TYPE_ASSERT)); } } private Builder buildAssertHelper() { final boolean isContactInsert = mValues.isInsert(); ContentProviderOperation.Builder builder = null; if (!isContactInsert) { // Assert version is consistent while persisting changes final Long beforeId = mValues.getId(); final Long beforeVersion = mValues.getAsLong(RawContacts.VERSION); if (beforeId == null || beforeVersion == null) return; final ContentProviderOperation.Builder builder = ContentProviderOperation .newAssertQuery(mContactsQueryUri); if (beforeId == null || beforeVersion == null) return builder; builder = ContentProviderOperation.newAssertQuery(mContactsQueryUri); builder.withSelection(RawContacts._ID + "=" + beforeId, null); builder.withValue(RawContacts.VERSION, beforeVersion); buildInto.add(builder.build()); } return builder; } /** Loading Loading @@ -492,6 +522,80 @@ public class RawContactDelta implements Parcelable { } } /** * For compatibility purpose, this method is copied from {@link #buildDiff} and takes an * ArrayList of CPOWrapper as parameter. */ public void buildDiffWrapper(ArrayList<CPOWrapper> buildInto) { final int firstIndex = buildInto.size(); final boolean isContactInsert = mValues.isInsert(); final boolean isContactDelete = mValues.isDelete(); final boolean isContactUpdate = !isContactInsert && !isContactDelete; final Long beforeId = mValues.getId(); if (isContactInsert) { // TODO: for now simply disabling aggregation when a new contact is // created on the phone. In the future, will show aggregation suggestions // after saving the contact. mValues.put(RawContacts.AGGREGATION_MODE, RawContacts.AGGREGATION_MODE_SUSPENDED); } // Build possible operation at Contact level BuilderWrapper bw = mValues.buildDiffWrapper(mContactsQueryUri); possibleAddWrapper(buildInto, bw); // Build operations for all children for (ArrayList<ValuesDelta> mimeEntries : mEntries.values()) { for (ValuesDelta child : mimeEntries) { // Ignore children if parent was deleted if (isContactDelete) continue; // Use the profile data URI if the contact is the profile. if (mContactsQueryUri.equals(Profile.CONTENT_RAW_CONTACTS_URI)) { bw = child.buildDiffWrapper(Uri.withAppendedPath(Profile.CONTENT_URI, RawContacts.Data.CONTENT_DIRECTORY)); } else { bw = child.buildDiffWrapper(Data.CONTENT_URI); } if (child.isInsert()) { if (isContactInsert) { // Parent is brand new insert, so back-reference _id bw.getBuilder().withValueBackReference(Data.RAW_CONTACT_ID, firstIndex); } else { // Inserting under existing, so fill with known _id bw.getBuilder().withValue(Data.RAW_CONTACT_ID, beforeId); } } else if (isContactInsert && bw != null && bw.getBuilder() != null) { // Child must be insert when Contact insert throw new IllegalArgumentException("When parent insert, child must be also"); } possibleAddWrapper(buildInto, bw); } } final boolean addedOperations = buildInto.size() > firstIndex; if (addedOperations && isContactUpdate) { // Suspend aggregation while persisting updates Builder builder = buildSetAggregationMode(beforeId, RawContacts.AGGREGATION_MODE_SUSPENDED); buildInto.add(firstIndex, new CPOWrapper(builder.build(), CompatUtils.TYPE_UPDATE)); // Restore aggregation mode as last operation builder = buildSetAggregationMode(beforeId, RawContacts.AGGREGATION_MODE_DEFAULT); buildInto.add(firstIndex, new CPOWrapper(builder.build(), CompatUtils.TYPE_UPDATE)); } else if (isContactInsert) { // Restore aggregation mode as last operation Builder builder = ContentProviderOperation.newUpdate(mContactsQueryUri); builder.withValue(RawContacts.AGGREGATION_MODE, RawContacts.AGGREGATION_MODE_DEFAULT); builder.withSelection(RawContacts._ID + "=?", new String[1]); builder.withSelectionBackReference(0, firstIndex); buildInto.add(new CPOWrapper(builder.build(), CompatUtils.TYPE_UPDATE)); } } /** * Build a {@link ContentProviderOperation} that changes * {@link RawContacts#AGGREGATION_MODE} to the given value. Loading
src/com/android/contacts/common/model/RawContactDeltaList.java +132 −12 Original line number Diff line number Diff line Loading @@ -30,7 +30,10 @@ import android.provider.ContactsContract.Contacts; import android.provider.ContactsContract.RawContacts; import android.util.Log; import com.android.contacts.common.compat.CompatUtils; import com.android.contacts.common.model.CPOWrapper; import com.android.contacts.common.model.ValuesDelta; import com.google.common.collect.Lists; import java.util.ArrayList; Loading Loading @@ -208,8 +211,96 @@ public class RawContactDeltaList extends ArrayList<RawContactDelta> implements P return diff; } /** * For compatibility purpose, this method is copied from {@link #buildDiff} and returns an * ArrayList of CPOWrapper. */ public ArrayList<CPOWrapper> buildDiffWrapper() { if (VERBOSE_LOGGING) { Log.v(TAG, "buildDiffWrapper: list=" + toString()); } final ArrayList<CPOWrapper> diffWrapper = Lists.newArrayList(); final long rawContactId = this.findRawContactId(); int firstInsertRow = -1; // First pass enforces versions remain consistent for (RawContactDelta delta : this) { delta.buildAssertWrapper(diffWrapper); } final int assertMark = diffWrapper.size(); int backRefs[] = new int[size()]; int rawContactIndex = 0; // Second pass builds actual operations for (RawContactDelta delta : this) { final int firstBatch = diffWrapper.size(); final boolean isInsert = delta.isContactInsert(); backRefs[rawContactIndex++] = isInsert ? firstBatch : -1; delta.buildDiffWrapper(diffWrapper); // If the user chose to join with some other existing raw contact(s) at save time, // add aggregation exceptions for all those raw contacts. if (mJoinWithRawContactIds != null) { for (Long joinedRawContactId : mJoinWithRawContactIds) { final Builder builder = beginKeepTogether(); builder.withValue(AggregationExceptions.RAW_CONTACT_ID1, joinedRawContactId); if (rawContactId != -1) { builder.withValue(AggregationExceptions.RAW_CONTACT_ID2, rawContactId); } else { builder.withValueBackReference( AggregationExceptions.RAW_CONTACT_ID2, firstBatch); } diffWrapper.add(new CPOWrapper(builder.build(), CompatUtils.TYPE_UPDATE)); } } // Only create rules for inserts if (!isInsert) continue; // If we are going to split all contacts, there is no point in first combining them if (mSplitRawContacts) continue; if (rawContactId != -1) { // Has existing contact, so bind to it strongly final Builder builder = beginKeepTogether(); builder.withValue(AggregationExceptions.RAW_CONTACT_ID1, rawContactId); builder.withValueBackReference(AggregationExceptions.RAW_CONTACT_ID2, firstBatch); diffWrapper.add(new CPOWrapper(builder.build(), CompatUtils.TYPE_UPDATE)); } else if (firstInsertRow == -1) { // First insert case, so record row firstInsertRow = firstBatch; } else { // Additional insert case, so point at first insert final Builder builder = beginKeepTogether(); builder.withValueBackReference(AggregationExceptions.RAW_CONTACT_ID1, firstInsertRow); builder.withValueBackReference(AggregationExceptions.RAW_CONTACT_ID2, firstBatch); diffWrapper.add(new CPOWrapper(builder.build(), CompatUtils.TYPE_UPDATE)); } } if (mSplitRawContacts) { buildSplitContactDiffWrapper(diffWrapper, backRefs); } // No real changes if only left with asserts if (diffWrapper.size() == assertMark) { diffWrapper.clear(); } if (VERBOSE_LOGGING) { Log.v(TAG, "buildDiff: ops=" + diffToStringWrapper(diffWrapper)); } return diffWrapper; } private static String diffToString(ArrayList<ContentProviderOperation> ops) { StringBuilder sb = new StringBuilder(); final StringBuilder sb = new StringBuilder(); sb.append("[\n"); for (ContentProviderOperation op : ops) { sb.append(op.toString()); Loading @@ -219,6 +310,17 @@ public class RawContactDeltaList extends ArrayList<RawContactDelta> implements P return sb.toString(); } /** * For compatibility purpose. */ private static String diffToStringWrapper(ArrayList<CPOWrapper> cpoWrappers) { ArrayList<ContentProviderOperation> ops = Lists.newArrayList(); for (CPOWrapper cpoWrapper : cpoWrappers) { ops.add(cpoWrapper.getOperation()); } return diffToString(ops); } /** * Start building a {@link ContentProviderOperation} that will keep two * {@link RawContacts} together. Loading @@ -236,22 +338,41 @@ public class RawContactDeltaList extends ArrayList<RawContactDelta> implements P */ private void buildSplitContactDiff(final ArrayList<ContentProviderOperation> diff, int[] backRefs) { int count = size(); final int count = size(); for (int i = 0; i < count; i++) { for (int j = 0; j < count; j++) { if (i != j) { buildSplitContactDiff(diff, i, j, backRefs); if (i == j) { continue; } final Builder builder = buildSplitContactDiffHelper(i, j, backRefs); if (builder != null) { diff.add(builder.build()); } } } } /** * Construct a {@link AggregationExceptions#TYPE_KEEP_SEPARATE}. * For compatibility purpose, this method is copied from {@link #buildSplitContactDiff} and * takes an ArrayList of CPOWrapper as parameter. */ private void buildSplitContactDiff(ArrayList<ContentProviderOperation> diff, int index1, int index2, int[] backRefs) { Builder builder = private void buildSplitContactDiffWrapper(final ArrayList<CPOWrapper> diff, int[] backRefs) { final int count = size(); for (int i = 0; i < count; i++) { for (int j = 0; j < count; j++) { if (i == j) { continue; } final Builder builder = buildSplitContactDiffHelper(i, j, backRefs); if (builder != null) { diff.add(new CPOWrapper(builder.build(), CompatUtils.TYPE_UPDATE)); } } } } private Builder buildSplitContactDiffHelper(int index1, int index2, int[] backRefs) { final Builder builder = ContentProviderOperation.newUpdate(AggregationExceptions.CONTENT_URI); builder.withValue(AggregationExceptions.TYPE, AggregationExceptions.TYPE_KEEP_SEPARATE); Loading @@ -262,7 +383,7 @@ public class RawContactDeltaList extends ArrayList<RawContactDelta> implements P } else if (backRef1 >= 0) { builder.withValueBackReference(AggregationExceptions.RAW_CONTACT_ID1, backRef1); } else { return; return null; } Long rawContactId2 = get(index2).getValues().getAsLong(RawContacts._ID); Loading @@ -272,10 +393,9 @@ public class RawContactDeltaList extends ArrayList<RawContactDelta> implements P } else if (backRef2 >= 0) { builder.withValueBackReference(AggregationExceptions.RAW_CONTACT_ID2, backRef2); } else { return; return null; } diff.add(builder.build()); return builder; } /** Loading