Commit 7a11ab43 authored by Richard Knoll's avatar Richard Knoll Committed by Commit Bot

[BackgroundTaskScheduler] Exact tasks receiver

The possibility of scheduling a task at an exact time is being added to
the BackgroundTaskScheduler. Previous CLs have handled refactoring,
interface and delegation changes.

This CL adds the functionality that starts a task. A task will be
started at the decided time, the scheduling being handled by
AlarmManager. The start of the task and all the additional actions are
done through a receiver class. The start and the wait for the task are
done in a detached thread, while keeping the CPU on through a wake lock.

Bug: 970160
Change-Id: I5e981ebf34c80b6fff6cdba16e4fe2811720560f
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1778880
Commit-Queue: Richard Knoll <knollr@chromium.org>
Reviewed-by: default avatarBo <boliu@chromium.org>
Reviewed-by: default avatarTommy Nyquist <nyquist@chromium.org>
Reviewed-by: default avatarMugdha Lakhani <nator@chromium.org>
Cr-Commit-Position: refs/heads/master@{#723456}
parent 3bec6f8a
...@@ -1135,6 +1135,10 @@ by a child template that "extends" this file. ...@@ -1135,6 +1135,10 @@ by a child template that "extends" this file.
</intent-filter> </intent-filter>
</service> </service>
<!-- Background Task Scheduler alarm receiver -->
<receiver android:name="org.chromium.components.background_task_scheduler.BackgroundTaskBroadcastReceiver"
android:exported="false" />
<!-- GcmTaskService implementation to wake Chrome on scheduled events --> <!-- GcmTaskService implementation to wake Chrome on scheduled events -->
<service android:name="org.chromium.chrome.browser.ChromeBackgroundService" <service android:name="org.chromium.chrome.browser.ChromeBackgroundService"
android:permission="com.google.android.gms.permission.BIND_NETWORK_TASK_SERVICE" android:permission="com.google.android.gms.permission.BIND_NETWORK_TASK_SERVICE"
......
...@@ -1400,6 +1400,9 @@ ...@@ -1400,6 +1400,9 @@
<action android:name="android.intent.action.MY_PACKAGE_REPLACED"/> <action android:name="android.intent.action.MY_PACKAGE_REPLACED"/>
</intent-filter> </intent-filter>
</receiver> </receiver>
<receiver
android:exported="false"
android:name="org.chromium.components.background_task_scheduler.BackgroundTaskBroadcastReceiver"/>
<receiver <receiver
android:exported="true" android:exported="true"
android:name="com.google.android.gms.gcm.GcmReceiver" android:name="com.google.android.gms.gcm.GcmReceiver"
......
...@@ -68,6 +68,7 @@ if (is_android) { ...@@ -68,6 +68,7 @@ if (is_android) {
testonly = true testonly = true
java_files = [ java_files = [
"android/javatests/src/org/chromium/components/background_task_scheduler/BackgroundTaskBroadcastReceiverTest.java",
"android/javatests/src/org/chromium/components/background_task_scheduler/BackgroundTaskSchedulerImplWithMockTest.java", "android/javatests/src/org/chromium/components/background_task_scheduler/BackgroundTaskSchedulerImplWithMockTest.java",
"android/javatests/src/org/chromium/components/background_task_scheduler/MockBackgroundTaskSchedulerDelegate.java", "android/javatests/src/org/chromium/components/background_task_scheduler/MockBackgroundTaskSchedulerDelegate.java",
"android/javatests/src/org/chromium/components/background_task_scheduler/BackgroundTaskSchedulerJobServiceTest.java", "android/javatests/src/org/chromium/components/background_task_scheduler/BackgroundTaskSchedulerJobServiceTest.java",
......
include_rules = [ include_rules = [
"-content/public/android",
"+content/public/android/java/src/org/chromium/content_public",
"+content/public/test/android", "+content/public/test/android",
] ]
\ No newline at end of file
...@@ -7,20 +7,113 @@ package org.chromium.components.background_task_scheduler; ...@@ -7,20 +7,113 @@ package org.chromium.components.background_task_scheduler;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.os.PowerManager;
import android.text.format.DateUtils;
import org.chromium.base.ContextUtils;
import org.chromium.base.Log;
import org.chromium.base.ThreadUtils;
import org.chromium.base.task.PostTask;
import org.chromium.content_public.browser.UiThreadTaskTraits;
/** /**
* Checks that all requirements set for a BackgroundTask are met and if so, starts running the task. * Starts running the BackgroundTask at the specified triggering time.
* TODO (crbug.com/970160): Check that the requirements set for the BackgroundTask are met before
* starting it.
* *
* Receives the information through a broadcast, which is synchronous in the Main thread. The * Receives the information through a broadcast, which is synchronous in the Main thread. The
* execution of the task will be detached to a different thread and the program will wait for the * execution of the task will be detached to a best effort task.
* task to finish through a separate Waiter class.
*/ */
public class BackgroundTaskBroadcastReceiver extends BroadcastReceiver { public class BackgroundTaskBroadcastReceiver extends BroadcastReceiver {
private static final String TAG = "BkgrdTaskBR";
private static final String WAKELOCK_TAG = "Chromium:" + TAG;
// Wakelock is only held for 3 minutes, in order to be consistent with the restrictions of
// the GcmTaskService:
// https://developers.google.com/android/reference/com/google/android/gms/gcm/GcmTaskService.
// Here the waiting is done for only 90% of this time.
private static final long MAX_TIMEOUT_MS = 162 * DateUtils.SECOND_IN_MILLIS;
private static class TaskExecutor implements BackgroundTask.TaskFinishedCallback {
private final Context mContext;
private final PowerManager.WakeLock mWakeLock;
private final TaskParameters mTaskParams;
private final BackgroundTask mBackgroundTask;
private boolean mHasExecuted;
public TaskExecutor(Context context, PowerManager.WakeLock wakeLock,
TaskParameters taskParams, BackgroundTask backgroundTask) {
mContext = context;
mWakeLock = wakeLock;
mTaskParams = taskParams;
mBackgroundTask = backgroundTask;
}
public void execute() {
ThreadUtils.assertOnUiThread();
boolean needsBackground = mBackgroundTask.onStartTask(mContext, mTaskParams, this);
BackgroundTaskSchedulerUma.getInstance().reportTaskStarted(mTaskParams.getTaskId());
if (!needsBackground) return;
PostTask.postDelayedTask(UiThreadTaskTraits.BEST_EFFORT, this::timeout, MAX_TIMEOUT_MS);
}
@Override
public void taskFinished(boolean needsReschedule) {
PostTask.postTask(UiThreadTaskTraits.BEST_EFFORT, () -> finished(needsReschedule));
}
private void timeout() {
ThreadUtils.assertOnUiThread();
if (mHasExecuted) return;
mHasExecuted = true;
Log.w(TAG, "Task execution failed. Task timed out.");
BackgroundTaskSchedulerUma.getInstance().reportTaskStopped(mTaskParams.getTaskId());
boolean reschedule = mBackgroundTask.onStopTask(mContext, mTaskParams);
if (reschedule) mBackgroundTask.reschedule(mContext);
}
private void finished(boolean reschedule) {
ThreadUtils.assertOnUiThread();
if (mHasExecuted) return;
mHasExecuted = true;
if (reschedule) mBackgroundTask.reschedule(mContext);
// TODO(crbug.com/970160): Add UMA to record how long the tasks need to complete.
}
}
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
// TODO(crbug.com/970160): Implement general logic. final TaskParameters taskParams =
BackgroundTaskSchedulerAlarmManager.getTaskParametersFromIntent(intent);
if (taskParams == null) {
Log.w(TAG, "Failed to retrieve task parameters.");
return;
}
final BackgroundTask backgroundTask =
BackgroundTaskSchedulerFactory.getBackgroundTaskFromTaskId(taskParams.getTaskId());
if (backgroundTask == null) {
Log.w(TAG, "Failed to start task. Could not instantiate BackgroundTask class.");
// Cancel task if the BackgroundTask class is not found anymore. We assume this means
// that the task has been deprecated.
BackgroundTaskSchedulerFactory.getScheduler().cancel(
ContextUtils.getApplicationContext(), taskParams.getTaskId());
return;
}
// Keep the CPU on through a wake lock.
PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
PowerManager.WakeLock wakeLock =
powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_TAG);
wakeLock.acquire(MAX_TIMEOUT_MS);
// Not implemented. This assures the method is not called by mistake. TaskExecutor taskExecutor = new TaskExecutor(context, wakeLock, taskParams, backgroundTask);
assert false; PostTask.postTask(UiThreadTaskTraits.BEST_EFFORT, taskExecutor::execute);
} }
} }
...@@ -9,9 +9,11 @@ import android.app.PendingIntent; ...@@ -9,9 +9,11 @@ import android.app.PendingIntent;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.os.Build; import android.os.Build;
import android.os.Bundle;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import org.chromium.base.Log;
import org.chromium.base.ThreadUtils; import org.chromium.base.ThreadUtils;
/** /**
...@@ -21,6 +23,40 @@ import org.chromium.base.ThreadUtils; ...@@ -21,6 +23,40 @@ import org.chromium.base.ThreadUtils;
public class BackgroundTaskSchedulerAlarmManager implements BackgroundTaskSchedulerDelegate { public class BackgroundTaskSchedulerAlarmManager implements BackgroundTaskSchedulerDelegate {
private static final String TAG = "BkgrdTaskSchedulerAM"; private static final String TAG = "BkgrdTaskSchedulerAM";
/**
* Retrieves the {@link TaskParameters} from the {@link Intent}.
*
* @param intent the {@link Intent} to extract the {@link TaskParameters} from.
* @return the {@link TaskParameters} for the current job.
*/
static TaskParameters getTaskParametersFromIntent(Intent intent) {
Bundle extras = intent.getExtras();
int taskId = extras.getInt(BACKGROUND_TASK_ID_KEY, /* defaultValue= */ 0);
if (taskId == 0) {
Log.e(TAG, "Cannot not get task ID from intent extras.");
return null;
}
ScheduledTaskProto.ScheduledTask scheduledTask =
BackgroundTaskSchedulerPrefs.getScheduledTask(taskId);
if (scheduledTask == null) {
Log.e(TAG, "Cannot get information about task with task ID " + taskId);
return null;
}
Bundle taskExtras =
ExtrasToProtoConverter.convertProtoExtrasToExtras(scheduledTask.getExtrasList());
if (taskExtras == null) {
Log.e(TAG, "Cannot get extras data for task ID " + taskId);
return null;
}
TaskParameters.Builder builder = TaskParameters.create(taskId);
builder.addExtras(taskExtras);
return builder.build();
}
@VisibleForTesting @VisibleForTesting
static PendingIntent createPendingIntentFromTaskId(Context context, int taskId) { static PendingIntent createPendingIntentFromTaskId(Context context, int taskId) {
Intent intent = new Intent(context, BackgroundTaskBroadcastReceiver.class) Intent intent = new Intent(context, BackgroundTaskBroadcastReceiver.class)
......
...@@ -326,14 +326,14 @@ public class TaskInfo { ...@@ -326,14 +326,14 @@ public class TaskInfo {
/** /**
* Specifies information regarding exact tasks. * Specifies information regarding exact tasks.
*/ */
static class ExactInfo implements TimingInfo { public static class ExactInfo implements TimingInfo {
private final long mTriggerAtMs; private final long mTriggerAtMs;
private ExactInfo(Builder builder) { private ExactInfo(Builder builder) {
mTriggerAtMs = builder.mTriggerAtMs; mTriggerAtMs = builder.mTriggerAtMs;
} }
long getTriggerAtMs() { public long getTriggerAtMs() {
return mTriggerAtMs; return mTriggerAtMs;
} }
...@@ -352,7 +352,7 @@ public class TaskInfo { ...@@ -352,7 +352,7 @@ public class TaskInfo {
/** /**
* @return a new {@link Builder} object to set the values of the exact task. * @return a new {@link Builder} object to set the values of the exact task.
*/ */
static Builder create() { public static Builder create() {
return new Builder(); return new Builder();
} }
...@@ -361,7 +361,7 @@ public class TaskInfo { ...@@ -361,7 +361,7 @@ public class TaskInfo {
* *
* @see #create() * @see #create()
*/ */
static final class Builder { public static final class Builder {
private long mTriggerAtMs; private long mTriggerAtMs;
/** /**
...@@ -369,7 +369,7 @@ public class TaskInfo { ...@@ -369,7 +369,7 @@ public class TaskInfo {
* @param triggerAtMs the UTC timestamp at which the task should be started. * @param triggerAtMs the UTC timestamp at which the task should be started.
* @return the {@link Builder} for creating the {@link ExactInfo} object. * @return the {@link Builder} for creating the {@link ExactInfo} object.
*/ */
Builder setTriggerAtMs(long triggerAtMs) { public Builder setTriggerAtMs(long triggerAtMs) {
mTriggerAtMs = triggerAtMs; mTriggerAtMs = triggerAtMs;
return this; return this;
} }
...@@ -379,7 +379,7 @@ public class TaskInfo { ...@@ -379,7 +379,7 @@ public class TaskInfo {
* *
* @return the {@link ExactInfo} object. * @return the {@link ExactInfo} object.
*/ */
ExactInfo build() { public ExactInfo build() {
return new ExactInfo(this); return new ExactInfo(this);
} }
} }
......
// Copyright 2019 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.components.background_task_scheduler;
import static org.junit.Assert.assertEquals;
import android.content.Context;
import android.content.Intent;
import android.support.test.filters.MediumTest;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.chromium.base.ContextUtils;
import org.chromium.base.ThreadUtils;
import org.chromium.base.test.BaseJUnit4ClassRunner;
import org.chromium.content_public.browser.test.util.CriteriaHelper;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
/** Tests for {@link BackgroundTaskBroadcastReceiver}. */
@RunWith(BaseJUnit4ClassRunner.class)
public class BackgroundTaskBroadcastReceiverTest {
class TestBackgroundTask implements BackgroundTask {
public TestBackgroundTask() {}
@Override
public boolean onStartTask(
Context context, TaskParameters taskParameters, TaskFinishedCallback callback) {
ThreadUtils.assertOnUiThread();
mScheduled++;
return false;
}
@Override
public boolean onStopTask(Context context, TaskParameters taskParameters) {
ThreadUtils.assertOnUiThread();
mStopped++;
return false;
}
@Override
public void reschedule(Context context) {
ThreadUtils.assertOnUiThread();
mRescheduled++;
}
}
class TestBackgroundTaskFactory implements BackgroundTaskFactory {
@Override
public BackgroundTask getBackgroundTaskFromTaskId(int taskId) {
if (taskId == TaskIds.TEST) {
return new TestBackgroundTask();
}
return null;
}
}
private int mScheduled;
private int mStopped;
private int mRescheduled;
@Before
public void setUp() {
mScheduled = 0;
mStopped = 0;
mRescheduled = 0;
BackgroundTaskSchedulerFactory.setBackgroundTaskFactory(new TestBackgroundTaskFactory());
}
@Test
@MediumTest
public void testStartExact() {
TaskInfo.TimingInfo exactInfo =
TaskInfo.ExactInfo.create().setTriggerAtMs(System.currentTimeMillis()).build();
TaskInfo exactTask = TaskInfo.createTask(TaskIds.TEST, exactInfo).build();
BackgroundTaskSchedulerPrefs.addScheduledTask(exactTask);
Intent intent = new Intent(
ContextUtils.getApplicationContext(), BackgroundTaskBroadcastReceiver.class)
.putExtra(BackgroundTaskSchedulerDelegate.BACKGROUND_TASK_ID_KEY,
TaskIds.TEST);
BackgroundTaskBroadcastReceiver receiver = new BackgroundTaskBroadcastReceiver();
receiver.onReceive(ContextUtils.getApplicationContext(), intent);
Callable<Boolean> hasScheduled = () -> mScheduled == 1;
CriteriaHelper.pollUiThread(hasScheduled, "Failed to schedule task",
/*maxTimeoutMs=*/TimeUnit.MINUTES.toMillis(2), /*checkIntervalMs=*/50);
assertEquals(0, mStopped);
assertEquals(0, mRescheduled);
}
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment