ChromiumSyncAdapter.java revision 3551c9c881056c480085172ff9840cab31610854
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;
18
19import org.chromium.base.ThreadUtils;
20import org.chromium.content.browser.AndroidBrowserProcess;
21import org.chromium.content.browser.BrowserStartupController;
22import org.chromium.content.common.ProcessInitException;
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_ID_KEY = "objectId";
36    @VisibleForTesting
37    public static final String INVALIDATION_VERSION_KEY = "version";
38    @VisibleForTesting
39    public static final String INVALIDATION_PAYLOAD_KEY = "payload";
40
41    private final Application mApplication;
42    private final boolean mAsyncStartup;
43
44    public ChromiumSyncAdapter(Context context, Application application) {
45        super(context, false);
46        mApplication = application;
47        mAsyncStartup = useAsyncStartup();
48    }
49
50    protected abstract boolean useAsyncStartup();
51
52    protected abstract void initCommandLine();
53
54    @Override
55    public void onPerformSync(Account account, Bundle extras, String authority,
56                              ContentProviderClient provider, SyncResult syncResult) {
57        if (!DelayedSyncController.getInstance().shouldPerformSync(getContext(), extras, account)) {
58            return;
59        }
60
61        // Browser startup is asynchronous, so we will need to wait for startup to finish.
62        Semaphore semaphore = new Semaphore(0);
63
64        // Configure the callback with all the data it needs.
65        BrowserStartupController.StartupCallback callback =
66                getStartupCallback(mApplication, account, extras, syncResult, semaphore);
67        startBrowserProcess(callback, syncResult, semaphore);
68
69        try {
70            // Wait for startup to complete.
71            semaphore.acquire();
72        } catch (InterruptedException e) {
73            Log.w(TAG, "Got InterruptedException when trying to request a sync.", e);
74            // Using numIoExceptions so Android will treat this as a soft error.
75            syncResult.stats.numIoExceptions++;
76        }
77    }
78
79    private void startBrowserProcess(
80            final BrowserStartupController.StartupCallback callback,
81            final SyncResult syncResult, Semaphore semaphore) {
82        try {
83            ThreadUtils.runOnUiThreadBlocking(new Runnable() {
84                @Override
85                public void run() {
86                    initCommandLine();
87                    if (mAsyncStartup) {
88                        BrowserStartupController.get(mApplication)
89                                .startBrowserProcessesAsync(callback);
90                    } else {
91                        startBrowserProcessesSync(callback);
92                    }
93                }
94            });
95        } catch (RuntimeException e) {
96            // It is still unknown why we ever experience this. See http://crbug.com/180044.
97            Log.w(TAG, "Got exception when trying to request a sync. Informing Android system.", e);
98            // Using numIoExceptions so Android will treat this as a soft error.
99            syncResult.stats.numIoExceptions++;
100            semaphore.release();
101        }
102    }
103
104    private void startBrowserProcessesSync(
105            final BrowserStartupController.StartupCallback callback) {
106        try {
107            AndroidBrowserProcess.init(
108                    mApplication, AndroidBrowserProcess.MAX_RENDERERS_LIMIT);
109            new Handler().post(new Runnable() {
110                @Override
111                public void run() {
112                    callback.onSuccess(false);
113                }
114            });
115        } catch (ProcessInitException e) {
116            Log.e(TAG, "Unable to start browser process.", e);
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 String objectId = syncAllTypes ? "" : extras.getString(INVALIDATION_OBJECT_ID_KEY);
131        final long version = syncAllTypes ? 0 : extras.getLong(INVALIDATION_VERSION_KEY);
132        final String payload = syncAllTypes ? "" : extras.getString(INVALIDATION_PAYLOAD_KEY);
133
134        return new BrowserStartupController.StartupCallback() {
135            @Override
136            public void onSuccess(boolean alreadyStarted) {
137                // Startup succeeded, so we can tickle the sync engine.
138                if (syncAllTypes) {
139                    Log.v(TAG, "Received sync tickle for all types.");
140                    requestSyncForAllTypes();
141                } else {
142                    Log.v(TAG, "Received sync tickle for " + objectId + ".");
143                    requestSync(objectId, version, payload);
144                }
145                semaphore.release();
146            }
147
148            @Override
149            public void onFailure() {
150                // The startup failed, so we reset the delayed sync state.
151                DelayedSyncController.getInstance().setDelayedSync(context, acct.name);
152                // Using numIoExceptions so Android will treat this as a soft error.
153                syncResult.stats.numIoExceptions++;
154                semaphore.release();
155            }
156        };
157    }
158
159    @VisibleForTesting
160    public void requestSync(String objectId, long version, String payload) {
161        ProfileSyncService.get(mApplication)
162                .requestSyncFromNativeChrome(objectId, version, payload);
163    }
164
165    @VisibleForTesting
166    public void requestSyncForAllTypes() {
167        ProfileSyncService.get(mApplication).requestSyncFromNativeChromeForAllTypes();
168    }
169}
170