1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.backup;
18 
19 import static com.android.server.backup.BackupManagerService.DEBUG_SCHEDULING;
20 
21 import android.app.AlarmManager;
22 import android.app.job.JobInfo;
23 import android.app.job.JobParameters;
24 import android.app.job.JobScheduler;
25 import android.app.job.JobService;
26 import android.content.ComponentName;
27 import android.content.Context;
28 import android.os.Bundle;
29 import android.os.RemoteException;
30 import android.util.Slog;
31 import android.util.SparseBooleanArray;
32 import android.util.SparseLongArray;
33 
34 import com.android.internal.annotations.GuardedBy;
35 import com.android.internal.annotations.VisibleForTesting;
36 
37 import java.util.Random;
38 
39 /**
40  * Job for scheduling key/value backup work.  This module encapsulates all
41  * of the policy around when those backup passes are executed.
42  */
43 public class KeyValueBackupJob extends JobService {
44     private static final String TAG = "KeyValueBackupJob";
45     private static ComponentName sKeyValueJobService =
46             new ComponentName("android", KeyValueBackupJob.class.getName());
47 
48     private static final String USER_ID_EXTRA_KEY = "userId";
49 
50     // Once someone asks for a backup, this is how long we hold off until we find
51     // an on-charging opportunity.  If we hit this max latency we will run the operation
52     // regardless.  Privileged callers can always trigger an immediate pass via
53     // BackupManager.backupNow().
54     private static final long MAX_DEFERRAL = AlarmManager.INTERVAL_DAY;
55 
56     @GuardedBy("KeyValueBackupJob.class")
57     private static final SparseBooleanArray sScheduledForUserId = new SparseBooleanArray();
58     @GuardedBy("KeyValueBackupJob.class")
59     private static final SparseLongArray sNextScheduledForUserId = new SparseLongArray();
60 
61     @VisibleForTesting
62     public static final int MIN_JOB_ID = 52417896;
63     @VisibleForTesting
64     public static final int MAX_JOB_ID = 52418896;
65 
schedule(int userId, Context ctx, BackupManagerConstants constants)66     public static void schedule(int userId, Context ctx, BackupManagerConstants constants) {
67         schedule(userId, ctx, 0, constants);
68     }
69 
schedule(int userId, Context ctx, long delay, BackupManagerConstants constants)70     public static void schedule(int userId, Context ctx, long delay,
71             BackupManagerConstants constants) {
72         synchronized (KeyValueBackupJob.class) {
73             if (sScheduledForUserId.get(userId)) {
74                 return;
75             }
76 
77             final long interval;
78             final long fuzz;
79             final int networkType;
80             final boolean needsCharging;
81 
82             synchronized (constants) {
83                 interval = constants.getKeyValueBackupIntervalMilliseconds();
84                 fuzz = constants.getKeyValueBackupFuzzMilliseconds();
85                 networkType = constants.getKeyValueBackupRequiredNetworkType();
86                 needsCharging = constants.getKeyValueBackupRequireCharging();
87             }
88             if (delay <= 0) {
89                 delay = interval + new Random().nextInt((int) fuzz);
90             }
91             if (DEBUG_SCHEDULING) {
92                 Slog.v(TAG, "Scheduling k/v pass in " + (delay / 1000 / 60) + " minutes");
93             }
94 
95             JobInfo.Builder builder = new JobInfo.Builder(getJobIdForUserId(userId),
96                     sKeyValueJobService)
97                     .setMinimumLatency(delay)
98                     .setRequiredNetworkType(networkType)
99                     .setRequiresCharging(needsCharging)
100                     .setOverrideDeadline(MAX_DEFERRAL);
101 
102             Bundle extraInfo = new Bundle();
103             extraInfo.putInt(USER_ID_EXTRA_KEY, userId);
104             builder.setTransientExtras(extraInfo);
105 
106             JobScheduler js = (JobScheduler) ctx.getSystemService(Context.JOB_SCHEDULER_SERVICE);
107             js.schedule(builder.build());
108 
109             sScheduledForUserId.put(userId, true);
110             sNextScheduledForUserId.put(userId, System.currentTimeMillis() + delay);
111         }
112     }
113 
cancel(int userId, Context ctx)114     public static void cancel(int userId, Context ctx) {
115         synchronized (KeyValueBackupJob.class) {
116             JobScheduler js = (JobScheduler) ctx.getSystemService(
117                     Context.JOB_SCHEDULER_SERVICE);
118             js.cancel(getJobIdForUserId(userId));
119 
120             clearScheduledForUserId(userId);
121         }
122     }
123 
nextScheduled(int userId)124     public static long nextScheduled(int userId) {
125         synchronized (KeyValueBackupJob.class) {
126             return sNextScheduledForUserId.get(userId);
127         }
128     }
129 
130     @VisibleForTesting
isScheduled(int userId)131     public static boolean isScheduled(int userId) {
132         synchronized (KeyValueBackupJob.class) {
133             return sScheduledForUserId.get(userId);
134         }
135     }
136 
137     @Override
onStartJob(JobParameters params)138     public boolean onStartJob(JobParameters params) {
139         int userId = params.getTransientExtras().getInt(USER_ID_EXTRA_KEY);
140 
141         synchronized (KeyValueBackupJob.class) {
142             clearScheduledForUserId(userId);
143         }
144 
145         // Time to run a key/value backup!
146         Trampoline service = BackupManagerService.getInstance();
147         try {
148             service.backupNowForUser(userId);
149         } catch (RemoteException e) {}
150 
151         // This was just a trigger; ongoing wakelock management is done by the
152         // rest of the backup system.
153         return false;
154     }
155 
156     @Override
onStopJob(JobParameters params)157     public boolean onStopJob(JobParameters params) {
158         // Intentionally empty; the job starting was just a trigger
159         return false;
160     }
161 
162     @GuardedBy("KeyValueBackupJob.class")
clearScheduledForUserId(int userId)163     private static void clearScheduledForUserId(int userId) {
164         sScheduledForUserId.delete(userId);
165         sNextScheduledForUserId.delete(userId);
166     }
167 
getJobIdForUserId(int userId)168     private static int getJobIdForUserId(int userId) {
169         return JobIdManager.getJobIdForUserId(MIN_JOB_ID, MAX_JOB_ID, userId);
170     }
171 }
172