/*
* 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;
// Minimum wait time between backups even while we're on charger
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;
// Once someone asks for a backup, this is how long we hold off until we find
// an on-charging opportunity. If we hit this max latency we will run the operation
// regardless. Privileged callers can always trigger an immediate pass via
// BackupManager.backupNow().
private static final long MAX_DEFERRAL = AlarmManager.INTERVAL_DAY;
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)
.setRequiresCharging(true)
.setOverrideDeadline(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;
}
}