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.content.pm.PackageManager;
     21 import android.content.ComponentName;
     22 import android.content.ContentResolver;
     23 import android.os.Bundle;
     24 import android.os.SystemClock;
     25 import android.util.Log;
     26 
     27 /**
     28  * Value type that represents a sync operation.
     29  * TODO: This is the class to flesh out with all the scheduling data - metered/unmetered,
     30  * transfer-size, etc.
     31  * {@hide}
     32  */
     33 public class SyncOperation implements Comparable {
     34     public static final String TAG = "SyncManager";
     35 
     36     public static final int REASON_BACKGROUND_DATA_SETTINGS_CHANGED = -1;
     37     public static final int REASON_ACCOUNTS_UPDATED = -2;
     38     public static final int REASON_SERVICE_CHANGED = -3;
     39     public static final int REASON_PERIODIC = -4;
     40     /** Sync started because it has just been set to isSyncable. */
     41     public static final int REASON_IS_SYNCABLE = -5;
     42     /** Sync started because it has just been set to sync automatically. */
     43     public static final int REASON_SYNC_AUTO = -6;
     44     /** Sync started because master sync automatically has been set to true. */
     45     public static final int REASON_MASTER_SYNC_AUTO = -7;
     46     public static final int REASON_USER_START = -8;
     47 
     48     private static String[] REASON_NAMES = new String[] {
     49             "DataSettingsChanged",
     50             "AccountsUpdated",
     51             "ServiceChanged",
     52             "Periodic",
     53             "IsSyncable",
     54             "AutoSync",
     55             "MasterSyncAuto",
     56             "UserStart",
     57     };
     58 
     59     public static final int SYNC_TARGET_UNKNOWN = 0;
     60     public static final int SYNC_TARGET_ADAPTER = 1;
     61     public static final int SYNC_TARGET_SERVICE = 2;
     62 
     63     /** Identifying info for the target for this operation. */
     64     public final SyncStorageEngine.EndPoint target;
     65     /** Why this sync was kicked off. {@link #REASON_NAMES} */
     66     public final int reason;
     67     /** Where this sync was initiated. */
     68     public final int syncSource;
     69     public final boolean allowParallelSyncs;
     70     public final String key;
     71     /** Internal boolean to avoid reading a bundle everytime we want to compare operations. */
     72     private final boolean expedited;
     73     public Bundle extras;
     74     /** Bare-bones version of this operation that is persisted across reboots. */
     75     public SyncStorageEngine.PendingOperation pendingOperation;
     76     /** Elapsed real time in millis at which to run this sync. */
     77     public long latestRunTime;
     78     /** Set by the SyncManager in order to delay retries. */
     79     public long backoff;
     80     /** Specified by the adapter to delay subsequent sync operations. */
     81     public long delayUntil;
     82     /**
     83      * Elapsed real time in millis when this sync will be run.
     84      * Depends on max(backoff, latestRunTime, and delayUntil).
     85      */
     86     public long effectiveRunTime;
     87     /** Amount of time before {@link #effectiveRunTime} from which this sync can run. */
     88     public long flexTime;
     89 
     90     /** Descriptive string key for this operation */
     91     public String wakeLockName;
     92 
     93     /** Whether this sync op was recently skipped due to the app being idle */
     94     public boolean appIdle;
     95 
     96     public SyncOperation(Account account, int userId, int reason, int source, String provider,
     97             Bundle extras, long runTimeFromNow, long flexTime, long backoff,
     98             long delayUntil, boolean allowParallelSyncs) {
     99         this(new SyncStorageEngine.EndPoint(account, provider, userId),
    100                 reason, source, extras, runTimeFromNow, flexTime, backoff, delayUntil,
    101                 allowParallelSyncs);
    102     }
    103 
    104     public SyncOperation(ComponentName service, int userId, int reason, int source,
    105             Bundle extras, long runTimeFromNow, long flexTime, long backoff,
    106             long delayUntil) {
    107         this(new SyncStorageEngine.EndPoint(service, userId), reason, source, extras,
    108                 runTimeFromNow, flexTime, backoff, delayUntil, true /* allowParallelSyncs */);
    109     }
    110 
    111     private SyncOperation(SyncStorageEngine.EndPoint info, int reason, int source, Bundle extras,
    112             long runTimeFromNow, long flexTime, long backoff, long delayUntil,
    113             boolean allowParallelSyncs) {
    114         this.target = info;
    115         this.reason = reason;
    116         this.syncSource = source;
    117         this.extras = new Bundle(extras);
    118         cleanBundle(this.extras);
    119         this.delayUntil = delayUntil;
    120         this.backoff = backoff;
    121         this.allowParallelSyncs = allowParallelSyncs;
    122         final long now = SystemClock.elapsedRealtime();
    123         // Set expedited based on runTimeFromNow. The SyncManager specifies whether the op is
    124         // expedited (Not done solely based on bundle).
    125         if (runTimeFromNow < 0) {
    126             this.expedited = true;
    127             // Sanity check: Will always be true.
    128             if (!this.extras.getBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, false)) {
    129                 this.extras.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
    130             }
    131             this.latestRunTime = now;
    132             this.flexTime = 0;
    133         } else {
    134             this.expedited = false;
    135             this.extras.remove(ContentResolver.SYNC_EXTRAS_EXPEDITED);
    136             this.latestRunTime = now + runTimeFromNow;
    137             this.flexTime = flexTime;
    138         }
    139         updateEffectiveRunTime();
    140         this.key = toKey(info, this.extras);
    141     }
    142 
    143     /** Used to reschedule a sync at a new point in time. */
    144     public SyncOperation(SyncOperation other, long newRunTimeFromNow) {
    145         this(other.target, other.reason, other.syncSource, new Bundle(other.extras),
    146                 newRunTimeFromNow,
    147                 0L /* In back-off so no flex */,
    148                 other.backoff,
    149                 other.delayUntil,
    150                 other.allowParallelSyncs);
    151     }
    152 
    153     public boolean matchesAuthority(SyncOperation other) {
    154         return this.target.matchesSpec(other.target);
    155     }
    156 
    157     /**
    158      * Make sure the bundle attached to this SyncOperation doesn't have unnecessary
    159      * flags set.
    160      * @param bundle to clean.
    161      */
    162     private void cleanBundle(Bundle bundle) {
    163         removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_UPLOAD);
    164         removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_MANUAL);
    165         removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS);
    166         removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF);
    167         removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_DO_NOT_RETRY);
    168         removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_DISCARD_LOCAL_DELETIONS);
    169         removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_EXPEDITED);
    170         removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_OVERRIDE_TOO_MANY_DELETIONS);
    171         removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_DISALLOW_METERED);
    172     }
    173 
    174     private void removeFalseExtra(Bundle bundle, String extraName) {
    175         if (!bundle.getBoolean(extraName, false)) {
    176             bundle.remove(extraName);
    177         }
    178     }
    179 
    180     /**
    181      * Determine whether if this sync operation is running, the provided operation would conflict
    182      * with it.
    183      * Parallel syncs allow multiple accounts to be synced at the same time.
    184      */
    185     public boolean isConflict(SyncOperation toRun) {
    186         final SyncStorageEngine.EndPoint other = toRun.target;
    187         if (target.target_provider) {
    188             return target.account.type.equals(other.account.type)
    189                     && target.provider.equals(other.provider)
    190                     && target.userId == other.userId
    191                     && (!allowParallelSyncs
    192                             || target.account.name.equals(other.account.name));
    193         } else {
    194             // Ops that target a service default to allow parallel syncs, which is handled by the
    195             // service returning SYNC_IN_PROGRESS if they don't.
    196             return target.service.equals(other.service) && !allowParallelSyncs;
    197         }
    198     }
    199 
    200     @Override
    201     public String toString() {
    202         return dump(null, true);
    203     }
    204 
    205     public String dump(PackageManager pm, boolean useOneLine) {
    206         StringBuilder sb = new StringBuilder();
    207         if (target.target_provider) {
    208             sb.append(target.account.name)
    209                 .append(" u")
    210                 .append(target.userId).append(" (")
    211                 .append(target.account.type)
    212                 .append(")")
    213                 .append(", ")
    214                 .append(target.provider)
    215                 .append(", ");
    216         } else if (target.target_service) {
    217             sb.append(target.service.getPackageName())
    218                 .append(" u")
    219                 .append(target.userId).append(" (")
    220                 .append(target.service.getClassName()).append(")")
    221                 .append(", ");
    222         }
    223         sb.append(SyncStorageEngine.SOURCES[syncSource])
    224             .append(", currentRunTime ")
    225             .append(effectiveRunTime);
    226         if (expedited) {
    227             sb.append(", EXPEDITED");
    228         }
    229         sb.append(", reason: ");
    230         sb.append(reasonToString(pm, reason));
    231         if (!useOneLine && !extras.keySet().isEmpty()) {
    232             sb.append("\n    ");
    233             extrasToStringBuilder(extras, sb);
    234         }
    235         return sb.toString();
    236     }
    237 
    238     public static String reasonToString(PackageManager pm, int reason) {
    239         if (reason >= 0) {
    240             if (pm != null) {
    241                 final String[] packages = pm.getPackagesForUid(reason);
    242                 if (packages != null && packages.length == 1) {
    243                     return packages[0];
    244                 }
    245                 final String name = pm.getNameForUid(reason);
    246                 if (name != null) {
    247                     return name;
    248                 }
    249                 return String.valueOf(reason);
    250             } else {
    251                 return String.valueOf(reason);
    252             }
    253         } else {
    254             final int index = -reason - 1;
    255             if (index >= REASON_NAMES.length) {
    256                 return String.valueOf(reason);
    257             } else {
    258                 return REASON_NAMES[index];
    259             }
    260         }
    261     }
    262 
    263     public boolean isInitialization() {
    264         return extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false);
    265     }
    266 
    267     public boolean isExpedited() {
    268         return expedited;
    269     }
    270 
    271     public boolean ignoreBackoff() {
    272         return extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, false);
    273     }
    274 
    275     public boolean isNotAllowedOnMetered() {
    276         return extras.getBoolean(ContentResolver.SYNC_EXTRAS_DISALLOW_METERED, false);
    277     }
    278 
    279     public boolean isManual() {
    280         return extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false);
    281     }
    282 
    283     public boolean isIgnoreSettings() {
    284         return extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, false);
    285     }
    286 
    287     /** Changed in V3. */
    288     public static String toKey(SyncStorageEngine.EndPoint info, Bundle extras) {
    289         StringBuilder sb = new StringBuilder();
    290         if (info.target_provider) {
    291             sb.append("provider: ").append(info.provider);
    292             sb.append(" account {name=" + info.account.name
    293                     + ", user="
    294                     + info.userId
    295                     + ", type="
    296                     + info.account.type
    297                     + "}");
    298         } else if (info.target_service) {
    299             sb.append("service {package=" )
    300                 .append(info.service.getPackageName())
    301                 .append(" user=")
    302                 .append(info.userId)
    303                 .append(", class=")
    304                 .append(info.service.getClassName())
    305                 .append("}");
    306         } else {
    307             Log.v(TAG, "Converting SyncOperaton to key, invalid target: " + info.toString());
    308             return "";
    309         }
    310         sb.append(" extras: ");
    311         extrasToStringBuilder(extras, sb);
    312         return sb.toString();
    313     }
    314 
    315     private static void extrasToStringBuilder(Bundle bundle, StringBuilder sb) {
    316         sb.append("[");
    317         for (String key : bundle.keySet()) {
    318             sb.append(key).append("=").append(bundle.get(key)).append(" ");
    319         }
    320         sb.append("]");
    321     }
    322 
    323     public String wakeLockName() {
    324         if (wakeLockName != null) {
    325             return wakeLockName;
    326         }
    327         if (target.target_provider) {
    328             return (wakeLockName = target.provider
    329                     + "/" + target.account.type
    330                     + "/" + target.account.name);
    331         } else if (target.target_service) {
    332             return (wakeLockName = target.service.getPackageName()
    333                     + "/" + target.service.getClassName());
    334         } else {
    335             Log.wtf(TAG, "Invalid target getting wakelock name for operation - " + key);
    336             return null;
    337         }
    338     }
    339 
    340     /**
    341      * Update the effective run time of this Operation based on latestRunTime (specified at
    342      * creation time of sync), delayUntil (specified by SyncAdapter), or backoff (specified by
    343      * SyncManager on soft failures).
    344      */
    345     public void updateEffectiveRunTime() {
    346         // Regardless of whether we're in backoff or honouring a delayUntil, we still incorporate
    347         // the flex time provided by the developer.
    348         effectiveRunTime = ignoreBackoff() ?
    349                 latestRunTime :
    350                     Math.max(Math.max(latestRunTime, delayUntil), backoff);
    351     }
    352 
    353     /**
    354      * SyncOperations are sorted based on their earliest effective run time.
    355      * This comparator is used to sort the SyncOps at a given time when
    356      * deciding which to run, so earliest run time is the best criteria.
    357      */
    358     @Override
    359     public int compareTo(Object o) {
    360         SyncOperation other = (SyncOperation) o;
    361         if (expedited != other.expedited) {
    362             return expedited ? -1 : 1;
    363         }
    364         long thisIntervalStart = Math.max(effectiveRunTime - flexTime, 0);
    365         long otherIntervalStart = Math.max(
    366             other.effectiveRunTime - other.flexTime, 0);
    367         if (thisIntervalStart < otherIntervalStart) {
    368             return -1;
    369         } else if (otherIntervalStart < thisIntervalStart) {
    370             return 1;
    371         } else {
    372             return 0;
    373         }
    374     }
    375 
    376     // TODO: Test this to make sure that casting to object doesn't lose the type info for EventLog.
    377     public Object[] toEventLog(int event) {
    378         Object[] logArray = new Object[4];
    379         logArray[1] = event;
    380         logArray[2] = syncSource;
    381         if (target.target_provider) {
    382             logArray[0] = target.provider;
    383             logArray[3] = target.account.name.hashCode();
    384         } else if (target.target_service) {
    385             logArray[0] = target.service.getPackageName();
    386             logArray[3] = target.service.hashCode();
    387         } else {
    388             Log.wtf(TAG, "sync op with invalid target: " + key);
    389         }
    390         return logArray;
    391     }
    392 }
    393