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