Home | History | Annotate | Download | only in emailsync
      1 /*
      2  * Copyright (C) 2008-2009 Marc Blank
      3  * Licensed to The Android Open Source Project.
      4  *
      5  * Licensed under the Apache License, Version 2.0 (the "License");
      6  * you may not use this file except in compliance with the License.
      7  * You may obtain a copy of the License at
      8  *
      9  *      http://www.apache.org/licenses/LICENSE-2.0
     10  *
     11  * Unless required by applicable law or agreed to in writing, software
     12  * distributed under the License is distributed on an "AS IS" BASIS,
     13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14  * See the License for the specific language governing permissions and
     15  * limitations under the License.
     16  */
     17 
     18 package com.android.emailsync;
     19 
     20 import android.app.AlarmManager;
     21 import android.app.PendingIntent;
     22 import android.app.Service;
     23 import android.content.BroadcastReceiver;
     24 import android.content.ContentResolver;
     25 import android.content.ContentUris;
     26 import android.content.ContentValues;
     27 import android.content.Context;
     28 import android.content.Intent;
     29 import android.content.IntentFilter;
     30 import android.database.ContentObserver;
     31 import android.database.Cursor;
     32 import android.net.ConnectivityManager;
     33 import android.net.NetworkInfo;
     34 import android.net.NetworkInfo.State;
     35 import android.net.Uri;
     36 import android.os.Bundle;
     37 import android.os.Handler;
     38 import android.os.PowerManager;
     39 import android.os.PowerManager.WakeLock;
     40 import android.os.Process;
     41 import android.provider.CalendarContract;
     42 import android.provider.CalendarContract.Calendars;
     43 import android.provider.CalendarContract.Events;
     44 import android.provider.ContactsContract;
     45 
     46 import com.android.emailcommon.TempDirectory;
     47 import com.android.emailcommon.provider.Account;
     48 import com.android.emailcommon.provider.EmailContent;
     49 import com.android.emailcommon.provider.EmailContent.Body;
     50 import com.android.emailcommon.provider.EmailContent.BodyColumns;
     51 import com.android.emailcommon.provider.EmailContent.MailboxColumns;
     52 import com.android.emailcommon.provider.EmailContent.Message;
     53 import com.android.emailcommon.provider.EmailContent.MessageColumns;
     54 import com.android.emailcommon.provider.EmailContent.SyncColumns;
     55 import com.android.emailcommon.provider.HostAuth;
     56 import com.android.emailcommon.provider.Mailbox;
     57 import com.android.emailcommon.provider.Policy;
     58 import com.android.emailcommon.provider.ProviderUnavailableException;
     59 import com.android.emailcommon.service.AccountServiceProxy;
     60 import com.android.emailcommon.service.EmailServiceProxy;
     61 import com.android.emailcommon.service.IEmailServiceCallback.Stub;
     62 import com.android.emailcommon.service.PolicyServiceProxy;
     63 import com.android.emailcommon.utility.EmailClientConnectionManager;
     64 import com.android.emailcommon.utility.Utility;
     65 import com.android.mail.utils.LogUtils;
     66 
     67 import org.apache.http.conn.params.ConnManagerPNames;
     68 import org.apache.http.conn.params.ConnPerRoute;
     69 import org.apache.http.conn.routing.HttpRoute;
     70 import org.apache.http.params.BasicHttpParams;
     71 import org.apache.http.params.HttpParams;
     72 
     73 import java.io.FileDescriptor;
     74 import java.io.PrintWriter;
     75 import java.util.ArrayList;
     76 import java.util.HashMap;
     77 import java.util.List;
     78 import java.util.WeakHashMap;
     79 import java.util.concurrent.ConcurrentHashMap;
     80 
     81 /**
     82  * The SyncServiceManager handles the lifecycle of various sync adapters used by services that
     83  * cannot rely on the system SyncManager
     84  *
     85  * SyncServiceManager uses ContentObservers to detect changes to accounts, mailboxes, & messages in
     86  * order to maintain proper 2-way syncing of data.  (More documentation to follow)
     87  *
     88  */
     89 public abstract class SyncManager extends Service implements Runnable {
     90 
     91     private static String TAG = "SyncManager";
     92 
     93     // The SyncServiceManager's mailbox "id"
     94     public static final int EXTRA_MAILBOX_ID = -1;
     95     public static final int SYNC_SERVICE_MAILBOX_ID = 0;
     96 
     97     private static final int SECONDS = 1000;
     98     private static final int MINUTES = 60*SECONDS;
     99     private static final int ONE_DAY_MINUTES = 1440;
    100 
    101     private static final int SYNC_SERVICE_HEARTBEAT_TIME = 15*MINUTES;
    102     private static final int CONNECTIVITY_WAIT_TIME = 10*MINUTES;
    103 
    104     // Sync hold constants for services with transient errors
    105     private static final int HOLD_DELAY_MAXIMUM = 4*MINUTES;
    106 
    107     // Reason codes when SyncServiceManager.kick is called (mainly for debugging)
    108     // UI has changed data, requiring an upsync of changes
    109     public static final int SYNC_UPSYNC = 0;
    110     // A scheduled sync (when not using push)
    111     public static final int SYNC_SCHEDULED = 1;
    112     // Mailbox was marked push
    113     public static final int SYNC_PUSH = 2;
    114     // A ping (EAS push signal) was received
    115     public static final int SYNC_PING = 3;
    116     // Misc.
    117     public static final int SYNC_KICK = 4;
    118     // A part request (attachment load, for now) was sent to SyncServiceManager
    119     public static final int SYNC_SERVICE_PART_REQUEST = 5;
    120 
    121     // Requests >= SYNC_CALLBACK_START generate callbacks to the UI
    122     public static final int SYNC_CALLBACK_START = 6;
    123     // startSync was requested of SyncServiceManager (other than due to user request)
    124     public static final int SYNC_SERVICE_START_SYNC = SYNC_CALLBACK_START + 0;
    125     // startSync was requested of SyncServiceManager (due to user request)
    126     public static final int SYNC_UI_REQUEST = SYNC_CALLBACK_START + 1;
    127 
    128     protected static final String WHERE_IN_ACCOUNT_AND_PUSHABLE =
    129         MailboxColumns.ACCOUNT_KEY + "=? and type in (" + Mailbox.TYPE_INBOX + ','
    130         + Mailbox.TYPE_EAS_ACCOUNT_MAILBOX + ',' + Mailbox.TYPE_CONTACTS + ','
    131         + Mailbox.TYPE_CALENDAR + ')';
    132     protected static final String WHERE_IN_ACCOUNT_AND_TYPE_INBOX =
    133         MailboxColumns.ACCOUNT_KEY + "=? and type = " + Mailbox.TYPE_INBOX ;
    134     private static final String WHERE_MAILBOX_KEY = Message.MAILBOX_KEY + "=?";
    135     private static final String WHERE_NOT_INTERVAL_NEVER_AND_ACCOUNT_KEY_IN =
    136         "(" + MailboxColumns.TYPE + '=' + Mailbox.TYPE_OUTBOX
    137         + " or " + MailboxColumns.SYNC_INTERVAL + "<" + Mailbox.CHECK_INTERVAL_NEVER + ')'
    138         + " and " + MailboxColumns.ACCOUNT_KEY + " in (";
    139 
    140     public static final int SEND_FAILED = 1;
    141     public static final String MAILBOX_KEY_AND_NOT_SEND_FAILED =
    142             MessageColumns.MAILBOX_KEY + "=? and (" + SyncColumns.SERVER_ID + " is null or " +
    143             SyncColumns.SERVER_ID + "!=" + SEND_FAILED + ')';
    144 
    145     public static final String CALENDAR_SELECTION =
    146             Calendars.ACCOUNT_NAME + "=? AND " + Calendars.ACCOUNT_TYPE + "=?";
    147     private static final String WHERE_CALENDAR_ID = Events.CALENDAR_ID + "=?";
    148 
    149     // Offsets into the syncStatus data for EAS that indicate type, exit status, and change count
    150     // The format is S<type_char>:<exit_char>:<change_count>
    151     public static final int STATUS_TYPE_CHAR = 1;
    152     public static final int STATUS_EXIT_CHAR = 3;
    153     public static final int STATUS_CHANGE_COUNT_OFFSET = 5;
    154 
    155     // Ready for ping
    156     public static final int PING_STATUS_OK = 0;
    157     // Service already running (can't ping)
    158     public static final int PING_STATUS_RUNNING = 1;
    159     // Service waiting after I/O error (can't ping)
    160     public static final int PING_STATUS_WAITING = 2;
    161     // Service had a fatal error; can't run
    162     public static final int PING_STATUS_UNABLE = 3;
    163     // Service is disabled by user (checkbox)
    164     public static final int PING_STATUS_DISABLED = 4;
    165 
    166     private static final int MAX_CLIENT_CONNECTION_MANAGER_SHUTDOWNS = 1;
    167 
    168     // We synchronize on this for all actions affecting the service and error maps
    169     private static final Object sSyncLock = new Object();
    170     // All threads can use this lock to wait for connectivity
    171     public static final Object sConnectivityLock = new Object();
    172     public static boolean sConnectivityHold = false;
    173 
    174     // Keeps track of running services (by mailbox id)
    175     public final HashMap<Long, AbstractSyncService> mServiceMap =
    176         new HashMap<Long, AbstractSyncService>();
    177     // Keeps track of services whose last sync ended with an error (by mailbox id)
    178     /*package*/ public ConcurrentHashMap<Long, SyncError> mSyncErrorMap =
    179         new ConcurrentHashMap<Long, SyncError>();
    180     // Keeps track of which services require a wake lock (by mailbox id)
    181     private final HashMap<Long, Long> mWakeLocks = new HashMap<Long, Long>();
    182     // Keeps track of which services have held a wake lock (by mailbox id)
    183     private final HashMap<Long, Long> mWakeLocksHistory = new HashMap<Long, Long>();
    184     // Keeps track of PendingIntents for mailbox alarms (by mailbox id)
    185     private final HashMap<Long, PendingIntent> mPendingIntents = new HashMap<Long, PendingIntent>();
    186     // The actual WakeLock obtained by SyncServiceManager
    187     private WakeLock mWakeLock = null;
    188     // Keep our cached list of active Accounts here
    189     public final AccountList mAccountList = new AccountList();
    190     // Keep track of when we started up
    191     private long mServiceStartTime;
    192 
    193     // Observers that we use to look for changed mail-related data
    194     private final Handler mHandler = new Handler();
    195     private AccountObserver mAccountObserver;
    196     private MailboxObserver mMailboxObserver;
    197     private SyncedMessageObserver mSyncedMessageObserver;
    198 
    199     // Concurrent because CalendarSyncAdapter can modify the map during a wipe
    200     private final ConcurrentHashMap<Long, CalendarObserver> mCalendarObservers =
    201         new ConcurrentHashMap<Long, CalendarObserver>();
    202 
    203     public ContentResolver mResolver;
    204 
    205     // The singleton SyncServiceManager object, with its thread and stop flag
    206     protected static SyncManager INSTANCE;
    207     protected static Thread sServiceThread = null;
    208     // Cached unique device id
    209     protected static String sDeviceId = null;
    210     // HashMap of ConnectionManagers that all EAS threads can use (by HostAuth id)
    211     private static HashMap<Long, EmailClientConnectionManager> sClientConnectionManagers =
    212             new HashMap<Long, EmailClientConnectionManager>();
    213     // Count of ClientConnectionManager shutdowns
    214     private static volatile int sClientConnectionManagerShutdownCount = 0;
    215 
    216     private static volatile boolean sStartingUp = false;
    217     private static volatile boolean sStop = false;
    218 
    219     // The reason for SyncServiceManager's next wakeup call
    220     private String mNextWaitReason;
    221     // Whether we have an unsatisfied "kick" pending
    222     private boolean mKicked = false;
    223 
    224     // Receiver of connectivity broadcasts
    225     private ConnectivityReceiver mConnectivityReceiver = null;
    226     // The most current NetworkInfo (from ConnectivityManager)
    227     private NetworkInfo mNetworkInfo;
    228 
    229     // For sync logging
    230     protected static boolean sUserLog = false;
    231     protected static boolean sFileLog = false;
    232 
    233     /**
    234      * Return an AccountObserver for this manager; the subclass must implement the newAccount()
    235      * method, which is called whenever the observer discovers that a new account has been created.
    236      * The subclass should do any housekeeping necessary
    237      * @param handler a Handler
    238      * @return the AccountObserver
    239      */
    240     public abstract AccountObserver getAccountObserver(Handler handler);
    241 
    242     /**
    243      * Perform any housekeeping necessary upon startup of the manager
    244      */
    245     public abstract void onStartup();
    246 
    247     /**
    248      * Returns a String that can be used as a WHERE clause in SQLite that selects accounts whose
    249      * syncs are managed by this manager
    250      * @return the account selector String
    251      */
    252     public abstract String getAccountsSelector();
    253 
    254     /**
    255      * Returns an appropriate sync service for the passed in mailbox
    256      * @param context the caller's context
    257      * @param mailbox the Mailbox to be synced
    258      * @return a service that will sync the Mailbox
    259      */
    260     public abstract AbstractSyncService getServiceForMailbox(Context context, Mailbox mailbox);
    261 
    262     /**
    263      * Return a list of all Accounts in EmailProvider.  Because the result of this call may be used
    264      * in account reconciliation, an exception is thrown if the result cannot be guaranteed accurate
    265      * @param context the caller's context
    266      * @param accounts a list that Accounts will be added into
    267      * @return the list of Accounts
    268      * @throws ProviderUnavailableException if the list of Accounts cannot be guaranteed valid
    269      */
    270     public abstract AccountList collectAccounts(Context context, AccountList accounts);
    271 
    272     /**
    273      * Returns the AccountManager type (e.g. com.android.exchange) for this sync service
    274      */
    275     public abstract String getAccountManagerType();
    276 
    277     /**
    278      * Returns the intent used for this sync service
    279      */
    280     public abstract Intent getServiceIntent();
    281 
    282     /**
    283      * Returns the callback proxy used for communicating back with the Email app
    284      */
    285     public abstract Stub getCallbackProxy();
    286 
    287     /**
    288      * Called when a sync service has started (in case any action is needed). This method must
    289      * not perform any long-lived actions (db access, network access, etc)
    290      */
    291     public abstract void onStartService(Mailbox mailbox);
    292 
    293     public class AccountList extends ArrayList<Account> {
    294         private static final long serialVersionUID = 1L;
    295 
    296         private final WeakHashMap<Account, android.accounts.Account> mAmMap =
    297                 new WeakHashMap<Account, android.accounts.Account>();
    298 
    299         @Override
    300         public boolean add(Account account) {
    301             // Cache the account manager account
    302             mAmMap.put(account, account.getAccountManagerAccount(getAccountManagerType()));
    303             super.add(account);
    304             return true;
    305         }
    306 
    307         public android.accounts.Account getAmAccount(Account account) {
    308             return mAmMap.get(account);
    309         }
    310 
    311         public boolean contains(long id) {
    312             for (Account account : this) {
    313                 if (account.mId == id) {
    314                     return true;
    315                 }
    316             }
    317             return false;
    318         }
    319 
    320         public Account getById(long id) {
    321             for (Account account : this) {
    322                 if (account.mId == id) {
    323                     return account;
    324                 }
    325             }
    326             return null;
    327         }
    328 
    329         public Account getByName(String accountName) {
    330             for (Account account : this) {
    331                 if (account.mEmailAddress.equalsIgnoreCase(accountName)) {
    332                     return account;
    333                 }
    334             }
    335             return null;
    336         }
    337     }
    338 
    339     public static void setUserDebug(int state) {
    340         sUserLog = (state & EmailServiceProxy.DEBUG_BIT) != 0;
    341         sFileLog = (state & EmailServiceProxy.DEBUG_FILE_BIT) != 0;
    342         if (sFileLog) {
    343             sUserLog = true;
    344         }
    345         LogUtils.d("Sync Debug", "Logging: " + (sUserLog ? "User " : "")
    346                 + (sFileLog ? "File" : ""));
    347     }
    348 
    349     private static boolean onSecurityHold(Account account) {
    350         return (account.mFlags & Account.FLAGS_SECURITY_HOLD) != 0;
    351     }
    352 
    353     public static String getAccountSelector() {
    354         SyncManager ssm = INSTANCE;
    355         if (ssm == null) return "";
    356         return ssm.getAccountsSelector();
    357     }
    358 
    359     public abstract class AccountObserver extends ContentObserver {
    360         String mSyncableMailboxSelector = null;
    361         String mAccountSelector = null;
    362 
    363         // Runs when SyncServiceManager first starts
    364         @SuppressWarnings("deprecation")
    365         public AccountObserver(Handler handler) {
    366             super(handler);
    367             // At startup, we want to see what EAS accounts exist and cache them
    368             // TODO: Move database work out of UI thread
    369             Context context = getContext();
    370             synchronized (mAccountList) {
    371                 try {
    372                     collectAccounts(context, mAccountList);
    373                 } catch (ProviderUnavailableException e) {
    374                     // Just leave if EmailProvider is unavailable
    375                     return;
    376                 }
    377                 // Create an account mailbox for any account without one
    378                 for (Account account : mAccountList) {
    379                     int cnt = Mailbox.count(context, Mailbox.CONTENT_URI, "accountKey="
    380                             + account.mId, null);
    381                     if (cnt == 0) {
    382                         // This case handles a newly created account
    383                         newAccount(account.mId);
    384                     }
    385                 }
    386             }
    387             // Run through accounts and update account hold information
    388             Utility.runAsync(new Runnable() {
    389                 @Override
    390                 public void run() {
    391                     synchronized (mAccountList) {
    392                         for (Account account : mAccountList) {
    393                             if (onSecurityHold(account)) {
    394                                 // If we're in a security hold, and our policies are active, release
    395                                 // the hold
    396                                 if (PolicyServiceProxy.isActive(SyncManager.this, null)) {
    397                                     PolicyServiceProxy.setAccountHoldFlag(SyncManager.this,
    398                                             account, false);
    399                                     log("isActive true; release hold for " + account.mDisplayName);
    400                                 }
    401                             }
    402                         }
    403                     }
    404                 }});
    405         }
    406 
    407         /**
    408          * Returns a String suitable for appending to a where clause that selects for all syncable
    409          * mailboxes in all eas accounts
    410          * @return a complex selection string that is not to be cached
    411          */
    412         public String getSyncableMailboxWhere() {
    413             if (mSyncableMailboxSelector == null) {
    414                 StringBuilder sb = new StringBuilder(WHERE_NOT_INTERVAL_NEVER_AND_ACCOUNT_KEY_IN);
    415                 boolean first = true;
    416                 synchronized (mAccountList) {
    417                     for (Account account : mAccountList) {
    418                         if (!first) {
    419                             sb.append(',');
    420                         } else {
    421                             first = false;
    422                         }
    423                         sb.append(account.mId);
    424                     }
    425                 }
    426                 sb.append(')');
    427                 mSyncableMailboxSelector = sb.toString();
    428             }
    429             return mSyncableMailboxSelector;
    430         }
    431 
    432         private void onAccountChanged() {
    433             try {
    434                 maybeStartSyncServiceManagerThread();
    435                 Context context = getContext();
    436 
    437                 // A change to the list requires us to scan for deletions (stop running syncs)
    438                 // At startup, we want to see what accounts exist and cache them
    439                 AccountList currentAccounts = new AccountList();
    440                 try {
    441                     collectAccounts(context, currentAccounts);
    442                 } catch (ProviderUnavailableException e) {
    443                     // Just leave if EmailProvider is unavailable
    444                     return;
    445                 }
    446                 synchronized (mAccountList) {
    447                     for (Account account : mAccountList) {
    448                         boolean accountIncomplete =
    449                             (account.mFlags & Account.FLAGS_INCOMPLETE) != 0;
    450                         // If the current list doesn't include this account and the account wasn't
    451                         // incomplete, then this is a deletion
    452                         if (!currentAccounts.contains(account.mId) && !accountIncomplete) {
    453                             // The implication is that the account has been deleted; let's find out
    454                             alwaysLog("Observer found deleted account: " + account.mDisplayName);
    455                             // Run the reconciler (the reconciliation itself runs in the Email app)
    456                             runAccountReconcilerSync(SyncManager.this);
    457                             // See if the account is still around
    458                             Account deletedAccount =
    459                                 Account.restoreAccountWithId(context, account.mId);
    460                             if (deletedAccount != null) {
    461                                 // It is; add it to our account list
    462                                 alwaysLog("Account still in provider: " + account.mDisplayName);
    463                                 currentAccounts.add(account);
    464                             } else {
    465                                 // It isn't; stop syncs and clear our selectors
    466                                 alwaysLog("Account deletion confirmed: " + account.mDisplayName);
    467                                 stopAccountSyncs(account.mId, true);
    468                                 mSyncableMailboxSelector = null;
    469                                 mAccountSelector = null;
    470                             }
    471                         } else {
    472                             // Get the newest version of this account
    473                             Account updatedAccount =
    474                                 Account.restoreAccountWithId(context, account.mId);
    475                             if (updatedAccount == null) continue;
    476                             if (account.mSyncInterval != updatedAccount.mSyncInterval
    477                                     || account.mSyncLookback != updatedAccount.mSyncLookback) {
    478                                 // Set the inbox interval to the interval of the Account
    479                                 // This setting should NOT affect other boxes
    480                                 ContentValues cv = new ContentValues();
    481                                 cv.put(MailboxColumns.SYNC_INTERVAL, updatedAccount.mSyncInterval);
    482                                 getContentResolver().update(Mailbox.CONTENT_URI, cv,
    483                                         WHERE_IN_ACCOUNT_AND_TYPE_INBOX, new String[] {
    484                                         Long.toString(account.mId)
    485                                 });
    486                                 // Stop all current syncs; the appropriate ones will restart
    487                                 log("Account " + account.mDisplayName + " changed; stop syncs");
    488                                 stopAccountSyncs(account.mId, true);
    489                             }
    490 
    491                             // See if this account is no longer on security hold
    492                             if (onSecurityHold(account) && !onSecurityHold(updatedAccount)) {
    493                                 releaseSyncHolds(SyncManager.this,
    494                                         AbstractSyncService.EXIT_SECURITY_FAILURE, account);
    495                             }
    496 
    497                             // Put current values into our cached account
    498                             account.mSyncInterval = updatedAccount.mSyncInterval;
    499                             account.mSyncLookback = updatedAccount.mSyncLookback;
    500                             account.mFlags = updatedAccount.mFlags;
    501                         }
    502                     }
    503                     // Look for new accounts
    504                     for (Account account : currentAccounts) {
    505                         if (!mAccountList.contains(account.mId)) {
    506                             // Don't forget to cache the HostAuth
    507                             HostAuth ha = HostAuth.restoreHostAuthWithId(getContext(),
    508                                     account.mHostAuthKeyRecv);
    509                             if (ha == null) continue;
    510                             account.mHostAuthRecv = ha;
    511                             // This is an addition; create our magic hidden mailbox...
    512                             log("Account observer found new account: " + account.mDisplayName);
    513                             newAccount(account.mId);
    514                             mAccountList.add(account);
    515                             mSyncableMailboxSelector = null;
    516                             mAccountSelector = null;
    517                         }
    518                     }
    519                     // Finally, make sure our account list is up to date
    520                     mAccountList.clear();
    521                     mAccountList.addAll(currentAccounts);
    522                 }
    523 
    524                 // See if there's anything to do...
    525                 kick("account changed");
    526             } catch (ProviderUnavailableException e) {
    527                 alwaysLog("Observer failed; provider unavailable");
    528             }
    529         }
    530 
    531         @Override
    532         public void onChange(boolean selfChange) {
    533             new Thread(new Runnable() {
    534                @Override
    535             public void run() {
    536                    onAccountChanged();
    537                 }}, "Account Observer").start();
    538         }
    539 
    540         public abstract void newAccount(long acctId);
    541     }
    542 
    543     /**
    544      * Register a specific Calendar's data observer; we need to recognize when the SYNC_EVENTS
    545      * column has changed (when sync has turned off or on)
    546      * @param account the Account whose Calendar we're observing
    547      */
    548     private void registerCalendarObserver(Account account) {
    549         // Get a new observer
    550         CalendarObserver observer = new CalendarObserver(mHandler, account);
    551         if (observer.mCalendarId != 0) {
    552             // If we find the Calendar (and we'd better) register it and store it in the map
    553             mCalendarObservers.put(account.mId, observer);
    554             mResolver.registerContentObserver(
    555                     ContentUris.withAppendedId(Calendars.CONTENT_URI, observer.mCalendarId), false,
    556                     observer);
    557         }
    558     }
    559 
    560     /**
    561      * Unregister all CalendarObserver's
    562      */
    563     static public void unregisterCalendarObservers() {
    564         SyncManager ssm = INSTANCE;
    565         if (ssm == null) return;
    566         ContentResolver resolver = ssm.mResolver;
    567         for (CalendarObserver observer: ssm.mCalendarObservers.values()) {
    568             resolver.unregisterContentObserver(observer);
    569         }
    570         ssm.mCalendarObservers.clear();
    571     }
    572 
    573     public static Uri asSyncAdapter(Uri uri, String account, String accountType) {
    574         return uri.buildUpon().appendQueryParameter(CalendarContract.CALLER_IS_SYNCADAPTER, "true")
    575                 .appendQueryParameter(Calendars.ACCOUNT_NAME, account)
    576                 .appendQueryParameter(Calendars.ACCOUNT_TYPE, accountType).build();
    577     }
    578 
    579     /**
    580      * Return the syncable state of an account's calendar, as determined by the sync_events column
    581      * of our Calendar (from CalendarProvider2)
    582      * Note that the current state of sync_events is cached in our CalendarObserver
    583      * @param accountId the id of the account whose calendar we are checking
    584      * @return whether or not syncing of events is enabled
    585      */
    586     private boolean isCalendarEnabled(long accountId) {
    587         CalendarObserver observer = mCalendarObservers.get(accountId);
    588         if (observer != null) {
    589             return (observer.mSyncEvents == 1);
    590         }
    591         // If there's no observer, there's no Calendar in CalendarProvider2, so we return true
    592         // to allow Calendar creation
    593         return true;
    594     }
    595 
    596     private class CalendarObserver extends ContentObserver {
    597         final long mAccountId;
    598         final String mAccountName;
    599         long mCalendarId;
    600         long mSyncEvents;
    601 
    602         public CalendarObserver(Handler handler, Account account) {
    603             super(handler);
    604             mAccountId = account.mId;
    605             mAccountName = account.mEmailAddress;
    606             // Find the Calendar for this account
    607             Cursor c = mResolver.query(Calendars.CONTENT_URI,
    608                     new String[] {Calendars._ID, Calendars.SYNC_EVENTS},
    609                     CALENDAR_SELECTION,
    610                     new String[] {account.mEmailAddress, getAccountManagerType()},
    611                     null);
    612             if (c != null) {
    613                 // Save its id and its sync events status
    614                 try {
    615                     if (c.moveToFirst()) {
    616                         mCalendarId = c.getLong(0);
    617                         mSyncEvents = c.getLong(1);
    618                     }
    619                 } finally {
    620                     c.close();
    621                 }
    622             }
    623         }
    624 
    625         @Override
    626         public synchronized void onChange(boolean selfChange) {
    627             // See if the user has changed syncing of our calendar
    628             if (!selfChange) {
    629                 new Thread(new Runnable() {
    630                     @Override
    631                     public void run() {
    632                         try {
    633                             Cursor c = mResolver.query(Calendars.CONTENT_URI,
    634                                     new String[] {Calendars.SYNC_EVENTS}, Calendars._ID + "=?",
    635                                     new String[] {Long.toString(mCalendarId)}, null);
    636                             if (c == null) return;
    637                             // Get its sync events; if it's changed, we've got work to do
    638                             try {
    639                                 if (c.moveToFirst()) {
    640                                     long newSyncEvents = c.getLong(0);
    641                                     if (newSyncEvents != mSyncEvents) {
    642                                         log("_sync_events changed for calendar in " + mAccountName);
    643                                         Mailbox mailbox = Mailbox.restoreMailboxOfType(INSTANCE,
    644                                                 mAccountId, Mailbox.TYPE_CALENDAR);
    645                                         // Sanity check for mailbox deletion
    646                                         if (mailbox == null) return;
    647                                         ContentValues cv = new ContentValues();
    648                                         if (newSyncEvents == 0) {
    649                                             // When sync is disabled, we're supposed to delete
    650                                             // all events in the calendar
    651                                             log("Deleting events and setting syncKey to 0 for " +
    652                                                     mAccountName);
    653                                             // First, stop any sync that's ongoing
    654                                             stopManualSync(mailbox.mId);
    655                                             // Set the syncKey to 0 (reset)
    656                                             AbstractSyncService service = getServiceForMailbox(
    657                                                     INSTANCE, mailbox);
    658                                             service.resetCalendarSyncKey();
    659                                             // Reset the sync key locally and stop syncing
    660                                             cv.put(Mailbox.SYNC_KEY, "0");
    661                                             cv.put(Mailbox.SYNC_INTERVAL,
    662                                                     Mailbox.CHECK_INTERVAL_NEVER);
    663                                             mResolver.update(ContentUris.withAppendedId(
    664                                                     Mailbox.CONTENT_URI, mailbox.mId), cv, null,
    665                                                     null);
    666                                             // Delete all events using the sync adapter
    667                                             // parameter so that the deletion is only local
    668                                             Uri eventsAsSyncAdapter =
    669                                                 asSyncAdapter(
    670                                                     Events.CONTENT_URI,
    671                                                     mAccountName,
    672                                                     getAccountManagerType());
    673                                             mResolver.delete(eventsAsSyncAdapter, WHERE_CALENDAR_ID,
    674                                                     new String[] {Long.toString(mCalendarId)});
    675                                         } else {
    676                                             // Make this a push mailbox and kick; this will start
    677                                             // a resync of the Calendar; the account mailbox will
    678                                             // ping on this during the next cycle of the ping loop
    679                                             cv.put(Mailbox.SYNC_INTERVAL,
    680                                                     Mailbox.CHECK_INTERVAL_PUSH);
    681                                             mResolver.update(ContentUris.withAppendedId(
    682                                                     Mailbox.CONTENT_URI, mailbox.mId), cv, null,
    683                                                     null);
    684                                             kick("calendar sync changed");
    685                                         }
    686 
    687                                         // Save away the new value
    688                                         mSyncEvents = newSyncEvents;
    689                                     }
    690                                 }
    691                             } finally {
    692                                 c.close();
    693                             }
    694                         } catch (ProviderUnavailableException e) {
    695                             LogUtils.w(TAG, "Observer failed; provider unavailable");
    696                         }
    697                     }}, "Calendar Observer").start();
    698             }
    699         }
    700     }
    701 
    702     private class MailboxObserver extends ContentObserver {
    703         public MailboxObserver(Handler handler) {
    704             super(handler);
    705         }
    706 
    707         @Override
    708         public void onChange(boolean selfChange) {
    709             // See if there's anything to do...
    710             if (!selfChange) {
    711                 kick("mailbox changed");
    712             }
    713         }
    714     }
    715 
    716     private class SyncedMessageObserver extends ContentObserver {
    717         Intent syncAlarmIntent = new Intent(INSTANCE, EmailSyncAlarmReceiver.class);
    718         PendingIntent syncAlarmPendingIntent =
    719             PendingIntent.getBroadcast(INSTANCE, 0, syncAlarmIntent, 0);
    720         AlarmManager alarmManager = (AlarmManager)INSTANCE.getSystemService(Context.ALARM_SERVICE);
    721 
    722         public SyncedMessageObserver(Handler handler) {
    723             super(handler);
    724         }
    725 
    726         @Override
    727         public void onChange(boolean selfChange) {
    728             alarmManager.set(AlarmManager.RTC_WAKEUP,
    729                     System.currentTimeMillis() + 10*SECONDS, syncAlarmPendingIntent);
    730         }
    731     }
    732 
    733     static public Account getAccountById(long accountId) {
    734         SyncManager ssm = INSTANCE;
    735         if (ssm != null) {
    736             AccountList accountList = ssm.mAccountList;
    737             synchronized (accountList) {
    738                 return accountList.getById(accountId);
    739             }
    740         }
    741         return null;
    742     }
    743 
    744     static public Account getAccountByName(String accountName) {
    745         SyncManager ssm = INSTANCE;
    746         if (ssm != null) {
    747             AccountList accountList = ssm.mAccountList;
    748             synchronized (accountList) {
    749                 return accountList.getByName(accountName);
    750             }
    751         }
    752         return null;
    753     }
    754 
    755     public class SyncStatus {
    756         static public final int NOT_RUNNING = 0;
    757         static public final int DIED = 1;
    758         static public final int SYNC = 2;
    759         static public final int IDLE = 3;
    760     }
    761 
    762     /*package*/ public class SyncError {
    763         int reason;
    764         public boolean fatal = false;
    765         long holdDelay = 15*SECONDS;
    766         public long holdEndTime = System.currentTimeMillis() + holdDelay;
    767 
    768         public SyncError(int _reason, boolean _fatal) {
    769             reason = _reason;
    770             fatal = _fatal;
    771         }
    772 
    773         /**
    774          * We double the holdDelay from 15 seconds through 8 mins
    775          */
    776         void escalate() {
    777             if (holdDelay <= HOLD_DELAY_MAXIMUM) {
    778                 holdDelay *= 2;
    779             }
    780             holdEndTime = System.currentTimeMillis() + holdDelay;
    781         }
    782     }
    783 
    784     private void logSyncHolds() {
    785         if (sUserLog) {
    786             log("Sync holds:");
    787             long time = System.currentTimeMillis();
    788             for (long mailboxId : mSyncErrorMap.keySet()) {
    789                 Mailbox m = Mailbox.restoreMailboxWithId(this, mailboxId);
    790                 if (m == null) {
    791                     log("Mailbox " + mailboxId + " no longer exists");
    792                 } else {
    793                     SyncError error = mSyncErrorMap.get(mailboxId);
    794                     if (error != null) {
    795                         log("Mailbox " + m.mDisplayName + ", error = " + error.reason
    796                                 + ", fatal = " + error.fatal);
    797                         if (error.holdEndTime > 0) {
    798                             log("Hold ends in " + ((error.holdEndTime - time) / 1000) + "s");
    799                         }
    800                     }
    801                 }
    802             }
    803         }
    804     }
    805 
    806     /**
    807      * Release security holds for the specified account
    808      * @param account the account whose Mailboxes should be released from security hold
    809      */
    810     static public void releaseSecurityHold(Account account) {
    811         SyncManager ssm = INSTANCE;
    812         if (ssm != null) {
    813             ssm.releaseSyncHolds(INSTANCE, AbstractSyncService.EXIT_SECURITY_FAILURE,
    814                     account);
    815         }
    816     }
    817 
    818     /**
    819      * Release a specific type of hold (the reason) for the specified Account; if the account
    820      * is null, mailboxes from all accounts with the specified hold will be released
    821      * @param reason the reason for the SyncError (AbstractSyncService.EXIT_XXX)
    822      * @param account an Account whose mailboxes should be released (or all if null)
    823      * @return whether or not any mailboxes were released
    824      */
    825     public /*package*/ boolean releaseSyncHolds(Context context, int reason, Account account) {
    826         boolean holdWasReleased = releaseSyncHoldsImpl(context, reason, account);
    827         kick("security release");
    828         return holdWasReleased;
    829     }
    830 
    831     private boolean releaseSyncHoldsImpl(Context context, int reason, Account account) {
    832         boolean holdWasReleased = false;
    833         for (long mailboxId: mSyncErrorMap.keySet()) {
    834             if (account != null) {
    835                 Mailbox m = Mailbox.restoreMailboxWithId(context, mailboxId);
    836                 if (m == null) {
    837                     mSyncErrorMap.remove(mailboxId);
    838                 } else if (m.mAccountKey != account.mId) {
    839                     continue;
    840                 }
    841             }
    842             SyncError error = mSyncErrorMap.get(mailboxId);
    843             if (error != null && error.reason == reason) {
    844                 mSyncErrorMap.remove(mailboxId);
    845                 holdWasReleased = true;
    846             }
    847         }
    848         return holdWasReleased;
    849     }
    850 
    851     public static void log(String str) {
    852         log(TAG, str);
    853     }
    854 
    855     public static void log(String tag, String str) {
    856         if (sUserLog) {
    857             LogUtils.d(tag, str);
    858             if (sFileLog) {
    859                 FileLogger.log(tag, str);
    860             }
    861         }
    862     }
    863 
    864     public static void alwaysLog(String str) {
    865         if (!sUserLog) {
    866             LogUtils.d(TAG, str);
    867         } else {
    868             log(str);
    869         }
    870     }
    871 
    872     /**
    873      * EAS requires a unique device id, so that sync is possible from a variety of different
    874      * devices (e.g. the syncKey is specific to a device)  If we're on an emulator or some other
    875      * device that doesn't provide one, we can create it as "device".
    876      * This would work on a real device as well, but it would be better to use the "real" id if
    877      * it's available
    878      */
    879     static public String getDeviceId(Context context) {
    880         if (sDeviceId == null) {
    881             sDeviceId = new AccountServiceProxy(context).getDeviceId();
    882             alwaysLog("Received deviceId from Email app: " + sDeviceId);
    883         }
    884         return sDeviceId;
    885     }
    886 
    887     static public ConnPerRoute sConnPerRoute = new ConnPerRoute() {
    888         @Override
    889         public int getMaxForRoute(HttpRoute route) {
    890             return 8;
    891         }
    892     };
    893 
    894     static public synchronized EmailClientConnectionManager getClientConnectionManager(
    895             Context context, HostAuth hostAuth) {
    896         // We'll use a different connection manager for each HostAuth
    897         EmailClientConnectionManager mgr = null;
    898         // We don't save managers for validation/autodiscover
    899         if (hostAuth.mId != HostAuth.NOT_SAVED) {
    900             mgr = sClientConnectionManagers.get(hostAuth.mId);
    901         }
    902         if (mgr == null) {
    903             // After two tries, kill the process.  Most likely, this will happen in the background
    904             // The service will restart itself after about 5 seconds
    905             if (sClientConnectionManagerShutdownCount > MAX_CLIENT_CONNECTION_MANAGER_SHUTDOWNS) {
    906                 alwaysLog("Shutting down process to unblock threads");
    907                 Process.killProcess(Process.myPid());
    908             }
    909             HttpParams params = new BasicHttpParams();
    910             params.setIntParameter(ConnManagerPNames.MAX_TOTAL_CONNECTIONS, 25);
    911             params.setParameter(ConnManagerPNames.MAX_CONNECTIONS_PER_ROUTE, sConnPerRoute);
    912             boolean ssl = hostAuth.shouldUseSsl();
    913             int port = hostAuth.mPort;
    914             mgr = EmailClientConnectionManager.newInstance(context, params, hostAuth);
    915             log("Creating connection manager for port " + port + ", ssl: " + ssl);
    916             sClientConnectionManagers.put(hostAuth.mId, mgr);
    917         }
    918         // Null is a valid return result if we get an exception
    919         return mgr;
    920     }
    921 
    922     static private synchronized void shutdownConnectionManager() {
    923         log("Shutting down ClientConnectionManagers");
    924         for (EmailClientConnectionManager mgr: sClientConnectionManagers.values()) {
    925             mgr.shutdown();
    926         }
    927         sClientConnectionManagers.clear();
    928     }
    929 
    930     public static void stopAccountSyncs(long acctId) {
    931         SyncManager ssm = INSTANCE;
    932         if (ssm != null) {
    933             ssm.stopAccountSyncs(acctId, true);
    934         }
    935     }
    936 
    937     public void stopAccountSyncs(long acctId, boolean includeAccountMailbox) {
    938         synchronized (sSyncLock) {
    939             List<Long> deletedBoxes = new ArrayList<Long>();
    940             for (Long mid : mServiceMap.keySet()) {
    941                 Mailbox box = Mailbox.restoreMailboxWithId(this, mid);
    942                 if (box != null) {
    943                     if (box.mAccountKey == acctId) {
    944                         if (!includeAccountMailbox &&
    945                                 box.mType == Mailbox.TYPE_EAS_ACCOUNT_MAILBOX) {
    946                             AbstractSyncService svc = mServiceMap.get(mid);
    947                             if (svc != null) {
    948                                 svc.stop();
    949                             }
    950                             continue;
    951                         }
    952                         AbstractSyncService svc = mServiceMap.get(mid);
    953                         if (svc != null) {
    954                             svc.stop();
    955                             Thread t = svc.mThread;
    956                             if (t != null) {
    957                                 t.interrupt();
    958                             }
    959                         }
    960                         deletedBoxes.add(mid);
    961                     }
    962                 }
    963             }
    964             for (Long mid : deletedBoxes) {
    965                 releaseMailbox(mid);
    966             }
    967         }
    968     }
    969 
    970     /**
    971      * Informs SyncServiceManager that an account has a new folder list; as a result, any existing
    972      * folder might have become invalid.  Therefore, we act as if the account has been deleted, and
    973      * then we reinitialize it.
    974      *
    975      * @param acctId
    976      */
    977     static public void stopNonAccountMailboxSyncsForAccount(long acctId) {
    978         SyncManager ssm = INSTANCE;
    979         if (ssm != null) {
    980             ssm.stopAccountSyncs(acctId, false);
    981             kick("reload folder list");
    982         }
    983     }
    984 
    985     private boolean hasWakeLock(long id) {
    986         synchronized (mWakeLocks) {
    987             return mWakeLocks.get(id) != null;
    988         }
    989     }
    990 
    991     private void acquireWakeLock(long id) {
    992         synchronized (mWakeLocks) {
    993             Long lock = mWakeLocks.get(id);
    994             if (lock == null) {
    995                 if (mWakeLock == null) {
    996                     PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
    997                     mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MAIL_SERVICE");
    998                     mWakeLock.acquire();
    999                     // STOPSHIP Remove
   1000                     log("+WAKE LOCK ACQUIRED");
   1001                 }
   1002                 mWakeLocks.put(id, System.currentTimeMillis());
   1003              }
   1004         }
   1005     }
   1006 
   1007     private void releaseWakeLock(long id) {
   1008         synchronized (mWakeLocks) {
   1009             Long lock = mWakeLocks.get(id);
   1010             if (lock != null) {
   1011                 Long startTime = mWakeLocks.remove(id);
   1012                 Long historicalTime = mWakeLocksHistory.get(id);
   1013                 if (historicalTime == null) {
   1014                     historicalTime = 0L;
   1015                 }
   1016                 mWakeLocksHistory.put(id,
   1017                         historicalTime + (System.currentTimeMillis() - startTime));
   1018                 if (mWakeLocks.isEmpty()) {
   1019                     if (mWakeLock != null) {
   1020                         mWakeLock.release();
   1021                     }
   1022                     mWakeLock = null;
   1023                     // STOPSHIP Remove
   1024                     log("+WAKE LOCK RELEASED");
   1025                 } else {
   1026                     log("Release request for lock not held: " + id);
   1027                 }
   1028             }
   1029         }
   1030     }
   1031 
   1032     static public String alarmOwner(long id) {
   1033         if (id == EXTRA_MAILBOX_ID) {
   1034             return TAG;
   1035         } else {
   1036             String name = Long.toString(id);
   1037             if (sUserLog && INSTANCE != null) {
   1038                 Mailbox m = Mailbox.restoreMailboxWithId(INSTANCE, id);
   1039                 if (m != null) {
   1040                     name = m.mDisplayName + '(' + m.mAccountKey + ')';
   1041                 }
   1042             }
   1043             return "Mailbox " + name;
   1044         }
   1045     }
   1046 
   1047     private void clearAlarm(long id) {
   1048         synchronized (mPendingIntents) {
   1049             PendingIntent pi = mPendingIntents.get(id);
   1050             if (pi != null) {
   1051                 AlarmManager alarmManager = (AlarmManager)getSystemService(Context.ALARM_SERVICE);
   1052                 alarmManager.cancel(pi);
   1053                 //log("+Alarm cleared for " + alarmOwner(id));
   1054                 mPendingIntents.remove(id);
   1055             }
   1056         }
   1057     }
   1058 
   1059     private void setAlarm(long id, long millis) {
   1060         synchronized (mPendingIntents) {
   1061             PendingIntent pi = mPendingIntents.get(id);
   1062             if (pi == null) {
   1063                 Intent i = new Intent(this, MailboxAlarmReceiver.class);
   1064                 i.putExtra("mailbox", id);
   1065                 i.setData(Uri.parse("Box" + id));
   1066                 pi = PendingIntent.getBroadcast(this, 0, i, 0);
   1067                 mPendingIntents.put(id, pi);
   1068 
   1069                 AlarmManager alarmManager = (AlarmManager)getSystemService(Context.ALARM_SERVICE);
   1070                 alarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + millis, pi);
   1071                 //log("+Alarm set for " + alarmOwner(id) + ", " + millis/1000 + "s");
   1072             }
   1073         }
   1074     }
   1075 
   1076     private void clearAlarms() {
   1077         AlarmManager alarmManager = (AlarmManager)getSystemService(Context.ALARM_SERVICE);
   1078         synchronized (mPendingIntents) {
   1079             for (PendingIntent pi : mPendingIntents.values()) {
   1080                 alarmManager.cancel(pi);
   1081             }
   1082             mPendingIntents.clear();
   1083         }
   1084     }
   1085 
   1086     static public boolean isHoldingWakeLock(long id) {
   1087         SyncManager ssm = INSTANCE;
   1088         if (ssm != null) {
   1089             return ssm.hasWakeLock(id);
   1090         }
   1091         return false;
   1092     }
   1093 
   1094     static public void runAwake(long id) {
   1095         SyncManager ssm = INSTANCE;
   1096         if (ssm != null) {
   1097             ssm.acquireWakeLock(id);
   1098             ssm.clearAlarm(id);
   1099         }
   1100     }
   1101 
   1102     static public void runAsleep(long id, long millis) {
   1103         SyncManager ssm = INSTANCE;
   1104         if (ssm != null) {
   1105             ssm.setAlarm(id, millis);
   1106             ssm.releaseWakeLock(id);
   1107         }
   1108     }
   1109 
   1110     static public void clearWatchdogAlarm(long id) {
   1111         SyncManager ssm = INSTANCE;
   1112         if (ssm != null) {
   1113             ssm.clearAlarm(id);
   1114         }
   1115     }
   1116 
   1117     static public void setWatchdogAlarm(long id, long millis) {
   1118         SyncManager ssm = INSTANCE;
   1119         if (ssm != null) {
   1120             ssm.setAlarm(id, millis);
   1121         }
   1122     }
   1123 
   1124     static public void alert(Context context, final long id) {
   1125         final SyncManager ssm = INSTANCE;
   1126         checkSyncManagerRunning();
   1127         if (id < 0) {
   1128             log("SyncServiceManager alert");
   1129             kick("ping SyncServiceManager");
   1130         } else if (ssm == null) {
   1131             context.startService(new Intent(context, SyncManager.class));
   1132         } else {
   1133             final AbstractSyncService service = ssm.getRunningService(id);
   1134             if (service != null) {
   1135                 // Handle alerts in a background thread, as we are typically called from a
   1136                 // broadcast receiver, and are therefore running in the UI thread
   1137                 String threadName = "SyncServiceManager Alert: ";
   1138                 if (service.mMailbox != null) {
   1139                     threadName += service.mMailbox.mDisplayName;
   1140                 }
   1141                 new Thread(new Runnable() {
   1142                    @Override
   1143                 public void run() {
   1144                        Mailbox m = Mailbox.restoreMailboxWithId(ssm, id);
   1145                        if (m != null) {
   1146                            // We ignore drafts completely (doesn't sync).  Changes in Outbox are
   1147                            // handled in the checkMailboxes loop, so we can ignore these pings.
   1148                            if (sUserLog) {
   1149                                LogUtils.d(TAG, "Alert for mailbox " + id + " ("
   1150                                        + m.mDisplayName + ")");
   1151                            }
   1152                            if (m.mType == Mailbox.TYPE_DRAFTS || m.mType == Mailbox.TYPE_OUTBOX) {
   1153                                String[] args = new String[] {Long.toString(m.mId)};
   1154                                ContentResolver resolver = INSTANCE.mResolver;
   1155                                resolver.delete(Message.DELETED_CONTENT_URI, WHERE_MAILBOX_KEY,
   1156                                        args);
   1157                                resolver.delete(Message.UPDATED_CONTENT_URI, WHERE_MAILBOX_KEY,
   1158                                        args);
   1159                                return;
   1160                            }
   1161                            service.mAccount = Account.restoreAccountWithId(INSTANCE, m.mAccountKey);
   1162                            service.mMailbox = m;
   1163                            // Send the alarm to the sync service
   1164                            if (!service.alarm()) {
   1165                                // A false return means that we were forced to interrupt the thread
   1166                                // In this case, we release the mailbox so that we can start another
   1167                                // thread to do the work
   1168                                log("Alarm failed; releasing mailbox");
   1169                                synchronized(sSyncLock) {
   1170                                    ssm.releaseMailbox(id);
   1171                                }
   1172                                // Shutdown the connection manager; this should close all of our
   1173                                // sockets and generate IOExceptions all around.
   1174                                SyncManager.shutdownConnectionManager();
   1175                            }
   1176                        }
   1177                     }}, threadName).start();
   1178             }
   1179         }
   1180     }
   1181 
   1182     public class ConnectivityReceiver extends BroadcastReceiver {
   1183         @SuppressWarnings("deprecation")
   1184         @Override
   1185         public void onReceive(Context context, Intent intent) {
   1186             Bundle b = intent.getExtras();
   1187             if (b != null) {
   1188                 NetworkInfo a = (NetworkInfo)b.get(ConnectivityManager.EXTRA_NETWORK_INFO);
   1189                 String info = "Connectivity alert for " + a.getTypeName();
   1190                 State state = a.getState();
   1191                 if (state == State.CONNECTED) {
   1192                     info += " CONNECTED";
   1193                     log(info);
   1194                     synchronized (sConnectivityLock) {
   1195                         sConnectivityLock.notifyAll();
   1196                     }
   1197                     kick("connected");
   1198                 } else if (state == State.DISCONNECTED) {
   1199                     info += " DISCONNECTED";
   1200                     log(info);
   1201                     kick("disconnected");
   1202                 }
   1203             }
   1204         }
   1205     }
   1206 
   1207     /**
   1208      * Starts a service thread and enters it into the service map
   1209      * This is the point of instantiation of all sync threads
   1210      * @param service the service to start
   1211      * @param m the Mailbox on which the service will operate
   1212      */
   1213     private void startServiceThread(AbstractSyncService service) {
   1214         final Mailbox mailbox = service.mMailbox;
   1215         synchronized (sSyncLock) {
   1216             String mailboxName = mailbox.mDisplayName;
   1217             String accountName = service.mAccount.mDisplayName;
   1218             Thread thread = new Thread(service, mailboxName + "[" + accountName + "]");
   1219             log("Starting thread for " + mailboxName + " in account " + accountName);
   1220             thread.start();
   1221             mServiceMap.put(mailbox.mId, service);
   1222             runAwake(mailbox.mId);
   1223         }
   1224         onStartService(mailbox);
   1225     }
   1226 
   1227     private void requestSync(Mailbox m, int reason, Request req) {
   1228         int syncStatus = EmailContent.SYNC_STATUS_BACKGROUND;
   1229         // Don't sync if there's no connectivity
   1230         if (sConnectivityHold || (m == null) || sStop) {
   1231             return;
   1232         }
   1233         synchronized (sSyncLock) {
   1234             Account acct = Account.restoreAccountWithId(this, m.mAccountKey);
   1235             if (acct != null) {
   1236                 // Always make sure there's not a running instance of this service
   1237                 AbstractSyncService service = mServiceMap.get(m.mId);
   1238                 if (service == null) {
   1239                     service = getServiceForMailbox(this, m);
   1240                     if (!service.mIsValid) return;
   1241                     service.mSyncReason = reason;
   1242                     if (req != null) {
   1243                         service.addRequest(req);
   1244                     }
   1245                     startServiceThread(service);
   1246                     if (reason >= SYNC_CALLBACK_START) {
   1247                         syncStatus = EmailContent.SYNC_STATUS_USER;
   1248                     }
   1249                     setMailboxSyncStatus(m.mId, syncStatus);
   1250                 }
   1251             }
   1252         }
   1253     }
   1254 
   1255     public void setMailboxSyncStatus(long id, int status) {
   1256         ContentValues values = new ContentValues();
   1257         values.put(Mailbox.UI_SYNC_STATUS, status);
   1258         mResolver.update(ContentUris.withAppendedId(Mailbox.CONTENT_URI, id), values, null, null);
   1259     }
   1260 
   1261     public void setMailboxLastSyncResult(long id, int result) {
   1262         ContentValues values = new ContentValues();
   1263         values.put(Mailbox.UI_LAST_SYNC_RESULT, result);
   1264         mResolver.update(ContentUris.withAppendedId(Mailbox.CONTENT_URI, id), values, null, null);
   1265     }
   1266 
   1267     private void stopServiceThreads() {
   1268         synchronized (sSyncLock) {
   1269             ArrayList<Long> toStop = new ArrayList<Long>();
   1270 
   1271             // Keep track of which services to stop
   1272             for (Long mailboxId : mServiceMap.keySet()) {
   1273                 toStop.add(mailboxId);
   1274             }
   1275 
   1276             // Shut down all of those running services
   1277             for (Long mailboxId : toStop) {
   1278                 AbstractSyncService svc = mServiceMap.get(mailboxId);
   1279                 if (svc != null) {
   1280                     log("Stopping " + svc.mAccount.mDisplayName + '/' + svc.mMailbox.mDisplayName);
   1281                     svc.stop();
   1282                     if (svc.mThread != null) {
   1283                         svc.mThread.interrupt();
   1284                     }
   1285                 }
   1286                 releaseWakeLock(mailboxId);
   1287             }
   1288         }
   1289     }
   1290 
   1291     private void waitForConnectivity() {
   1292         boolean waiting = false;
   1293         ConnectivityManager cm =
   1294             (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
   1295         while (!sStop) {
   1296             NetworkInfo info = cm.getActiveNetworkInfo();
   1297             if (info != null) {
   1298                 mNetworkInfo = info;
   1299                 // We're done if there's an active network
   1300                 if (waiting) {
   1301                     // If we've been waiting, release any I/O error holds
   1302                     releaseSyncHolds(this, AbstractSyncService.EXIT_IO_ERROR, null);
   1303                     // And log what's still being held
   1304                     logSyncHolds();
   1305                 }
   1306                 return;
   1307             } else {
   1308                 // If this is our first time through the loop, shut down running service threads
   1309                 if (!waiting) {
   1310                     waiting = true;
   1311                     stopServiceThreads();
   1312                 }
   1313                 // Wait until a network is connected (or 10 mins), but let the device sleep
   1314                 // We'll set an alarm just in case we don't get notified (bugs happen)
   1315                 synchronized (sConnectivityLock) {
   1316                     runAsleep(EXTRA_MAILBOX_ID, CONNECTIVITY_WAIT_TIME+5*SECONDS);
   1317                     try {
   1318                         log("Connectivity lock...");
   1319                         sConnectivityHold = true;
   1320                         sConnectivityLock.wait(CONNECTIVITY_WAIT_TIME);
   1321                         log("Connectivity lock released...");
   1322                     } catch (InterruptedException e) {
   1323                         // This is fine; we just go around the loop again
   1324                     } finally {
   1325                         sConnectivityHold = false;
   1326                     }
   1327                     runAwake(EXTRA_MAILBOX_ID);
   1328                 }
   1329             }
   1330         }
   1331     }
   1332 
   1333     /**
   1334      * Note that there are two ways the EAS SyncServiceManager service can be created:
   1335      *
   1336      * 1) as a background service instantiated via startService (which happens on boot, when the
   1337      * first EAS account is created, etc), in which case the service thread is spun up, mailboxes
   1338      * sync, etc. and
   1339      * 2) to execute an RPC call from the UI, in which case the background service will already be
   1340      * running most of the time (unless we're creating a first EAS account)
   1341      *
   1342      * If the running background service detects that there are no EAS accounts (on boot, if none
   1343      * were created, or afterward if the last remaining EAS account is deleted), it will call
   1344      * stopSelf() to terminate operation.
   1345      *
   1346      * The goal is to ensure that the background service is running at all times when there is at
   1347      * least one EAS account in existence
   1348      *
   1349      * Because there are edge cases in which our process can crash (typically, this has been seen
   1350      * in UI crashes, ANR's, etc.), it's possible for the UI to start up again without the
   1351      * background service having been started.  We explicitly try to start the service in Welcome
   1352      * (to handle the case of the app having been reloaded).  We also start the service on any
   1353      * startSync call (if it isn't already running)
   1354      */
   1355     @SuppressWarnings("deprecation")
   1356     @Override
   1357     public void onCreate() {
   1358         TAG = getClass().getSimpleName();
   1359         EmailContent.init(this);
   1360         Utility.runAsync(new Runnable() {
   1361             @Override
   1362             public void run() {
   1363                 // Quick checks first, before getting the lock
   1364                 if (sStartingUp) return;
   1365                 synchronized (sSyncLock) {
   1366                     alwaysLog("!!! onCreate");
   1367                     // Try to start up properly; we might be coming back from a crash that the Email
   1368                     // application isn't aware of.
   1369                     startService(getServiceIntent());
   1370                     if (sStop) {
   1371                         return;
   1372                     }
   1373                 }
   1374             }});
   1375     }
   1376 
   1377     @SuppressWarnings("deprecation")
   1378     @Override
   1379     public int onStartCommand(Intent intent, int flags, int startId) {
   1380         alwaysLog("!!! onStartCommand, startingUp = " + sStartingUp + ", running = " +
   1381                 (INSTANCE != null));
   1382         if (!sStartingUp && INSTANCE == null) {
   1383             sStartingUp = true;
   1384             Utility.runAsync(new Runnable() {
   1385                 @Override
   1386                 public void run() {
   1387                     try {
   1388                         synchronized (sSyncLock) {
   1389                             // SyncServiceManager cannot start unless we connect to AccountService
   1390                             if (!new AccountServiceProxy(SyncManager.this).test()) {
   1391                                 alwaysLog("!!! Email application not found; stopping self");
   1392                                 stopSelf();
   1393                             }
   1394                             String deviceId = getDeviceId(SyncManager.this);
   1395                             if (deviceId == null) {
   1396                                 alwaysLog("!!! deviceId unknown; stopping self and retrying");
   1397                                 stopSelf();
   1398                                 // Try to restart ourselves in a few seconds
   1399                                 Utility.runAsync(new Runnable() {
   1400                                     @Override
   1401                                     public void run() {
   1402                                         try {
   1403                                             Thread.sleep(5000);
   1404                                         } catch (InterruptedException e) {
   1405                                         }
   1406                                         startService(getServiceIntent());
   1407                                     }});
   1408                                 return;
   1409                             }
   1410                             // Run the reconciler and clean up mismatched accounts - if we weren't
   1411                             // running when accounts were deleted, it won't have been called.
   1412                             runAccountReconcilerSync(SyncManager.this);
   1413                             // Update other services depending on final account configuration
   1414                             maybeStartSyncServiceManagerThread();
   1415                             if (sServiceThread == null) {
   1416                                 log("!!! EAS SyncServiceManager, stopping self");
   1417                                 stopSelf();
   1418                             } else if (sStop) {
   1419                                 // If we were trying to stop, attempt a restart in 5 secs
   1420                                 setAlarm(SYNC_SERVICE_MAILBOX_ID, 5*SECONDS);
   1421                             } else {
   1422                                 mServiceStartTime = System.currentTimeMillis();
   1423                             }
   1424                         }
   1425                     } finally {
   1426                         sStartingUp = false;
   1427                     }
   1428                 }});
   1429         }
   1430         return Service.START_STICKY;
   1431     }
   1432 
   1433     public static void reconcileAccounts(Context context) {
   1434         SyncManager ssm = INSTANCE;
   1435         if (ssm != null) {
   1436             ssm.runAccountReconcilerSync(context);
   1437         }
   1438     }
   1439 
   1440     protected abstract void runAccountReconcilerSync(Context context);
   1441 
   1442     @SuppressWarnings("deprecation")
   1443     @Override
   1444     public void onDestroy() {
   1445         log("!!! onDestroy");
   1446         // Handle shutting down off the UI thread
   1447         Utility.runAsync(new Runnable() {
   1448             @Override
   1449             public void run() {
   1450                 // Quick checks first, before getting the lock
   1451                 if (INSTANCE == null || sServiceThread == null) return;
   1452                 synchronized(sSyncLock) {
   1453                     // Stop the sync manager thread and return
   1454                     if (sServiceThread != null) {
   1455                         sStop = true;
   1456                         sServiceThread.interrupt();
   1457                     }
   1458                 }
   1459             }});
   1460     }
   1461 
   1462     void maybeStartSyncServiceManagerThread() {
   1463         // Start our thread...
   1464         // See if there are any EAS accounts; otherwise, just go away
   1465         if (sServiceThread == null || !sServiceThread.isAlive()) {
   1466             AccountList currentAccounts = new AccountList();
   1467             try {
   1468                 collectAccounts(this, currentAccounts);
   1469             } catch (ProviderUnavailableException e) {
   1470                 // Just leave if EmailProvider is unavailable
   1471                 return;
   1472             }
   1473             if (!currentAccounts.isEmpty()) {
   1474                 log(sServiceThread == null ? "Starting thread..." : "Restarting thread...");
   1475                 sServiceThread = new Thread(this, TAG);
   1476                 INSTANCE = this;
   1477                 sServiceThread.start();
   1478             }
   1479         }
   1480     }
   1481 
   1482     /**
   1483      * Start up the SyncManager service if it's not already running
   1484      * This is a stopgap for cases in which SyncServiceManager died (due to a crash somewhere in
   1485      * com.android.email) and hasn't been restarted. See the comment for onCreate for details
   1486      */
   1487     static void checkSyncManagerRunning() {
   1488         SyncManager ssm = INSTANCE;
   1489         if (ssm == null) return;
   1490         if (sServiceThread == null) {
   1491             log("!!! checkSyncServiceManagerServiceRunning; starting service...");
   1492             ssm.startService(new Intent(ssm, SyncManager.class));
   1493         }
   1494     }
   1495 
   1496     @SuppressWarnings("deprecation")
   1497     @Override
   1498     public void run() {
   1499         sStop = false;
   1500         alwaysLog("Service thread running");
   1501 
   1502         TempDirectory.setTempDirectory(this);
   1503 
   1504         // Synchronize here to prevent a shutdown from happening while we initialize our observers
   1505         // and receivers
   1506         synchronized (sSyncLock) {
   1507             if (INSTANCE != null) {
   1508                 mResolver = getContentResolver();
   1509 
   1510                 // Set up our observers; we need them to know when to start/stop various syncs based
   1511                 // on the insert/delete/update of mailboxes and accounts
   1512                 // We also observe synced messages to trigger upsyncs at the appropriate time
   1513                 mAccountObserver = getAccountObserver(mHandler);
   1514                 mResolver.registerContentObserver(Account.NOTIFIER_URI, true, mAccountObserver);
   1515                 mMailboxObserver = new MailboxObserver(mHandler);
   1516                 mResolver.registerContentObserver(Mailbox.CONTENT_URI, false, mMailboxObserver);
   1517                 mSyncedMessageObserver = new SyncedMessageObserver(mHandler);
   1518                 mResolver.registerContentObserver(Message.SYNCED_CONTENT_URI, true,
   1519                         mSyncedMessageObserver);
   1520 
   1521                 mConnectivityReceiver = new ConnectivityReceiver();
   1522                 registerReceiver(mConnectivityReceiver, new IntentFilter(
   1523                         ConnectivityManager.CONNECTIVITY_ACTION));
   1524 
   1525                 onStartup();
   1526             }
   1527         }
   1528 
   1529         try {
   1530             // Loop indefinitely until we're shut down
   1531             while (!sStop) {
   1532                 runAwake(EXTRA_MAILBOX_ID);
   1533                 waitForConnectivity();
   1534                 mNextWaitReason = null;
   1535                 long nextWait = checkMailboxes();
   1536                 try {
   1537                     synchronized (this) {
   1538                         if (!mKicked) {
   1539                             if (nextWait < 0) {
   1540                                 log("Negative wait? Setting to 1s");
   1541                                 nextWait = 1*SECONDS;
   1542                             }
   1543                             if (nextWait > 10*SECONDS) {
   1544                                 if (mNextWaitReason != null) {
   1545                                     log("Next awake " + nextWait / 1000 + "s: " + mNextWaitReason);
   1546                                 }
   1547                                 runAsleep(EXTRA_MAILBOX_ID, nextWait + (3*SECONDS));
   1548                             }
   1549                             wait(nextWait);
   1550                         }
   1551                     }
   1552                 } catch (InterruptedException e) {
   1553                     // Needs to be caught, but causes no problem
   1554                     log("SyncServiceManager interrupted");
   1555                 } finally {
   1556                     synchronized (this) {
   1557                         if (mKicked) {
   1558                             //log("Wait deferred due to kick");
   1559                             mKicked = false;
   1560                         }
   1561                     }
   1562                 }
   1563             }
   1564             log("Shutdown requested");
   1565         } catch (ProviderUnavailableException pue) {
   1566             // Shutdown cleanly in this case
   1567             // NOTE: Sync adapters will also crash with this error, but that is already handled
   1568             // in the adapters themselves, i.e. they return cleanly via done().  When the Email
   1569             // process starts running again, remote processes will be started again in due course
   1570             LogUtils.e(TAG, "EmailProvider unavailable; shutting down");
   1571             // Ask for our service to be restarted; this should kick-start the Email process as well
   1572             startService(new Intent(this, SyncManager.class));
   1573         } catch (RuntimeException e) {
   1574             // Crash; this is a completely unexpected runtime error
   1575             LogUtils.e(TAG, "RuntimeException", e);
   1576             throw e;
   1577         } finally {
   1578             shutdown();
   1579         }
   1580     }
   1581 
   1582     private void shutdown() {
   1583         synchronized (sSyncLock) {
   1584             // If INSTANCE is null, we've already been shut down
   1585             if (INSTANCE != null) {
   1586                 log("Shutting down...");
   1587 
   1588                 // Stop our running syncs
   1589                 stopServiceThreads();
   1590 
   1591                 // Stop receivers
   1592                 if (mConnectivityReceiver != null) {
   1593                     unregisterReceiver(mConnectivityReceiver);
   1594                 }
   1595 
   1596                 // Unregister observers
   1597                 ContentResolver resolver = getContentResolver();
   1598                 if (mSyncedMessageObserver != null) {
   1599                     resolver.unregisterContentObserver(mSyncedMessageObserver);
   1600                     mSyncedMessageObserver = null;
   1601                 }
   1602                 if (mAccountObserver != null) {
   1603                     resolver.unregisterContentObserver(mAccountObserver);
   1604                     mAccountObserver = null;
   1605                 }
   1606                 if (mMailboxObserver != null) {
   1607                     resolver.unregisterContentObserver(mMailboxObserver);
   1608                     mMailboxObserver = null;
   1609                 }
   1610                 unregisterCalendarObservers();
   1611 
   1612                 // Clear pending alarms and associated Intents
   1613                 clearAlarms();
   1614 
   1615                 // Release our wake lock, if we have one
   1616                 synchronized (mWakeLocks) {
   1617                     if (mWakeLock != null) {
   1618                         mWakeLock.release();
   1619                         mWakeLock = null;
   1620                     }
   1621                 }
   1622 
   1623                 INSTANCE = null;
   1624                 sServiceThread = null;
   1625                 sStop = false;
   1626                 log("Goodbye");
   1627             }
   1628         }
   1629     }
   1630 
   1631     /**
   1632      * Release a mailbox from the service map and release its wake lock.
   1633      * NOTE: This method MUST be called while holding sSyncLock!
   1634      *
   1635      * @param mailboxId the id of the mailbox to be released
   1636      */
   1637     public void releaseMailbox(long mailboxId) {
   1638         mServiceMap.remove(mailboxId);
   1639         releaseWakeLock(mailboxId);
   1640     }
   1641 
   1642     /**
   1643      * Retrieve a running sync service for the passed-in mailbox id in a threadsafe manner
   1644      *
   1645      * @param mailboxId the id of the mailbox whose service is to be found
   1646      * @return the running service (a subclass of AbstractSyncService) or null if none
   1647      */
   1648     public AbstractSyncService getRunningService(long mailboxId) {
   1649         synchronized(sSyncLock) {
   1650             return mServiceMap.get(mailboxId);
   1651         }
   1652     }
   1653 
   1654     /**
   1655      * Check whether an Outbox (referenced by a Cursor) has any messages that can be sent
   1656      * @param c the cursor to an Outbox
   1657      * @return true if there is mail to be sent
   1658      */
   1659     private boolean hasSendableMessages(Cursor outboxCursor) {
   1660         Cursor c = mResolver.query(Message.CONTENT_URI, Message.ID_COLUMN_PROJECTION,
   1661                 MAILBOX_KEY_AND_NOT_SEND_FAILED,
   1662                 new String[] {Long.toString(outboxCursor.getLong(Mailbox.CONTENT_ID_COLUMN))},
   1663                 null);
   1664         try {
   1665             while (c.moveToNext()) {
   1666                 if (!Utility.hasUnloadedAttachments(this, c.getLong(Message.CONTENT_ID_COLUMN))) {
   1667                     return true;
   1668                 }
   1669             }
   1670         } finally {
   1671             if (c != null) {
   1672                 c.close();
   1673             }
   1674         }
   1675         return false;
   1676     }
   1677 
   1678     /**
   1679      * Taken from ConnectivityManager using public constants
   1680      */
   1681     public static boolean isNetworkTypeMobile(int networkType) {
   1682         switch (networkType) {
   1683             case ConnectivityManager.TYPE_MOBILE:
   1684             case ConnectivityManager.TYPE_MOBILE_MMS:
   1685             case ConnectivityManager.TYPE_MOBILE_SUPL:
   1686             case ConnectivityManager.TYPE_MOBILE_DUN:
   1687             case ConnectivityManager.TYPE_MOBILE_HIPRI:
   1688                 return true;
   1689             default:
   1690                 return false;
   1691         }
   1692     }
   1693 
   1694     /**
   1695      * Determine whether the account is allowed to sync automatically, as opposed to manually, based
   1696      * on whether the "require manual sync when roaming" policy is in force and applicable
   1697      * @param account the account
   1698      * @return whether or not the account can sync automatically
   1699      */
   1700     /*package*/ public static boolean canAutoSync(Account account) {
   1701         SyncManager ssm = INSTANCE;
   1702         if (ssm == null) {
   1703             return false;
   1704         }
   1705         NetworkInfo networkInfo = ssm.mNetworkInfo;
   1706 
   1707         // Enforce manual sync only while roaming here
   1708         long policyKey = account.mPolicyKey;
   1709         // Quick exit from this check
   1710         if ((policyKey != 0) && (networkInfo != null) &&
   1711                 isNetworkTypeMobile(networkInfo.getType())) {
   1712             // We'll cache the Policy data here
   1713             Policy policy = account.mPolicy;
   1714             if (policy == null) {
   1715                 policy = Policy.restorePolicyWithId(INSTANCE, policyKey);
   1716                 account.mPolicy = policy;
   1717                 if (!PolicyServiceProxy.isActive(ssm, policy)) {
   1718                     PolicyServiceProxy.setAccountHoldFlag(ssm, account, true);
   1719                     log("canAutoSync; policies not active, set hold flag");
   1720                     return false;
   1721                 }
   1722             }
   1723             if (policy != null && policy.mRequireManualSyncWhenRoaming && networkInfo.isRoaming()) {
   1724                 return false;
   1725             }
   1726         }
   1727         return true;
   1728     }
   1729 
   1730     /**
   1731      * Convenience method to determine whether Email sync is enabled for a given account
   1732      * @param account the Account in question
   1733      * @return whether Email sync is enabled
   1734      */
   1735     private static boolean canSyncEmail(android.accounts.Account account) {
   1736         return ContentResolver.getSyncAutomatically(account, EmailContent.AUTHORITY);
   1737     }
   1738 
   1739     /**
   1740      * Determine whether a mailbox of a given type in a given account can be synced automatically
   1741      * by SyncServiceManager.  This is an increasingly complex determination, taking into account
   1742      * security policies and user settings (both within the Email application and in the Settings
   1743      * application)
   1744      *
   1745      * @param account the Account that the mailbox is in
   1746      * @param type the type of the Mailbox
   1747      * @return whether or not to start a sync
   1748      */
   1749     private boolean isMailboxSyncable(Account account, int type) {
   1750         // This 'if' statement performs checks to see whether or not a mailbox is a
   1751         // candidate for syncing based on policies, user settings, & other restrictions
   1752         if (type == Mailbox.TYPE_OUTBOX) {
   1753             // Outbox is always syncable
   1754             return true;
   1755         } else if (type == Mailbox.TYPE_EAS_ACCOUNT_MAILBOX) {
   1756             // Always sync EAS mailbox unless master sync is off
   1757             return ContentResolver.getMasterSyncAutomatically();
   1758         } else if (type == Mailbox.TYPE_CONTACTS || type == Mailbox.TYPE_CALENDAR) {
   1759             // Contacts/Calendar obey this setting from ContentResolver
   1760             if (!ContentResolver.getMasterSyncAutomatically()) {
   1761                 return false;
   1762             }
   1763             // Get the right authority for the mailbox
   1764             String authority;
   1765             if (type == Mailbox.TYPE_CONTACTS) {
   1766                 authority = ContactsContract.AUTHORITY;
   1767             } else {
   1768                 authority = CalendarContract.AUTHORITY;
   1769                 if (!mCalendarObservers.containsKey(account.mId)){
   1770                     // Make sure we have an observer for this Calendar, as
   1771                     // we need to be able to detect sync state changes, sigh
   1772                     registerCalendarObserver(account);
   1773                 }
   1774             }
   1775             // See if "sync automatically" is set; if not, punt
   1776             if (!ContentResolver.getSyncAutomatically(mAccountList.getAmAccount(account),
   1777                     authority)) {
   1778                 return false;
   1779             // See if the calendar is enabled from the Calendar app UI; if not, punt
   1780             } else if ((type == Mailbox.TYPE_CALENDAR) && !isCalendarEnabled(account.mId)) {
   1781                 return false;
   1782             }
   1783         // Never automatically sync trash
   1784         } else if (type == Mailbox.TYPE_TRASH) {
   1785             return false;
   1786         // For non-outbox, non-account mail, we do two checks:
   1787         // 1) are we restricted by policy (i.e. manual sync only),
   1788         // 2) has the user checked the "Sync Email" box in Account Settings, and
   1789         } else if (!canAutoSync(account) || !canSyncEmail(mAccountList.getAmAccount(account))) {
   1790             return false;
   1791         }
   1792         return true;
   1793     }
   1794 
   1795     private long checkMailboxes () {
   1796         // First, see if any running mailboxes have been deleted
   1797         ArrayList<Long> deletedMailboxes = new ArrayList<Long>();
   1798         synchronized (sSyncLock) {
   1799             for (long mailboxId: mServiceMap.keySet()) {
   1800                 Mailbox m = Mailbox.restoreMailboxWithId(this, mailboxId);
   1801                 if (m == null) {
   1802                     deletedMailboxes.add(mailboxId);
   1803                 }
   1804             }
   1805             // If so, stop them or remove them from the map
   1806             for (Long mailboxId: deletedMailboxes) {
   1807                 AbstractSyncService svc = mServiceMap.get(mailboxId);
   1808                 if (svc == null || svc.mThread == null) {
   1809                     releaseMailbox(mailboxId);
   1810                     continue;
   1811                 } else {
   1812                     boolean alive = svc.mThread.isAlive();
   1813                     log("Deleted mailbox: " + svc.mMailboxName);
   1814                     if (alive) {
   1815                         stopManualSync(mailboxId);
   1816                     } else {
   1817                         log("Removing from serviceMap");
   1818                         releaseMailbox(mailboxId);
   1819                     }
   1820                 }
   1821             }
   1822         }
   1823 
   1824         long nextWait = SYNC_SERVICE_HEARTBEAT_TIME;
   1825         long now = System.currentTimeMillis();
   1826 
   1827         // Start up threads that need it; use a query which finds eas mailboxes where the
   1828         // the sync interval is not "never".  This is the set of mailboxes that we control
   1829         if (mAccountObserver == null) {
   1830             log("mAccountObserver null; service died??");
   1831             return nextWait;
   1832         }
   1833 
   1834         Cursor c = getContentResolver().query(Mailbox.CONTENT_URI, Mailbox.CONTENT_PROJECTION,
   1835                 mAccountObserver.getSyncableMailboxWhere(), null, null);
   1836         if (c == null) throw new ProviderUnavailableException();
   1837         try {
   1838             while (c.moveToNext()) {
   1839                 long mailboxId = c.getLong(Mailbox.CONTENT_ID_COLUMN);
   1840                 AbstractSyncService service = getRunningService(mailboxId);
   1841                 if (service == null) {
   1842                     // Get the cached account
   1843                     Account account = getAccountById(c.getInt(Mailbox.CONTENT_ACCOUNT_KEY_COLUMN));
   1844                     if (account == null) continue;
   1845 
   1846                     // We handle a few types of mailboxes specially
   1847                     int mailboxType = c.getInt(Mailbox.CONTENT_TYPE_COLUMN);
   1848                     if (!isMailboxSyncable(account, mailboxType)) {
   1849                         continue;
   1850                     }
   1851 
   1852                     // Check whether we're in a hold (temporary or permanent)
   1853                     SyncError syncError = mSyncErrorMap.get(mailboxId);
   1854                     if (syncError != null) {
   1855                         // Nothing we can do about fatal errors
   1856                         if (syncError.fatal) continue;
   1857                         if (now < syncError.holdEndTime) {
   1858                             // If release time is earlier than next wait time,
   1859                             // move next wait time up to the release time
   1860                             if (syncError.holdEndTime < now + nextWait) {
   1861                                 nextWait = syncError.holdEndTime - now;
   1862                                 mNextWaitReason = "Release hold";
   1863                             }
   1864                             continue;
   1865                         } else {
   1866                             // Keep the error around, but clear the end time
   1867                             syncError.holdEndTime = 0;
   1868                         }
   1869                     }
   1870 
   1871                     // Otherwise, we use the sync interval
   1872                     long syncInterval = c.getInt(Mailbox.CONTENT_SYNC_INTERVAL_COLUMN);
   1873                     if (syncInterval == Mailbox.CHECK_INTERVAL_PUSH) {
   1874                         Mailbox m = EmailContent.getContent(c, Mailbox.class);
   1875                         requestSync(m, SYNC_PUSH, null);
   1876                     } else if (mailboxType == Mailbox.TYPE_OUTBOX) {
   1877                         if (hasSendableMessages(c)) {
   1878                             Mailbox m = EmailContent.getContent(c, Mailbox.class);
   1879                             startServiceThread(getServiceForMailbox(this, m));
   1880                         }
   1881                     } else if (syncInterval > 0 && syncInterval <= ONE_DAY_MINUTES) {
   1882                         // TODO: Migrating to use system SyncManager, so this should be dead code.
   1883                         long lastSync = c.getLong(Mailbox.CONTENT_SYNC_TIME_COLUMN);
   1884                         long sinceLastSync = now - lastSync;
   1885                         long toNextSync = syncInterval*MINUTES - sinceLastSync;
   1886                         String name = c.getString(Mailbox.CONTENT_DISPLAY_NAME_COLUMN);
   1887                         if (toNextSync <= 0) {
   1888                             Mailbox m = EmailContent.getContent(c, Mailbox.class);
   1889                             requestSync(m, SYNC_SCHEDULED, null);
   1890                         } else if (toNextSync < nextWait) {
   1891                             nextWait = toNextSync;
   1892                             if (sUserLog) {
   1893                                 log("Next sync for " + name + " in " + nextWait/1000 + "s");
   1894                             }
   1895                             mNextWaitReason = "Scheduled sync, " + name;
   1896                         } else if (sUserLog) {
   1897                             log("Next sync for " + name + " in " + toNextSync/1000 + "s");
   1898                         }
   1899                     }
   1900                 } else {
   1901                     Thread thread = service.mThread;
   1902                     // Look for threads that have died and remove them from the map
   1903                     if (thread != null && !thread.isAlive()) {
   1904                         if (sUserLog) {
   1905                             log("Dead thread, mailbox released: " +
   1906                                     c.getString(Mailbox.CONTENT_DISPLAY_NAME_COLUMN));
   1907                         }
   1908                         synchronized (sSyncLock) {
   1909                             releaseMailbox(mailboxId);
   1910                         }
   1911                         // Restart this if necessary
   1912                         if (nextWait > 3*SECONDS) {
   1913                             nextWait = 3*SECONDS;
   1914                             mNextWaitReason = "Clean up dead thread(s)";
   1915                         }
   1916                     } else {
   1917                         long requestTime = service.mRequestTime;
   1918                         if (requestTime > 0) {
   1919                             long timeToRequest = requestTime - now;
   1920                             if (timeToRequest <= 0) {
   1921                                 service.mRequestTime = 0;
   1922                                 service.alarm();
   1923                             } else if (requestTime > 0 && timeToRequest < nextWait) {
   1924                                 if (timeToRequest < 11*MINUTES) {
   1925                                     nextWait = timeToRequest < 250 ? 250 : timeToRequest;
   1926                                     mNextWaitReason = "Sync data change";
   1927                                 } else {
   1928                                     log("Illegal timeToRequest: " + timeToRequest);
   1929                                 }
   1930                             }
   1931                         }
   1932                     }
   1933                 }
   1934             }
   1935         } finally {
   1936             c.close();
   1937         }
   1938         return nextWait;
   1939     }
   1940 
   1941     static public void serviceRequest(long mailboxId, int reason) {
   1942         serviceRequest(mailboxId, 5*SECONDS, reason);
   1943     }
   1944 
   1945     /**
   1946      * Return a boolean indicating whether the mailbox can be synced
   1947      * @param m the mailbox
   1948      * @return whether or not the mailbox can be synced
   1949      */
   1950     public static boolean isSyncable(Mailbox m) {
   1951         return m.mType != Mailbox.TYPE_DRAFTS
   1952                 && m.mType != Mailbox.TYPE_OUTBOX
   1953                 && m.mType != Mailbox.TYPE_SEARCH
   1954                 && m.mType < Mailbox.TYPE_NOT_SYNCABLE;
   1955     }
   1956 
   1957     static public void serviceRequest(long mailboxId, long ms, int reason) {
   1958         SyncManager ssm = INSTANCE;
   1959         if (ssm == null) return;
   1960         Mailbox m = Mailbox.restoreMailboxWithId(ssm, mailboxId);
   1961         if (m == null || !isSyncable(m)) return;
   1962         try {
   1963             AbstractSyncService service = ssm.getRunningService(mailboxId);
   1964             if (service != null) {
   1965                 service.mRequestTime = System.currentTimeMillis() + ms;
   1966                 kick("service request");
   1967             } else {
   1968                 startManualSync(mailboxId, reason, null);
   1969             }
   1970         } catch (Exception e) {
   1971             e.printStackTrace();
   1972         }
   1973     }
   1974 
   1975     static public void serviceRequestImmediate(long mailboxId) {
   1976         SyncManager ssm = INSTANCE;
   1977         if (ssm == null) return;
   1978         AbstractSyncService service = ssm.getRunningService(mailboxId);
   1979         if (service != null) {
   1980             service.mRequestTime = System.currentTimeMillis();
   1981             Mailbox m = Mailbox.restoreMailboxWithId(ssm, mailboxId);
   1982             if (m != null) {
   1983                 service.mAccount = Account.restoreAccountWithId(ssm, m.mAccountKey);
   1984                 service.mMailbox = m;
   1985                 kick("service request immediate");
   1986             }
   1987         }
   1988     }
   1989 
   1990     static public void sendMessageRequest(Request req) {
   1991         SyncManager ssm = INSTANCE;
   1992         if (ssm == null) return;
   1993         Message msg = Message.restoreMessageWithId(ssm, req.mMessageId);
   1994         if (msg == null) return;
   1995         long mailboxId = msg.mMailboxKey;
   1996         Mailbox mailbox = Mailbox.restoreMailboxWithId(ssm, mailboxId);
   1997         if (mailbox == null) return;
   1998 
   1999         // If we're loading an attachment for Outbox, we want to look at the source message
   2000         // to find the loading mailbox
   2001         if (mailbox.mType == Mailbox.TYPE_OUTBOX) {
   2002             long sourceId = Utility.getFirstRowLong(ssm, Body.CONTENT_URI,
   2003                     new String[] {BodyColumns.SOURCE_MESSAGE_KEY},
   2004                     BodyColumns.MESSAGE_KEY + "=?",
   2005                     new String[] {Long.toString(msg.mId)}, null, 0, -1L);
   2006             if (sourceId != -1L) {
   2007                 EmailContent.Message sourceMsg =
   2008                         EmailContent.Message.restoreMessageWithId(ssm, sourceId);
   2009                 if (sourceMsg != null) {
   2010                     mailboxId = sourceMsg.mMailboxKey;
   2011                 }
   2012             }
   2013         }
   2014         sendRequest(mailboxId, req);
   2015     }
   2016 
   2017     static public void sendRequest(long mailboxId, Request req) {
   2018         SyncManager ssm = INSTANCE;
   2019         if (ssm == null) return;
   2020         AbstractSyncService service = ssm.getRunningService(mailboxId);
   2021         if (service == null) {
   2022             startManualSync(mailboxId, SYNC_SERVICE_PART_REQUEST, req);
   2023             kick("part request");
   2024         } else {
   2025             service.addRequest(req);
   2026         }
   2027     }
   2028 
   2029     /**
   2030      * Determine whether a given Mailbox can be synced, i.e. is not already syncing and is not in
   2031      * an error state
   2032      *
   2033      * @param mailboxId
   2034      * @return whether or not the Mailbox is available for syncing (i.e. is a valid push target)
   2035      */
   2036     static public int pingStatus(long mailboxId) {
   2037         SyncManager ssm = INSTANCE;
   2038         if (ssm == null) return PING_STATUS_OK;
   2039         // Already syncing...
   2040         if (ssm.getRunningService(mailboxId) != null) {
   2041             return PING_STATUS_RUNNING;
   2042         }
   2043         // No errors or a transient error, don't ping...
   2044         SyncError error = ssm.mSyncErrorMap.get(mailboxId);
   2045         if (error != null) {
   2046             if (error.fatal) {
   2047                 return PING_STATUS_UNABLE;
   2048             } else if (error.holdEndTime > 0) {
   2049                 return PING_STATUS_WAITING;
   2050             }
   2051         }
   2052         return PING_STATUS_OK;
   2053     }
   2054 
   2055     static public void startManualSync(long mailboxId, int reason, Request req) {
   2056         SyncManager ssm = INSTANCE;
   2057         if (ssm == null) return;
   2058         synchronized (sSyncLock) {
   2059             AbstractSyncService svc = ssm.mServiceMap.get(mailboxId);
   2060             if (svc == null) {
   2061                 if (ssm.mSyncErrorMap.containsKey(mailboxId) && reason == SyncManager.SYNC_UPSYNC) {
   2062                     return;
   2063                 } else if (reason != SyncManager.SYNC_UPSYNC) {
   2064                     ssm.mSyncErrorMap.remove(mailboxId);
   2065                 }
   2066                 Mailbox m = Mailbox.restoreMailboxWithId(ssm, mailboxId);
   2067                 if (m != null) {
   2068                     log("Starting sync for " + m.mDisplayName);
   2069                     ssm.requestSync(m, reason, req);
   2070                 }
   2071             } else {
   2072                 // If this is a ui request, set the sync reason for the service
   2073                 if (reason >= SYNC_CALLBACK_START) {
   2074                     svc.mSyncReason = reason;
   2075                 }
   2076             }
   2077         }
   2078     }
   2079 
   2080     // DO NOT CALL THIS IN A LOOP ON THE SERVICEMAP
   2081     static public void stopManualSync(long mailboxId) {
   2082         SyncManager ssm = INSTANCE;
   2083         if (ssm == null) return;
   2084         synchronized (sSyncLock) {
   2085             AbstractSyncService svc = ssm.mServiceMap.get(mailboxId);
   2086             if (svc != null) {
   2087                 log("Stopping sync for " + svc.mMailboxName);
   2088                 svc.stop();
   2089                 svc.mThread.interrupt();
   2090                 ssm.releaseWakeLock(mailboxId);
   2091             }
   2092         }
   2093     }
   2094 
   2095     /**
   2096      * Wake up SyncServiceManager to check for mailboxes needing service
   2097      */
   2098     static public void kick(String reason) {
   2099        SyncManager ssm = INSTANCE;
   2100        if (ssm != null) {
   2101             synchronized (ssm) {
   2102                 //INSTANCE.log("Kick: " + reason);
   2103                 ssm.mKicked = true;
   2104                 ssm.notify();
   2105             }
   2106         }
   2107         if (sConnectivityLock != null) {
   2108             synchronized (sConnectivityLock) {
   2109                 sConnectivityLock.notify();
   2110             }
   2111         }
   2112     }
   2113 
   2114     /**
   2115      * Tell SyncServiceManager to remove the mailbox from the map of mailboxes with sync errors
   2116      * @param mailboxId the id of the mailbox
   2117      */
   2118     static public void removeFromSyncErrorMap(long mailboxId) {
   2119         SyncManager ssm = INSTANCE;
   2120         if (ssm != null) {
   2121             ssm.mSyncErrorMap.remove(mailboxId);
   2122         }
   2123     }
   2124 
   2125     private boolean isRunningInServiceThread(long mailboxId) {
   2126         AbstractSyncService syncService = getRunningService(mailboxId);
   2127         Thread thisThread = Thread.currentThread();
   2128         return syncService != null && syncService.mThread != null &&
   2129             thisThread == syncService.mThread;
   2130     }
   2131 
   2132     /**
   2133      * Sent by services indicating that their thread is finished; action depends on the exitStatus
   2134      * of the service.
   2135      *
   2136      * @param svc the service that is finished
   2137      */
   2138     static public void done(AbstractSyncService svc) {
   2139         SyncManager ssm = INSTANCE;
   2140         if (ssm == null) return;
   2141         synchronized(sSyncLock) {
   2142             long mailboxId = svc.mMailboxId;
   2143             // If we're no longer the syncing thread for the mailbox, just return
   2144             if (!ssm.isRunningInServiceThread(mailboxId)) {
   2145                 return;
   2146             }
   2147             ssm.releaseMailbox(mailboxId);
   2148             ssm.setMailboxSyncStatus(mailboxId, EmailContent.SYNC_STATUS_NONE);
   2149 
   2150             ConcurrentHashMap<Long, SyncError> errorMap = ssm.mSyncErrorMap;
   2151             SyncError syncError = errorMap.get(mailboxId);
   2152 
   2153             int exitStatus = svc.mExitStatus;
   2154             Mailbox m = Mailbox.restoreMailboxWithId(ssm, mailboxId);
   2155             if (m == null) return;
   2156 
   2157             if (exitStatus != AbstractSyncService.EXIT_LOGIN_FAILURE) {
   2158                 long accountId = m.mAccountKey;
   2159                 Account account = Account.restoreAccountWithId(ssm, accountId);
   2160                 if (account == null) return;
   2161                 if (ssm.releaseSyncHolds(ssm,
   2162                         AbstractSyncService.EXIT_LOGIN_FAILURE, account)) {
   2163                     new AccountServiceProxy(ssm).notifyLoginSucceeded(accountId);
   2164                 }
   2165             }
   2166 
   2167             int lastResult = EmailContent.LAST_SYNC_RESULT_SUCCESS;
   2168             // For error states, whether the error is fatal (won't automatically be retried)
   2169             boolean errorIsFatal = true;
   2170             try {
   2171                 switch (exitStatus) {
   2172                     case AbstractSyncService.EXIT_DONE:
   2173                         if (svc.hasPendingRequests()) {
   2174                             // TODO Handle this case
   2175                         }
   2176                         errorMap.remove(mailboxId);
   2177                         // If we've had a successful sync, clear the shutdown count
   2178                         synchronized (SyncManager.class) {
   2179                             sClientConnectionManagerShutdownCount = 0;
   2180                         }
   2181                         // Leave now; other statuses are errors
   2182                         return;
   2183                     // I/O errors get retried at increasing intervals
   2184                     case AbstractSyncService.EXIT_IO_ERROR:
   2185                         if (syncError != null) {
   2186                             syncError.escalate();
   2187                             log(m.mDisplayName + " held for " + (syncError.holdDelay/ 1000) + "s");
   2188                             return;
   2189                         } else {
   2190                             log(m.mDisplayName + " added to syncErrorMap, hold for 15s");
   2191                         }
   2192                         lastResult = EmailContent.LAST_SYNC_RESULT_CONNECTION_ERROR;
   2193                         errorIsFatal = false;
   2194                         break;
   2195                     // These errors are not retried automatically
   2196                     case AbstractSyncService.EXIT_LOGIN_FAILURE:
   2197                         new AccountServiceProxy(ssm).notifyLoginFailed(m.mAccountKey, svc.mExitReason);
   2198                         lastResult = EmailContent.LAST_SYNC_RESULT_AUTH_ERROR;
   2199                         break;
   2200                     case AbstractSyncService.EXIT_SECURITY_FAILURE:
   2201                     case AbstractSyncService.EXIT_ACCESS_DENIED:
   2202                         lastResult = EmailContent.LAST_SYNC_RESULT_SECURITY_ERROR;
   2203                         break;
   2204                     case AbstractSyncService.EXIT_EXCEPTION:
   2205                         lastResult = EmailContent.LAST_SYNC_RESULT_INTERNAL_ERROR;
   2206                         break;
   2207                 }
   2208                 // Add this box to the error map
   2209                 errorMap.put(mailboxId, ssm.new SyncError(exitStatus, errorIsFatal));
   2210             } finally {
   2211                 // Always set the last result
   2212                 ssm.setMailboxLastSyncResult(mailboxId, lastResult);
   2213                 kick("sync completed");
   2214             }
   2215         }
   2216     }
   2217 
   2218     /**
   2219      * Given the status string from a Mailbox, return the type code for the last sync
   2220      * @param status the syncStatus column of a Mailbox
   2221      * @return
   2222      */
   2223     static public int getStatusType(String status) {
   2224         if (status == null) {
   2225             return -1;
   2226         } else {
   2227             return status.charAt(STATUS_TYPE_CHAR) - '0';
   2228         }
   2229     }
   2230 
   2231     /**
   2232      * Given the status string from a Mailbox, return the change count for the last sync
   2233      * The change count is the number of adds + deletes + changes in the last sync
   2234      * @param status the syncStatus column of a Mailbox
   2235      * @return
   2236      */
   2237     static public int getStatusChangeCount(String status) {
   2238         try {
   2239             String s = status.substring(STATUS_CHANGE_COUNT_OFFSET);
   2240             return Integer.parseInt(s);
   2241         } catch (RuntimeException e) {
   2242             return -1;
   2243         }
   2244     }
   2245 
   2246     static public Context getContext() {
   2247         return INSTANCE;
   2248     }
   2249 
   2250     private void writeWakeLockTimes(PrintWriter pw, HashMap<Long, Long> map, boolean historical) {
   2251         long now = System.currentTimeMillis();
   2252         for (long mailboxId: map.keySet()) {
   2253             Long time = map.get(mailboxId);
   2254             if (time == null) {
   2255                 // Just in case...
   2256                 continue;
   2257             }
   2258             Mailbox mailbox = Mailbox.restoreMailboxWithId(this, mailboxId);
   2259             StringBuilder sb = new StringBuilder();
   2260             if (mailboxId == EXTRA_MAILBOX_ID) {
   2261                 sb.append("    SyncManager");
   2262             } else if (mailbox == null) {
   2263                 sb.append("    Mailbox " + mailboxId + " (deleted?)");
   2264             } else {
   2265                 String protocol = Account.getProtocol(this, mailbox.mAccountKey);
   2266                 sb.append("    Mailbox " + mailboxId + " (" + protocol + ", type " +
   2267                         mailbox.mType + ")");
   2268             }
   2269             long logTime = historical ? time : (now - time);
   2270             sb.append(" held for " + (logTime / 1000) + "s");
   2271             pw.println(sb.toString());
   2272         }
   2273     }
   2274 
   2275     @Override
   2276     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
   2277         long uptime = System.currentTimeMillis() - mServiceStartTime;
   2278         pw.println("SyncManager: " + TAG + " up for " + (uptime / 1000 / 60) + " m");
   2279         if (mWakeLock != null) {
   2280             pw.println("  Holding WakeLock");
   2281             writeWakeLockTimes(pw, mWakeLocks, false);
   2282         } else {
   2283             pw.println("  Not holding WakeLock");
   2284         }
   2285         if (!mWakeLocksHistory.isEmpty()) {
   2286             pw.println("  Historical times");
   2287             writeWakeLockTimes(pw, mWakeLocksHistory, true);
   2288         }
   2289     }
   2290 }
   2291