SyncOperation.java revision 6428046767ee4195617fb41b5639eefa2ca7a939
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 17package com.android.server.content; 18 19import android.accounts.Account; 20import android.content.pm.PackageManager; 21import android.content.ComponentName; 22import android.content.ContentResolver; 23import android.os.Bundle; 24import android.os.SystemClock; 25import android.util.Log; 26 27/** 28 * Value type that represents a sync operation. 29 * TODO: This is the class to flesh out with all the scheduling data - metered/unmetered, 30 * transfer-size, etc. 31 * {@hide} 32 */ 33public class SyncOperation implements Comparable { 34 public static final String TAG = "SyncManager"; 35 36 public static final int REASON_BACKGROUND_DATA_SETTINGS_CHANGED = -1; 37 public static final int REASON_ACCOUNTS_UPDATED = -2; 38 public static final int REASON_SERVICE_CHANGED = -3; 39 public static final int REASON_PERIODIC = -4; 40 /** Sync started because it has just been set to isSyncable. */ 41 public static final int REASON_IS_SYNCABLE = -5; 42 /** Sync started because it has just been set to sync automatically. */ 43 public static final int REASON_SYNC_AUTO = -6; 44 /** Sync started because master sync automatically has been set to true. */ 45 public static final int REASON_MASTER_SYNC_AUTO = -7; 46 public static final int REASON_USER_START = -8; 47 48 private static String[] REASON_NAMES = new String[] { 49 "DataSettingsChanged", 50 "AccountsUpdated", 51 "ServiceChanged", 52 "Periodic", 53 "IsSyncable", 54 "AutoSync", 55 "MasterSyncAuto", 56 "UserStart", 57 }; 58 59 public static final int SYNC_TARGET_UNKNOWN = 0; 60 public static final int SYNC_TARGET_ADAPTER = 1; 61 public static final int SYNC_TARGET_SERVICE = 2; 62 63 /** Identifying info for the target for this operation. */ 64 public final SyncStorageEngine.EndPoint target; 65 /** Why this sync was kicked off. {@link #REASON_NAMES} */ 66 public final int reason; 67 /** Where this sync was initiated. */ 68 public final int syncSource; 69 public final boolean allowParallelSyncs; 70 public final String key; 71 /** Internal boolean to avoid reading a bundle everytime we want to compare operations. */ 72 private final boolean expedited; 73 public Bundle extras; 74 /** Bare-bones version of this operation that is persisted across reboots. */ 75 public SyncStorageEngine.PendingOperation pendingOperation; 76 /** Elapsed real time in millis at which to run this sync. */ 77 public long latestRunTime; 78 /** Set by the SyncManager in order to delay retries. */ 79 public long backoff; 80 /** Specified by the adapter to delay subsequent sync operations. */ 81 public long delayUntil; 82 /** 83 * Elapsed real time in millis when this sync will be run. 84 * Depends on max(backoff, latestRunTime, and delayUntil). 85 */ 86 public long effectiveRunTime; 87 /** Amount of time before {@link #effectiveRunTime} from which this sync can run. */ 88 public long flexTime; 89 90 public SyncOperation(Account account, int userId, int reason, int source, String provider, 91 Bundle extras, long runTimeFromNow, long flexTime, long backoff, 92 long delayUntil, boolean allowParallelSyncs) { 93 this(new SyncStorageEngine.EndPoint(account, provider, userId), 94 reason, source, extras, runTimeFromNow, flexTime, backoff, delayUntil, 95 allowParallelSyncs); 96 } 97 98 public SyncOperation(ComponentName service, int userId, int reason, int source, 99 Bundle extras, long runTimeFromNow, long flexTime, long backoff, 100 long delayUntil) { 101 this(new SyncStorageEngine.EndPoint(service, userId), reason, source, extras, 102 runTimeFromNow, flexTime, backoff, delayUntil, true /* allowParallelSyncs */); 103 } 104 105 private SyncOperation(SyncStorageEngine.EndPoint info, int reason, int source, Bundle extras, 106 long runTimeFromNow, long flexTime, long backoff, long delayUntil, 107 boolean allowParallelSyncs) { 108 this.target = info; 109 this.reason = reason; 110 this.syncSource = source; 111 this.extras = new Bundle(extras); 112 cleanBundle(this.extras); 113 this.delayUntil = delayUntil; 114 this.backoff = backoff; 115 this.allowParallelSyncs = allowParallelSyncs; 116 final long now = SystemClock.elapsedRealtime(); 117 // Set expedited based on runTimeFromNow. The SyncManager specifies whether the op is 118 // expedited (Not done solely based on bundle). 119 if (runTimeFromNow < 0) { 120 this.expedited = true; 121 // Sanity check: Will always be true. 122 if (!this.extras.getBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, false)) { 123 this.extras.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true); 124 } 125 this.latestRunTime = now; 126 this.flexTime = 0; 127 } else { 128 this.expedited = false; 129 this.extras.remove(ContentResolver.SYNC_EXTRAS_EXPEDITED); 130 this.latestRunTime = now + runTimeFromNow; 131 this.flexTime = flexTime; 132 } 133 updateEffectiveRunTime(); 134 this.key = toKey(info, this.extras); 135 } 136 137 /** Used to reschedule a sync at a new point in time. */ 138 public SyncOperation(SyncOperation other, long newRunTimeFromNow) { 139 this(other.target, other.reason, other.syncSource, new Bundle(other.extras), 140 newRunTimeFromNow, 141 0L /* In back-off so no flex */, 142 other.backoff, 143 other.delayUntil, 144 other.allowParallelSyncs); 145 } 146 147 public boolean matchesAuthority(SyncOperation other) { 148 return this.target.matchesSpec(other.target); 149 } 150 151 /** 152 * Make sure the bundle attached to this SyncOperation doesn't have unnecessary 153 * flags set. 154 * @param bundle to clean. 155 */ 156 private void cleanBundle(Bundle bundle) { 157 removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_UPLOAD); 158 removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_MANUAL); 159 removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS); 160 removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF); 161 removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_DO_NOT_RETRY); 162 removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_DISCARD_LOCAL_DELETIONS); 163 removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_EXPEDITED); 164 removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_OVERRIDE_TOO_MANY_DELETIONS); 165 removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_DISALLOW_METERED); 166 } 167 168 private void removeFalseExtra(Bundle bundle, String extraName) { 169 if (!bundle.getBoolean(extraName, false)) { 170 bundle.remove(extraName); 171 } 172 } 173 174 /** 175 * Determine whether if this sync operation is running, the provided operation would conflict 176 * with it. 177 * Parallel syncs allow multiple accounts to be synced at the same time. 178 */ 179 public boolean isConflict(SyncOperation toRun) { 180 final SyncStorageEngine.EndPoint other = toRun.target; 181 if (target.target_provider) { 182 return target.account.type.equals(other.account.type) 183 && target.provider.equals(other.provider) 184 && target.userId == other.userId 185 && (!allowParallelSyncs 186 || target.account.name.equals(other.account.name)); 187 } else { 188 // Ops that target a service default to allow parallel syncs, which is handled by the 189 // service returning SYNC_IN_PROGRESS if they don't. 190 return target.service.equals(other.service) && !allowParallelSyncs; 191 } 192 } 193 194 @Override 195 public String toString() { 196 return dump(null, true); 197 } 198 199 public String dump(PackageManager pm, boolean useOneLine) { 200 StringBuilder sb = new StringBuilder(); 201 if (target.target_provider) { 202 sb.append(target.account.name) 203 .append(" u") 204 .append(target.userId).append(" (") 205 .append(target.account.type) 206 .append(")") 207 .append(", ") 208 .append(target.provider) 209 .append(", "); 210 } else if (target.target_service) { 211 sb.append(target.service.getPackageName()) 212 .append(" u") 213 .append(target.userId).append(" (") 214 .append(target.service.getClassName()).append(")") 215 .append(", "); 216 } 217 sb.append(SyncStorageEngine.SOURCES[syncSource]) 218 .append(", currentRunTime ") 219 .append(effectiveRunTime); 220 if (expedited) { 221 sb.append(", EXPEDITED"); 222 } 223 sb.append(", reason: "); 224 sb.append(reasonToString(pm, reason)); 225 if (!useOneLine && !extras.keySet().isEmpty()) { 226 sb.append("\n "); 227 extrasToStringBuilder(extras, sb); 228 } 229 return sb.toString(); 230 } 231 232 public static String reasonToString(PackageManager pm, int reason) { 233 if (reason >= 0) { 234 if (pm != null) { 235 final String[] packages = pm.getPackagesForUid(reason); 236 if (packages != null && packages.length == 1) { 237 return packages[0]; 238 } 239 final String name = pm.getNameForUid(reason); 240 if (name != null) { 241 return name; 242 } 243 return String.valueOf(reason); 244 } else { 245 return String.valueOf(reason); 246 } 247 } else { 248 final int index = -reason - 1; 249 if (index >= REASON_NAMES.length) { 250 return String.valueOf(reason); 251 } else { 252 return REASON_NAMES[index]; 253 } 254 } 255 } 256 257 public boolean isInitialization() { 258 return extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false); 259 } 260 261 public boolean isExpedited() { 262 return expedited; 263 } 264 265 public boolean ignoreBackoff() { 266 return extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, false); 267 } 268 269 public boolean isNotAllowedOnMetered() { 270 return extras.getBoolean(ContentResolver.SYNC_EXTRAS_DISALLOW_METERED, false); 271 } 272 273 /** Changed in V3. */ 274 public static String toKey(SyncStorageEngine.EndPoint info, Bundle extras) { 275 StringBuilder sb = new StringBuilder(); 276 if (info.target_provider) { 277 sb.append("provider: ").append(info.provider); 278 sb.append(" account {name=" + info.account.name 279 + ", user=" 280 + info.userId 281 + ", type=" 282 + info.account.type 283 + "}"); 284 } else if (info.target_service) { 285 sb.append("service {package=" ) 286 .append(info.service.getPackageName()) 287 .append(" user=") 288 .append(info.userId) 289 .append(", class=") 290 .append(info.service.getClassName()) 291 .append("}"); 292 } else { 293 Log.v(TAG, "Converting SyncOperaton to key, invalid target: " + info.toString()); 294 return ""; 295 } 296 sb.append(" extras: "); 297 extrasToStringBuilder(extras, sb); 298 return sb.toString(); 299 } 300 301 private static void extrasToStringBuilder(Bundle bundle, StringBuilder sb) { 302 sb.append("["); 303 for (String key : bundle.keySet()) { 304 sb.append(key).append("=").append(bundle.get(key)).append(" "); 305 } 306 sb.append("]"); 307 } 308 309 public String wakeLockKey() { 310 if (target.target_provider) { 311 return target.account.name + "/" + target.account.type + ":" + target.provider; 312 } else if (target.target_service) { 313 return target.service.getPackageName() + "/" + target.service.getClassName(); 314 } else { 315 Log.wtf(TAG, "Invalid target getting wakelock for operation - " + key); 316 return null; 317 } 318 } 319 320 public String wakeLockName() { 321 if (target.target_provider) { 322 return "/" + target.provider 323 + "/" + target.account.type 324 + "/" + target.account.name; 325 } else if (target.target_service) { 326 return "/" + target.service.getPackageName() 327 + "/" + target.service.getClassName(); 328 } else { 329 Log.wtf(TAG, "Invalid target getting wakelock name for operation - " + key); 330 return null; 331 } 332 } 333 334 /** 335 * Update the effective run time of this Operation based on latestRunTime (specified at 336 * creation time of sync), delayUntil (specified by SyncAdapter), or backoff (specified by 337 * SyncManager on soft failures). 338 */ 339 public void updateEffectiveRunTime() { 340 // Regardless of whether we're in backoff or honouring a delayUntil, we still incorporate 341 // the flex time provided by the developer. 342 effectiveRunTime = ignoreBackoff() ? 343 latestRunTime : 344 Math.max(Math.max(latestRunTime, delayUntil), backoff); 345 } 346 347 /** 348 * SyncOperations are sorted based on their earliest effective run time. 349 * This comparator is used to sort the SyncOps at a given time when 350 * deciding which to run, so earliest run time is the best criteria. 351 */ 352 @Override 353 public int compareTo(Object o) { 354 SyncOperation other = (SyncOperation) o; 355 if (expedited != other.expedited) { 356 return expedited ? -1 : 1; 357 } 358 long thisIntervalStart = Math.max(effectiveRunTime - flexTime, 0); 359 long otherIntervalStart = Math.max( 360 other.effectiveRunTime - other.flexTime, 0); 361 if (thisIntervalStart < otherIntervalStart) { 362 return -1; 363 } else if (otherIntervalStart < thisIntervalStart) { 364 return 1; 365 } else { 366 return 0; 367 } 368 } 369 370 // TODO: Test this to make sure that casting to object doesn't lose the type info for EventLog. 371 public Object[] toEventLog(int event) { 372 Object[] logArray = new Object[4]; 373 logArray[1] = event; 374 logArray[2] = syncSource; 375 if (target.target_provider) { 376 logArray[0] = target.provider; 377 logArray[3] = target.account.name.hashCode(); 378 } else if (target.target_service) { 379 logArray[0] = target.service.getPackageName(); 380 logArray[3] = target.service.hashCode(); 381 } else { 382 Log.wtf(TAG, "sync op with invalid target: " + key); 383 } 384 return logArray; 385 } 386} 387