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