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