1 /* 2 * Copyright (C) 2010 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.accounts.Account; 20 import android.app.job.JobInfo; 21 import android.content.pm.PackageManager; 22 import android.content.ContentResolver; 23 import android.os.Bundle; 24 import android.os.PersistableBundle; 25 import android.os.UserHandle; 26 import android.util.Slog; 27 28 /** 29 * Value type that represents a sync operation. 30 * This holds all information related to a sync operation - both one off and periodic. 31 * Data stored in this is used to schedule a job with the JobScheduler. 32 * {@hide} 33 */ 34 public class SyncOperation { 35 public static final String TAG = "SyncManager"; 36 37 /** 38 * This is used in the {@link #sourcePeriodicId} field if the operation is not initiated by a failed 39 * periodic sync. 40 */ 41 public static final int NO_JOB_ID = -1; 42 43 public static final int REASON_BACKGROUND_DATA_SETTINGS_CHANGED = -1; 44 public static final int REASON_ACCOUNTS_UPDATED = -2; 45 public static final int REASON_SERVICE_CHANGED = -3; 46 public static final int REASON_PERIODIC = -4; 47 /** Sync started because it has just been set to isSyncable. */ 48 public static final int REASON_IS_SYNCABLE = -5; 49 /** Sync started because it has just been set to sync automatically. */ 50 public static final int REASON_SYNC_AUTO = -6; 51 /** Sync started because master sync automatically has been set to true. */ 52 public static final int REASON_MASTER_SYNC_AUTO = -7; 53 public static final int REASON_USER_START = -8; 54 55 private static String[] REASON_NAMES = new String[] { 56 "DataSettingsChanged", 57 "AccountsUpdated", 58 "ServiceChanged", 59 "Periodic", 60 "IsSyncable", 61 "AutoSync", 62 "MasterSyncAuto", 63 "UserStart", 64 }; 65 66 /** Identifying info for the target for this operation. */ 67 public final SyncStorageEngine.EndPoint target; 68 public final int owningUid; 69 public final String owningPackage; 70 /** Why this sync was kicked off. {@link #REASON_NAMES} */ 71 public final int reason; 72 /** Where this sync was initiated. */ 73 public final int syncSource; 74 public final boolean allowParallelSyncs; 75 public final Bundle extras; 76 public final boolean isPeriodic; 77 /** jobId of the periodic SyncOperation that initiated this one */ 78 public final int sourcePeriodicId; 79 /** Operations are considered duplicates if keys are equal */ 80 public final String key; 81 82 /** Poll frequency of periodic sync in milliseconds */ 83 public final long periodMillis; 84 /** Flex time of periodic sync in milliseconds */ 85 public final long flexMillis; 86 /** Descriptive string key for this operation */ 87 public String wakeLockName; 88 /** 89 * Used when duplicate pending syncs are present. The one with the lowest expectedRuntime 90 * is kept, others are discarded. 91 */ 92 public long expectedRuntime; 93 94 /** Stores the number of times this sync operation failed and had to be retried. */ 95 int retries; 96 97 /** jobId of the JobScheduler job corresponding to this sync */ 98 public int jobId; 99 100 public SyncOperation(Account account, int userId, int owningUid, String owningPackage, 101 int reason, int source, String provider, Bundle extras, 102 boolean allowParallelSyncs) { 103 this(new SyncStorageEngine.EndPoint(account, provider, userId), owningUid, owningPackage, 104 reason, source, extras, allowParallelSyncs); 105 } 106 107 private SyncOperation(SyncStorageEngine.EndPoint info, int owningUid, String owningPackage, 108 int reason, int source, Bundle extras, boolean allowParallelSyncs) { 109 this(info, owningUid, owningPackage, reason, source, extras, allowParallelSyncs, false, 110 NO_JOB_ID, 0, 0); 111 } 112 113 public SyncOperation(SyncOperation op, long periodMillis, long flexMillis) { 114 this(op.target, op.owningUid, op.owningPackage, op.reason, op.syncSource, 115 new Bundle(op.extras), op.allowParallelSyncs, op.isPeriodic, op.sourcePeriodicId, 116 periodMillis, flexMillis); 117 } 118 119 public SyncOperation(SyncStorageEngine.EndPoint info, int owningUid, String owningPackage, 120 int reason, int source, Bundle extras, boolean allowParallelSyncs, 121 boolean isPeriodic, int sourcePeriodicId, long periodMillis, 122 long flexMillis) { 123 this.target = info; 124 this.owningUid = owningUid; 125 this.owningPackage = owningPackage; 126 this.reason = reason; 127 this.syncSource = source; 128 this.extras = new Bundle(extras); 129 this.allowParallelSyncs = allowParallelSyncs; 130 this.isPeriodic = isPeriodic; 131 this.sourcePeriodicId = sourcePeriodicId; 132 this.periodMillis = periodMillis; 133 this.flexMillis = flexMillis; 134 this.jobId = NO_JOB_ID; 135 this.key = toKey(); 136 } 137 138 /* Get a one off sync operation instance from a periodic sync. */ 139 public SyncOperation createOneTimeSyncOperation() { 140 if (!isPeriodic) { 141 return null; 142 } 143 SyncOperation op = new SyncOperation(target, owningUid, owningPackage, reason, syncSource, 144 new Bundle(extras), allowParallelSyncs, false, jobId /* sourcePeriodicId */, 145 periodMillis, flexMillis); 146 return op; 147 } 148 149 public SyncOperation(SyncOperation other) { 150 target = other.target; 151 owningUid = other.owningUid; 152 owningPackage = other.owningPackage; 153 reason = other.reason; 154 syncSource = other.syncSource; 155 allowParallelSyncs = other.allowParallelSyncs; 156 extras = new Bundle(other.extras); 157 wakeLockName = other.wakeLockName(); 158 isPeriodic = other.isPeriodic; 159 sourcePeriodicId = other.sourcePeriodicId; 160 periodMillis = other.periodMillis; 161 flexMillis = other.flexMillis; 162 this.key = other.key; 163 } 164 165 /** 166 * All fields are stored in a corresponding key in the persistable bundle. 167 * 168 * {@link #extras} is a Bundle and can contain parcelable objects. But only the type Account 169 * is allowed {@link ContentResolver#validateSyncExtrasBundle(Bundle)} that can't be stored in 170 * a PersistableBundle. For every value of type Account with key 'key', we store a 171 * PersistableBundle containing account information at key 'ACCOUNT:key'. The Account object 172 * can be reconstructed using this. 173 * 174 * We put a flag with key 'SyncManagerJob', to identify while reconstructing a sync operation 175 * from a bundle whether the bundle actually contains information about a sync. 176 * @return A persistable bundle containing all information to re-construct the sync operation. 177 */ 178 PersistableBundle toJobInfoExtras() { 179 // This will be passed as extras bundle to a JobScheduler job. 180 PersistableBundle jobInfoExtras = new PersistableBundle(); 181 182 PersistableBundle syncExtrasBundle = new PersistableBundle(); 183 for (String key: extras.keySet()) { 184 Object value = extras.get(key); 185 if (value instanceof Account) { 186 Account account = (Account) value; 187 PersistableBundle accountBundle = new PersistableBundle(); 188 accountBundle.putString("accountName", account.name); 189 accountBundle.putString("accountType", account.type); 190 // This is stored in jobInfoExtras so that we don't override a user specified 191 // sync extra with the same key. 192 jobInfoExtras.putPersistableBundle("ACCOUNT:" + key, accountBundle); 193 } else if (value instanceof Long) { 194 syncExtrasBundle.putLong(key, (Long) value); 195 } else if (value instanceof Integer) { 196 syncExtrasBundle.putInt(key, (Integer) value); 197 } else if (value instanceof Boolean) { 198 syncExtrasBundle.putBoolean(key, (Boolean) value); 199 } else if (value instanceof Float) { 200 syncExtrasBundle.putDouble(key, (double) (float) value); 201 } else if (value instanceof Double) { 202 syncExtrasBundle.putDouble(key, (Double) value); 203 } else if (value instanceof String) { 204 syncExtrasBundle.putString(key, (String) value); 205 } else if (value == null) { 206 syncExtrasBundle.putString(key, null); 207 } else { 208 Slog.e(TAG, "Unknown extra type."); 209 } 210 } 211 jobInfoExtras.putPersistableBundle("syncExtras", syncExtrasBundle); 212 213 jobInfoExtras.putBoolean("SyncManagerJob", true); 214 215 jobInfoExtras.putString("provider", target.provider); 216 jobInfoExtras.putString("accountName", target.account.name); 217 jobInfoExtras.putString("accountType", target.account.type); 218 jobInfoExtras.putInt("userId", target.userId); 219 jobInfoExtras.putInt("owningUid", owningUid); 220 jobInfoExtras.putString("owningPackage", owningPackage); 221 jobInfoExtras.putInt("reason", reason); 222 jobInfoExtras.putInt("source", syncSource); 223 jobInfoExtras.putBoolean("allowParallelSyncs", allowParallelSyncs); 224 jobInfoExtras.putInt("jobId", jobId); 225 jobInfoExtras.putBoolean("isPeriodic", isPeriodic); 226 jobInfoExtras.putInt("sourcePeriodicId", sourcePeriodicId); 227 jobInfoExtras.putLong("periodMillis", periodMillis); 228 jobInfoExtras.putLong("flexMillis", flexMillis); 229 jobInfoExtras.putLong("expectedRuntime", expectedRuntime); 230 jobInfoExtras.putInt("retries", retries); 231 return jobInfoExtras; 232 } 233 234 /** 235 * Reconstructs a sync operation from an extras Bundle. Returns null if the bundle doesn't 236 * contain a valid sync operation. 237 */ 238 static SyncOperation maybeCreateFromJobExtras(PersistableBundle jobExtras) { 239 String accountName, accountType; 240 String provider; 241 int userId, owningUid; 242 String owningPackage; 243 int reason, source; 244 int initiatedBy; 245 Bundle extras; 246 boolean allowParallelSyncs, isPeriodic; 247 long periodMillis, flexMillis; 248 249 if (!jobExtras.getBoolean("SyncManagerJob", false)) { 250 return null; 251 } 252 253 accountName = jobExtras.getString("accountName"); 254 accountType = jobExtras.getString("accountType"); 255 provider = jobExtras.getString("provider"); 256 userId = jobExtras.getInt("userId", Integer.MAX_VALUE); 257 owningUid = jobExtras.getInt("owningUid"); 258 owningPackage = jobExtras.getString("owningPackage"); 259 reason = jobExtras.getInt("reason", Integer.MAX_VALUE); 260 source = jobExtras.getInt("source", Integer.MAX_VALUE); 261 allowParallelSyncs = jobExtras.getBoolean("allowParallelSyncs", false); 262 isPeriodic = jobExtras.getBoolean("isPeriodic", false); 263 initiatedBy = jobExtras.getInt("sourcePeriodicId", NO_JOB_ID); 264 periodMillis = jobExtras.getLong("periodMillis"); 265 flexMillis = jobExtras.getLong("flexMillis"); 266 extras = new Bundle(); 267 268 PersistableBundle syncExtras = jobExtras.getPersistableBundle("syncExtras"); 269 if (syncExtras != null) { 270 extras.putAll(syncExtras); 271 } 272 273 for (String key: jobExtras.keySet()) { 274 if (key!= null && key.startsWith("ACCOUNT:")) { 275 String newKey = key.substring(8); // Strip off the 'ACCOUNT:' prefix. 276 PersistableBundle accountsBundle = jobExtras.getPersistableBundle(key); 277 Account account = new Account(accountsBundle.getString("accountName"), 278 accountsBundle.getString("accountType")); 279 extras.putParcelable(newKey, account); 280 } 281 } 282 283 Account account = new Account(accountName, accountType); 284 SyncStorageEngine.EndPoint target = 285 new SyncStorageEngine.EndPoint(account, provider, userId); 286 SyncOperation op = new SyncOperation(target, owningUid, owningPackage, reason, source, 287 extras, allowParallelSyncs, isPeriodic, initiatedBy, periodMillis, flexMillis); 288 op.jobId = jobExtras.getInt("jobId"); 289 op.expectedRuntime = jobExtras.getLong("expectedRuntime"); 290 op.retries = jobExtras.getInt("retries"); 291 return op; 292 } 293 294 /** 295 * Determine whether if this sync operation is running, the provided operation would conflict 296 * with it. 297 * Parallel syncs allow multiple accounts to be synced at the same time. 298 */ 299 boolean isConflict(SyncOperation toRun) { 300 final SyncStorageEngine.EndPoint other = toRun.target; 301 return target.account.type.equals(other.account.type) 302 && target.provider.equals(other.provider) 303 && target.userId == other.userId 304 && (!allowParallelSyncs 305 || target.account.name.equals(other.account.name)); 306 } 307 308 boolean isReasonPeriodic() { 309 return reason == REASON_PERIODIC; 310 } 311 312 boolean matchesPeriodicOperation(SyncOperation other) { 313 return target.matchesSpec(other.target) 314 && SyncManager.syncExtrasEquals(extras, other.extras, true) 315 && periodMillis == other.periodMillis && flexMillis == other.flexMillis; 316 } 317 318 boolean isDerivedFromFailedPeriodicSync() { 319 return sourcePeriodicId != NO_JOB_ID; 320 } 321 322 int findPriority() { 323 if (isInitialization()) { 324 return JobInfo.PRIORITY_SYNC_INITIALIZATION; 325 } else if (isExpedited()) { 326 return JobInfo.PRIORITY_SYNC_EXPEDITED; 327 } 328 return JobInfo.PRIORITY_DEFAULT; 329 } 330 331 private String toKey() { 332 StringBuilder sb = new StringBuilder(); 333 sb.append("provider: ").append(target.provider); 334 sb.append(" account {name=" + target.account.name 335 + ", user=" 336 + target.userId 337 + ", type=" 338 + target.account.type 339 + "}"); 340 sb.append(" isPeriodic: ").append(isPeriodic); 341 sb.append(" period: ").append(periodMillis); 342 sb.append(" flex: ").append(flexMillis); 343 sb.append(" extras: "); 344 extrasToStringBuilder(extras, sb); 345 return sb.toString(); 346 } 347 348 @Override 349 public String toString() { 350 return dump(null, true); 351 } 352 353 String dump(PackageManager pm, boolean useOneLine) { 354 StringBuilder sb = new StringBuilder(); 355 sb.append("JobId: ").append(jobId) 356 .append(", ") 357 .append(target.account.name) 358 .append(" u") 359 .append(target.userId).append(" (") 360 .append(target.account.type) 361 .append(")") 362 .append(", ") 363 .append(target.provider) 364 .append(", "); 365 sb.append(SyncStorageEngine.SOURCES[syncSource]); 366 if (extras.getBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, false)) { 367 sb.append(", EXPEDITED"); 368 } 369 sb.append(", reason: "); 370 sb.append(reasonToString(pm, reason)); 371 if (isPeriodic) { 372 sb.append(", period: " + periodMillis).append(", flexMillis: " + flexMillis); 373 } 374 if (!useOneLine) { 375 sb.append("\n "); 376 sb.append("owningUid="); 377 UserHandle.formatUid(sb, owningUid); 378 sb.append(" owningPackage="); 379 sb.append(owningPackage); 380 } 381 if (!useOneLine && !extras.keySet().isEmpty()) { 382 sb.append("\n "); 383 extrasToStringBuilder(extras, sb); 384 } 385 return sb.toString(); 386 } 387 388 static String reasonToString(PackageManager pm, int reason) { 389 if (reason >= 0) { 390 if (pm != null) { 391 final String[] packages = pm.getPackagesForUid(reason); 392 if (packages != null && packages.length == 1) { 393 return packages[0]; 394 } 395 final String name = pm.getNameForUid(reason); 396 if (name != null) { 397 return name; 398 } 399 return String.valueOf(reason); 400 } else { 401 return String.valueOf(reason); 402 } 403 } else { 404 final int index = -reason - 1; 405 if (index >= REASON_NAMES.length) { 406 return String.valueOf(reason); 407 } else { 408 return REASON_NAMES[index]; 409 } 410 } 411 } 412 413 boolean isInitialization() { 414 return extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false); 415 } 416 417 boolean isExpedited() { 418 return extras.getBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, false); 419 } 420 421 boolean ignoreBackoff() { 422 return extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, false); 423 } 424 425 boolean isNotAllowedOnMetered() { 426 return extras.getBoolean(ContentResolver.SYNC_EXTRAS_DISALLOW_METERED, false); 427 } 428 429 boolean isManual() { 430 return extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false); 431 } 432 433 boolean isIgnoreSettings() { 434 return extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, false); 435 } 436 437 private static void extrasToStringBuilder(Bundle bundle, StringBuilder sb) { 438 sb.append("["); 439 for (String key : bundle.keySet()) { 440 sb.append(key).append("=").append(bundle.get(key)).append(" "); 441 } 442 sb.append("]"); 443 } 444 445 String wakeLockName() { 446 if (wakeLockName != null) { 447 return wakeLockName; 448 } 449 return (wakeLockName = target.provider 450 + "/" + target.account.type 451 + "/" + target.account.name); 452 } 453 454 // TODO: Test this to make sure that casting to object doesn't lose the type info for EventLog. 455 public Object[] toEventLog(int event) { 456 Object[] logArray = new Object[4]; 457 logArray[1] = event; 458 logArray[2] = syncSource; 459 logArray[0] = target.provider; 460 logArray[3] = target.account.name.hashCode(); 461 return logArray; 462 } 463 } 464