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.content.SyncRequest; 24 import android.os.Bundle; 25 import android.os.SystemClock; 26 import android.util.Pair; 27 28 /** 29 * Value type that represents a sync operation. 30 * TODO: This is the class to flesh out with all the scheduling data - metered/unmetered, 31 * transfer-size, etc. 32 * {@hide} 33 */ 34 public class SyncOperation implements Comparable { 35 public static final int REASON_BACKGROUND_DATA_SETTINGS_CHANGED = -1; 36 public static final int REASON_ACCOUNTS_UPDATED = -2; 37 public static final int REASON_SERVICE_CHANGED = -3; 38 public static final int REASON_PERIODIC = -4; 39 public static final int REASON_IS_SYNCABLE = -5; 40 /** Sync started because it has just been set to sync automatically. */ 41 public static final int REASON_SYNC_AUTO = -6; 42 /** Sync started because master sync automatically has been set to true. */ 43 public static final int REASON_MASTER_SYNC_AUTO = -7; 44 public static final int REASON_USER_START = -8; 45 46 private static String[] REASON_NAMES = new String[] { 47 "DataSettingsChanged", 48 "AccountsUpdated", 49 "ServiceChanged", 50 "Periodic", 51 "IsSyncable", 52 "AutoSync", 53 "MasterSyncAuto", 54 "UserStart", 55 }; 56 57 /** Account info to identify a SyncAdapter registered with the system. */ 58 public final Account account; 59 /** Authority info to identify a SyncAdapter registered with the system. */ 60 public final String authority; 61 /** Service to which this operation will bind to perform the sync. */ 62 public final ComponentName service; 63 public final int userId; 64 public final int reason; 65 public int syncSource; 66 public final boolean allowParallelSyncs; 67 public Bundle extras; 68 public final String key; 69 /** Internal boolean to avoid reading a bundle everytime we want to compare operations. */ 70 private final boolean expedited; 71 public SyncStorageEngine.PendingOperation pendingOperation; 72 /** Elapsed real time in millis at which to run this sync. */ 73 public long latestRunTime; 74 /** Set by the SyncManager in order to delay retries. */ 75 public Long backoff; 76 /** Specified by the adapter to delay subsequent sync operations. */ 77 public long delayUntil; 78 /** 79 * Elapsed real time in millis when this sync will be run. 80 * Depends on max(backoff, latestRunTime, and delayUntil). 81 */ 82 public long effectiveRunTime; 83 /** Amount of time before {@link #effectiveRunTime} from which this sync can run. */ 84 public long flexTime; 85 86 public SyncOperation(Account account, int userId, int reason, int source, String authority, 87 Bundle extras, long runTimeFromNow, long flexTime, long backoff, 88 long delayUntil, boolean allowParallelSyncs) { 89 this.service = null; 90 this.account = account; 91 this.authority = authority; 92 this.userId = userId; 93 this.reason = reason; 94 this.syncSource = source; 95 this.allowParallelSyncs = allowParallelSyncs; 96 this.extras = new Bundle(extras); 97 cleanBundle(this.extras); 98 this.delayUntil = delayUntil; 99 this.backoff = backoff; 100 final long now = SystemClock.elapsedRealtime(); 101 // Checks the extras bundle. Must occur after we set the internal bundle. 102 if (runTimeFromNow < 0) { 103 // Sanity check: Will always be true. 104 if (!this.extras.getBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, false)) { 105 this.extras.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true); 106 } 107 this.expedited = true; 108 this.latestRunTime = now; 109 this.flexTime = 0; 110 } else { 111 this.extras.remove(ContentResolver.SYNC_EXTRAS_EXPEDITED); 112 this.expedited = false; 113 this.latestRunTime = now + runTimeFromNow; 114 this.flexTime = flexTime; 115 } 116 updateEffectiveRunTime(); 117 this.key = toKey(); 118 } 119 120 /** Only used to immediately reschedule a sync. */ 121 SyncOperation(SyncOperation other) { 122 this.service = other.service; 123 this.account = other.account; 124 this.authority = other.authority; 125 this.userId = other.userId; 126 this.reason = other.reason; 127 this.syncSource = other.syncSource; 128 this.extras = new Bundle(other.extras); 129 this.expedited = other.expedited; 130 this.latestRunTime = SystemClock.elapsedRealtime(); 131 this.flexTime = 0L; 132 this.backoff = other.backoff; 133 this.allowParallelSyncs = other.allowParallelSyncs; 134 this.updateEffectiveRunTime(); 135 this.key = toKey(); 136 } 137 138 /** 139 * Make sure the bundle attached to this SyncOperation doesn't have unnecessary 140 * flags set. 141 * @param bundle to clean. 142 */ 143 private void cleanBundle(Bundle bundle) { 144 removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_UPLOAD); 145 removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_MANUAL); 146 removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS); 147 removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF); 148 removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_DO_NOT_RETRY); 149 removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_DISCARD_LOCAL_DELETIONS); 150 removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_EXPEDITED); 151 removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_OVERRIDE_TOO_MANY_DELETIONS); 152 removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_DISALLOW_METERED); 153 154 // Remove Config data. 155 bundle.remove(ContentResolver.SYNC_EXTRAS_EXPECTED_UPLOAD); 156 bundle.remove(ContentResolver.SYNC_EXTRAS_EXPECTED_DOWNLOAD); 157 } 158 159 private void removeFalseExtra(Bundle bundle, String extraName) { 160 if (!bundle.getBoolean(extraName, false)) { 161 bundle.remove(extraName); 162 } 163 } 164 165 @Override 166 public String toString() { 167 return dump(null, true); 168 } 169 170 public String dump(PackageManager pm, boolean useOneLine) { 171 StringBuilder sb = new StringBuilder() 172 .append(account.name) 173 .append(" u") 174 .append(userId).append(" (") 175 .append(account.type) 176 .append(")") 177 .append(", ") 178 .append(authority) 179 .append(", ") 180 .append(SyncStorageEngine.SOURCES[syncSource]) 181 .append(", latestRunTime ") 182 .append(latestRunTime); 183 if (expedited) { 184 sb.append(", EXPEDITED"); 185 } 186 sb.append(", reason: "); 187 sb.append(reasonToString(pm, reason)); 188 if (!useOneLine && !extras.keySet().isEmpty()) { 189 sb.append("\n "); 190 extrasToStringBuilder(extras, sb); 191 } 192 return sb.toString(); 193 } 194 195 public static String reasonToString(PackageManager pm, int reason) { 196 if (reason >= 0) { 197 if (pm != null) { 198 final String[] packages = pm.getPackagesForUid(reason); 199 if (packages != null && packages.length == 1) { 200 return packages[0]; 201 } 202 final String name = pm.getNameForUid(reason); 203 if (name != null) { 204 return name; 205 } 206 return String.valueOf(reason); 207 } else { 208 return String.valueOf(reason); 209 } 210 } else { 211 final int index = -reason - 1; 212 if (index >= REASON_NAMES.length) { 213 return String.valueOf(reason); 214 } else { 215 return REASON_NAMES[index]; 216 } 217 } 218 } 219 220 public boolean isMeteredDisallowed() { 221 return extras.getBoolean(ContentResolver.SYNC_EXTRAS_DISALLOW_METERED, false); 222 } 223 224 public boolean isInitialization() { 225 return extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false); 226 } 227 228 public boolean isExpedited() { 229 return expedited; 230 } 231 232 public boolean ignoreBackoff() { 233 return extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, false); 234 } 235 236 /** Changed in V3. */ 237 private String toKey() { 238 StringBuilder sb = new StringBuilder(); 239 if (service == null) { 240 sb.append("authority: ").append(authority); 241 sb.append(" account {name=" + account.name + ", user=" + userId + ", type=" + account.type 242 + "}"); 243 } else { 244 sb.append("service {package=" ) 245 .append(service.getPackageName()) 246 .append(" user=") 247 .append(userId) 248 .append(", class=") 249 .append(service.getClassName()) 250 .append("}"); 251 } 252 sb.append(" extras: "); 253 extrasToStringBuilder(extras, sb); 254 return sb.toString(); 255 } 256 257 public static void extrasToStringBuilder(Bundle bundle, StringBuilder sb) { 258 sb.append("["); 259 for (String key : bundle.keySet()) { 260 sb.append(key).append("=").append(bundle.get(key)).append(" "); 261 } 262 sb.append("]"); 263 } 264 265 /** 266 * Update the effective run time of this Operation based on latestRunTime (specified at 267 * creation time of sync), delayUntil (specified by SyncAdapter), or backoff (specified by 268 * SyncManager on soft failures). 269 */ 270 public void updateEffectiveRunTime() { 271 // Regardless of whether we're in backoff or honouring a delayUntil, we still incorporate 272 // the flex time provided by the developer. 273 effectiveRunTime = ignoreBackoff() ? 274 latestRunTime : 275 Math.max(Math.max(latestRunTime, delayUntil), backoff); 276 } 277 278 /** 279 * SyncOperations are sorted based on their earliest effective run time. 280 * This comparator is used to sort the SyncOps at a given time when 281 * deciding which to run, so earliest run time is the best criteria. 282 */ 283 @Override 284 public int compareTo(Object o) { 285 SyncOperation other = (SyncOperation) o; 286 if (expedited != other.expedited) { 287 return expedited ? -1 : 1; 288 } 289 long thisIntervalStart = Math.max(effectiveRunTime - flexTime, 0); 290 long otherIntervalStart = Math.max( 291 other.effectiveRunTime - other.flexTime, 0); 292 if (thisIntervalStart < otherIntervalStart) { 293 return -1; 294 } else if (otherIntervalStart < thisIntervalStart) { 295 return 1; 296 } else { 297 return 0; 298 } 299 } 300 } 301