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 import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
21 
22 import android.app.AlarmManager;
23 import android.app.job.JobInfo;
24 import android.app.job.JobParameters;
25 import android.app.job.JobScheduler;
26 import android.app.job.JobService;
27 import android.content.ComponentName;
28 import android.content.Context;
29 import android.os.Bundle;
30 import android.os.RemoteException;
31 import android.util.Slog;
32 import android.util.SparseBooleanArray;
33 import android.util.SparseLongArray;
34 
35 import com.android.internal.annotations.GuardedBy;
36 import com.android.internal.annotations.VisibleForTesting;
37 
38 import java.util.Random;
39 
40 /**
41  * Job for scheduling key/value backup work.  This module encapsulates all
42  * of the policy around when those backup passes are executed.
43  */
44 public class KeyValueBackupJob extends JobService {
45     private static final String TAG = "KeyValueBackupJob";
46     private static ComponentName sKeyValueJobService =
47             new ComponentName(PLATFORM_PACKAGE_NAME, KeyValueBackupJob.class.getName());
48 
49     private static final String USER_ID_EXTRA_KEY = "userId";
50 
51     // Once someone asks for a backup, this is how long we hold off until we find
52     // an on-charging opportunity.  If we hit this max latency we will run the operation
53     // regardless.  Privileged callers can always trigger an immediate pass via
54     // BackupManager.backupNow().
55     private static final long MAX_DEFERRAL = AlarmManager.INTERVAL_DAY;
56 
57     @GuardedBy("KeyValueBackupJob.class")
58     private static final SparseBooleanArray sScheduledForUserId = new SparseBooleanArray();
59     @GuardedBy("KeyValueBackupJob.class")
60     private static final SparseLongArray sNextScheduledForUserId = new SparseLongArray();
61 
62     @VisibleForTesting
63     public static final int MIN_JOB_ID = 52417896;
64     @VisibleForTesting
65     public static final int MAX_JOB_ID = 52418896;
66 
schedule(int userId, Context ctx, BackupManagerConstants constants)67     public static void schedule(int userId, Context ctx, BackupManagerConstants constants) {
68         schedule(userId, ctx, 0, constants);
69     }
70 
schedule(int userId, Context ctx, long delay, BackupManagerConstants constants)71     public static void schedule(int userId, Context ctx, long delay,
72             BackupManagerConstants constants) {
73         synchronized (KeyValueBackupJob.class) {
74             if (sScheduledForUserId.get(userId)) {
75                 return;
76             }
77 
78             final long interval;
79             final long fuzz;
80             final int networkType;
81             final boolean needsCharging;
82 
83             synchronized (constants) {
84                 interval = constants.getKeyValueBackupIntervalMilliseconds();
85                 fuzz = constants.getKeyValueBackupFuzzMilliseconds();
86                 networkType = constants.getKeyValueBackupRequiredNetworkType();
87                 needsCharging = constants.getKeyValueBackupRequireCharging();
88             }
89             if (delay <= 0) {
90                 delay = interval + new Random().nextInt((int) fuzz);
91             }
92             if (DEBUG_SCHEDULING) {
93                 Slog.v(TAG, "Scheduling k/v pass in " + (delay / 1000 / 60) + " minutes");
94             }
95 
96             JobInfo.Builder builder = new JobInfo.Builder(getJobIdForUserId(userId),
97                     sKeyValueJobService)
98                     .setMinimumLatency(delay)
99                     .setRequiredNetworkType(networkType)
100                     .setRequiresCharging(needsCharging)
101                     .setOverrideDeadline(MAX_DEFERRAL);
102 
103             Bundle extraInfo = new Bundle();
104             extraInfo.putInt(USER_ID_EXTRA_KEY, userId);
105             builder.setTransientExtras(extraInfo);
106 
107             JobScheduler js = (JobScheduler) ctx.getSystemService(Context.JOB_SCHEDULER_SERVICE);
108             js.schedule(builder.build());
109 
110             sScheduledForUserId.put(userId, true);
111             sNextScheduledForUserId.put(userId, System.currentTimeMillis() + delay);
112         }
113     }
114 
cancel(int userId, Context ctx)115     public static void cancel(int userId, Context ctx) {
116         synchronized (KeyValueBackupJob.class) {
117             JobScheduler js = (JobScheduler) ctx.getSystemService(
118                     Context.JOB_SCHEDULER_SERVICE);
119             js.cancel(getJobIdForUserId(userId));
120 
121             clearScheduledForUserId(userId);
122         }
123     }
124 
nextScheduled(int userId)125     public static long nextScheduled(int userId) {
126         synchronized (KeyValueBackupJob.class) {
127             return sNextScheduledForUserId.get(userId);
128         }
129     }
130 
131     @VisibleForTesting
isScheduled(int userId)132     public static boolean isScheduled(int userId) {
133         synchronized (KeyValueBackupJob.class) {
134             return sScheduledForUserId.get(userId);
135         }
136     }
137 
138     @Override
onStartJob(JobParameters params)139     public boolean onStartJob(JobParameters params) {
140         int userId = params.getTransientExtras().getInt(USER_ID_EXTRA_KEY);
141 
142         synchronized (KeyValueBackupJob.class) {
143             clearScheduledForUserId(userId);
144         }
145 
146         // Time to run a key/value backup!
147         BackupManagerService service = BackupManagerService.getInstance();
148         try {
149             service.backupNowForUser(userId);
150         } catch (RemoteException e) {}
151 
152         // This was just a trigger; ongoing wakelock management is done by the
153         // rest of the backup system.
154         return false;
155     }
156 
157     @Override
onStopJob(JobParameters params)158     public boolean onStopJob(JobParameters params) {
159         // Intentionally empty; the job starting was just a trigger
160         return false;
161     }
162 
163     @GuardedBy("KeyValueBackupJob.class")
clearScheduledForUserId(int userId)164     private static void clearScheduledForUserId(int userId) {
165         sScheduledForUserId.delete(userId);
166         sNextScheduledForUserId.delete(userId);
167     }
168 
getJobIdForUserId(int userId)169     private static int getJobIdForUserId(int userId) {
170         return JobIdManager.getJobIdForUserId(MIN_JOB_ID, MAX_JOB_ID, userId);
171     }
172 }
173