ChromiumSyncAdapter.java revision 5f1c94371a64b3196d4be9466099bb892df9b88e
1// Copyright 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5package org.chromium.chrome.browser.sync;
6
7import android.accounts.Account;
8import android.app.Application;
9import android.content.AbstractThreadedSyncAdapter;
10import android.content.ContentProviderClient;
11import android.content.Context;
12import android.content.SyncResult;
13import android.os.Bundle;
14import android.os.Handler;
15import android.util.Log;
16
17import com.google.common.annotations.VisibleForTesting;
18import com.google.protos.ipc.invalidation.Types;
19
20import org.chromium.base.ThreadUtils;
21import org.chromium.base.library_loader.ProcessInitException;
22import org.chromium.content.browser.BrowserStartupController;
23
24import java.util.concurrent.Semaphore;
25
26/**
27 * A sync adapter for Chromium.
28 */
29public abstract class ChromiumSyncAdapter extends AbstractThreadedSyncAdapter {
30    private static final String TAG = "ChromiumSyncAdapter";
31
32    // TODO(nyquist) Make these fields package protected once downstream sync adapter tests are
33    // removed.
34    @VisibleForTesting
35    public static final String INVALIDATION_OBJECT_SOURCE_KEY = "objectSource";
36    @VisibleForTesting
37    public static final String INVALIDATION_OBJECT_ID_KEY = "objectId";
38    @VisibleForTesting
39    public static final String INVALIDATION_VERSION_KEY = "version";
40    @VisibleForTesting
41    public static final String INVALIDATION_PAYLOAD_KEY = "payload";
42
43    private final Application mApplication;
44    private final boolean mAsyncStartup;
45
46    public ChromiumSyncAdapter(Context context, Application application) {
47        super(context, false);
48        mApplication = application;
49        mAsyncStartup = useAsyncStartup();
50    }
51
52    protected abstract boolean useAsyncStartup();
53
54    protected abstract void initCommandLine();
55
56    @Override
57    public void onPerformSync(Account account, Bundle extras, String authority,
58                              ContentProviderClient provider, SyncResult syncResult) {
59        if (!DelayedSyncController.getInstance().shouldPerformSync(getContext(), extras, account)) {
60            return;
61        }
62
63        // Browser startup is asynchronous, so we will need to wait for startup to finish.
64        Semaphore semaphore = new Semaphore(0);
65
66        // Configure the callback with all the data it needs.
67        BrowserStartupController.StartupCallback callback =
68                getStartupCallback(mApplication, account, extras, syncResult, semaphore);
69        startBrowserProcess(callback, syncResult, semaphore);
70
71        try {
72            // Wait for startup to complete.
73            semaphore.acquire();
74        } catch (InterruptedException e) {
75            Log.w(TAG, "Got InterruptedException when trying to request a sync.", e);
76            // Using numIoExceptions so Android will treat this as a soft error.
77            syncResult.stats.numIoExceptions++;
78        }
79    }
80
81    private void startBrowserProcess(
82            final BrowserStartupController.StartupCallback callback,
83            final SyncResult syncResult, Semaphore semaphore) {
84        try {
85            ThreadUtils.runOnUiThreadBlocking(new Runnable() {
86                @Override
87                public void run() {
88                    initCommandLine();
89                    if (mAsyncStartup) {
90                        try {
91                            BrowserStartupController.get(mApplication)
92                                    .startBrowserProcessesAsync(callback);
93                        }
94                        catch (ProcessInitException e) {
95                            Log.e(TAG, "Unable to load native library.", e);
96                            System.exit(-1);
97                        }
98                    } else {
99                        startBrowserProcessesSync(callback);
100                    }
101                }
102            });
103        } catch (RuntimeException e) {
104            // It is still unknown why we ever experience this. See http://crbug.com/180044.
105            Log.w(TAG, "Got exception when trying to request a sync. Informing Android system.", e);
106            // Using numIoExceptions so Android will treat this as a soft error.
107            syncResult.stats.numIoExceptions++;
108            semaphore.release();
109        }
110    }
111
112    private void startBrowserProcessesSync(
113            final BrowserStartupController.StartupCallback callback) {
114        try {
115            BrowserStartupController.get(mApplication).startBrowserProcessesSync(false);
116        } catch (ProcessInitException e) {
117            Log.e(TAG, "Unable to load native library.", e);
118            System.exit(-1);
119        }
120        new Handler().post(new Runnable() {
121            @Override
122            public void run() {
123                callback.onSuccess(false);
124            }
125        });
126    }
127
128    private BrowserStartupController.StartupCallback getStartupCallback(
129            final Context context, final Account acct, Bundle extras,
130            final SyncResult syncResult, final Semaphore semaphore) {
131        final boolean syncAllTypes = extras.getString(INVALIDATION_OBJECT_ID_KEY) == null;
132        final int objectSource = syncAllTypes ? 0 : extras.getInt(INVALIDATION_OBJECT_SOURCE_KEY);
133        final String objectId = syncAllTypes ? "" : extras.getString(INVALIDATION_OBJECT_ID_KEY);
134        final long version = syncAllTypes ? 0 : extras.getLong(INVALIDATION_VERSION_KEY);
135        final String payload = syncAllTypes ? "" : extras.getString(INVALIDATION_PAYLOAD_KEY);
136
137        return new BrowserStartupController.StartupCallback() {
138            @Override
139            public void onSuccess(boolean alreadyStarted) {
140                // Startup succeeded, so we can tickle the sync engine.
141                if (syncAllTypes) {
142                    Log.v(TAG, "Received sync tickle for all types.");
143                    requestSyncForAllTypes();
144                } else {
145                    // Invalidations persisted before objectSource was added should be assumed to be
146                    // for Sync objects. TODO(stepco): Remove this check once all persisted
147                    // invalidations can be expected to have the objectSource.
148                    int resolvedSource = objectSource;
149                    if (resolvedSource == 0) {
150                        resolvedSource = Types.ObjectSource.Type.CHROME_SYNC.getNumber();
151                    }
152                    Log.v(TAG, "Received sync tickle for " + resolvedSource + " " + objectId + ".");
153                    requestSync(resolvedSource, objectId, version, payload);
154                }
155                semaphore.release();
156            }
157
158            @Override
159            public void onFailure() {
160                // The startup failed, so we reset the delayed sync state.
161                DelayedSyncController.getInstance().setDelayedSync(context, acct.name);
162                // Using numIoExceptions so Android will treat this as a soft error.
163                syncResult.stats.numIoExceptions++;
164                semaphore.release();
165            }
166        };
167    }
168
169    @VisibleForTesting
170    public void requestSync(int objectSource, String objectId, long version, String payload) {
171        ProfileSyncService.get(mApplication)
172                .requestSyncFromNativeChrome(objectSource, objectId, version, payload);
173    }
174
175    @VisibleForTesting
176    public void requestSyncForAllTypes() {
177        ProfileSyncService.get(mApplication).requestSyncFromNativeChromeForAllTypes();
178    }
179}
180