AbstractThreadedSyncAdapter.java revision 5ebe32deb5ce9935b9e38eef6105850c4e726083
1/* 2 * Copyright (C) 2009 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.content; 18 19import android.accounts.Account; 20import android.os.Bundle; 21import android.os.Process; 22 23import java.util.concurrent.atomic.AtomicInteger; 24 25/** 26 * An abstract implementation of a SyncAdapter that spawns a thread to invoke a sync operation. 27 * If a sync operation is already in progress when a startSync() request is received then an error 28 * will be returned to the new request and the existing request will be allowed to continue. 29 * When a startSync() is received and there is no sync operation in progress then a thread 30 * will be started to run the operation and {@link #performSync} will be invoked on that thread. 31 * If a cancelSync() is received that matches an existing sync operation then the thread 32 * that is running that sync operation will be interrupted, which will indicate to the thread 33 * that the sync has been canceled. 34 * 35 * @hide 36 */ 37public abstract class AbstractThreadedSyncAdapter { 38 private final Context mContext; 39 private final AtomicInteger mNumSyncStarts; 40 private final ISyncAdapterImpl mISyncAdapterImpl; 41 42 // all accesses to this member variable must be synchronized on mSyncThreadLock 43 private SyncThread mSyncThread; 44 private final Object mSyncThreadLock = new Object(); 45 46 /** Kernel event log tag. Also listed in data/etc/event-log-tags. */ 47 public static final int LOG_SYNC_DETAILS = 2743; 48 private final boolean mAutoInitialize; 49 50 /** 51 * Creates an {@link AbstractThreadedSyncAdapter}. 52 * @param context the {@link android.content.Context} that this is running within. 53 * @param autoInitialize if true then sync requests that have 54 * {@link ContentResolver#SYNC_EXTRAS_INITIALIZE} set will be internally handled by 55 * {@link AbstractThreadedSyncAdapter} by calling 56 * {@link ContentResolver#setIsSyncable(android.accounts.Account, String, int)} with 1 if it 57 * is currently set to <0. 58 */ 59 public AbstractThreadedSyncAdapter(Context context, boolean autoInitialize) { 60 mContext = context; 61 mISyncAdapterImpl = new ISyncAdapterImpl(); 62 mNumSyncStarts = new AtomicInteger(0); 63 mSyncThread = null; 64 mAutoInitialize = autoInitialize; 65 } 66 67 public Context getContext() { 68 return mContext; 69 } 70 71 class ISyncAdapterImpl extends ISyncAdapter.Stub { 72 public void startSync(ISyncContext syncContext, String authority, Account account, 73 Bundle extras) { 74 final SyncContext syncContextClient = new SyncContext(syncContext); 75 76 boolean alreadyInProgress; 77 // synchronize to make sure that mSyncThread doesn't change between when we 78 // check it and when we use it 79 synchronized (mSyncThreadLock) { 80 if (mSyncThread == null) { 81 if (mAutoInitialize 82 && extras != null 83 && extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false)) { 84 if (ContentResolver.getIsSyncable(account, authority) < 0) { 85 ContentResolver.setIsSyncable(account, authority, 1); 86 } 87 syncContextClient.onFinished(new SyncResult()); 88 return; 89 } 90 mSyncThread = new SyncThread( 91 "SyncAdapterThread-" + mNumSyncStarts.incrementAndGet(), 92 syncContextClient, authority, account, extras); 93 mSyncThread.start(); 94 alreadyInProgress = false; 95 } else { 96 alreadyInProgress = true; 97 } 98 } 99 100 // do this outside since we don't want to call back into the syncContext while 101 // holding the synchronization lock 102 if (alreadyInProgress) { 103 syncContextClient.onFinished(SyncResult.ALREADY_IN_PROGRESS); 104 } 105 } 106 107 public void cancelSync(ISyncContext syncContext) { 108 // synchronize to make sure that mSyncThread doesn't change between when we 109 // check it and when we use it 110 synchronized (mSyncThreadLock) { 111 if (mSyncThread != null 112 && mSyncThread.mSyncContext.getISyncContext().asBinder() 113 == syncContext.asBinder()) { 114 // TODO: figure out why canceling causes a hang 115// mSyncThread.interrupt(); 116 } 117 } 118 } 119 } 120 121 /** 122 * The thread that invokes performSync(). It also acquires the provider for this sync 123 * before calling performSync and releases it afterwards. Cancel this thread in order to 124 * cancel the sync. 125 */ 126 private class SyncThread extends Thread { 127 private final SyncContext mSyncContext; 128 private final String mAuthority; 129 private final Account mAccount; 130 private final Bundle mExtras; 131 132 private SyncThread(String name, SyncContext syncContext, String authority, 133 Account account, Bundle extras) { 134 super(name); 135 mSyncContext = syncContext; 136 mAuthority = authority; 137 mAccount = account; 138 mExtras = extras; 139 } 140 141 public void run() { 142 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); 143 144 if (isCanceled()) { 145 return; 146 } 147 148 SyncResult syncResult = new SyncResult(); 149 ContentProviderClient provider = null; 150 try { 151 provider = mContext.getContentResolver().acquireContentProviderClient(mAuthority); 152 if (provider != null) { 153 AbstractThreadedSyncAdapter.this.performSync(mAccount, mExtras, 154 mAuthority, provider, syncResult); 155 } else { 156 // TODO(fredq) update the syncResults to indicate that we were unable to 157 // find the provider. maybe with a ProviderError? 158 } 159 } finally { 160 if (provider != null) { 161 provider.release(); 162 } 163 if (!isCanceled()) { 164 mSyncContext.onFinished(syncResult); 165 } 166 // synchronize so that the assignment will be seen by other threads 167 // that also synchronize accesses to mSyncThread 168 synchronized (mSyncThreadLock) { 169 mSyncThread = null; 170 } 171 } 172 } 173 174 private boolean isCanceled() { 175 return Thread.currentThread().isInterrupted(); 176 } 177 } 178 179 /** 180 * @return a reference to the ISyncAdapter interface into this SyncAdapter implementation. 181 */ 182 public final ISyncAdapter getISyncAdapter() { 183 return mISyncAdapterImpl; 184 } 185 186 /** 187 * Perform a sync for this account. SyncAdapter-specific parameters may 188 * be specified in extras, which is guaranteed to not be null. Invocations 189 * of this method are guaranteed to be serialized. 190 * 191 * @param account the account that should be synced 192 * @param extras SyncAdapter-specific parameters 193 * @param authority the authority of this sync request 194 * @param provider a ContentProviderClient that points to the ContentProvider for this 195 * authority 196 * @param syncResult SyncAdapter-specific parameters 197 */ 198 public abstract void performSync(Account account, Bundle extras, 199 String authority, ContentProviderClient provider, SyncResult syncResult); 200}