blob: 53fe9f73e1f7ed1e446d1574dfcc81f5fd228b42 [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.background_task_scheduler;
import android.content.Context;
import android.support.annotation.IntDef;
import org.chromium.base.Log;
import org.chromium.base.ThreadUtils;
import org.chromium.base.VisibleForTesting;
import org.chromium.base.library_loader.LibraryProcessType;
import org.chromium.base.library_loader.ProcessInitException;
import org.chromium.base.task.PostTask;
import org.chromium.chrome.browser.init.BrowserParts;
import org.chromium.chrome.browser.init.ChromeBrowserInitializer;
import org.chromium.chrome.browser.init.EmptyBrowserParts;
import org.chromium.components.background_task_scheduler.BackgroundTask;
import org.chromium.components.background_task_scheduler.BackgroundTaskSchedulerExternalUma;
import org.chromium.components.background_task_scheduler.TaskParameters;
import org.chromium.content_public.browser.BrowserStartupController;
import org.chromium.content_public.browser.UiThreadTaskTraits;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* Base class implementing {@link BackgroundTask} that adds native initialization, ensuring that
* tasks are run after Chrome is successfully started.
*/
public abstract class NativeBackgroundTask implements BackgroundTask {
private static final String TAG = "BTS_NativeBkgrdTask";
/** Specifies which action to take following onStartTaskBeforeNativeLoaded. */
@IntDef({StartBeforeNativeResult.LOAD_NATIVE, StartBeforeNativeResult.RESCHEDULE,
StartBeforeNativeResult.DONE})
@Retention(RetentionPolicy.SOURCE)
public @interface StartBeforeNativeResult {
/** Task should continue to load native parts of browser. */
int LOAD_NATIVE = 0;
/** Task should request rescheduling, without loading native parts of browser. */
int RESCHEDULE = 1;
/** Task should neither load native parts of browser nor reschedule. */
int DONE = 2;
}
protected NativeBackgroundTask() {}
/** Indicates that the task has already been stopped. Should only be accessed on UI Thread. */
private boolean mTaskStopped;
/** The id of the task from {@link TaskParameters} used for metrics logging. */
private int mTaskId;
@Override
public final boolean onStartTask(
Context context, TaskParameters taskParameters, TaskFinishedCallback callback) {
ThreadUtils.assertOnUiThread();
mTaskId = taskParameters.getTaskId();
TaskFinishedCallback wrappedCallback = needsReschedule -> {
BackgroundTaskSchedulerExternalUma.reportNativeTaskFinished(mTaskId);
callback.taskFinished(needsReschedule);
};
// WrappedCallback will only be called when the work is done or in onStopTask. If the task
// is short-circuited early (by returning DONE or RESCHEDULE as a StartBeforeNativeResult),
// the wrappedCallback is not called. Thus task-finished metrics are only recorded if
// task-started metrics are.
@StartBeforeNativeResult
int beforeNativeResult =
onStartTaskBeforeNativeLoaded(context, taskParameters, wrappedCallback);
if (beforeNativeResult == StartBeforeNativeResult.DONE) return false;
if (beforeNativeResult == StartBeforeNativeResult.RESCHEDULE) {
// Do not pass in wrappedCallback because this is a short-circuit reschedule. For UMA
// purposes, tasks are started when runWithNative is called and does not consider
// short-circuit reschedules such as this.
PostTask.postTask(UiThreadTaskTraits.DEFAULT, buildRescheduleRunnable(callback));
return true;
}
BackgroundTaskSchedulerExternalUma.reportNativeTaskStarted(mTaskId);
assert beforeNativeResult == StartBeforeNativeResult.LOAD_NATIVE;
runWithNative(context,
buildStartWithNativeRunnable(context, taskParameters, wrappedCallback),
buildRescheduleRunnable(wrappedCallback));
return true;
}
@Override
public final boolean onStopTask(Context context, TaskParameters taskParameters) {
ThreadUtils.assertOnUiThread();
mTaskStopped = true;
BackgroundTaskSchedulerExternalUma.reportNativeTaskFinished(mTaskId);
if (isNativeLoaded()) {
return onStopTaskWithNative(context, taskParameters);
} else {
return onStopTaskBeforeNativeLoaded(context, taskParameters);
}
}
/**
* Ensure that native is started before running the task. If native fails to start, the task is
* going to be rescheduled, by issuing a {@see TaskFinishedCallback} with parameter set to
* <c>true</c>.
*
* @param context the current context
* @param startWithNativeRunnable A runnable that will execute #onStartTaskWithNative, after the
* native is loaded.
* @param rescheduleRunnable A runnable that will be called to reschedule the task in case
* native initialization fails.
*/
protected final void runWithNative(final Context context,
final Runnable startWithNativeRunnable, final Runnable rescheduleRunnable) {
if (isNativeLoaded()) {
PostTask.postTask(UiThreadTaskTraits.DEFAULT, startWithNativeRunnable);
return;
}
final BrowserParts parts = new EmptyBrowserParts() {
@Override
public void finishNativeInitialization() {
PostTask.postTask(UiThreadTaskTraits.DEFAULT, startWithNativeRunnable);
}
@Override
public boolean startServiceManagerOnly() {
return supportsServiceManagerOnly();
}
@Override
public void onStartupFailure() {
PostTask.postTask(UiThreadTaskTraits.DEFAULT, rescheduleRunnable);
}
};
PostTask.postTask(UiThreadTaskTraits.DEFAULT, new Runnable() {
@Override
public void run() {
// If task was stopped before we got here, don't start native initialization.
if (mTaskStopped) return;
BackgroundTaskSchedulerExternalUma.reportTaskStartedNative(mTaskId);
try {
ChromeBrowserInitializer.getInstance(context).handlePreNativeStartup(parts);
ChromeBrowserInitializer.getInstance(context).handlePostNativeStartup(
true /* isAsync */, parts);
} catch (ProcessInitException e) {
Log.e(TAG, "ProcessInitException while starting the browser process.");
rescheduleRunnable.run();
return;
}
}
});
}
/**
* Descendant classes should override this method if they support running in service manager
* only mode.
*
* TODO(https://crbug.com/913480): implement in a subclass once it can support running in
* ServiceManager only mode.
*
* @return if the task supports running in service manager only mode.
*/
protected boolean supportsServiceManagerOnly() {
return false;
}
/**
* Method that should be implemented in derived classes to provide implementation of {@link
* BackgroundTask#onStartTask(Context, TaskParameters, TaskFinishedCallback)} run before native
* is loaded. Task implementing the method may decide to not load native if it hasn't been
* loaded yet, by returning DONE, meaning no more work is required for the task, or RESCHEDULE,
* meaning task needs to be immediately rescheduled.
* This method is guaranteed to be called before {@link #onStartTaskWithNative}.
*/
@StartBeforeNativeResult
protected abstract int onStartTaskBeforeNativeLoaded(
Context context, TaskParameters taskParameters, TaskFinishedCallback callback);
/**
* Method that should be implemented in derived classes to provide implementation of {@link
* BackgroundTask#onStartTask(Context, TaskParameters, TaskFinishedCallback)} when native is
* loaded.
* This method will not be called unless {@link #onStartTaskBeforeNativeLoaded} returns
* LOAD_NATIVE.
*/
protected abstract void onStartTaskWithNative(
Context context, TaskParameters taskParameters, TaskFinishedCallback callback);
/** Called by {@link #onStopTask} if native part of browser was not loaded. */
protected abstract boolean onStopTaskBeforeNativeLoaded(
Context context, TaskParameters taskParameters);
/** Called by {@link #onStopTask} if native part of browser was loaded. */
protected abstract boolean onStopTaskWithNative(Context context, TaskParameters taskParameters);
/** Builds a runnable rescheduling task. */
private Runnable buildRescheduleRunnable(final TaskFinishedCallback callback) {
return new Runnable() {
@Override
public void run() {
ThreadUtils.assertOnUiThread();
if (mTaskStopped) return;
callback.taskFinished(true);
}
};
}
/** Builds a runnable starting task with native portion. */
private Runnable buildStartWithNativeRunnable(final Context context,
final TaskParameters taskParameters, final TaskFinishedCallback callback) {
return new Runnable() {
@Override
public void run() {
ThreadUtils.assertOnUiThread();
if (mTaskStopped) return;
onStartTaskWithNative(context, taskParameters, callback);
}
};
}
/** Whether the native part of the browser is loaded. */
private boolean isNativeLoaded() {
return getBrowserStartupController().isStartupSuccessfullyCompleted();
}
@VisibleForTesting
protected BrowserStartupController getBrowserStartupController() {
return BrowserStartupController.get(LibraryProcessType.PROCESS_BROWSER);
}
}