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