blob: ca96e36df9dcbef470bc6963f8e1ab0a1f2dccea [file] [log] [blame]
// 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.sync;
import android.accounts.Account;
import android.app.Application;
import android.content.AbstractThreadedSyncAdapter;
import android.content.ContentProviderClient;
import android.content.Context;
import android.content.SyncResult;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import com.google.protos.ipc.invalidation.Types;
import org.chromium.base.ThreadUtils;
import org.chromium.base.VisibleForTesting;
import org.chromium.base.library_loader.ProcessInitException;
import org.chromium.chrome.browser.invalidation.InvalidationServiceFactory;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.content.app.ContentApplication;
import org.chromium.content.browser.BrowserStartupController;
import java.util.concurrent.Semaphore;
/**
* A sync adapter for Chromium.
*/
public abstract class ChromiumSyncAdapter extends AbstractThreadedSyncAdapter {
private static final String TAG = "ChromiumSyncAdapter";
// TODO(nyquist) Make these fields package protected once downstream sync adapter tests are
// removed.
@VisibleForTesting
public static final String INVALIDATION_OBJECT_SOURCE_KEY = "objectSource";
@VisibleForTesting
public static final String INVALIDATION_OBJECT_ID_KEY = "objectId";
@VisibleForTesting
public static final String INVALIDATION_VERSION_KEY = "version";
@VisibleForTesting
public static final String INVALIDATION_PAYLOAD_KEY = "payload";
private final Application mApplication;
private final boolean mAsyncStartup;
public ChromiumSyncAdapter(Context context, Application application) {
super(context, false);
mApplication = application;
mAsyncStartup = useAsyncStartup();
}
protected abstract boolean useAsyncStartup();
@Override
public void onPerformSync(Account account, Bundle extras, String authority,
ContentProviderClient provider, SyncResult syncResult) {
if (!DelayedSyncController.getInstance().shouldPerformSync(getContext(), extras, account)) {
return;
}
// Browser startup is asynchronous, so we will need to wait for startup to finish.
Semaphore semaphore = new Semaphore(0);
// Configure the callback with all the data it needs.
BrowserStartupController.StartupCallback callback =
getStartupCallback(mApplication, account, extras, syncResult, semaphore);
startBrowserProcess(callback, syncResult, semaphore);
try {
// Wait for startup to complete.
semaphore.acquire();
} catch (InterruptedException e) {
Log.w(TAG, "Got InterruptedException when trying to request a sync.", e);
// Using numIoExceptions so Android will treat this as a soft error.
syncResult.stats.numIoExceptions++;
}
}
private void startBrowserProcess(
final BrowserStartupController.StartupCallback callback,
final SyncResult syncResult, Semaphore semaphore) {
try {
ThreadUtils.runOnUiThreadBlocking(new Runnable() {
@Override
public void run() {
ContentApplication.initCommandLine(getContext());
if (mAsyncStartup) {
try {
BrowserStartupController.get(mApplication)
.startBrowserProcessesAsync(callback);
} catch (ProcessInitException e) {
Log.e(TAG, "Unable to load native library.", e);
System.exit(-1);
}
} else {
startBrowserProcessesSync(callback);
}
}
});
} 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 request a sync. Informing Android system.", e);
// Using numIoExceptions so Android will treat this as a soft error.
syncResult.stats.numIoExceptions++;
semaphore.release();
}
}
private void startBrowserProcessesSync(
final BrowserStartupController.StartupCallback callback) {
try {
BrowserStartupController.get(mApplication).startBrowserProcessesSync(false);
} catch (ProcessInitException e) {
Log.e(TAG, "Unable to load native library.", e);
System.exit(-1);
}
new Handler().post(new Runnable() {
@Override
public void run() {
callback.onSuccess(false);
}
});
}
private BrowserStartupController.StartupCallback getStartupCallback(
final Context context, final Account acct, Bundle extras,
final SyncResult syncResult, final Semaphore semaphore) {
final boolean syncAllTypes = extras.getString(INVALIDATION_OBJECT_ID_KEY) == null;
final int objectSource = syncAllTypes ? 0 : extras.getInt(INVALIDATION_OBJECT_SOURCE_KEY);
final String objectId = syncAllTypes ? "" : extras.getString(INVALIDATION_OBJECT_ID_KEY);
final long version = syncAllTypes ? 0 : extras.getLong(INVALIDATION_VERSION_KEY);
final String payload = syncAllTypes ? "" : extras.getString(INVALIDATION_PAYLOAD_KEY);
return new BrowserStartupController.StartupCallback() {
@Override
public void onSuccess(boolean alreadyStarted) {
// Startup succeeded, so we can tickle the sync engine.
if (syncAllTypes) {
Log.v(TAG, "Received sync tickle for all types.");
requestSyncForAllTypes();
} else {
// Invalidations persisted before objectSource was added should be assumed to be
// for Sync objects. TODO(stepco): Remove this check once all persisted
// invalidations can be expected to have the objectSource.
int resolvedSource = objectSource;
if (resolvedSource == 0) {
resolvedSource = Types.ObjectSource.CHROME_SYNC;
}
Log.v(TAG, "Received sync tickle for " + resolvedSource + " " + objectId + ".");
requestSync(resolvedSource, objectId, version, payload);
}
semaphore.release();
}
@Override
public void onFailure() {
// The startup failed, so we reset the delayed sync state.
DelayedSyncController.getInstance().setDelayedSync(context, acct.name);
// Using numIoExceptions so Android will treat this as a soft error.
syncResult.stats.numIoExceptions++;
semaphore.release();
}
};
}
@VisibleForTesting
public void requestSync(int objectSource, String objectId, long version, String payload) {
InvalidationServiceFactory.getForProfile(Profile.getLastUsedProfile())
.requestSyncFromNativeChrome(objectSource, objectId, version, payload);
}
@VisibleForTesting
public void requestSyncForAllTypes() {
InvalidationServiceFactory.getForProfile(Profile.getLastUsedProfile())
.requestSyncFromNativeChromeForAllTypes();
}
}