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 17 package android.content; 18 19 import android.accounts.Account; 20 import android.os.Bundle; 21 import android.os.IBinder; 22 import android.os.Process; 23 import android.os.RemoteException; 24 25 import java.util.concurrent.atomic.AtomicInteger; 26 27 /** 28 * An abstract implementation of a SyncAdapter that spawns a thread to invoke a sync operation. 29 * If a sync operation is already in progress when a startSync() request is received then an error 30 * will be returned to the new request and the existing request will be allowed to continue. 31 * When a startSync() is received and there is no sync operation in progress then a thread 32 * will be started to run the operation and {@link #onPerformSync} will be invoked on that thread. 33 * If a cancelSync() is received that matches an existing sync operation then the thread 34 * that is running that sync operation will be interrupted, which will indicate to the thread 35 * that the sync has been canceled. 36 */ 37 public abstract class AbstractThreadedSyncAdapter { 38 /** 39 * Kernel event log tag. Also listed in data/etc/event-log-tags. 40 * @Deprecated 41 */ 42 public static final int LOG_SYNC_DETAILS = 2743; 43 44 private final Context mContext; 45 private final AtomicInteger mNumSyncStarts; 46 private final ISyncAdapterImpl mISyncAdapterImpl; 47 48 // all accesses to this member variable must be synchronized on mSyncThreadLock 49 private SyncThread mSyncThread; 50 private final Object mSyncThreadLock = new Object(); 51 52 private final boolean mAutoInitialize; 53 54 /** 55 * Creates an {@link AbstractThreadedSyncAdapter}. 56 * @param context the {@link android.content.Context} that this is running within. 57 * @param autoInitialize if true then sync requests that have 58 * {@link ContentResolver#SYNC_EXTRAS_INITIALIZE} set will be internally handled by 59 * {@link AbstractThreadedSyncAdapter} by calling 60 * {@link ContentResolver#setIsSyncable(android.accounts.Account, String, int)} with 1 if it 61 * is currently set to <0. 62 */ 63 public AbstractThreadedSyncAdapter(Context context, boolean autoInitialize) { 64 mContext = context; 65 mISyncAdapterImpl = new ISyncAdapterImpl(); 66 mNumSyncStarts = new AtomicInteger(0); 67 mSyncThread = null; 68 mAutoInitialize = autoInitialize; 69 } 70 71 public Context getContext() { 72 return mContext; 73 } 74 75 private class ISyncAdapterImpl extends ISyncAdapter.Stub { 76 public void startSync(ISyncContext syncContext, String authority, Account account, 77 Bundle extras) { 78 final SyncContext syncContextClient = new SyncContext(syncContext); 79 80 boolean alreadyInProgress; 81 // synchronize to make sure that mSyncThread doesn't change between when we 82 // check it and when we use it 83 synchronized (mSyncThreadLock) { 84 if (mSyncThread == null) { 85 if (mAutoInitialize 86 && extras != null 87 && extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false)) { 88 if (ContentResolver.getIsSyncable(account, authority) < 0) { 89 ContentResolver.setIsSyncable(account, authority, 1); 90 } 91 syncContextClient.onFinished(new SyncResult()); 92 return; 93 } 94 mSyncThread = new SyncThread( 95 "SyncAdapterThread-" + mNumSyncStarts.incrementAndGet(), 96 syncContextClient, authority, account, extras); 97 mSyncThread.start(); 98 alreadyInProgress = false; 99 } else { 100 alreadyInProgress = true; 101 } 102 } 103 104 // do this outside since we don't want to call back into the syncContext while 105 // holding the synchronization lock 106 if (alreadyInProgress) { 107 syncContextClient.onFinished(SyncResult.ALREADY_IN_PROGRESS); 108 } 109 } 110 111 public void cancelSync(ISyncContext syncContext) { 112 // synchronize to make sure that mSyncThread doesn't change between when we 113 // check it and when we use it 114 final SyncThread syncThread; 115 synchronized (mSyncThreadLock) { 116 syncThread = mSyncThread; 117 } 118 if (syncThread != null 119 && syncThread.mSyncContext.getSyncContextBinder() == syncContext.asBinder()) { 120 onSyncCanceled(); 121 } 122 } 123 124 public void initialize(Account account, String authority) throws RemoteException { 125 Bundle extras = new Bundle(); 126 extras.putBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, true); 127 startSync(null, authority, account, extras); 128 } 129 } 130 131 /** 132 * The thread that invokes {@link AbstractThreadedSyncAdapter#onPerformSync}. It also acquires 133 * the provider for this sync before calling onPerformSync and releases it afterwards. Cancel 134 * this thread in order to cancel the sync. 135 */ 136 private class SyncThread extends Thread { 137 private final SyncContext mSyncContext; 138 private final String mAuthority; 139 private final Account mAccount; 140 private final Bundle mExtras; 141 142 private SyncThread(String name, SyncContext syncContext, String authority, 143 Account account, Bundle extras) { 144 super(name); 145 mSyncContext = syncContext; 146 mAuthority = authority; 147 mAccount = account; 148 mExtras = extras; 149 } 150 151 public void run() { 152 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); 153 154 if (isCanceled()) { 155 return; 156 } 157 158 SyncResult syncResult = new SyncResult(); 159 ContentProviderClient provider = null; 160 try { 161 provider = mContext.getContentResolver().acquireContentProviderClient(mAuthority); 162 if (provider != null) { 163 AbstractThreadedSyncAdapter.this.onPerformSync(mAccount, mExtras, 164 mAuthority, provider, syncResult); 165 } else { 166 syncResult.databaseError = true; 167 } 168 } finally { 169 if (provider != null) { 170 provider.release(); 171 } 172 if (!isCanceled()) { 173 mSyncContext.onFinished(syncResult); 174 } 175 // synchronize so that the assignment will be seen by other threads 176 // that also synchronize accesses to mSyncThread 177 synchronized (mSyncThreadLock) { 178 mSyncThread = null; 179 } 180 } 181 } 182 183 private boolean isCanceled() { 184 return Thread.currentThread().isInterrupted(); 185 } 186 } 187 188 /** 189 * @return a reference to the IBinder of the SyncAdapter service. 190 */ 191 public final IBinder getSyncAdapterBinder() { 192 return mISyncAdapterImpl.asBinder(); 193 } 194 195 /** 196 * Perform a sync for this account. SyncAdapter-specific parameters may 197 * be specified in extras, which is guaranteed to not be null. Invocations 198 * of this method are guaranteed to be serialized. 199 * 200 * @param account the account that should be synced 201 * @param extras SyncAdapter-specific parameters 202 * @param authority the authority of this sync request 203 * @param provider a ContentProviderClient that points to the ContentProvider for this 204 * authority 205 * @param syncResult SyncAdapter-specific parameters 206 */ 207 public abstract void onPerformSync(Account account, Bundle extras, 208 String authority, ContentProviderClient provider, SyncResult syncResult); 209 210 /** 211 * Indicates that a sync operation has been canceled. This will be invoked on a separate 212 * thread than the sync thread and so you must consider the multi-threaded implications 213 * of the work that you do in this method. 214 * 215 */ 216 public void onSyncCanceled() { 217 final SyncThread syncThread; 218 synchronized (mSyncThreadLock) { 219 syncThread = mSyncThread; 220 } 221 if (syncThread != null) { 222 syncThread.interrupt(); 223 } 224 } 225 } 226