Home | History | Annotate | Download | only in content
      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 
    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 
    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 
    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 
    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. */
    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 
    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      */
    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      */
    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      */
    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 
    308     boolean isReasonPeriodic() {
    309         return reason == REASON_PERIODIC;
    310     }
    311 
    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 
    318     boolean isDerivedFromFailedPeriodicSync() {
    319         return sourcePeriodicId != NO_JOB_ID;
    320     }
    321 
    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 
    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
    349     public String toString() {
    350         return dump(null, true);
    351     }
    352 
    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 
    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 
    413     boolean isInitialization() {
    414         return extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false);
    415     }
    416 
    417     boolean isExpedited() {
    418         return extras.getBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, false);
    419     }
    420 
    421     boolean ignoreBackoff() {
    422         return extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, false);
    423     }
    424 
    425     boolean isNotAllowedOnMetered() {
    426         return extras.getBoolean(ContentResolver.SYNC_EXTRAS_DISALLOW_METERED, false);
    427     }
    428 
    429     boolean isManual() {
    430         return extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false);
    431     }
    432 
    433     boolean isIgnoreSettings() {
    434         return extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, false);
    435     }
    436 
    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 
    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.
    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