// Copyright 2013 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package org.chromium.chrome.browser.invalidation; import android.accounts.Account; import android.app.Application; import android.content.AbstractThreadedSyncAdapter; import android.content.ContentProviderClient; import android.content.ContentResolver; import android.content.Context; import android.content.SyncResult; import android.os.Bundle; import org.chromium.base.Log; import org.chromium.base.ThreadUtils; import org.chromium.base.VisibleForTesting; import org.chromium.base.annotations.SuppressFBWarnings; import org.chromium.base.library_loader.ProcessInitException; import org.chromium.chrome.browser.init.BrowserParts; import org.chromium.chrome.browser.init.ChromeBrowserInitializer; import org.chromium.chrome.browser.init.EmptyBrowserParts; import org.chromium.chrome.browser.profiles.Profile; import org.chromium.components.invalidation.PendingInvalidation; import org.chromium.components.signin.ChromeSigninController; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; /** * A Sync adapter that receives invalidations from {@link InvalidationClientService} and dispatches * it to the native side with a caching layer in {@link DelayedInvalidationsController}. */ public class ChromeBrowserSyncAdapter extends AbstractThreadedSyncAdapter { private static final String TAG = "invalidation"; private final Application mApplication; public ChromeBrowserSyncAdapter(Context context, Application application) { super(context, false); mApplication = application; } @Override public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) { if (extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE)) { Account signedInAccount = ChromeSigninController.get(getContext()).getSignedInUser(); if (account.equals(signedInAccount)) { ContentResolver.setIsSyncable(account, authority, 1); } else { ContentResolver.setIsSyncable(account, authority, 0); } return; } PendingInvalidation invalidation = new PendingInvalidation(extras); DelayedInvalidationsController controller = DelayedInvalidationsController.getInstance(); if (!controller.shouldNotifyInvalidation(extras)) { controller.addPendingInvalidation(getContext(), account.name, invalidation); return; } // Browser startup is asynchronous, so we will need to wait for startup to finish. Semaphore semaphore = new Semaphore(0); // Configure the BrowserParts with all the data it needs. BrowserParts parts = getBrowserParts(mApplication, account.name, invalidation, syncResult, semaphore); startBrowserProcess(parts, syncResult, semaphore); try { // This code is only synchronously calling a single native method // to trigger and asynchronous sync cycle, so 5 minutes is generous. if (!semaphore.tryAcquire(5, TimeUnit.MINUTES)) { Log.w(TAG, "Sync request timed out!"); syncResult.stats.numIoExceptions++; } } catch (InterruptedException e) { Log.w(TAG, "Got InterruptedException when trying to request an invalidation.", e); // Using numIoExceptions so Android will treat this as a soft error. syncResult.stats.numIoExceptions++; } } private void startBrowserProcess(final BrowserParts parts, final SyncResult syncResult, Semaphore semaphore) { try { ThreadUtils.runOnUiThreadBlocking(new Runnable() { @Override @SuppressFBWarnings("DM_EXIT") public void run() { ChromeBrowserInitializer.getInstance(getContext()).handlePreNativeStartup( parts); try { ChromeBrowserInitializer.getInstance(getContext()) .handlePostNativeStartup(false, parts); } catch (ProcessInitException e) { Log.e(TAG, "Unable to load native library.", e); System.exit(-1); } } }); } catch (RuntimeException e) { // It is still unknown why we ever experience this. See http://crbug.com/180044. Log.w(TAG, "Got exception when trying to notify the invalidation.", e); // Using numIoExceptions so Android will treat this as a soft error. syncResult.stats.numIoExceptions++; semaphore.release(); } } private BrowserParts getBrowserParts(final Context context, final String account, final PendingInvalidation invalidation, final SyncResult syncResult, final Semaphore semaphore) { return new EmptyBrowserParts() { @Override public void finishNativeInitialization() { // Startup succeeded, so we can notify the invalidation. notifyInvalidation(invalidation.mObjectSource, invalidation.mObjectId, invalidation.mVersion, invalidation.mPayload); semaphore.release(); } @Override public void onStartupFailure() { // The startup failed, so we defer the invalidation. DelayedInvalidationsController.getInstance().addPendingInvalidation( context, account, invalidation); // Using numIoExceptions so Android will treat this as a soft error. syncResult.stats.numIoExceptions++; semaphore.release(); } }; } @VisibleForTesting public void notifyInvalidation( int objectSource, String objectId, long version, String payload) { InvalidationServiceFactory.getForProfile(Profile.getLastUsedProfile()) .notifyInvalidationToNativeChrome(objectSource, objectId, version, payload); } }