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