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.app.job.JobInfo; 21 import android.content.pm.PackageManager; 22 import android.content.ContentResolver; 23 import android.os.Bundle; 24 import android.os.PersistableBundle; 25 import android.os.UserHandle; 26 import android.util.Slog; 27 28 /** 29 * Value type that represents a sync operation. 30 * This holds all information related to a sync operation - both one off and periodic. 31 * Data stored in this is used to schedule a job with the JobScheduler. 32 * {@hide} 33 */ 34 public class SyncOperation { 35 public static final String TAG = "SyncManager"; 36 37 /** 38 * This is used in the {@link #sourcePeriodicId} field if the operation is not initiated by a failed 39 * periodic sync. 40 */ 41 public static final int NO_JOB_ID = -1; 42 43 public static final int REASON_BACKGROUND_DATA_SETTINGS_CHANGED = -1; 44 public static final int REASON_ACCOUNTS_UPDATED = -2; 45 public static final int REASON_SERVICE_CHANGED = -3; 46 public static final int REASON_PERIODIC = -4; 47 /** Sync started because it has just been set to isSyncable. */ 48 public static final int REASON_IS_SYNCABLE = -5; 49 /** Sync started because it has just been set to sync automatically. */ 50 public static final int REASON_SYNC_AUTO = -6; 51 /** Sync started because master sync automatically has been set to true. */ 52 public static final int REASON_MASTER_SYNC_AUTO = -7; 53 public static final int REASON_USER_START = -8; 54 55 private static String[] REASON_NAMES = new String[] { 56 "DataSettingsChanged", 57 "AccountsUpdated", 58 "ServiceChanged", 59 "Periodic", 60 "IsSyncable", 61 "AutoSync", 62 "MasterSyncAuto", 63 "UserStart", 64 }; 65 66 /** Identifying info for the target for this operation. */ 67 public final SyncStorageEngine.EndPoint target; 68 public final int owningUid; 69 public final String owningPackage; 70 /** Why this sync was kicked off. {@link #REASON_NAMES} */ 71 public final int reason; 72 /** Where this sync was initiated. */ 73 public final int syncSource; 74 public final boolean allowParallelSyncs; 75 public final Bundle extras; 76 public final boolean isPeriodic; 77 /** jobId of the periodic SyncOperation that initiated this one */ 78 public final int sourcePeriodicId; 79 /** Operations are considered duplicates if keys are equal */ 80 public final String key; 81 82 /** Poll frequency of periodic sync in milliseconds */ 83 public final long periodMillis; 84 /** Flex time of periodic sync in milliseconds */ 85 public final long flexMillis; 86 /** Descriptive string key for this operation */ 87 public String wakeLockName; 88 /** 89 * Used when duplicate pending syncs are present. The one with the lowest expectedRuntime 90 * is kept, others are discarded. 91 */ 92 public long expectedRuntime; 93 94 /** Stores the number of times this sync operation failed and had to be retried. */ 95 int retries; 96 97 /** jobId of the JobScheduler job corresponding to this sync */ 98 public int jobId; 99 SyncOperation(Account account, int userId, int owningUid, String owningPackage, int reason, int source, String provider, Bundle extras, boolean allowParallelSyncs)100 public SyncOperation(Account account, int userId, int owningUid, String owningPackage, 101 int reason, int source, String provider, Bundle extras, 102 boolean allowParallelSyncs) { 103 this(new SyncStorageEngine.EndPoint(account, provider, userId), owningUid, owningPackage, 104 reason, source, extras, allowParallelSyncs); 105 } 106 SyncOperation(SyncStorageEngine.EndPoint info, int owningUid, String owningPackage, int reason, int source, Bundle extras, boolean allowParallelSyncs)107 private SyncOperation(SyncStorageEngine.EndPoint info, int owningUid, String owningPackage, 108 int reason, int source, Bundle extras, boolean allowParallelSyncs) { 109 this(info, owningUid, owningPackage, reason, source, extras, allowParallelSyncs, false, 110 NO_JOB_ID, 0, 0); 111 } 112 SyncOperation(SyncOperation op, long periodMillis, long flexMillis)113 public SyncOperation(SyncOperation op, long periodMillis, long flexMillis) { 114 this(op.target, op.owningUid, op.owningPackage, op.reason, op.syncSource, 115 new Bundle(op.extras), op.allowParallelSyncs, op.isPeriodic, op.sourcePeriodicId, 116 periodMillis, flexMillis); 117 } 118 SyncOperation(SyncStorageEngine.EndPoint info, int owningUid, String owningPackage, int reason, int source, Bundle extras, boolean allowParallelSyncs, boolean isPeriodic, int sourcePeriodicId, long periodMillis, long flexMillis)119 public SyncOperation(SyncStorageEngine.EndPoint info, int owningUid, String owningPackage, 120 int reason, int source, Bundle extras, boolean allowParallelSyncs, 121 boolean isPeriodic, int sourcePeriodicId, long periodMillis, 122 long flexMillis) { 123 this.target = info; 124 this.owningUid = owningUid; 125 this.owningPackage = owningPackage; 126 this.reason = reason; 127 this.syncSource = source; 128 this.extras = new Bundle(extras); 129 this.allowParallelSyncs = allowParallelSyncs; 130 this.isPeriodic = isPeriodic; 131 this.sourcePeriodicId = sourcePeriodicId; 132 this.periodMillis = periodMillis; 133 this.flexMillis = flexMillis; 134 this.jobId = NO_JOB_ID; 135 this.key = toKey(); 136 } 137 138 /* Get a one off sync operation instance from a periodic sync. */ createOneTimeSyncOperation()139 public SyncOperation createOneTimeSyncOperation() { 140 if (!isPeriodic) { 141 return null; 142 } 143 SyncOperation op = new SyncOperation(target, owningUid, owningPackage, reason, syncSource, 144 new Bundle(extras), allowParallelSyncs, false, jobId /* sourcePeriodicId */, 145 periodMillis, flexMillis); 146 return op; 147 } 148 SyncOperation(SyncOperation other)149 public SyncOperation(SyncOperation other) { 150 target = other.target; 151 owningUid = other.owningUid; 152 owningPackage = other.owningPackage; 153 reason = other.reason; 154 syncSource = other.syncSource; 155 allowParallelSyncs = other.allowParallelSyncs; 156 extras = new Bundle(other.extras); 157 wakeLockName = other.wakeLockName(); 158 isPeriodic = other.isPeriodic; 159 sourcePeriodicId = other.sourcePeriodicId; 160 periodMillis = other.periodMillis; 161 flexMillis = other.flexMillis; 162 this.key = other.key; 163 } 164 165 /** 166 * All fields are stored in a corresponding key in the persistable bundle. 167 * 168 * {@link #extras} is a Bundle and can contain parcelable objects. But only the type Account 169 * is allowed {@link ContentResolver#validateSyncExtrasBundle(Bundle)} that can't be stored in 170 * a PersistableBundle. For every value of type Account with key 'key', we store a 171 * PersistableBundle containing account information at key 'ACCOUNT:key'. The Account object 172 * can be reconstructed using this. 173 * 174 * We put a flag with key 'SyncManagerJob', to identify while reconstructing a sync operation 175 * from a bundle whether the bundle actually contains information about a sync. 176 * @return A persistable bundle containing all information to re-construct the sync operation. 177 */ toJobInfoExtras()178 PersistableBundle toJobInfoExtras() { 179 // This will be passed as extras bundle to a JobScheduler job. 180 PersistableBundle jobInfoExtras = new PersistableBundle(); 181 182 PersistableBundle syncExtrasBundle = new PersistableBundle(); 183 for (String key: extras.keySet()) { 184 Object value = extras.get(key); 185 if (value instanceof Account) { 186 Account account = (Account) value; 187 PersistableBundle accountBundle = new PersistableBundle(); 188 accountBundle.putString("accountName", account.name); 189 accountBundle.putString("accountType", account.type); 190 // This is stored in jobInfoExtras so that we don't override a user specified 191 // sync extra with the same key. 192 jobInfoExtras.putPersistableBundle("ACCOUNT:" + key, accountBundle); 193 } else if (value instanceof Long) { 194 syncExtrasBundle.putLong(key, (Long) value); 195 } else if (value instanceof Integer) { 196 syncExtrasBundle.putInt(key, (Integer) value); 197 } else if (value instanceof Boolean) { 198 syncExtrasBundle.putBoolean(key, (Boolean) value); 199 } else if (value instanceof Float) { 200 syncExtrasBundle.putDouble(key, (double) (float) value); 201 } else if (value instanceof Double) { 202 syncExtrasBundle.putDouble(key, (Double) value); 203 } else if (value instanceof String) { 204 syncExtrasBundle.putString(key, (String) value); 205 } else if (value == null) { 206 syncExtrasBundle.putString(key, null); 207 } else { 208 Slog.e(TAG, "Unknown extra type."); 209 } 210 } 211 jobInfoExtras.putPersistableBundle("syncExtras", syncExtrasBundle); 212 213 jobInfoExtras.putBoolean("SyncManagerJob", true); 214 215 jobInfoExtras.putString("provider", target.provider); 216 jobInfoExtras.putString("accountName", target.account.name); 217 jobInfoExtras.putString("accountType", target.account.type); 218 jobInfoExtras.putInt("userId", target.userId); 219 jobInfoExtras.putInt("owningUid", owningUid); 220 jobInfoExtras.putString("owningPackage", owningPackage); 221 jobInfoExtras.putInt("reason", reason); 222 jobInfoExtras.putInt("source", syncSource); 223 jobInfoExtras.putBoolean("allowParallelSyncs", allowParallelSyncs); 224 jobInfoExtras.putInt("jobId", jobId); 225 jobInfoExtras.putBoolean("isPeriodic", isPeriodic); 226 jobInfoExtras.putInt("sourcePeriodicId", sourcePeriodicId); 227 jobInfoExtras.putLong("periodMillis", periodMillis); 228 jobInfoExtras.putLong("flexMillis", flexMillis); 229 jobInfoExtras.putLong("expectedRuntime", expectedRuntime); 230 jobInfoExtras.putInt("retries", retries); 231 return jobInfoExtras; 232 } 233 234 /** 235 * Reconstructs a sync operation from an extras Bundle. Returns null if the bundle doesn't 236 * contain a valid sync operation. 237 */ maybeCreateFromJobExtras(PersistableBundle jobExtras)238 static SyncOperation maybeCreateFromJobExtras(PersistableBundle jobExtras) { 239 String accountName, accountType; 240 String provider; 241 int userId, owningUid; 242 String owningPackage; 243 int reason, source; 244 int initiatedBy; 245 Bundle extras; 246 boolean allowParallelSyncs, isPeriodic; 247 long periodMillis, flexMillis; 248 249 if (!jobExtras.getBoolean("SyncManagerJob", false)) { 250 return null; 251 } 252 253 accountName = jobExtras.getString("accountName"); 254 accountType = jobExtras.getString("accountType"); 255 provider = jobExtras.getString("provider"); 256 userId = jobExtras.getInt("userId", Integer.MAX_VALUE); 257 owningUid = jobExtras.getInt("owningUid"); 258 owningPackage = jobExtras.getString("owningPackage"); 259 reason = jobExtras.getInt("reason", Integer.MAX_VALUE); 260 source = jobExtras.getInt("source", Integer.MAX_VALUE); 261 allowParallelSyncs = jobExtras.getBoolean("allowParallelSyncs", false); 262 isPeriodic = jobExtras.getBoolean("isPeriodic", false); 263 initiatedBy = jobExtras.getInt("sourcePeriodicId", NO_JOB_ID); 264 periodMillis = jobExtras.getLong("periodMillis"); 265 flexMillis = jobExtras.getLong("flexMillis"); 266 extras = new Bundle(); 267 268 PersistableBundle syncExtras = jobExtras.getPersistableBundle("syncExtras"); 269 if (syncExtras != null) { 270 extras.putAll(syncExtras); 271 } 272 273 for (String key: jobExtras.keySet()) { 274 if (key!= null && key.startsWith("ACCOUNT:")) { 275 String newKey = key.substring(8); // Strip off the 'ACCOUNT:' prefix. 276 PersistableBundle accountsBundle = jobExtras.getPersistableBundle(key); 277 Account account = new Account(accountsBundle.getString("accountName"), 278 accountsBundle.getString("accountType")); 279 extras.putParcelable(newKey, account); 280 } 281 } 282 283 Account account = new Account(accountName, accountType); 284 SyncStorageEngine.EndPoint target = 285 new SyncStorageEngine.EndPoint(account, provider, userId); 286 SyncOperation op = new SyncOperation(target, owningUid, owningPackage, reason, source, 287 extras, allowParallelSyncs, isPeriodic, initiatedBy, periodMillis, flexMillis); 288 op.jobId = jobExtras.getInt("jobId"); 289 op.expectedRuntime = jobExtras.getLong("expectedRuntime"); 290 op.retries = jobExtras.getInt("retries"); 291 return op; 292 } 293 294 /** 295 * Determine whether if this sync operation is running, the provided operation would conflict 296 * with it. 297 * Parallel syncs allow multiple accounts to be synced at the same time. 298 */ isConflict(SyncOperation toRun)299 boolean isConflict(SyncOperation toRun) { 300 final SyncStorageEngine.EndPoint other = toRun.target; 301 return target.account.type.equals(other.account.type) 302 && target.provider.equals(other.provider) 303 && target.userId == other.userId 304 && (!allowParallelSyncs 305 || target.account.name.equals(other.account.name)); 306 } 307 isReasonPeriodic()308 boolean isReasonPeriodic() { 309 return reason == REASON_PERIODIC; 310 } 311 matchesPeriodicOperation(SyncOperation other)312 boolean matchesPeriodicOperation(SyncOperation other) { 313 return target.matchesSpec(other.target) 314 && SyncManager.syncExtrasEquals(extras, other.extras, true) 315 && periodMillis == other.periodMillis && flexMillis == other.flexMillis; 316 } 317 isDerivedFromFailedPeriodicSync()318 boolean isDerivedFromFailedPeriodicSync() { 319 return sourcePeriodicId != NO_JOB_ID; 320 } 321 findPriority()322 int findPriority() { 323 if (isInitialization()) { 324 return JobInfo.PRIORITY_SYNC_INITIALIZATION; 325 } else if (isExpedited()) { 326 return JobInfo.PRIORITY_SYNC_EXPEDITED; 327 } 328 return JobInfo.PRIORITY_DEFAULT; 329 } 330 toKey()331 private String toKey() { 332 StringBuilder sb = new StringBuilder(); 333 sb.append("provider: ").append(target.provider); 334 sb.append(" account {name=" + target.account.name 335 + ", user=" 336 + target.userId 337 + ", type=" 338 + target.account.type 339 + "}"); 340 sb.append(" isPeriodic: ").append(isPeriodic); 341 sb.append(" period: ").append(periodMillis); 342 sb.append(" flex: ").append(flexMillis); 343 sb.append(" extras: "); 344 extrasToStringBuilder(extras, sb); 345 return sb.toString(); 346 } 347 348 @Override toString()349 public String toString() { 350 return dump(null, true); 351 } 352 dump(PackageManager pm, boolean useOneLine)353 String dump(PackageManager pm, boolean useOneLine) { 354 StringBuilder sb = new StringBuilder(); 355 sb.append("JobId: ").append(jobId) 356 .append(", ") 357 .append(target.account.name) 358 .append(" u") 359 .append(target.userId).append(" (") 360 .append(target.account.type) 361 .append(")") 362 .append(", ") 363 .append(target.provider) 364 .append(", "); 365 sb.append(SyncStorageEngine.SOURCES[syncSource]); 366 if (extras.getBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, false)) { 367 sb.append(", EXPEDITED"); 368 } 369 sb.append(", reason: "); 370 sb.append(reasonToString(pm, reason)); 371 if (isPeriodic) { 372 sb.append(", period: " + periodMillis).append(", flexMillis: " + flexMillis); 373 } 374 if (!useOneLine) { 375 sb.append("\n "); 376 sb.append("owningUid="); 377 UserHandle.formatUid(sb, owningUid); 378 sb.append(" owningPackage="); 379 sb.append(owningPackage); 380 } 381 if (!useOneLine && !extras.keySet().isEmpty()) { 382 sb.append("\n "); 383 extrasToStringBuilder(extras, sb); 384 } 385 return sb.toString(); 386 } 387 reasonToString(PackageManager pm, int reason)388 static String reasonToString(PackageManager pm, int reason) { 389 if (reason >= 0) { 390 if (pm != null) { 391 final String[] packages = pm.getPackagesForUid(reason); 392 if (packages != null && packages.length == 1) { 393 return packages[0]; 394 } 395 final String name = pm.getNameForUid(reason); 396 if (name != null) { 397 return name; 398 } 399 return String.valueOf(reason); 400 } else { 401 return String.valueOf(reason); 402 } 403 } else { 404 final int index = -reason - 1; 405 if (index >= REASON_NAMES.length) { 406 return String.valueOf(reason); 407 } else { 408 return REASON_NAMES[index]; 409 } 410 } 411 } 412 isInitialization()413 boolean isInitialization() { 414 return extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false); 415 } 416 isExpedited()417 boolean isExpedited() { 418 return extras.getBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, false); 419 } 420 ignoreBackoff()421 boolean ignoreBackoff() { 422 return extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, false); 423 } 424 isNotAllowedOnMetered()425 boolean isNotAllowedOnMetered() { 426 return extras.getBoolean(ContentResolver.SYNC_EXTRAS_DISALLOW_METERED, false); 427 } 428 isManual()429 boolean isManual() { 430 return extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false); 431 } 432 isIgnoreSettings()433 boolean isIgnoreSettings() { 434 return extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, false); 435 } 436 extrasToStringBuilder(Bundle bundle, StringBuilder sb)437 private static void extrasToStringBuilder(Bundle bundle, StringBuilder sb) { 438 sb.append("["); 439 for (String key : bundle.keySet()) { 440 sb.append(key).append("=").append(bundle.get(key)).append(" "); 441 } 442 sb.append("]"); 443 } 444 wakeLockName()445 String wakeLockName() { 446 if (wakeLockName != null) { 447 return wakeLockName; 448 } 449 return (wakeLockName = target.provider 450 + "/" + target.account.type 451 + "/" + target.account.name); 452 } 453 454 // TODO: Test this to make sure that casting to object doesn't lose the type info for EventLog. toEventLog(int event)455 public Object[] toEventLog(int event) { 456 Object[] logArray = new Object[4]; 457 logArray[1] = event; 458 logArray[2] = syncSource; 459 logArray[0] = target.provider; 460 logArray[3] = target.account.name.hashCode(); 461 return logArray; 462 } 463 } 464