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 static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; 20 21 import android.accounts.Account; 22 import android.annotation.MainThread; 23 import android.annotation.NonNull; 24 import android.os.Binder; 25 import android.os.Build; 26 import android.os.Bundle; 27 import android.os.Handler; 28 import android.os.IBinder; 29 import android.os.Process; 30 import android.os.RemoteException; 31 import android.os.Trace; 32 import android.util.Log; 33 34 import java.util.HashMap; 35 import java.util.concurrent.atomic.AtomicInteger; 36 37 /** 38 * An abstract implementation of a SyncAdapter that spawns a thread to invoke a sync operation. 39 * If a sync operation is already in progress when a sync request is received, an error will be 40 * returned to the new request and the existing request will be allowed to continue. 41 * However if there is no sync in progress then a thread will be spawned and {@link #onPerformSync} 42 * will be invoked on that thread. 43 * <p> 44 * Syncs can be cancelled at any time by the framework. For example a sync that was not 45 * user-initiated and lasts longer than 30 minutes will be considered timed-out and cancelled. 46 * Similarly the framework will attempt to determine whether or not an adapter is making progress 47 * by monitoring its network activity over the course of a minute. If the network traffic over this 48 * window is close enough to zero the sync will be cancelled. You can also request the sync be 49 * cancelled via {@link ContentResolver#cancelSync(Account, String)} or 50 * {@link ContentResolver#cancelSync(SyncRequest)}. 51 * <p> 52 * A sync is cancelled by issuing a {@link Thread#interrupt()} on the syncing thread. <strong>Either 53 * your code in {@link #onPerformSync(Account, Bundle, String, ContentProviderClient, SyncResult)} 54 * must check {@link Thread#interrupted()}, or you you must override one of 55 * {@link #onSyncCanceled(Thread)}/{@link #onSyncCanceled()}</strong> (depending on whether or not 56 * your adapter supports syncing of multiple accounts in parallel). If your adapter does not 57 * respect the cancel issued by the framework you run the risk of your app's entire process being 58 * killed. 59 * <p> 60 * In order to be a sync adapter one must extend this class, provide implementations for the 61 * abstract methods and write a service that returns the result of {@link #getSyncAdapterBinder()} 62 * in the service's {@link android.app.Service#onBind(android.content.Intent)} when invoked 63 * with an intent with action <code>android.content.SyncAdapter</code>. This service 64 * must specify the following intent filter and metadata tags in its AndroidManifest.xml file 65 * <pre> 66 * <intent-filter> 67 * <action android:name="android.content.SyncAdapter" /> 68 * </intent-filter> 69 * <meta-data android:name="android.content.SyncAdapter" 70 * android:resource="@xml/syncadapter" /> 71 * </pre> 72 * The <code>android:resource</code> attribute must point to a resource that looks like: 73 * <pre> 74 * <sync-adapter xmlns:android="http://schemas.android.com/apk/res/android" 75 * android:contentAuthority="authority" 76 * android:accountType="accountType" 77 * android:userVisible="true|false" 78 * android:supportsUploading="true|false" 79 * android:allowParallelSyncs="true|false" 80 * android:isAlwaysSyncable="true|false" 81 * android:syncAdapterSettingsAction="ACTION_OF_SETTINGS_ACTIVITY" 82 * /> 83 * </pre> 84 * <ul> 85 * <li>The <code>android:contentAuthority</code> and <code>android:accountType</code> attributes 86 * indicate which content authority and for which account types this sync adapter serves. 87 * <li><code>android:userVisible</code> defaults to true and controls whether or not this sync 88 * adapter shows up in the Sync Settings screen. 89 * <li><code>android:supportsUploading</code> defaults 90 * to true and if true an upload-only sync will be requested for all syncadapters associated 91 * with an authority whenever that authority's content provider does a 92 * {@link ContentResolver#notifyChange(android.net.Uri, android.database.ContentObserver, boolean)} 93 * with syncToNetwork set to true. 94 * <li><code>android:allowParallelSyncs</code> defaults to false and if true indicates that 95 * the sync adapter can handle syncs for multiple accounts at the same time. Otherwise 96 * the SyncManager will wait until the sync adapter is not in use before requesting that 97 * it sync an account's data. 98 * <li><code>android:isAlwaysSyncable</code> defaults to false and if true tells the SyncManager 99 * to initialize the isSyncable state to 1 for that sync adapter for each account that is added. 100 * <li><code>android:syncAdapterSettingsAction</code> defaults to null and if supplied it 101 * specifies an Intent action of an activity that can be used to adjust the sync adapter's 102 * sync settings. The activity must live in the same package as the sync adapter. 103 * </ul> 104 */ 105 public abstract class AbstractThreadedSyncAdapter { 106 private static final String TAG = "SyncAdapter"; 107 108 /** 109 * Kernel event log tag. Also listed in data/etc/event-log-tags. 110 * @deprecated Private constant. May go away in the next release. 111 */ 112 @Deprecated 113 public static final int LOG_SYNC_DETAILS = 2743; 114 115 private static final boolean ENABLE_LOG = Build.IS_DEBUGGABLE && Log.isLoggable(TAG, Log.DEBUG); 116 117 private final Context mContext; 118 private final AtomicInteger mNumSyncStarts; 119 private final ISyncAdapterImpl mISyncAdapterImpl; 120 121 // all accesses to this member variable must be synchronized on mSyncThreadLock 122 private final HashMap<Account, SyncThread> mSyncThreads = new HashMap<Account, SyncThread>(); 123 private final Object mSyncThreadLock = new Object(); 124 125 private final boolean mAutoInitialize; 126 private boolean mAllowParallelSyncs; 127 128 /** 129 * Creates an {@link AbstractThreadedSyncAdapter}. 130 * @param context the {@link android.content.Context} that this is running within. 131 * @param autoInitialize if true then sync requests that have 132 * {@link ContentResolver#SYNC_EXTRAS_INITIALIZE} set will be internally handled by 133 * {@link AbstractThreadedSyncAdapter} by calling 134 * {@link ContentResolver#setIsSyncable(android.accounts.Account, String, int)} with 1 if it 135 * is currently set to <0. 136 */ AbstractThreadedSyncAdapter(Context context, boolean autoInitialize)137 public AbstractThreadedSyncAdapter(Context context, boolean autoInitialize) { 138 this(context, autoInitialize, false /* allowParallelSyncs */); 139 } 140 141 /** 142 * Creates an {@link AbstractThreadedSyncAdapter}. 143 * @param context the {@link android.content.Context} that this is running within. 144 * @param autoInitialize if true then sync requests that have 145 * {@link ContentResolver#SYNC_EXTRAS_INITIALIZE} set will be internally handled by 146 * {@link AbstractThreadedSyncAdapter} by calling 147 * {@link ContentResolver#setIsSyncable(android.accounts.Account, String, int)} with 1 if it 148 * is currently set to <0. 149 * @param allowParallelSyncs if true then allow syncs for different accounts to run 150 * at the same time, each in their own thread. This must be consistent with the setting 151 * in the SyncAdapter's configuration file. 152 */ AbstractThreadedSyncAdapter(Context context, boolean autoInitialize, boolean allowParallelSyncs)153 public AbstractThreadedSyncAdapter(Context context, 154 boolean autoInitialize, boolean allowParallelSyncs) { 155 mContext = context; 156 mISyncAdapterImpl = new ISyncAdapterImpl(); 157 mNumSyncStarts = new AtomicInteger(0); 158 mAutoInitialize = autoInitialize; 159 mAllowParallelSyncs = allowParallelSyncs; 160 } 161 getContext()162 public Context getContext() { 163 return mContext; 164 } 165 toSyncKey(Account account)166 private Account toSyncKey(Account account) { 167 if (mAllowParallelSyncs) { 168 return account; 169 } else { 170 return null; 171 } 172 } 173 174 private class ISyncAdapterImpl extends ISyncAdapter.Stub { isCallerSystem()175 private boolean isCallerSystem() { 176 final long callingUid = Binder.getCallingUid(); 177 if (callingUid != Process.SYSTEM_UID) { 178 android.util.EventLog.writeEvent(0x534e4554, "203229608", -1, ""); 179 return false; 180 } 181 return true; 182 } 183 184 @Override onUnsyncableAccount(ISyncAdapterUnsyncableAccountCallback cb)185 public void onUnsyncableAccount(ISyncAdapterUnsyncableAccountCallback cb) { 186 if (!isCallerSystem()) { 187 return; 188 } 189 Handler.getMain().sendMessage(obtainMessage( 190 AbstractThreadedSyncAdapter::handleOnUnsyncableAccount, 191 AbstractThreadedSyncAdapter.this, cb)); 192 } 193 194 @Override startSync(ISyncContext syncContext, String authority, Account account, Bundle extras)195 public void startSync(ISyncContext syncContext, String authority, Account account, 196 Bundle extras) { 197 if (!isCallerSystem()) { 198 return; 199 } 200 if (ENABLE_LOG) { 201 if (extras != null) { 202 extras.size(); // Unparcel so its toString() will show the contents. 203 } 204 Log.d(TAG, "startSync() start " + authority + " " + account + " " + extras); 205 } 206 207 try { 208 final SyncContext syncContextClient = new SyncContext(syncContext); 209 210 boolean alreadyInProgress; 211 // synchronize to make sure that mSyncThreads doesn't change between when we 212 // check it and when we use it 213 final Account threadsKey = toSyncKey(account); 214 synchronized (mSyncThreadLock) { 215 if (!mSyncThreads.containsKey(threadsKey)) { 216 if (mAutoInitialize 217 && extras != null 218 && extras.getBoolean( 219 ContentResolver.SYNC_EXTRAS_INITIALIZE, false)) { 220 try { 221 if (ContentResolver.getIsSyncable(account, authority) < 0) { 222 ContentResolver.setIsSyncable(account, authority, 1); 223 } 224 } finally { 225 syncContextClient.onFinished(new SyncResult()); 226 } 227 return; 228 } 229 SyncThread syncThread = new SyncThread( 230 "SyncAdapterThread-" + mNumSyncStarts.incrementAndGet(), 231 syncContextClient, authority, account, extras); 232 mSyncThreads.put(threadsKey, syncThread); 233 syncThread.start(); 234 alreadyInProgress = false; 235 } else { 236 if (ENABLE_LOG) { 237 Log.d(TAG, " alreadyInProgress"); 238 } 239 alreadyInProgress = true; 240 } 241 } 242 243 // do this outside since we don't want to call back into the syncContext while 244 // holding the synchronization lock 245 if (alreadyInProgress) { 246 syncContextClient.onFinished(SyncResult.ALREADY_IN_PROGRESS); 247 } 248 } catch (RuntimeException | Error th) { 249 if (ENABLE_LOG) { 250 Log.d(TAG, "startSync() caught exception", th); 251 } 252 throw th; 253 } finally { 254 if (ENABLE_LOG) { 255 Log.d(TAG, "startSync() finishing"); 256 } 257 } 258 } 259 260 @Override cancelSync(ISyncContext syncContext)261 public void cancelSync(ISyncContext syncContext) { 262 if (!isCallerSystem()) { 263 return; 264 } 265 try { 266 // synchronize to make sure that mSyncThreads doesn't change between when we 267 // check it and when we use it 268 SyncThread info = null; 269 synchronized (mSyncThreadLock) { 270 for (SyncThread current : mSyncThreads.values()) { 271 if (current.mSyncContext.getSyncContextBinder() == syncContext.asBinder()) { 272 info = current; 273 break; 274 } 275 } 276 } 277 if (info != null) { 278 if (ENABLE_LOG) { 279 Log.d(TAG, "cancelSync() " + info.mAuthority + " " + info.mAccount); 280 } 281 if (mAllowParallelSyncs) { 282 onSyncCanceled(info); 283 } else { 284 onSyncCanceled(); 285 } 286 } else { 287 if (ENABLE_LOG) { 288 Log.w(TAG, "cancelSync() unknown context"); 289 } 290 } 291 } catch (RuntimeException | Error th) { 292 if (ENABLE_LOG) { 293 Log.d(TAG, "cancelSync() caught exception", th); 294 } 295 throw th; 296 } finally { 297 if (ENABLE_LOG) { 298 Log.d(TAG, "cancelSync() finishing"); 299 } 300 } 301 } 302 } 303 304 /** 305 * The thread that invokes {@link AbstractThreadedSyncAdapter#onPerformSync}. It also acquires 306 * the provider for this sync before calling onPerformSync and releases it afterwards. Cancel 307 * this thread in order to cancel the sync. 308 */ 309 private class SyncThread extends Thread { 310 private final SyncContext mSyncContext; 311 private final String mAuthority; 312 private final Account mAccount; 313 private final Bundle mExtras; 314 private final Account mThreadsKey; 315 SyncThread(String name, SyncContext syncContext, String authority, Account account, Bundle extras)316 private SyncThread(String name, SyncContext syncContext, String authority, 317 Account account, Bundle extras) { 318 super(name); 319 mSyncContext = syncContext; 320 mAuthority = authority; 321 mAccount = account; 322 mExtras = extras; 323 mThreadsKey = toSyncKey(account); 324 } 325 326 @Override run()327 public void run() { 328 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); 329 330 if (ENABLE_LOG) { 331 Log.d(TAG, "Thread started"); 332 } 333 334 // Trace this sync instance. Note, conceptually this should be in 335 // SyncStorageEngine.insertStartSyncEvent(), but the trace functions require unique 336 // threads in order to track overlapping operations, so we'll do it here for now. 337 Trace.traceBegin(Trace.TRACE_TAG_SYNC_MANAGER, mAuthority); 338 339 SyncResult syncResult = new SyncResult(); 340 ContentProviderClient provider = null; 341 try { 342 if (isCanceled()) { 343 if (ENABLE_LOG) { 344 Log.d(TAG, "Already canceled"); 345 } 346 return; 347 } 348 if (ENABLE_LOG) { 349 Log.d(TAG, "Calling onPerformSync..."); 350 } 351 352 provider = mContext.getContentResolver().acquireContentProviderClient(mAuthority); 353 if (provider != null) { 354 AbstractThreadedSyncAdapter.this.onPerformSync(mAccount, mExtras, 355 mAuthority, provider, syncResult); 356 } else { 357 syncResult.databaseError = true; 358 } 359 360 if (ENABLE_LOG) { 361 Log.d(TAG, "onPerformSync done"); 362 } 363 364 } catch (SecurityException e) { 365 if (ENABLE_LOG) { 366 Log.d(TAG, "SecurityException", e); 367 } 368 AbstractThreadedSyncAdapter.this.onSecurityException(mAccount, mExtras, 369 mAuthority, syncResult); 370 syncResult.databaseError = true; 371 } catch (RuntimeException | Error th) { 372 if (ENABLE_LOG) { 373 Log.d(TAG, "caught exception", th); 374 } 375 throw th; 376 } finally { 377 Trace.traceEnd(Trace.TRACE_TAG_SYNC_MANAGER); 378 379 if (provider != null) { 380 provider.release(); 381 } 382 if (!isCanceled()) { 383 mSyncContext.onFinished(syncResult); 384 } 385 // synchronize so that the assignment will be seen by other threads 386 // that also synchronize accesses to mSyncThreads 387 synchronized (mSyncThreadLock) { 388 mSyncThreads.remove(mThreadsKey); 389 } 390 391 if (ENABLE_LOG) { 392 Log.d(TAG, "Thread finished"); 393 } 394 } 395 } 396 isCanceled()397 private boolean isCanceled() { 398 return Thread.currentThread().isInterrupted(); 399 } 400 } 401 402 /** 403 * @return a reference to the IBinder of the SyncAdapter service. 404 */ getSyncAdapterBinder()405 public final IBinder getSyncAdapterBinder() { 406 return mISyncAdapterImpl.asBinder(); 407 } 408 409 /** 410 * Handle a call of onUnsyncableAccount. 411 * 412 * @param cb The callback to report the return value to 413 */ handleOnUnsyncableAccount(@onNull ISyncAdapterUnsyncableAccountCallback cb)414 private void handleOnUnsyncableAccount(@NonNull ISyncAdapterUnsyncableAccountCallback cb) { 415 boolean doSync; 416 try { 417 doSync = onUnsyncableAccount(); 418 } catch (RuntimeException e) { 419 Log.e(TAG, "Exception while calling onUnsyncableAccount, assuming 'true'", e); 420 doSync = true; 421 } 422 423 try { 424 cb.onUnsyncableAccountDone(doSync); 425 } catch (RemoteException e) { 426 Log.e(TAG, "Could not report result of onUnsyncableAccount", e); 427 } 428 } 429 430 /** 431 * Allows to defer syncing until all accounts are properly set up. 432 * 433 * <p>Called when a account / authority pair 434 * <ul> 435 * <li>that can be handled by this adapter</li> 436 * <li>{@link ContentResolver#requestSync(SyncRequest) is synced}</li> 437 * <li>and the account/provider {@link ContentResolver#getIsSyncable(Account, String) has 438 * unknown state (<0)}.</li> 439 * </ul> 440 * 441 * <p>This might be called on a different service connection as {@link #onPerformSync}. 442 * 443 * <p>The system expects this method to immediately return. If the call stalls the system 444 * behaves as if this method returned {@code true}. If it is required to perform a longer task 445 * (such as interacting with the user), return {@code false} and proceed in a difference 446 * context, such as an {@link android.app.Activity}, or foreground service. The sync can then be 447 * rescheduled once the account becomes syncable. 448 * 449 * @return If {@code false} syncing is deferred. Returns {@code true} by default, i.e. by 450 * default syncing starts immediately. 451 */ 452 @MainThread onUnsyncableAccount()453 public boolean onUnsyncableAccount() { 454 return true; 455 } 456 457 /** 458 * Perform a sync for this account. SyncAdapter-specific parameters may 459 * be specified in extras, which is guaranteed to not be null. Invocations 460 * of this method are guaranteed to be serialized. 461 * 462 * @param account the account that should be synced 463 * @param extras SyncAdapter-specific parameters 464 * @param authority the authority of this sync request 465 * @param provider a ContentProviderClient that points to the ContentProvider for this 466 * authority 467 * @param syncResult SyncAdapter-specific parameters 468 */ onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult)469 public abstract void onPerformSync(Account account, Bundle extras, 470 String authority, ContentProviderClient provider, SyncResult syncResult); 471 472 /** 473 * Report that there was a security exception when opening the content provider 474 * prior to calling {@link #onPerformSync}. This will be treated as a sync 475 * database failure. 476 * 477 * @param account the account that attempted to sync 478 * @param extras SyncAdapter-specific parameters 479 * @param authority the authority of the failed sync request 480 * @param syncResult SyncAdapter-specific parameters 481 */ onSecurityException(Account account, Bundle extras, String authority, SyncResult syncResult)482 public void onSecurityException(Account account, Bundle extras, 483 String authority, SyncResult syncResult) { 484 } 485 486 /** 487 * Indicates that a sync operation has been canceled. This will be invoked on a separate 488 * thread than the sync thread and so you must consider the multi-threaded implications 489 * of the work that you do in this method. 490 * <p> 491 * This will only be invoked when the SyncAdapter indicates that it doesn't support 492 * parallel syncs. 493 */ onSyncCanceled()494 public void onSyncCanceled() { 495 final SyncThread syncThread; 496 synchronized (mSyncThreadLock) { 497 syncThread = mSyncThreads.get(null); 498 } 499 if (syncThread != null) { 500 syncThread.interrupt(); 501 } 502 } 503 504 /** 505 * Indicates that a sync operation has been canceled. This will be invoked on a separate 506 * thread than the sync thread and so you must consider the multi-threaded implications 507 * of the work that you do in this method. 508 * <p> 509 * This will only be invoked when the SyncAdapter indicates that it does support 510 * parallel syncs. 511 * @param thread the Thread of the sync that is to be canceled. 512 */ onSyncCanceled(Thread thread)513 public void onSyncCanceled(Thread thread) { 514 thread.interrupt(); 515 } 516 } 517