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 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 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 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. */ 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 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 */ 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 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 */ 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 201 public String toString() { 202 return dump(null, true); 203 } 204 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 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 263 public boolean isInitialization() { 264 return extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false); 265 } 266 267 public boolean isExpedited() { 268 return expedited; 269 } 270 271 public boolean ignoreBackoff() { 272 return extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, false); 273 } 274 275 public boolean isNotAllowedOnMetered() { 276 return extras.getBoolean(ContentResolver.SYNC_EXTRAS_DISALLOW_METERED, false); 277 } 278 279 public boolean isManual() { 280 return extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false); 281 } 282 283 public boolean isIgnoreSettings() { 284 return extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, false); 285 } 286 287 /** Changed in V3. */ 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 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 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 */ 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 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. 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