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