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, UserBackupManagerService userBackupManagerService)67     public static void schedule(int userId, Context ctx,
68             UserBackupManagerService userBackupManagerService) {
69         schedule(userId, ctx, 0, userBackupManagerService);
70     }
71 
schedule(int userId, Context ctx, long delay, UserBackupManagerService userBackupManagerService)72     public static void schedule(int userId, Context ctx, long delay,
73             UserBackupManagerService userBackupManagerService) {
74         synchronized (KeyValueBackupJob.class) {
75             if (sScheduledForUserId.get(userId)
76                     || !userBackupManagerService.isFrameworkSchedulingEnabled()) {
77                 return;
78             }
79 
80             final long interval;
81             final long fuzz;
82             final int networkType;
83             final boolean needsCharging;
84 
85             final BackupManagerConstants constants = userBackupManagerService.getConstants();
86             synchronized (constants) {
87                 interval = constants.getKeyValueBackupIntervalMilliseconds();
88                 fuzz = constants.getKeyValueBackupFuzzMilliseconds();
89                 networkType = constants.getKeyValueBackupRequiredNetworkType();
90                 needsCharging = constants.getKeyValueBackupRequireCharging();
91             }
92             if (delay <= 0) {
93                 delay = interval + new Random().nextInt((int) fuzz);
94             }
95             if (DEBUG_SCHEDULING) {
96                 Slog.v(TAG, "Scheduling k/v pass in " + (delay / 1000 / 60) + " minutes");
97             }
98 
99             JobInfo.Builder builder = new JobInfo.Builder(getJobIdForUserId(userId),
100                     sKeyValueJobService)
101                     .setMinimumLatency(delay)
102                     .setRequiredNetworkType(networkType)
103                     .setRequiresCharging(needsCharging)
104                     .setOverrideDeadline(MAX_DEFERRAL);
105 
106             Bundle extraInfo = new Bundle();
107             extraInfo.putInt(USER_ID_EXTRA_KEY, userId);
108             builder.setTransientExtras(extraInfo);
109 
110             JobScheduler js = (JobScheduler) ctx.getSystemService(Context.JOB_SCHEDULER_SERVICE);
111             js.schedule(builder.build());
112 
113             sScheduledForUserId.put(userId, true);
114             sNextScheduledForUserId.put(userId, System.currentTimeMillis() + delay);
115         }
116     }
117 
cancel(int userId, Context ctx)118     public static void cancel(int userId, Context ctx) {
119         synchronized (KeyValueBackupJob.class) {
120             JobScheduler js = (JobScheduler) ctx.getSystemService(
121                     Context.JOB_SCHEDULER_SERVICE);
122             js.cancel(getJobIdForUserId(userId));
123 
124             clearScheduledForUserId(userId);
125         }
126     }
127 
nextScheduled(int userId)128     public static long nextScheduled(int userId) {
129         synchronized (KeyValueBackupJob.class) {
130             return sNextScheduledForUserId.get(userId);
131         }
132     }
133 
134     @VisibleForTesting
isScheduled(int userId)135     public static boolean isScheduled(int userId) {
136         synchronized (KeyValueBackupJob.class) {
137             return sScheduledForUserId.get(userId);
138         }
139     }
140 
141     @Override
onStartJob(JobParameters params)142     public boolean onStartJob(JobParameters params) {
143         int userId = params.getTransientExtras().getInt(USER_ID_EXTRA_KEY);
144 
145         synchronized (KeyValueBackupJob.class) {
146             clearScheduledForUserId(userId);
147         }
148 
149         // Time to run a key/value backup!
150         BackupManagerService service = BackupManagerService.getInstance();
151         try {
152             service.backupNowForUser(userId);
153         } catch (RemoteException e) {}
154 
155         // This was just a trigger; ongoing wakelock management is done by the
156         // rest of the backup system.
157         return false;
158     }
159 
160     @Override
onStopJob(JobParameters params)161     public boolean onStopJob(JobParameters params) {
162         // Intentionally empty; the job starting was just a trigger
163         return false;
164     }
165 
166     @GuardedBy("KeyValueBackupJob.class")
clearScheduledForUserId(int userId)167     private static void clearScheduledForUserId(int userId) {
168         sScheduledForUserId.delete(userId);
169         sNextScheduledForUserId.delete(userId);
170     }
171 
172     @VisibleForTesting
getJobIdForUserId(int userId)173     static int getJobIdForUserId(int userId) {
174         return JobIdManager.getJobIdForUserId(MIN_JOB_ID, MAX_JOB_ID, userId);
175     }
176 }
177