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