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.protos.ipc.invalidation.Types; 18 19import org.chromium.base.ThreadUtils; 20import org.chromium.base.VisibleForTesting; 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 } catch (ProcessInitException e) { 94 Log.e(TAG, "Unable to load native library.", e); 95 System.exit(-1); 96 } 97 } else { 98 startBrowserProcessesSync(callback); 99 } 100 } 101 }); 102 } catch (RuntimeException e) { 103 // It is still unknown why we ever experience this. See http://crbug.com/180044. 104 Log.w(TAG, "Got exception when trying to request a sync. Informing Android system.", e); 105 // Using numIoExceptions so Android will treat this as a soft error. 106 syncResult.stats.numIoExceptions++; 107 semaphore.release(); 108 } 109 } 110 111 private void startBrowserProcessesSync( 112 final BrowserStartupController.StartupCallback callback) { 113 try { 114 BrowserStartupController.get(mApplication).startBrowserProcessesSync(false); 115 } catch (ProcessInitException e) { 116 Log.e(TAG, "Unable to load native library.", e); 117 System.exit(-1); 118 } 119 new Handler().post(new Runnable() { 120 @Override 121 public void run() { 122 callback.onSuccess(false); 123 } 124 }); 125 } 126 127 private BrowserStartupController.StartupCallback getStartupCallback( 128 final Context context, final Account acct, Bundle extras, 129 final SyncResult syncResult, final Semaphore semaphore) { 130 final boolean syncAllTypes = extras.getString(INVALIDATION_OBJECT_ID_KEY) == null; 131 final int objectSource = syncAllTypes ? 0 : extras.getInt(INVALIDATION_OBJECT_SOURCE_KEY); 132 final String objectId = syncAllTypes ? "" : extras.getString(INVALIDATION_OBJECT_ID_KEY); 133 final long version = syncAllTypes ? 0 : extras.getLong(INVALIDATION_VERSION_KEY); 134 final String payload = syncAllTypes ? "" : extras.getString(INVALIDATION_PAYLOAD_KEY); 135 136 return new BrowserStartupController.StartupCallback() { 137 @Override 138 public void onSuccess(boolean alreadyStarted) { 139 // Startup succeeded, so we can tickle the sync engine. 140 if (syncAllTypes) { 141 Log.v(TAG, "Received sync tickle for all types."); 142 requestSyncForAllTypes(); 143 } else { 144 // Invalidations persisted before objectSource was added should be assumed to be 145 // for Sync objects. TODO(stepco): Remove this check once all persisted 146 // invalidations can be expected to have the objectSource. 147 int resolvedSource = objectSource; 148 if (resolvedSource == 0) { 149 resolvedSource = Types.ObjectSource.CHROME_SYNC; 150 } 151 Log.v(TAG, "Received sync tickle for " + resolvedSource + " " + objectId + "."); 152 requestSync(resolvedSource, objectId, version, payload); 153 } 154 semaphore.release(); 155 } 156 157 @Override 158 public void onFailure() { 159 // The startup failed, so we reset the delayed sync state. 160 DelayedSyncController.getInstance().setDelayedSync(context, acct.name); 161 // Using numIoExceptions so Android will treat this as a soft error. 162 syncResult.stats.numIoExceptions++; 163 semaphore.release(); 164 } 165 }; 166 } 167 168 @VisibleForTesting 169 public void requestSync(int objectSource, String objectId, long version, String payload) { 170 ProfileSyncService.get(mApplication) 171 .requestSyncFromNativeChrome(objectSource, objectId, version, payload); 172 } 173 174 @VisibleForTesting 175 public void requestSyncForAllTypes() { 176 ProfileSyncService.get(mApplication).requestSyncFromNativeChromeForAllTypes(); 177 } 178} 179