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.getInternalStopReasonCode()); 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.getInternalStopReasonCode() != JobParameters.INTERNAL_STOP_REASON_CANCELED 165 ? 1 : 0; 166 // Apply backoff only if stop is called due to timeout. 167 m.arg2 = params.getInternalStopReasonCode() == JobParameters.INTERNAL_STOP_REASON_TIMEOUT 168 ? 1 : 0; 169 170 SyncManager.sendMessage(m); 171 return false; 172 } 173 callJobFinished(int jobId, boolean needsReschedule, String why)174 public static void callJobFinished(int jobId, boolean needsReschedule, String why) { 175 final SyncJobService instance = getInstance(); 176 if (instance != null) { 177 instance.callJobFinishedInner(jobId, needsReschedule, why); 178 } 179 } 180 callJobFinishedInner(int jobId, boolean needsReschedule, String why)181 public void callJobFinishedInner(int jobId, boolean needsReschedule, String why) { 182 synchronized (sLock) { 183 JobParameters params = sJobParamsMap.get(jobId); 184 sLogger.log("callJobFinished()", 185 " jobid=", jobId, 186 " needsReschedule=", needsReschedule, 187 " ", sLogger.jobParametersToString(params), 188 " why=", why); 189 if (params != null) { 190 jobFinished(params, needsReschedule); 191 sJobParamsMap.remove(jobId); 192 } else { 193 Slog.e(TAG, "Job params not found for " + String.valueOf(jobId)); 194 } 195 } 196 } 197 markSyncStarted(int jobId)198 public static void markSyncStarted(int jobId) { 199 synchronized (sLock) { 200 sStartedSyncs.put(jobId, true); 201 } 202 } 203 jobParametersToString(JobParameters params)204 public static String jobParametersToString(JobParameters params) { 205 if (params == null) { 206 return "job:null"; 207 } else { 208 return "job:#" + params.getJobId() + ":" 209 + "sr=[" + params.getInternalStopReasonCode() 210 + "/" + params.getDebugStopReason() + "]:" 211 + SyncOperation.maybeCreateFromJobExtras(params.getExtras()); 212 } 213 } 214 wtf(String message)215 private static void wtf(String message) { 216 sLogger.log(message); 217 Slog.wtf(TAG, message); 218 } 219 } 220