Commit f647de8f authored by Mugdha Lakhani's avatar Mugdha Lakhani Committed by Commit Bot

[BackgroundTaskScheduler] Check requirements before firing exact task.

Check that the requirements set for the BackgroundTask that is supposed
to fire at a fixed time are met before starting it.

Bug: 970160
Change-Id: I03edb499cd607c747b583b4eca2f500c1e105ff1
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1964613
Commit-Queue: Mugdha Lakhani <nator@chromium.org>
Reviewed-by: default avatarDavid Trainor <dtrainor@chromium.org>
Reviewed-by: default avatarRichard Knoll <knollr@chromium.org>
Cr-Commit-Position: refs/heads/master@{#728165}
parent bb9c4049
...@@ -70,7 +70,6 @@ if (is_android) { ...@@ -70,7 +70,6 @@ if (is_android) {
testonly = true testonly = true
sources = [ sources = [
"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/BackgroundTaskSchedulerJobServiceTest.java", "android/javatests/src/org/chromium/components/background_task_scheduler/BackgroundTaskSchedulerJobServiceTest.java",
"android/javatests/src/org/chromium/components/background_task_scheduler/BundleToPersistableBundleConverterTest.java", "android/javatests/src/org/chromium/components/background_task_scheduler/BundleToPersistableBundleConverterTest.java",
...@@ -101,6 +100,7 @@ if (is_android) { ...@@ -101,6 +100,7 @@ if (is_android) {
"android/junit/src/org/chromium/components/background_task_scheduler/BackgroundTaskSchedulerImplTest.java", "android/junit/src/org/chromium/components/background_task_scheduler/BackgroundTaskSchedulerImplTest.java",
"android/junit/src/org/chromium/components/background_task_scheduler/BackgroundTaskSchedulerPrefsTest.java", "android/junit/src/org/chromium/components/background_task_scheduler/BackgroundTaskSchedulerPrefsTest.java",
"android/junit/src/org/chromium/components/background_task_scheduler/BackgroundTaskSchedulerUmaTest.java", "android/junit/src/org/chromium/components/background_task_scheduler/BackgroundTaskSchedulerUmaTest.java",
"android/junit/src/org/chromium/components/background_task_scheduler/BroadcastReceiverRobolectricTest.java",
"android/junit/src/org/chromium/components/background_task_scheduler/ExtrasToProtoConverterTest.java", "android/junit/src/org/chromium/components/background_task_scheduler/ExtrasToProtoConverterTest.java",
"android/junit/src/org/chromium/components/background_task_scheduler/ShadowGcmNetworkManager.java", "android/junit/src/org/chromium/components/background_task_scheduler/ShadowGcmNetworkManager.java",
"android/junit/src/org/chromium/components/background_task_scheduler/TaskInfoTest.java", "android/junit/src/org/chromium/components/background_task_scheduler/TaskInfoTest.java",
......
...@@ -7,6 +7,12 @@ package org.chromium.components.background_task_scheduler; ...@@ -7,6 +7,12 @@ 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.content.IntentFilter;
import android.net.ConnectivityManager;
import android.net.Network;
import android.net.NetworkInfo;
import android.os.BatteryManager;
import android.os.Build;
import android.os.PowerManager; import android.os.PowerManager;
import android.text.format.DateUtils; import android.text.format.DateUtils;
...@@ -18,8 +24,6 @@ import org.chromium.content_public.browser.UiThreadTaskTraits; ...@@ -18,8 +24,6 @@ import org.chromium.content_public.browser.UiThreadTaskTraits;
/** /**
* Starts running the BackgroundTask at the specified triggering time. * 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 best effort task. * execution of the task will be detached to a best effort task.
...@@ -96,8 +100,33 @@ public class BackgroundTaskBroadcastReceiver extends BroadcastReceiver { ...@@ -96,8 +100,33 @@ public class BackgroundTaskBroadcastReceiver extends BroadcastReceiver {
return; return;
} }
int taskId = taskParams.getTaskId();
ScheduledTaskProto.ScheduledTask scheduledTask =
BackgroundTaskSchedulerPrefs.getScheduledTask(taskId);
if (scheduledTask == null) {
Log.e(TAG, "Cannot get information about task with task ID " + taskId);
return;
}
// Only continue if network requirements match network status.
if (!networkRequirementsMet(context, taskId,
convertToTaskInfoNetworkType(scheduledTask.getRequiredNetworkType()))) {
Log.w(TAG,
"Failed to start task. Network requirements not satisfied for task with task ID"
+ taskId);
return;
}
// Check if battery requirements match.
if (!batteryRequirementsMet(context, taskId, scheduledTask.getRequiresCharging())) {
Log.w(TAG,
"Failed to start task. Battery requirements not satisfied for task with task ID"
+ taskId);
return;
}
final BackgroundTask backgroundTask = final BackgroundTask backgroundTask =
BackgroundTaskSchedulerFactory.getBackgroundTaskFromTaskId(taskParams.getTaskId()); BackgroundTaskSchedulerFactory.getBackgroundTaskFromTaskId(taskId);
if (backgroundTask == null) { if (backgroundTask == null) {
Log.w(TAG, "Failed to start task. Could not instantiate BackgroundTask class."); 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 // Cancel task if the BackgroundTask class is not found anymore. We assume this means
...@@ -116,4 +145,52 @@ public class BackgroundTaskBroadcastReceiver extends BroadcastReceiver { ...@@ -116,4 +145,52 @@ public class BackgroundTaskBroadcastReceiver extends BroadcastReceiver {
TaskExecutor taskExecutor = new TaskExecutor(context, wakeLock, taskParams, backgroundTask); TaskExecutor taskExecutor = new TaskExecutor(context, wakeLock, taskParams, backgroundTask);
PostTask.postTask(UiThreadTaskTraits.BEST_EFFORT, taskExecutor::execute); PostTask.postTask(UiThreadTaskTraits.BEST_EFFORT, taskExecutor::execute);
} }
private boolean networkRequirementsMet(Context context, int taskId, int requiredNetworkType) {
if (requiredNetworkType == TaskInfo.NetworkType.NONE) return true;
ConnectivityManager connectivityManager =
(ConnectivityManager) context.getApplicationContext().getSystemService(
Context.CONNECTIVITY_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
Network network = connectivityManager.getActiveNetwork();
if (requiredNetworkType == TaskInfo.NetworkType.ANY) return (network != null);
} else {
NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
if (requiredNetworkType == TaskInfo.NetworkType.ANY) return (networkInfo != null);
}
return (!connectivityManager.isActiveNetworkMetered());
}
private boolean batteryRequirementsMet(Context context, int taskId, boolean requiresCharging) {
if (!requiresCharging) return true;
BatteryManager batteryManager =
(BatteryManager) context.getSystemService(Context.BATTERY_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
return batteryManager.isCharging();
}
IntentFilter intentFilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
Intent batteryStatus = context.registerReceiver(null, intentFilter);
int status = batteryStatus.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
return status == BatteryManager.BATTERY_STATUS_CHARGING
|| status == BatteryManager.BATTERY_STATUS_FULL;
}
private @TaskInfo.NetworkType int convertToTaskInfoNetworkType(
ScheduledTaskProto.ScheduledTask.RequiredNetworkType networkType) {
switch (networkType) {
case NONE:
return TaskInfo.NetworkType.NONE;
case ANY:
return TaskInfo.NetworkType.ANY;
case UNMETERED:
return TaskInfo.NetworkType.UNMETERED;
default:
assert false : "Incorrect value of RequiredNetworkType";
return -1;
}
}
} }
...@@ -40,6 +40,7 @@ public class BackgroundTaskSchedulerAlarmManager implements BackgroundTaskSchedu ...@@ -40,6 +40,7 @@ public class BackgroundTaskSchedulerAlarmManager implements BackgroundTaskSchedu
ScheduledTaskProto.ScheduledTask scheduledTask = ScheduledTaskProto.ScheduledTask scheduledTask =
BackgroundTaskSchedulerPrefs.getScheduledTask(taskId); BackgroundTaskSchedulerPrefs.getScheduledTask(taskId);
if (scheduledTask == null) { if (scheduledTask == null) {
Log.e(TAG, "Cannot get information about task with task ID " + taskId); Log.e(TAG, "Cannot get information about task with task ID " + taskId);
return null; return null;
......
...@@ -109,7 +109,8 @@ public class BackgroundTaskSchedulerPrefs { ...@@ -109,7 +109,8 @@ public class BackgroundTaskSchedulerPrefs {
public static void addScheduledTask(TaskInfo taskInfo) { public static void addScheduledTask(TaskInfo taskInfo) {
try (TraceEvent te = TraceEvent.scoped("BackgroundTaskSchedulerPrefs.addScheduledTask", try (TraceEvent te = TraceEvent.scoped("BackgroundTaskSchedulerPrefs.addScheduledTask",
Integer.toString(taskInfo.getTaskId()))) { Integer.toString(taskInfo.getTaskId()))) {
ScheduledTaskProtoVisitor visitor = new ScheduledTaskProtoVisitor(taskInfo.getExtras()); ScheduledTaskProtoVisitor visitor = new ScheduledTaskProtoVisitor(taskInfo.getExtras(),
taskInfo.getRequiredNetworkType(), taskInfo.requiresCharging());
taskInfo.getTimingInfo().accept(visitor); taskInfo.getTimingInfo().accept(visitor);
getSharedPreferences() getSharedPreferences()
...@@ -123,9 +124,15 @@ public class BackgroundTaskSchedulerPrefs { ...@@ -123,9 +124,15 @@ public class BackgroundTaskSchedulerPrefs {
private static class ScheduledTaskProtoVisitor implements TaskInfo.TimingInfoVisitor { private static class ScheduledTaskProtoVisitor implements TaskInfo.TimingInfoVisitor {
private String mSerializedScheduledTask; private String mSerializedScheduledTask;
private final Bundle mExtras; private final Bundle mExtras;
@TaskInfo.NetworkType
private final int mRequiredNetworkType;
private final boolean mRequiresCharging;
ScheduledTaskProtoVisitor(Bundle extras) { ScheduledTaskProtoVisitor(Bundle extras, @TaskInfo.NetworkType int requiredNetworkType,
boolean requiresCharging) {
mExtras = extras; mExtras = extras;
mRequiredNetworkType = requiredNetworkType;
mRequiresCharging = requiresCharging;
} }
// Only valid after a TimingInfo object was visited. // Only valid after a TimingInfo object was visited.
...@@ -159,6 +166,9 @@ public class BackgroundTaskSchedulerPrefs { ...@@ -159,6 +166,9 @@ public class BackgroundTaskSchedulerPrefs {
ScheduledTaskProto.ScheduledTask.newBuilder() ScheduledTaskProto.ScheduledTask.newBuilder()
.setType(ScheduledTaskProto.ScheduledTask.Type.EXACT) .setType(ScheduledTaskProto.ScheduledTask.Type.EXACT)
.setTriggerMs(exactInfo.getTriggerAtMs()) .setTriggerMs(exactInfo.getTriggerAtMs())
.setRequiredNetworkType(
convertToRequiredNetworkType(mRequiredNetworkType))
.setRequiresCharging(mRequiresCharging)
.addAllExtras( .addAllExtras(
ExtrasToProtoConverter.convertExtrasToProtoExtras(mExtras)) ExtrasToProtoConverter.convertExtrasToProtoExtras(mExtras))
.build(); .build();
...@@ -283,4 +293,19 @@ public class BackgroundTaskSchedulerPrefs { ...@@ -283,4 +293,19 @@ public class BackgroundTaskSchedulerPrefs {
.apply(); .apply();
} }
} }
private static ScheduledTaskProto.ScheduledTask.RequiredNetworkType
convertToRequiredNetworkType(@TaskInfo.NetworkType int networkType) {
switch (networkType) {
case TaskInfo.NetworkType.NONE:
return ScheduledTaskProto.ScheduledTask.RequiredNetworkType.NONE;
case TaskInfo.NetworkType.ANY:
return ScheduledTaskProto.ScheduledTask.RequiredNetworkType.ANY;
case TaskInfo.NetworkType.UNMETERED:
return ScheduledTaskProto.ScheduledTask.RequiredNetworkType.UNMETERED;
default:
assert false : "Incorrect value of TaskInfo.NetworkType";
return ScheduledTaskProto.ScheduledTask.RequiredNetworkType.NONE;
}
}
} }
\ No newline at end of file
...@@ -18,6 +18,12 @@ message ScheduledTask { ...@@ -18,6 +18,12 @@ message ScheduledTask {
EXACT = 2; EXACT = 2;
} }
enum RequiredNetworkType {
NONE = 0;
ANY = 1;
UNMETERED = 2;
}
// The type of the scheduled task. // The type of the scheduled task.
Type type = 1; Type type = 1;
...@@ -62,4 +68,14 @@ message ScheduledTask { ...@@ -62,4 +68,14 @@ message ScheduledTask {
// PeriodicTask objects store their extras through their scheduling backend // PeriodicTask objects store their extras through their scheduling backend
// (JobScheduler and GcmNetworkManager). // (JobScheduler and GcmNetworkManager).
repeated ExtraItem extras = 3; repeated ExtraItem extras = 3;
// Required network type, stored for an ExactTask. OneOffTask and PeriodicTask
// objects store their required network type through their scheduling backend
// (JobScheduler and GCMNetworkManager).
RequiredNetworkType required_network_type = 4;
// Bool representing whether the task requires charging. Stored for an
// ExactTask. OneOffTask and PeriodicTask objects store this info through
// their scheduling backend (JobScheduler and GCMNetworkManager).
bool requires_charging = 5;
} }
\ No newline at end of file
// 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