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