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.content;
18 
19 import android.annotation.Nullable;
20 import android.app.job.JobParameters;
21 import android.app.job.JobService;
22 import android.os.Message;
23 import android.os.SystemClock;
24 import android.util.Log;
25 import android.util.Slog;
26 import android.util.SparseArray;
27 import android.util.SparseBooleanArray;
28 import android.util.SparseLongArray;
29 
30 import com.android.internal.annotations.GuardedBy;
31 
32 public class SyncJobService extends JobService {
33     private static final String TAG = "SyncManager";
34 
35     private static final Object sLock = new Object();
36 
37     @GuardedBy("sLock")
38     private static SyncJobService sInstance;
39 
40     @GuardedBy("sLock")
41     private static final SparseArray<JobParameters> sJobParamsMap = new SparseArray<>();
42 
43     @GuardedBy("sLock")
44     private static final SparseBooleanArray sStartedSyncs = new SparseBooleanArray();
45 
46     @GuardedBy("sLock")
47     private static final SparseLongArray sJobStartUptimes = new SparseLongArray();
48 
49     private static final SyncLogger sLogger = SyncLogger.getInstance();
50 
updateInstance()51     private void updateInstance() {
52         synchronized (SyncJobService.class) {
53             sInstance = this;
54         }
55     }
56 
57     @Nullable
getInstance()58     private static SyncJobService getInstance() {
59         synchronized (sLock) {
60             if (sInstance == null) {
61                 Slog.wtf(TAG, "sInstance == null");
62             }
63             return sInstance;
64         }
65     }
66 
isReady()67     public static boolean isReady() {
68         synchronized (sLock) {
69             return sInstance != null;
70         }
71     }
72 
73     @Override
onStartJob(JobParameters params)74     public boolean onStartJob(JobParameters params) {
75         updateInstance();
76 
77         sLogger.purgeOldLogs();
78 
79         SyncOperation op = SyncOperation.maybeCreateFromJobExtras(params.getExtras());
80 
81         if (op == null) {
82             Slog.wtf(TAG, "Got invalid job " + params.getJobId());
83             return false;
84         }
85 
86         final boolean readyToSync = SyncManager.readyToSync(op.target.userId);
87 
88         sLogger.log("onStartJob() jobid=", params.getJobId(), " op=", op,
89                 " readyToSync", readyToSync);
90 
91         if (!readyToSync) {
92             // If the user isn't unlocked or the device has been provisioned yet, just stop the job
93             // at this point. If it's a non-periodic sync, ask the job scheduler to reschedule it.
94             // If it's a periodic sync, then just wait until the next cycle.
95             final boolean wantsReschedule = !op.isPeriodic;
96             jobFinished(params, wantsReschedule);
97             return true;
98         }
99 
100         boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
101         synchronized (sLock) {
102             final int jobId = params.getJobId();
103             sJobParamsMap.put(jobId, params);
104 
105             sStartedSyncs.delete(jobId);
106             sJobStartUptimes.put(jobId, SystemClock.uptimeMillis());
107         }
108         Message m = Message.obtain();
109         m.what = SyncManager.SyncHandler.MESSAGE_START_SYNC;
110         if (isLoggable) {
111             Slog.v(TAG, "Got start job message " + op.target);
112         }
113         m.obj = op;
114         SyncManager.sendMessage(m);
115         return true;
116     }
117 
118     @Override
onStopJob(JobParameters params)119     public boolean onStopJob(JobParameters params) {
120         if (Log.isLoggable(TAG, Log.VERBOSE)) {
121             Slog.v(TAG, "onStopJob called " + params.getJobId() + ", reason: "
122                     + params.getStopReason());
123         }
124         final SyncOperation op = SyncOperation.maybeCreateFromJobExtras(params.getExtras());
125         if (op == null) {
126             Slog.wtf(TAG, "Got invalid job " + params.getJobId());
127             return false;
128         }
129 
130         final boolean readyToSync = SyncManager.readyToSync(op.target.userId);
131 
132         sLogger.log("onStopJob() ", sLogger.jobParametersToString(params),
133                 " readyToSync=", readyToSync);
134 
135         synchronized (sLock) {
136             final int jobId = params.getJobId();
137             sJobParamsMap.remove(jobId);
138 
139             final long startUptime = sJobStartUptimes.get(jobId);
140             final long nowUptime = SystemClock.uptimeMillis();
141             final long runtime = nowUptime - startUptime;
142 
143 
144             if (runtime > 60 * 1000) {
145                 // WTF if startSyncH() hasn't happened, *unless* onStopJob() was called too soon.
146                 // (1 minute threshold.)
147                 // Also don't wtf when it's not ready to sync.
148                 if (readyToSync && !sStartedSyncs.get(jobId)) {
149                     wtf("Job " + jobId + " didn't start: "
150                             + " startUptime=" + startUptime
151                             + " nowUptime=" + nowUptime
152                             + " params=" + jobParametersToString(params));
153                 }
154             }
155 
156             sStartedSyncs.delete(jobId);
157             sJobStartUptimes.delete(jobId);
158         }
159         Message m = Message.obtain();
160         m.what = SyncManager.SyncHandler.MESSAGE_STOP_SYNC;
161         m.obj = op;
162 
163         // Reschedule if this job was NOT explicitly canceled.
164         m.arg1 = params.getStopReason() != JobParameters.REASON_CANCELED ? 1 : 0;
165         // Apply backoff only if stop is called due to timeout.
166         m.arg2 = params.getStopReason() == JobParameters.REASON_TIMEOUT ? 1 : 0;
167 
168         SyncManager.sendMessage(m);
169         return false;
170     }
171 
callJobFinished(int jobId, boolean needsReschedule, String why)172     public static void callJobFinished(int jobId, boolean needsReschedule, String why) {
173         final SyncJobService instance = getInstance();
174         if (instance != null) {
175             instance.callJobFinishedInner(jobId, needsReschedule, why);
176         }
177     }
178 
callJobFinishedInner(int jobId, boolean needsReschedule, String why)179     public void callJobFinishedInner(int jobId, boolean needsReschedule, String why) {
180         synchronized (sLock) {
181             JobParameters params = sJobParamsMap.get(jobId);
182             sLogger.log("callJobFinished()",
183                     " jobid=", jobId,
184                     " needsReschedule=", needsReschedule,
185                     " ", sLogger.jobParametersToString(params),
186                     " why=", why);
187             if (params != null) {
188                 jobFinished(params, needsReschedule);
189                 sJobParamsMap.remove(jobId);
190             } else {
191                 Slog.e(TAG, "Job params not found for " + String.valueOf(jobId));
192             }
193         }
194     }
195 
markSyncStarted(int jobId)196     public static void markSyncStarted(int jobId) {
197         synchronized (sLock) {
198             sStartedSyncs.put(jobId, true);
199         }
200     }
201 
jobParametersToString(JobParameters params)202     public static String jobParametersToString(JobParameters params) {
203         if (params == null) {
204             return "job:null";
205         } else {
206             return "job:#" + params.getJobId() + ":"
207                     + "sr=[" + params.getStopReason() + "/" + params.getDebugStopReason() + "]:"
208                     + SyncOperation.maybeCreateFromJobExtras(params.getExtras());
209         }
210     }
211 
wtf(String message)212     private static void wtf(String message) {
213         sLogger.log(message);
214         Slog.wtf(TAG, message);
215     }
216 }
217