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