1ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank/*
2ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank * Copyright (C) 2008-2009 Marc Blank
3ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank * Licensed to The Android Open Source Project.
4ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank *
5ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank * Licensed under the Apache License, Version 2.0 (the "License");
6ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank * you may not use this file except in compliance with the License.
7ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank * You may obtain a copy of the License at
8ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank *
9ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank *      http://www.apache.org/licenses/LICENSE-2.0
10ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank *
11ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank * Unless required by applicable law or agreed to in writing, software
12ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank * distributed under the License is distributed on an "AS IS" BASIS,
13ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank * See the License for the specific language governing permissions and
15ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank * limitations under the License.
16ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank */
17ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
18ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blankpackage com.android.exchange.adapter;
19ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
20151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blankimport android.content.ContentProviderOperation;
21151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blankimport android.content.ContentProviderResult;
226f898deac953e5838fa28f47a16e0fb92bbc81ebMarc Blankimport android.content.ContentResolver;
23151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blankimport android.content.ContentUris;
240a4d05f0d8753c67364f7167e62cea82aef9a81eMarc Blankimport android.content.Context;
25151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blankimport android.content.OperationApplicationException;
26151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blankimport android.net.Uri;
27151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blankimport android.os.RemoteException;
28151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blankimport android.os.TransactionTooLargeException;
290a4d05f0d8753c67364f7167e62cea82aef9a81eMarc Blank
306e66ab513197793c34f5dcda159043da39224ff9Yu Ping Huimport com.android.emailcommon.provider.Account;
316e66ab513197793c34f5dcda159043da39224ff9Yu Ping Huimport com.android.emailcommon.provider.Mailbox;
326e66ab513197793c34f5dcda159043da39224ff9Yu Ping Huimport com.android.exchange.CommandStatusException;
336e66ab513197793c34f5dcda159043da39224ff9Yu Ping Huimport com.android.exchange.Eas;
34bb0141b49e7eff978fa445249dc888461ea581e3Martin Hibdonimport com.android.mail.utils.LogUtils;
356e66ab513197793c34f5dcda159043da39224ff9Yu Ping Huimport com.google.common.annotations.VisibleForTesting;
366e66ab513197793c34f5dcda159043da39224ff9Yu Ping Hu
37ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blankimport java.io.IOException;
388047ef058e41c164c2c8ab230ae8d123f042c167Marc Blankimport java.io.InputStream;
39151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blankimport java.util.ArrayList;
40ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
41ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank/**
42ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank * Parent class of all sync adapters (EasMailbox, EasCalendar, and EasContacts)
43ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank *
44ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank */
457c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blankpublic abstract class AbstractSyncAdapter {
4636e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank
4736e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank    public static final int SECONDS = 1000;
4836e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank    public static final int MINUTES = SECONDS*60;
4936e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank    public static final int HOURS = MINUTES*60;
5036e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank    public static final int DAYS = HOURS*24;
5136e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank    public static final int WEEKS = DAYS*7;
5236e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank
53151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank    private static final long SEPARATOR_ID = Long.MAX_VALUE;
54151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank
55ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    public Mailbox mMailbox;
560a4d05f0d8753c67364f7167e62cea82aef9a81eMarc Blank    public Context mContext;
570a4d05f0d8753c67364f7167e62cea82aef9a81eMarc Blank    public Account mAccount;
586f898deac953e5838fa28f47a16e0fb92bbc81ebMarc Blank    public final ContentResolver mContentResolver;
59aa288fe7ccbd28abcf990ce8337f2da677a1d370Marc Blank    public final android.accounts.Account mAccountManagerAccount;
60ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
61ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    // Create the data for local changes that need to be sent up to the server
6277186bb1a174432ef272584374942d8b9228e39cMarc Blank    public abstract boolean sendLocalChanges(Serializer s) throws IOException;
63ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    // Parse incoming data from the EAS server, creating, modifying, and deleting objects as
64ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    // required through the EmailProvider
6577186bb1a174432ef272584374942d8b9228e39cMarc Blank    public abstract boolean parse(InputStream is) throws IOException, CommandStatusException;
66ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    // The name used to specify the collection type of the target (Email, Calendar, or Contacts)
67ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    public abstract String getCollectionName();
6848af7392c82262d17700e3fbdccf3a582809d449Marc Blank    public abstract void cleanup();
69aa288fe7ccbd28abcf990ce8337f2da677a1d370Marc Blank    public abstract boolean isSyncable();
704f15001bdfd11c79524b4e44d60041967779e763Marc Blank    // Add sync options (filter, body type - html vs plain, and truncation)
71e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank    public abstract void sendSyncOptions(Double protocolVersion, Serializer s, boolean initialSync)
72e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank            throws IOException;
736f898deac953e5838fa28f47a16e0fb92bbc81ebMarc Blank    /**
746f898deac953e5838fa28f47a16e0fb92bbc81ebMarc Blank     * Delete all records of this class in this account
756f898deac953e5838fa28f47a16e0fb92bbc81ebMarc Blank     */
766f898deac953e5838fa28f47a16e0fb92bbc81ebMarc Blank    public abstract void wipe();
77ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
788efd25be4e1db3c0c79aae2ca1b4664b21bb410bMarc Blank    public boolean isLooping() {
798efd25be4e1db3c0c79aae2ca1b4664b21bb410bMarc Blank        return false;
808efd25be4e1db3c0c79aae2ca1b4664b21bb410bMarc Blank    }
818efd25be4e1db3c0c79aae2ca1b4664b21bb410bMarc Blank
82bb0141b49e7eff978fa445249dc888461ea581e3Martin Hibdon    public AbstractSyncAdapter(final Context context, final Mailbox mailbox,
83bb0141b49e7eff978fa445249dc888461ea581e3Martin Hibdon                               final Account account) {
84bb0141b49e7eff978fa445249dc888461ea581e3Martin Hibdon        mContext = context;
85bb0141b49e7eff978fa445249dc888461ea581e3Martin Hibdon        mMailbox = mailbox;
86bb0141b49e7eff978fa445249dc888461ea581e3Martin Hibdon        mAccount = account;
87aa288fe7ccbd28abcf990ce8337f2da677a1d370Marc Blank        mAccountManagerAccount = new android.accounts.Account(mAccount.mEmailAddress,
88c8e4352ea6cfa67f15140512e84af8ccede222d2Marc Blank                Eas.EXCHANGE_ACCOUNT_MANAGER_TYPE);
896f898deac953e5838fa28f47a16e0fb92bbc81ebMarc Blank        mContentResolver = mContext.getContentResolver();
900a4d05f0d8753c67364f7167e62cea82aef9a81eMarc Blank    }
910a4d05f0d8753c67364f7167e62cea82aef9a81eMarc Blank
9248af7392c82262d17700e3fbdccf3a582809d449Marc Blank    /**
9348af7392c82262d17700e3fbdccf3a582809d449Marc Blank     * Returns the current SyncKey; override if the SyncKey is stored elsewhere (as for Contacts)
9448af7392c82262d17700e3fbdccf3a582809d449Marc Blank     * @return the current SyncKey for the Mailbox
9548af7392c82262d17700e3fbdccf3a582809d449Marc Blank     * @throws IOException
9648af7392c82262d17700e3fbdccf3a582809d449Marc Blank     */
9748af7392c82262d17700e3fbdccf3a582809d449Marc Blank    public String getSyncKey() throws IOException {
9848af7392c82262d17700e3fbdccf3a582809d449Marc Blank        if (mMailbox.mSyncKey == null) {
99bb0141b49e7eff978fa445249dc888461ea581e3Martin Hibdon            LogUtils.d(LogUtils.TAG, "Reset SyncKey to 0");
10048af7392c82262d17700e3fbdccf3a582809d449Marc Blank            mMailbox.mSyncKey = "0";
10148af7392c82262d17700e3fbdccf3a582809d449Marc Blank        }
10248af7392c82262d17700e3fbdccf3a582809d449Marc Blank        return mMailbox.mSyncKey;
10348af7392c82262d17700e3fbdccf3a582809d449Marc Blank    }
10448af7392c82262d17700e3fbdccf3a582809d449Marc Blank
10548af7392c82262d17700e3fbdccf3a582809d449Marc Blank    public void setSyncKey(String syncKey, boolean inCommands) throws IOException {
10648af7392c82262d17700e3fbdccf3a582809d449Marc Blank        mMailbox.mSyncKey = syncKey;
10748af7392c82262d17700e3fbdccf3a582809d449Marc Blank    }
108151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank
109151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank    /**
110151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank     * Operation is our binder-safe ContentProviderOperation (CPO) construct; an Operation can
111151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank     * be created from a CPO, a CPO Builder, or a CPO Builder with a "back reference" column name
112151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank     * and offset (that might be used in Builder.withValueBackReference).  The CPO is not actually
113151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank     * built until it is ready to be executed (with applyBatch); this allows us to recalculate
114151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank     * back reference offsets if we are required to re-send a large batch in smaller chunks.
115151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank     *
116151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank     * NOTE: A failed binder transaction is something of an emergency case, and shouldn't happen
117151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank     * with any frequency.  When it does, and we are forced to re-send the data to the content
118151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank     * provider in smaller chunks, we DO lose the sync-window atomicity, and thereby add another
119151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank     * small risk to the data.  Of course, this is far, far better than dropping the data on the
120151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank     * floor, as was done before the framework implemented TransactionTooLargeException
121151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank     */
122151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank    protected static class Operation {
123151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank        final ContentProviderOperation mOp;
124151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank        final ContentProviderOperation.Builder mBuilder;
125151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank        final String mColumnName;
126151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank        final int mOffset;
127151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank        // Is this Operation a separator? (a good place to break up a large transaction)
128151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank        boolean mSeparator = false;
129151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank
130151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank        // For toString()
131151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank        final String[] TYPES = new String[] {"???", "Ins", "Upd", "Del", "Assert"};
132151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank
133151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank        Operation(ContentProviderOperation.Builder builder, String columnName, int offset) {
134151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank            mOp = null;
135151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank            mBuilder = builder;
136151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank            mColumnName = columnName;
137151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank            mOffset = offset;
138151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank        }
139151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank
140151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank        Operation(ContentProviderOperation.Builder builder) {
141151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank            mOp = null;
142151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank            mBuilder = builder;
143151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank            mColumnName = null;
144151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank            mOffset = 0;
145151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank        }
146151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank
147151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank        Operation(ContentProviderOperation op) {
148151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank            mOp = op;
149151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank            mBuilder = null;
150151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank            mColumnName = null;
151151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank            mOffset = 0;
152151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank        }
153151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank
154df177fdec2013e94eb77405ff1c4d0cb95707addScott Kennedy        @Override
155151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank        public String toString() {
156151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank            StringBuilder sb = new StringBuilder("Op: ");
157151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank            ContentProviderOperation op = operationToContentProviderOperation(this, 0);
158151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank            int type = 0;
159151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank            //DO NOT SHIP WITH THE FOLLOWING LINE (the API is hidden!)
160151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank            //type = op.getType();
161151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank            sb.append(TYPES[type]);
162151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank            Uri uri = op.getUri();
163151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank            sb.append(' ');
164151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank            sb.append(uri.getPath());
165151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank            if (mColumnName != null) {
166151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank                sb.append(" Back value of " + mColumnName + ": " + mOffset);
167151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank            }
168151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank            return sb.toString();
169151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank        }
170151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank    }
171151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank
172151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank    /**
173151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank     * We apply the batch of CPO's here.  We synchronize on the service to avoid thread-nasties,
174151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank     * and we just return quickly if the service has already been stopped.
175151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank     */
1766e66ab513197793c34f5dcda159043da39224ff9Yu Ping Hu    private static ContentProviderResult[] execute(final ContentResolver contentResolver,
1776e66ab513197793c34f5dcda159043da39224ff9Yu Ping Hu            final String authority, final ArrayList<ContentProviderOperation> ops)
178151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank            throws RemoteException, OperationApplicationException {
1796e66ab513197793c34f5dcda159043da39224ff9Yu Ping Hu        if (!ops.isEmpty()) {
1806e66ab513197793c34f5dcda159043da39224ff9Yu Ping Hu            ContentProviderResult[] result = contentResolver.applyBatch(authority, ops);
1816e66ab513197793c34f5dcda159043da39224ff9Yu Ping Hu            return result;
182151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank        }
183151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank        return new ContentProviderResult[0];
184151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank    }
185151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank
186151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank    /**
187151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank     * Convert an Operation to a CPO; if the Operation has a back reference, apply it with the
188151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank     * passed-in offset
189151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank     */
190151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank    @VisibleForTesting
191151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank    static ContentProviderOperation operationToContentProviderOperation(Operation op, int offset) {
192151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank        if (op.mOp != null) {
193151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank            return op.mOp;
194151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank        } else if (op.mBuilder == null) {
195151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank            throw new IllegalArgumentException("Operation must have CPO.Builder");
196151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank        }
197151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank        ContentProviderOperation.Builder builder = op.mBuilder;
198151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank        if (op.mColumnName != null) {
199151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank            builder.withValueBackReference(op.mColumnName, op.mOffset - offset);
200151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank        }
201151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank        return builder.build();
202151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank    }
203151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank
204151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank    /**
205151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank     * Create a list of CPOs from a list of Operations, and then apply them in a batch
206151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank     */
2076e66ab513197793c34f5dcda159043da39224ff9Yu Ping Hu    private static ContentProviderResult[] applyBatch(final ContentResolver contentResolver,
2086e66ab513197793c34f5dcda159043da39224ff9Yu Ping Hu            final String authority, final ArrayList<Operation> ops, final int offset)
2096e66ab513197793c34f5dcda159043da39224ff9Yu Ping Hu            throws RemoteException, OperationApplicationException {
210151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank        // Handle the empty case
211151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank        if (ops.isEmpty()) {
212151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank            return new ContentProviderResult[0];
213151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank        }
214151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank        ArrayList<ContentProviderOperation> cpos = new ArrayList<ContentProviderOperation>();
215151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank        for (Operation op: ops) {
216151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank            cpos.add(operationToContentProviderOperation(op, offset));
217151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank        }
2186e66ab513197793c34f5dcda159043da39224ff9Yu Ping Hu        return execute(contentResolver, authority, cpos);
219151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank    }
220151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank
221151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank    /**
222151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank     * Apply the list of CPO's in the provider and copy the "mini" result into our full result array
223151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank     */
2246e66ab513197793c34f5dcda159043da39224ff9Yu Ping Hu    private static void applyAndCopyResults(final ContentResolver contentResolver,
2256e66ab513197793c34f5dcda159043da39224ff9Yu Ping Hu            final String authority, final ArrayList<Operation> mini,
2266e66ab513197793c34f5dcda159043da39224ff9Yu Ping Hu            final ContentProviderResult[] result, final int offset) throws RemoteException {
227151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank        // Empty lists are ok; we just ignore them
228151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank        if (mini.isEmpty()) return;
229151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank        try {
2306e66ab513197793c34f5dcda159043da39224ff9Yu Ping Hu            ContentProviderResult[] miniResult = applyBatch(contentResolver, authority, mini,
2316e66ab513197793c34f5dcda159043da39224ff9Yu Ping Hu                    offset);
232151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank            // Copy the results from this mini-batch into our results array
233151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank            System.arraycopy(miniResult, 0, result, offset, miniResult.length);
234151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank        } catch (OperationApplicationException e) {
235151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank            // Not possible since we're building the ops ourselves
236151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank        }
237151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank    }
238151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank
239151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank    /**
240151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank     * Called by a sync adapter to execute a list of Operations in the ContentProvider handling
241151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank     * the passed-in authority.  If the attempt to apply the batch fails due to a too-large
242151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank     * binder transaction, we split the Operations as directed by separators.  If any of the
243151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank     * "mini" batches fails due to a too-large transaction, we're screwed, but this would be
244151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank     * vanishingly rare.  Other, possibly transient, errors are handled by throwing a
245151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank     * RemoteException, which the caller will likely re-throw as an IOException so that the sync
246151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank     * can be attempted again.
247151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank     *
248151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank     * Callers MAY leave a dangling separator at the end of the list; note that the separators
249151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank     * themselves are only markers and are not sent to the provider.
250151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank     */
2516e66ab513197793c34f5dcda159043da39224ff9Yu Ping Hu    protected static ContentProviderResult[] safeExecute(final ContentResolver contentResolver,
2526e66ab513197793c34f5dcda159043da39224ff9Yu Ping Hu            final String authority, final ArrayList<Operation> ops) throws RemoteException {
253151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank        ContentProviderResult[] result = null;
254151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank        try {
255151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank            // Try to execute the whole thing
2566e66ab513197793c34f5dcda159043da39224ff9Yu Ping Hu            return applyBatch(contentResolver, authority, ops, 0);
257151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank        } catch (TransactionTooLargeException e) {
258151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank            // Nope; split into smaller chunks, demarcated by the separator operation
259151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank            ArrayList<Operation> mini = new ArrayList<Operation>();
260151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank            // Build a result array with the total size we're sending
261151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank            result = new ContentProviderResult[ops.size()];
262151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank            int count = 0;
263151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank            int offset = 0;
264151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank            for (Operation op: ops) {
265151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank                if (op.mSeparator) {
266151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank                    try {
2676e66ab513197793c34f5dcda159043da39224ff9Yu Ping Hu                        applyAndCopyResults(contentResolver, authority, mini, result, offset);
268151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank                        mini.clear();
269151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank                        // Save away the offset here; this will need to be subtracted out of the
270151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank                        // value originally set by the adapter
271151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank                        offset = count + 1; // Remember to add 1 for the separator!
272151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank                    } catch (TransactionTooLargeException e1) {
273151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank                        throw new RuntimeException("Can't send transaction; sync stopped.");
274151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank                    } catch (RemoteException e1) {
275151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank                        throw e1;
276151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank                    }
277151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank                } else {
278151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank                    mini.add(op);
279151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank                }
280151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank                count++;
281151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank            }
282151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank            // Check out what's left; if it's more than just a separator, apply the batch
283151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank            int miniSize = mini.size();
284151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank            if ((miniSize > 0) && !(miniSize == 1 && mini.get(0).mSeparator)) {
2856e66ab513197793c34f5dcda159043da39224ff9Yu Ping Hu                applyAndCopyResults(contentResolver, authority, mini, result, offset);
286151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank            }
287151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank        } catch (RemoteException e) {
288151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank            throw e;
289151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank        } catch (OperationApplicationException e) {
290151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank            // Not possible since we're building the ops ourselves
291151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank        }
292151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank        return result;
293151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank    }
294151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank
295151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank    /**
296151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank     * Called by a sync adapter to indicate a relatively safe place to split a batch of CPO's
297151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank     */
2986e66ab513197793c34f5dcda159043da39224ff9Yu Ping Hu    protected static void addSeparatorOperation(ArrayList<Operation> ops, Uri uri) {
299151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank        Operation op = new Operation(
300151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank                ContentProviderOperation.newDelete(ContentUris.withAppendedId(uri, SEPARATOR_ID)));
301151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank        op.mSeparator = true;
302151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank        ops.add(op);
303151bc93f5ff1da031ff0500ab460c2aac79f7416Marc Blank    }
304ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank}
305