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.content.SyncRequest; 24import android.os.Bundle; 25import android.os.SystemClock; 26import 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 */ 34public 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 public boolean expedited; 70 public SyncStorageEngine.PendingOperation pendingOperation; 71 /** Elapsed real time in millis at which to run this sync. */ 72 public long latestRunTime; 73 /** Set by the SyncManager in order to delay retries. */ 74 public Long backoff; 75 /** Specified by the adapter to delay subsequent sync operations. */ 76 public long delayUntil; 77 /** 78 * Elapsed real time in millis when this sync will be run. 79 * Depends on max(backoff, latestRunTime, and delayUntil). 80 */ 81 public long effectiveRunTime; 82 /** Amount of time before {@link effectiveRunTime} from which this sync can run. */ 83 public long flexTime; 84 85 public SyncOperation(Account account, int userId, int reason, int source, String authority, 86 Bundle extras, long runTimeFromNow, long flexTime, long backoff, 87 long delayUntil, boolean allowParallelSyncs) { 88 this.service = null; 89 this.account = account; 90 this.authority = authority; 91 this.userId = userId; 92 this.reason = reason; 93 this.syncSource = source; 94 this.allowParallelSyncs = allowParallelSyncs; 95 this.extras = new Bundle(extras); 96 cleanBundle(this.extras); 97 this.delayUntil = delayUntil; 98 this.backoff = backoff; 99 final long now = SystemClock.elapsedRealtime(); 100 // Checks the extras bundle. Must occur after we set the internal bundle. 101 if (runTimeFromNow < 0 || isExpedited()) { 102 this.expedited = true; 103 this.latestRunTime = now; 104 this.flexTime = 0; 105 } else { 106 this.expedited = false; 107 this.latestRunTime = now + runTimeFromNow; 108 this.flexTime = flexTime; 109 } 110 updateEffectiveRunTime(); 111 this.key = toKey(); 112 } 113 114 /** 115 * Make sure the bundle attached to this SyncOperation doesn't have unnecessary 116 * flags set. 117 * @param bundle to clean. 118 */ 119 private void cleanBundle(Bundle bundle) { 120 removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_UPLOAD); 121 removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_MANUAL); 122 removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS); 123 removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF); 124 removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_DO_NOT_RETRY); 125 removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_DISCARD_LOCAL_DELETIONS); 126 removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_EXPEDITED); 127 removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_OVERRIDE_TOO_MANY_DELETIONS); 128 removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_DISALLOW_METERED); 129 130 // Remove Config data. 131 bundle.remove(ContentResolver.SYNC_EXTRAS_EXPECTED_UPLOAD); 132 bundle.remove(ContentResolver.SYNC_EXTRAS_EXPECTED_DOWNLOAD); 133 } 134 135 private void removeFalseExtra(Bundle bundle, String extraName) { 136 if (!bundle.getBoolean(extraName, false)) { 137 bundle.remove(extraName); 138 } 139 } 140 141 /** Only used to immediately reschedule a sync. */ 142 SyncOperation(SyncOperation other) { 143 this.service = other.service; 144 this.account = other.account; 145 this.authority = other.authority; 146 this.userId = other.userId; 147 this.reason = other.reason; 148 this.syncSource = other.syncSource; 149 this.extras = new Bundle(other.extras); 150 this.expedited = other.expedited; 151 this.latestRunTime = SystemClock.elapsedRealtime(); 152 this.flexTime = 0L; 153 this.backoff = other.backoff; 154 this.allowParallelSyncs = other.allowParallelSyncs; 155 this.updateEffectiveRunTime(); 156 this.key = toKey(); 157 } 158 159 @Override 160 public String toString() { 161 return dump(null, true); 162 } 163 164 public String dump(PackageManager pm, boolean useOneLine) { 165 StringBuilder sb = new StringBuilder() 166 .append(account.name) 167 .append(" u") 168 .append(userId).append(" (") 169 .append(account.type) 170 .append(")") 171 .append(", ") 172 .append(authority) 173 .append(", ") 174 .append(SyncStorageEngine.SOURCES[syncSource]) 175 .append(", latestRunTime ") 176 .append(latestRunTime); 177 if (expedited) { 178 sb.append(", EXPEDITED"); 179 } 180 sb.append(", reason: "); 181 sb.append(reasonToString(pm, reason)); 182 if (!useOneLine && !extras.keySet().isEmpty()) { 183 sb.append("\n "); 184 extrasToStringBuilder(extras, sb); 185 } 186 return sb.toString(); 187 } 188 189 public static String reasonToString(PackageManager pm, int reason) { 190 if (reason >= 0) { 191 if (pm != null) { 192 final String[] packages = pm.getPackagesForUid(reason); 193 if (packages != null && packages.length == 1) { 194 return packages[0]; 195 } 196 final String name = pm.getNameForUid(reason); 197 if (name != null) { 198 return name; 199 } 200 return String.valueOf(reason); 201 } else { 202 return String.valueOf(reason); 203 } 204 } else { 205 final int index = -reason - 1; 206 if (index >= REASON_NAMES.length) { 207 return String.valueOf(reason); 208 } else { 209 return REASON_NAMES[index]; 210 } 211 } 212 } 213 214 public boolean isMeteredDisallowed() { 215 return extras.getBoolean(ContentResolver.SYNC_EXTRAS_DISALLOW_METERED, false); 216 } 217 218 public boolean isInitialization() { 219 return extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false); 220 } 221 222 public boolean isExpedited() { 223 return extras.getBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, false) || expedited; 224 } 225 226 public boolean ignoreBackoff() { 227 return extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, false); 228 } 229 230 /** Changed in V3. */ 231 private String toKey() { 232 StringBuilder sb = new StringBuilder(); 233 if (service == null) { 234 sb.append("authority: ").append(authority); 235 sb.append(" account {name=" + account.name + ", user=" + userId + ", type=" + account.type 236 + "}"); 237 } else { 238 sb.append("service {package=" ) 239 .append(service.getPackageName()) 240 .append(" user=") 241 .append(userId) 242 .append(", class=") 243 .append(service.getClassName()) 244 .append("}"); 245 } 246 sb.append(" extras: "); 247 extrasToStringBuilder(extras, sb); 248 return sb.toString(); 249 } 250 251 public static void extrasToStringBuilder(Bundle bundle, StringBuilder sb) { 252 sb.append("["); 253 for (String key : bundle.keySet()) { 254 sb.append(key).append("=").append(bundle.get(key)).append(" "); 255 } 256 sb.append("]"); 257 } 258 259 /** 260 * Update the effective run time of this Operation based on latestRunTime (specified at 261 * creation time of sync), delayUntil (specified by SyncAdapter), or backoff (specified by 262 * SyncManager on soft failures). 263 */ 264 public void updateEffectiveRunTime() { 265 // Regardless of whether we're in backoff or honouring a delayUntil, we still incorporate 266 // the flex time provided by the developer. 267 effectiveRunTime = ignoreBackoff() ? 268 latestRunTime : 269 Math.max(Math.max(latestRunTime, delayUntil), backoff); 270 } 271 272 /** 273 * SyncOperations are sorted based on their earliest effective run time. 274 * This comparator is used to sort the SyncOps at a given time when 275 * deciding which to run, so earliest run time is the best criteria. 276 */ 277 @Override 278 public int compareTo(Object o) { 279 SyncOperation other = (SyncOperation) o; 280 if (expedited != other.expedited) { 281 return expedited ? -1 : 1; 282 } 283 long thisIntervalStart = Math.max(effectiveRunTime - flexTime, 0); 284 long otherIntervalStart = Math.max( 285 other.effectiveRunTime - other.flexTime, 0); 286 if (thisIntervalStart < otherIntervalStart) { 287 return -1; 288 } else if (otherIntervalStart < thisIntervalStart) { 289 return 1; 290 } else { 291 return 0; 292 } 293 } 294} 295