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.ContentResolver.SyncExemption; 23 import android.content.pm.PackageManager; 24 import android.os.Bundle; 25 import android.os.PersistableBundle; 26 import android.os.SystemClock; 27 import android.os.UserHandle; 28 import android.util.Slog; 29 30 /** 31 * Value type that represents a sync operation. 32 * This holds all information related to a sync operation - both one off and periodic. 33 * Data stored in this is used to schedule a job with the JobScheduler. 34 * {@hide} 35 */ 36 public class SyncOperation { 37 public static final String TAG = "SyncManager"; 38 39 /** 40 * This is used in the {@link #sourcePeriodicId} field if the operation is not initiated by a failed 41 * periodic sync. 42 */ 43 public static final int NO_JOB_ID = -1; 44 45 public static final int REASON_BACKGROUND_DATA_SETTINGS_CHANGED = -1; 46 public static final int REASON_ACCOUNTS_UPDATED = -2; 47 public static final int REASON_SERVICE_CHANGED = -3; 48 public static final int REASON_PERIODIC = -4; 49 /** Sync started because it has just been set to isSyncable. */ 50 public static final int REASON_IS_SYNCABLE = -5; 51 /** Sync started because it has just been set to sync automatically. */ 52 public static final int REASON_SYNC_AUTO = -6; 53 /** Sync started because global sync automatically has been set to true. */ 54 public static final int REASON_MASTER_SYNC_AUTO = -7; 55 public static final int REASON_USER_START = -8; 56 57 private static String[] REASON_NAMES = new String[] { 58 "DataSettingsChanged", 59 "AccountsUpdated", 60 "ServiceChanged", 61 "Periodic", 62 "IsSyncable", 63 "AutoSync", 64 "MasterSyncAuto", 65 "UserStart", 66 }; 67 68 /** Identifying info for the target for this operation. */ 69 public final SyncStorageEngine.EndPoint target; 70 public final int owningUid; 71 public final String owningPackage; 72 /** Why this sync was kicked off. {@link #REASON_NAMES} */ 73 public final int reason; 74 /** Where this sync was initiated. */ 75 public final int syncSource; 76 public final boolean allowParallelSyncs; 77 78 /** 79 * Sync extras. Note, DO NOT modify this bundle directly. When changing the content, always 80 * create a copy, update it, set it in this field. This is to avoid concurrent modifications 81 * when other threads are reading it. 82 */ 83 private volatile Bundle mImmutableExtras; 84 85 public final boolean isPeriodic; 86 /** jobId of the periodic SyncOperation that initiated this one */ 87 public final int sourcePeriodicId; 88 /** Operations are considered duplicates if keys are equal */ 89 public final String key; 90 91 /** Poll frequency of periodic sync in milliseconds */ 92 public final long periodMillis; 93 /** Flex time of periodic sync in milliseconds */ 94 public final long flexMillis; 95 /** Descriptive string key for this operation */ 96 public String wakeLockName; 97 /** 98 * Used when duplicate pending syncs are present. The one with the lowest expectedRuntime 99 * is kept, others are discarded. 100 */ 101 public long expectedRuntime; 102 103 /** Stores the number of times this sync operation failed and had to be retried. */ 104 int retries; 105 106 /** 107 * Indicates if a sync that was originally scheduled as an EJ is being re-scheduled as a 108 * regular job. Specifically, this will be {@code true} if a sync is being backed-off but 109 * {@link ContentResolver#SYNC_EXTRAS_IGNORE_BACKOFF} is not set. 110 */ 111 boolean scheduleEjAsRegularJob; 112 113 /** jobId of the JobScheduler job corresponding to this sync */ 114 public int jobId; 115 116 @SyncExemption 117 public int syncExemptionFlag; 118 SyncOperation(Account account, int userId, int owningUid, String owningPackage, int reason, int source, String provider, Bundle extras, boolean allowParallelSyncs, @SyncExemption int syncExemptionFlag)119 public SyncOperation(Account account, int userId, int owningUid, String owningPackage, 120 int reason, int source, String provider, Bundle extras, 121 boolean allowParallelSyncs, @SyncExemption int syncExemptionFlag) { 122 this(new SyncStorageEngine.EndPoint(account, provider, userId), owningUid, owningPackage, 123 reason, source, extras, allowParallelSyncs, syncExemptionFlag); 124 } 125 SyncOperation(SyncStorageEngine.EndPoint info, int owningUid, String owningPackage, int reason, int source, Bundle extras, boolean allowParallelSyncs, @SyncExemption int syncExemptionFlag)126 private SyncOperation(SyncStorageEngine.EndPoint info, int owningUid, String owningPackage, 127 int reason, int source, Bundle extras, boolean allowParallelSyncs, 128 @SyncExemption int syncExemptionFlag) { 129 this(info, owningUid, owningPackage, reason, source, extras, allowParallelSyncs, false, 130 NO_JOB_ID, 0, 0, syncExemptionFlag); 131 } 132 SyncOperation(SyncOperation op, long periodMillis, long flexMillis)133 public SyncOperation(SyncOperation op, long periodMillis, long flexMillis) { 134 this(op.target, op.owningUid, op.owningPackage, op.reason, op.syncSource, 135 op.mImmutableExtras, op.allowParallelSyncs, op.isPeriodic, op.sourcePeriodicId, 136 periodMillis, flexMillis, ContentResolver.SYNC_EXEMPTION_NONE); 137 } 138 SyncOperation(SyncStorageEngine.EndPoint info, int owningUid, String owningPackage, int reason, int source, Bundle extras, boolean allowParallelSyncs, boolean isPeriodic, int sourcePeriodicId, long periodMillis, long flexMillis, @SyncExemption int syncExemptionFlag)139 public SyncOperation(SyncStorageEngine.EndPoint info, int owningUid, String owningPackage, 140 int reason, int source, Bundle extras, 141 boolean allowParallelSyncs, 142 boolean isPeriodic, int sourcePeriodicId, long periodMillis, 143 long flexMillis, @SyncExemption int syncExemptionFlag) { 144 this.target = info; 145 this.owningUid = owningUid; 146 this.owningPackage = owningPackage; 147 this.reason = reason; 148 this.syncSource = source; 149 this.mImmutableExtras = new Bundle(extras); 150 this.allowParallelSyncs = allowParallelSyncs; 151 this.isPeriodic = isPeriodic; 152 this.sourcePeriodicId = sourcePeriodicId; 153 this.periodMillis = periodMillis; 154 this.flexMillis = flexMillis; 155 this.jobId = NO_JOB_ID; 156 this.key = toKey(); 157 this.syncExemptionFlag = syncExemptionFlag; 158 } 159 160 /* Get a one off sync operation instance from a periodic sync. */ createOneTimeSyncOperation()161 public SyncOperation createOneTimeSyncOperation() { 162 if (!isPeriodic) { 163 return null; 164 } 165 SyncOperation op = new SyncOperation(target, owningUid, owningPackage, reason, syncSource, 166 mImmutableExtras, allowParallelSyncs, false, jobId /* sourcePeriodicId */, 167 periodMillis, flexMillis, ContentResolver.SYNC_EXEMPTION_NONE); 168 return op; 169 } 170 SyncOperation(SyncOperation other)171 public SyncOperation(SyncOperation other) { 172 target = other.target; 173 owningUid = other.owningUid; 174 owningPackage = other.owningPackage; 175 reason = other.reason; 176 syncSource = other.syncSource; 177 allowParallelSyncs = other.allowParallelSyncs; 178 179 // Since we treat this field as immutable, it's okay to use a shallow copy here. 180 // No need to create a copy. 181 mImmutableExtras = other.mImmutableExtras; 182 wakeLockName = other.wakeLockName(); 183 isPeriodic = other.isPeriodic; 184 sourcePeriodicId = other.sourcePeriodicId; 185 periodMillis = other.periodMillis; 186 flexMillis = other.flexMillis; 187 this.key = other.key; 188 syncExemptionFlag = other.syncExemptionFlag; 189 } 190 191 /** 192 * All fields are stored in a corresponding key in the persistable bundle. 193 * 194 * {@link #mImmutableExtras} is a Bundle and can contain parcelable objects. 195 * But only the type Account 196 * is allowed {@link ContentResolver#validateSyncExtrasBundle(Bundle)} that can't be stored in 197 * a PersistableBundle. For every value of type Account with key 'key', we store a 198 * PersistableBundle containing account information at key 'ACCOUNT:key'. The Account object 199 * can be reconstructed using this. 200 * 201 * We put a flag with key 'SyncManagerJob', to identify while reconstructing a sync operation 202 * from a bundle whether the bundle actually contains information about a sync. 203 * @return A persistable bundle containing all information to re-construct the sync operation. 204 */ toJobInfoExtras()205 PersistableBundle toJobInfoExtras() { 206 // This will be passed as extras bundle to a JobScheduler job. 207 PersistableBundle jobInfoExtras = new PersistableBundle(); 208 209 PersistableBundle syncExtrasBundle = new PersistableBundle(); 210 211 final Bundle extras = mImmutableExtras; 212 for (String key : extras.keySet()) { 213 Object value = extras.get(key); 214 if (value instanceof Account) { 215 Account account = (Account) value; 216 PersistableBundle accountBundle = new PersistableBundle(); 217 accountBundle.putString("accountName", account.name); 218 accountBundle.putString("accountType", account.type); 219 // This is stored in jobInfoExtras so that we don't override a user specified 220 // sync extra with the same key. 221 jobInfoExtras.putPersistableBundle("ACCOUNT:" + key, accountBundle); 222 } else if (value instanceof Long) { 223 syncExtrasBundle.putLong(key, (Long) value); 224 } else if (value instanceof Integer) { 225 syncExtrasBundle.putInt(key, (Integer) value); 226 } else if (value instanceof Boolean) { 227 syncExtrasBundle.putBoolean(key, (Boolean) value); 228 } else if (value instanceof Float) { 229 syncExtrasBundle.putDouble(key, (double) (float) value); 230 } else if (value instanceof Double) { 231 syncExtrasBundle.putDouble(key, (Double) value); 232 } else if (value instanceof String) { 233 syncExtrasBundle.putString(key, (String) value); 234 } else if (value == null) { 235 syncExtrasBundle.putString(key, null); 236 } else { 237 Slog.e(TAG, "Unknown extra type."); 238 } 239 } 240 jobInfoExtras.putPersistableBundle("syncExtras", syncExtrasBundle); 241 242 jobInfoExtras.putBoolean("SyncManagerJob", true); 243 244 jobInfoExtras.putString("provider", target.provider); 245 jobInfoExtras.putString("accountName", target.account.name); 246 jobInfoExtras.putString("accountType", target.account.type); 247 jobInfoExtras.putInt("userId", target.userId); 248 jobInfoExtras.putInt("owningUid", owningUid); 249 jobInfoExtras.putString("owningPackage", owningPackage); 250 jobInfoExtras.putInt("reason", reason); 251 jobInfoExtras.putInt("source", syncSource); 252 jobInfoExtras.putBoolean("allowParallelSyncs", allowParallelSyncs); 253 jobInfoExtras.putInt("jobId", jobId); 254 jobInfoExtras.putBoolean("isPeriodic", isPeriodic); 255 jobInfoExtras.putInt("sourcePeriodicId", sourcePeriodicId); 256 jobInfoExtras.putLong("periodMillis", periodMillis); 257 jobInfoExtras.putLong("flexMillis", flexMillis); 258 jobInfoExtras.putLong("expectedRuntime", expectedRuntime); 259 jobInfoExtras.putInt("retries", retries); 260 jobInfoExtras.putInt("syncExemptionFlag", syncExemptionFlag); 261 jobInfoExtras.putBoolean("ejDowngradedToRegular", scheduleEjAsRegularJob); 262 return jobInfoExtras; 263 } 264 265 /** 266 * Reconstructs a sync operation from an extras Bundle. Returns null if the bundle doesn't 267 * contain a valid sync operation. 268 */ maybeCreateFromJobExtras(PersistableBundle jobExtras)269 static SyncOperation maybeCreateFromJobExtras(PersistableBundle jobExtras) { 270 if (jobExtras == null) { 271 return null; 272 } 273 String accountName, accountType; 274 String provider; 275 int userId, owningUid; 276 String owningPackage; 277 int reason, source; 278 int initiatedBy; 279 Bundle extras; 280 boolean allowParallelSyncs, isPeriodic; 281 long periodMillis, flexMillis; 282 int syncExemptionFlag; 283 284 if (!jobExtras.getBoolean("SyncManagerJob", false)) { 285 return null; 286 } 287 288 accountName = jobExtras.getString("accountName"); 289 accountType = jobExtras.getString("accountType"); 290 provider = jobExtras.getString("provider"); 291 userId = jobExtras.getInt("userId", Integer.MAX_VALUE); 292 owningUid = jobExtras.getInt("owningUid"); 293 owningPackage = jobExtras.getString("owningPackage"); 294 reason = jobExtras.getInt("reason", Integer.MAX_VALUE); 295 source = jobExtras.getInt("source", Integer.MAX_VALUE); 296 allowParallelSyncs = jobExtras.getBoolean("allowParallelSyncs", false); 297 isPeriodic = jobExtras.getBoolean("isPeriodic", false); 298 initiatedBy = jobExtras.getInt("sourcePeriodicId", NO_JOB_ID); 299 periodMillis = jobExtras.getLong("periodMillis"); 300 flexMillis = jobExtras.getLong("flexMillis"); 301 syncExemptionFlag = jobExtras.getInt("syncExemptionFlag", 302 ContentResolver.SYNC_EXEMPTION_NONE); 303 extras = new Bundle(); 304 305 PersistableBundle syncExtras = jobExtras.getPersistableBundle("syncExtras"); 306 if (syncExtras != null) { 307 extras.putAll(syncExtras); 308 } 309 310 for (String key: jobExtras.keySet()) { 311 if (key!= null && key.startsWith("ACCOUNT:")) { 312 String newKey = key.substring(8); // Strip off the 'ACCOUNT:' prefix. 313 PersistableBundle accountsBundle = jobExtras.getPersistableBundle(key); 314 Account account = new Account(accountsBundle.getString("accountName"), 315 accountsBundle.getString("accountType")); 316 extras.putParcelable(newKey, account); 317 } 318 } 319 320 Account account = new Account(accountName, accountType); 321 SyncStorageEngine.EndPoint target = 322 new SyncStorageEngine.EndPoint(account, provider, userId); 323 SyncOperation op = new SyncOperation(target, owningUid, owningPackage, reason, source, 324 extras, allowParallelSyncs, isPeriodic, initiatedBy, periodMillis, flexMillis, 325 syncExemptionFlag); 326 op.jobId = jobExtras.getInt("jobId"); 327 op.expectedRuntime = jobExtras.getLong("expectedRuntime"); 328 op.retries = jobExtras.getInt("retries"); 329 op.scheduleEjAsRegularJob = jobExtras.getBoolean("ejDowngradedToRegular"); 330 return op; 331 } 332 333 /** 334 * Determine whether if this sync operation is running, the provided operation would conflict 335 * with it. 336 * Parallel syncs allow multiple accounts to be synced at the same time. 337 */ isConflict(SyncOperation toRun)338 boolean isConflict(SyncOperation toRun) { 339 final SyncStorageEngine.EndPoint other = toRun.target; 340 return target.account.type.equals(other.account.type) 341 && target.provider.equals(other.provider) 342 && target.userId == other.userId 343 && (!allowParallelSyncs 344 || target.account.name.equals(other.account.name)); 345 } 346 isReasonPeriodic()347 boolean isReasonPeriodic() { 348 return reason == REASON_PERIODIC; 349 } 350 matchesPeriodicOperation(SyncOperation other)351 boolean matchesPeriodicOperation(SyncOperation other) { 352 return target.matchesSpec(other.target) 353 && SyncManager.syncExtrasEquals(mImmutableExtras, other.mImmutableExtras, true) 354 && periodMillis == other.periodMillis && flexMillis == other.flexMillis; 355 } 356 isDerivedFromFailedPeriodicSync()357 boolean isDerivedFromFailedPeriodicSync() { 358 return sourcePeriodicId != NO_JOB_ID; 359 } 360 findPriority()361 int findPriority() { 362 if (isInitialization()) { 363 return JobInfo.PRIORITY_SYNC_INITIALIZATION; 364 } else if (isExpedited()) { 365 return JobInfo.PRIORITY_SYNC_EXPEDITED; 366 } 367 return JobInfo.PRIORITY_DEFAULT; 368 } 369 toKey()370 private String toKey() { 371 final Bundle extras = mImmutableExtras; 372 StringBuilder sb = new StringBuilder(); 373 sb.append("provider: ").append(target.provider); 374 sb.append(" account {name=" + target.account.name 375 + ", user=" 376 + target.userId 377 + ", type=" 378 + target.account.type 379 + "}"); 380 sb.append(" isPeriodic: ").append(isPeriodic); 381 sb.append(" period: ").append(periodMillis); 382 sb.append(" flex: ").append(flexMillis); 383 sb.append(" extras: "); 384 extrasToStringBuilder(extras, sb); 385 return sb.toString(); 386 } 387 388 @Override toString()389 public String toString() { 390 return dump(null, true, null, false); 391 } 392 toSafeString()393 public String toSafeString() { 394 return dump(null, true, null, true); 395 } 396 dump(PackageManager pm, boolean shorter, SyncAdapterStateFetcher appStates, boolean logSafe)397 String dump(PackageManager pm, boolean shorter, SyncAdapterStateFetcher appStates, 398 boolean logSafe) { 399 final Bundle extras = mImmutableExtras; 400 StringBuilder sb = new StringBuilder(); 401 sb.append("JobId=").append(jobId) 402 .append(" ") 403 .append(logSafe ? "***" : target.account.name) 404 .append("/") 405 .append(target.account.type) 406 .append(" u") 407 .append(target.userId) 408 .append(" [") 409 .append(target.provider) 410 .append("] "); 411 sb.append(SyncStorageEngine.SOURCES[syncSource]); 412 if (expectedRuntime != 0) { 413 sb.append(" ExpectedIn="); 414 SyncManager.formatDurationHMS(sb, 415 (expectedRuntime - SystemClock.elapsedRealtime())); 416 } 417 if (extras.getBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, false)) { 418 sb.append(" EXPEDITED"); 419 } 420 if (extras.getBoolean(ContentResolver.SYNC_EXTRAS_SCHEDULE_AS_EXPEDITED_JOB, false)) { 421 sb.append(" EXPEDITED-JOB"); 422 if (scheduleEjAsRegularJob) { 423 sb.append("(scheduled-as-regular)"); 424 } 425 } 426 switch (syncExemptionFlag) { 427 case ContentResolver.SYNC_EXEMPTION_NONE: 428 break; 429 case ContentResolver.SYNC_EXEMPTION_PROMOTE_BUCKET: 430 sb.append(" STANDBY-EXEMPTED"); 431 break; 432 case ContentResolver.SYNC_EXEMPTION_PROMOTE_BUCKET_WITH_TEMP: 433 sb.append(" STANDBY-EXEMPTED(TOP)"); 434 break; 435 default: 436 sb.append(" ExemptionFlag=" + syncExemptionFlag); 437 break; 438 } 439 sb.append(" Reason="); 440 sb.append(reasonToString(pm, reason)); 441 if (isPeriodic) { 442 sb.append(" (period="); 443 SyncManager.formatDurationHMS(sb, periodMillis); 444 sb.append(" flex="); 445 SyncManager.formatDurationHMS(sb, flexMillis); 446 sb.append(")"); 447 } 448 if (retries > 0) { 449 sb.append(" Retries="); 450 sb.append(retries); 451 } 452 if (!shorter) { 453 sb.append(" Owner={"); 454 UserHandle.formatUid(sb, owningUid); 455 sb.append(" "); 456 sb.append(owningPackage); 457 if (appStates != null) { 458 sb.append(" ["); 459 sb.append(appStates.getStandbyBucket( 460 UserHandle.getUserId(owningUid), owningPackage)); 461 sb.append("]"); 462 463 if (appStates.isAppActive(owningUid)) { 464 sb.append(" [ACTIVE]"); 465 } 466 } 467 sb.append("}"); 468 if (!extras.keySet().isEmpty()) { 469 sb.append(" "); 470 extrasToStringBuilder(extras, sb); 471 } 472 } 473 return sb.toString(); 474 } 475 reasonToString(PackageManager pm, int reason)476 static String reasonToString(PackageManager pm, int reason) { 477 if (reason >= 0) { 478 if (pm != null) { 479 final String[] packages = pm.getPackagesForUid(reason); 480 if (packages != null && packages.length == 1) { 481 return packages[0]; 482 } 483 final String name = pm.getNameForUid(reason); 484 if (name != null) { 485 return name; 486 } 487 return String.valueOf(reason); 488 } else { 489 return String.valueOf(reason); 490 } 491 } else { 492 final int index = -reason - 1; 493 if (index >= REASON_NAMES.length) { 494 return String.valueOf(reason); 495 } else { 496 return REASON_NAMES[index]; 497 } 498 } 499 } 500 isInitialization()501 boolean isInitialization() { 502 return mImmutableExtras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false); 503 } 504 isExpedited()505 boolean isExpedited() { 506 return mImmutableExtras.getBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, false); 507 } 508 isUpload()509 boolean isUpload() { 510 return mImmutableExtras.getBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, false); 511 } 512 513 /** 514 * Disable SYNC_EXTRAS_UPLOAD, so it will be a two-way (normal) sync. 515 */ enableTwoWaySync()516 void enableTwoWaySync() { 517 removeExtra(ContentResolver.SYNC_EXTRAS_UPLOAD); 518 } 519 hasIgnoreBackoff()520 boolean hasIgnoreBackoff() { 521 return mImmutableExtras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, false); 522 } 523 524 /** 525 * Disable SYNC_EXTRAS_IGNORE_BACKOFF. 526 * 527 * The SYNC_EXTRAS_IGNORE_BACKOFF only applies to the first attempt to sync a given 528 * request. Retries of the request will always honor the backoff, so clear the 529 * flag in case we retry this request. 530 */ enableBackoff()531 void enableBackoff() { 532 removeExtra(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF); 533 } 534 hasDoNotRetry()535 boolean hasDoNotRetry() { 536 return mImmutableExtras.getBoolean(ContentResolver.SYNC_EXTRAS_DO_NOT_RETRY, false); 537 } 538 isNotAllowedOnMetered()539 boolean isNotAllowedOnMetered() { 540 return mImmutableExtras.getBoolean(ContentResolver.SYNC_EXTRAS_DISALLOW_METERED, false); 541 } 542 isManual()543 boolean isManual() { 544 return mImmutableExtras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false); 545 } 546 isIgnoreSettings()547 boolean isIgnoreSettings() { 548 return mImmutableExtras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, false); 549 } 550 hasRequireCharging()551 boolean hasRequireCharging() { 552 return mImmutableExtras.getBoolean(ContentResolver.SYNC_EXTRAS_REQUIRE_CHARGING, false); 553 } 554 isScheduledAsExpeditedJob()555 boolean isScheduledAsExpeditedJob() { 556 return mImmutableExtras.getBoolean( 557 ContentResolver.SYNC_EXTRAS_SCHEDULE_AS_EXPEDITED_JOB, false); 558 } 559 isAppStandbyExempted()560 boolean isAppStandbyExempted() { 561 return syncExemptionFlag != ContentResolver.SYNC_EXEMPTION_NONE; 562 } 563 areExtrasEqual(Bundle other, boolean includeSyncSettings)564 boolean areExtrasEqual(Bundle other, boolean includeSyncSettings) { 565 return SyncManager.syncExtrasEquals(mImmutableExtras, other, includeSyncSettings); 566 } 567 extrasToStringBuilder(Bundle bundle, StringBuilder sb)568 static void extrasToStringBuilder(Bundle bundle, StringBuilder sb) { 569 if (bundle == null) { 570 sb.append("null"); 571 return; 572 } 573 sb.append("["); 574 for (String key : bundle.keySet()) { 575 sb.append(key).append("=").append(bundle.get(key)).append(" "); 576 } 577 sb.append("]"); 578 } 579 extrasToString(Bundle bundle)580 private static String extrasToString(Bundle bundle) { 581 final StringBuilder sb = new StringBuilder(); 582 extrasToStringBuilder(bundle, sb); 583 return sb.toString(); 584 } 585 wakeLockName()586 String wakeLockName() { 587 if (wakeLockName != null) { 588 return wakeLockName; 589 } 590 return (wakeLockName = target.provider 591 + "/" + target.account.type 592 + "/" + target.account.name); 593 } 594 595 // TODO: Test this to make sure that casting to object doesn't lose the type info for EventLog. toEventLog(int event)596 public Object[] toEventLog(int event) { 597 Object[] logArray = new Object[4]; 598 logArray[1] = event; 599 logArray[2] = syncSource; 600 logArray[0] = target.provider; 601 logArray[3] = target.account.name.hashCode(); 602 return logArray; 603 } 604 605 /** 606 * Removes a sync extra. Note do not call it from multiple threads simultaneously. 607 */ removeExtra(String key)608 private void removeExtra(String key) { 609 final Bundle b = mImmutableExtras; 610 if (!b.containsKey(key)) { 611 return; 612 } 613 final Bundle clone = new Bundle(b); 614 clone.remove(key); 615 mImmutableExtras = clone; 616 } 617 getClonedExtras()618 public Bundle getClonedExtras() { 619 return new Bundle(mImmutableExtras); 620 } 621 getExtrasAsString()622 public String getExtrasAsString() { 623 return extrasToString(mImmutableExtras); 624 } 625 } 626