blob: dc1c9d53ae24da05d89f351d93be6346b70347c2 [file] [log] [blame]
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.backup;
import android.app.AlarmManager;
import android.app.job.JobInfo;
import android.app.job.JobParameters;
import android.app.job.JobScheduler;
import android.app.job.JobService;
import android.content.ComponentName;
import android.content.Context;
import android.os.RemoteException;
import android.util.Slog;
import java.util.Random;
/**
* Job for scheduling key/value backup work. This module encapsulates all
* of the policy around when those backup passes are executed.
*/
public class KeyValueBackupJob extends JobService {
private static final String TAG = "KeyValueBackupJob";
private static ComponentName sKeyValueJobService =
new ComponentName("android", KeyValueBackupJob.class.getName());
private static final int JOB_ID = 0x5039;
// Once someone asks for a backup, this is how long we hold off, batching
// up additional requests, before running the actual backup pass. Privileged
// callers can always trigger an immediate pass via BackupManager.backupNow().
private static final long BATCH_INTERVAL = 4 * AlarmManager.INTERVAL_HOUR;
// Random variation in next-backup scheduling time to avoid server load spikes
private static final int FUZZ_MILLIS = 10 * 60 * 1000;
// Don't let the job scheduler defer forever; give it a (lenient) deadline
private static final long MAX_DEFERRAL = 1 * AlarmManager.INTERVAL_HOUR;
private static boolean sScheduled = false;
private static long sNextScheduled = 0;
public static void schedule(Context ctx) {
schedule(ctx, 0);
}
public static void schedule(Context ctx, long delay) {
synchronized (KeyValueBackupJob.class) {
if (!sScheduled) {
if (delay <= 0) {
delay = BATCH_INTERVAL + new Random().nextInt(FUZZ_MILLIS);
}
if (BackupManagerService.DEBUG_SCHEDULING) {
Slog.v(TAG, "Scheduling k/v pass in "
+ (delay / 1000 / 60) + " minutes");
}
JobScheduler js = (JobScheduler) ctx.getSystemService(Context.JOB_SCHEDULER_SERVICE);
JobInfo.Builder builder = new JobInfo.Builder(JOB_ID, sKeyValueJobService)
.setMinimumLatency(delay)
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
.setOverrideDeadline(delay + MAX_DEFERRAL);
js.schedule(builder.build());
sNextScheduled = System.currentTimeMillis() + delay;
sScheduled = true;
}
}
}
public static void cancel(Context ctx) {
synchronized (KeyValueBackupJob.class) {
JobScheduler js = (JobScheduler) ctx.getSystemService(Context.JOB_SCHEDULER_SERVICE);
js.cancel(JOB_ID);
sNextScheduled = 0;
sScheduled = false;
}
}
public static long nextScheduled() {
synchronized (KeyValueBackupJob.class) {
return sNextScheduled;
}
}
@Override
public boolean onStartJob(JobParameters params) {
synchronized (KeyValueBackupJob.class) {
sNextScheduled = 0;
sScheduled = false;
}
// Time to run a key/value backup!
Trampoline service = BackupManagerService.getInstance();
try {
service.backupNow();
} catch (RemoteException e) {}
// This was just a trigger; ongoing wakelock management is done by the
// rest of the backup system.
return false;
}
@Override
public boolean onStopJob(JobParameters params) {
// Intentionally empty; the job starting was just a trigger
return false;
}
}