Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 78fe7a12 authored by Wenyi Wang's avatar Wenyi Wang Committed by Android (Google) Code Review
Browse files

Merge "Backport isInsert() using wrapper class for ContentProviderOperation...

Merge "Backport isInsert() using wrapper class for ContentProviderOperation (2/2)" into ub-contactsdialer-b-dev
parents ac99d622 93fdd48d
Loading
Loading
Loading
Loading
+23 −0
Original line number Diff line number Diff line
@@ -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.
     */
+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;
    }
}
+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;
    }
}
+109 −5
Original line number Diff line number Diff line
@@ -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;
@@ -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;
    }

    /**
@@ -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.
+132 −12
Original line number Diff line number Diff line
@@ -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;
@@ -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());
@@ -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.
@@ -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);

@@ -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);
@@ -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