/**
  * Return a list of all Accounts in EmailProvider. Because the result of this call may be used in
  * account reconciliation, an exception is thrown if the result cannot be guaranteed accurate
  *
  * @param context the caller's context
  * @param accounts a list that Accounts will be added into
  * @return the list of Accounts
  * @throws ProviderUnavailableException if the list of Accounts cannot be guaranteed valid
  */
 @Override
 public AccountList collectAccounts(Context context, AccountList accounts) {
   ContentResolver resolver = context.getContentResolver();
   Cursor c = resolver.query(Account.CONTENT_URI, Account.CONTENT_PROJECTION, null, null, null);
   // We must throw here; callers might use the information we provide for reconciliation, etc.
   if (c == null) throw new ProviderUnavailableException();
   try {
     ContentValues cv = new ContentValues();
     while (c.moveToNext()) {
       long hostAuthId = c.getLong(Account.CONTENT_HOST_AUTH_KEY_RECV_COLUMN);
       if (hostAuthId > 0) {
         HostAuth ha = HostAuth.restoreHostAuthWithId(context, hostAuthId);
         if (ha != null && ha.mProtocol.equals(Eas.PROTOCOL)) {
           Account account = new Account();
           account.restore(c);
           // Cache the HostAuth
           account.mHostAuthRecv = ha;
           accounts.add(account);
           // Fixup flags for inbox (should accept moved mail)
           Mailbox inbox = Mailbox.restoreMailboxOfType(context, account.mId, Mailbox.TYPE_INBOX);
           if (inbox != null && ((inbox.mFlags & Mailbox.FLAG_ACCEPTS_MOVED_MAIL) == 0)) {
             cv.put(MailboxColumns.FLAGS, inbox.mFlags | Mailbox.FLAG_ACCEPTS_MOVED_MAIL);
             resolver.update(
                 ContentUris.withAppendedId(Mailbox.CONTENT_URI, inbox.mId), cv, null, null);
           }
         }
       }
     }
   } finally {
     c.close();
   }
   return accounts;
 }
 private CacheEntry getEntry(Context context, Account account) {
   CacheEntry entry;
   if (account.isSaved() && !account.isTemporary()) {
     entry = mCache.get(account.mId);
     if (entry == null) {
       LogUtils.d(Logging.LOG_TAG, "initializing entry from database");
       final HostAuth hostAuth = account.getOrCreateHostAuthRecv(context);
       final Credential credential = hostAuth.getOrCreateCredential(context);
       entry =
           new CacheEntry(
               account.mId,
               credential.mProviderId,
               credential.mAccessToken,
               credential.mRefreshToken,
               credential.mExpiration);
       mCache.put(account.mId, entry);
     }
   } else {
     // This account is temporary, just create a temporary entry. Don't store
     // it in the cache, it won't be findable because we don't yet have an account Id.
     final HostAuth hostAuth = account.getOrCreateHostAuthRecv(context);
     final Credential credential = hostAuth.getCredential(context);
     entry =
         new CacheEntry(
             account.mId,
             credential.mProviderId,
             credential.mAccessToken,
             credential.mRefreshToken,
             credential.mExpiration);
   }
   return entry;
 }
 @Deprecated
 @Override
 public void startSync(long mailboxId, boolean userRequest, int deltaMessageCount)
     throws RemoteException {
   final Mailbox mailbox = Mailbox.restoreMailboxWithId(mContext, mailboxId);
   if (mailbox == null) return;
   final Account account = Account.restoreAccountWithId(mContext, mailbox.mAccountKey);
   if (account == null) return;
   final EmailServiceInfo info = EmailServiceUtils.getServiceInfoForAccount(mContext, account.mId);
   final android.accounts.Account acct =
       new android.accounts.Account(account.mEmailAddress, info.accountType);
   final Bundle extras = Mailbox.createSyncBundle(mailboxId);
   if (userRequest) {
     extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
     extras.putBoolean(ContentResolver.SYNC_EXTRAS_DO_NOT_RETRY, true);
     extras.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
   }
   if (deltaMessageCount != 0) {
     extras.putInt(Mailbox.SYNC_EXTRA_DELTA_MESSAGE_COUNT, deltaMessageCount);
   }
   ContentResolver.requestSync(acct, EmailContent.AUTHORITY, extras);
   LogUtils.i(
       Logging.LOG_TAG,
       "requestSync EmailServiceStub startSync %s, %s",
       account.toString(),
       extras.toString());
 }
 public void testAddHeaders() {
   HttpRequestBase method = new HttpPost();
   EasSyncService svc = new EasSyncService();
   svc.mAuthString = "auth";
   svc.mProtocolVersion = "12.1";
   svc.mAccount = null;
   // With second argument false, there should be no header
   svc.setHeaders(method, false);
   Header[] headers = method.getHeaders("X-MS-PolicyKey");
   assertEquals(0, headers.length);
   // With second argument true, there should always be a header
   // The value will be "0" without an account
   method.removeHeaders("X-MS-PolicyKey");
   svc.setHeaders(method, true);
   headers = method.getHeaders("X-MS-PolicyKey");
   assertEquals(1, headers.length);
   assertEquals("0", headers[0].getValue());
   // With an account, but null security key, the header's value should be "0"
   Account account = new Account();
   account.mSecuritySyncKey = null;
   svc.mAccount = account;
   method.removeHeaders("X-MS-PolicyKey");
   svc.setHeaders(method, true);
   headers = method.getHeaders("X-MS-PolicyKey");
   assertEquals(1, headers.length);
   assertEquals("0", headers[0].getValue());
   // With an account and security key, the header's value should be the security key
   account.mSecuritySyncKey = "key";
   svc.mAccount = account;
   method.removeHeaders("X-MS-PolicyKey");
   svc.setHeaders(method, true);
   headers = method.getHeaders("X-MS-PolicyKey");
   assertEquals(1, headers.length);
   assertEquals("key", headers[0].getValue());
 }
 /**
  * API: Set/Clear the "hold" flag in any account. This flag serves a dual purpose: Setting it
  * gives us an indication that it was blocked, and clearing it gives EAS a signal to try syncing
  * again.
  *
  * @param context context
  * @param account the account whose hold flag is to be set/cleared
  * @param newState true = security hold, false = free to sync
  */
 public static void setAccountHoldFlag(Context context, Account account, boolean newState) {
   if (newState) {
     account.mFlags |= Account.FLAGS_SECURITY_HOLD;
   } else {
     account.mFlags &= ~Account.FLAGS_SECURITY_HOLD;
   }
   ContentValues cv = new ContentValues();
   cv.put(AccountColumns.FLAGS, account.mFlags);
   account.update(context, cv);
 }
  private void saveEntry(Context context, CacheEntry entry) {
    LogUtils.d(Logging.LOG_TAG, "saveEntry");

    final Account account = Account.restoreAccountWithId(context, entry.mAccountId);
    final HostAuth hostAuth = account.getOrCreateHostAuthRecv(context);
    final Credential cred = hostAuth.getOrCreateCredential(context);
    cred.mProviderId = entry.mProviderId;
    cred.mAccessToken = entry.mAccessToken;
    cred.mRefreshToken = entry.mRefreshToken;
    cred.mExpiration = entry.mExpirationTime;
    cred.update(context, cred.toContentValues());
  }
 @Override
 public void onSelected(Account account, long mailboxId) {
   String shortcutName;
   /* M: Get display name from FolderProperty, not from database.
    * Aiming to accomplish multi-language.*/
   Mailbox mailbox = Mailbox.restoreMailboxWithId(this, mailboxId);
   if (Account.isNormalAccount(account.mId) && (mailbox.mType != Mailbox.TYPE_INBOX)) {
     shortcutName = FolderProperties.getInstance(this).getDisplayName(mailbox);
   } else {
     shortcutName = account.getDisplayName();
   }
   setupShortcut(account, mailboxId, shortcutName);
   finish();
 }
 /**
  * For all accounts that require password expiration, put them in security hold and wipe their
  * data.
  *
  * @param context context
  * @return true if one or more accounts were wiped
  */
 @VisibleForTesting
 /*package*/ static boolean wipeExpiredAccounts(Context context) {
   boolean result = false;
   Cursor c =
       context
           .getContentResolver()
           .query(Policy.CONTENT_URI, Policy.ID_PROJECTION, HAS_PASSWORD_EXPIRATION, null, null);
   if (c == null) {
     return false;
   }
   try {
     while (c.moveToNext()) {
       long policyId = c.getLong(Policy.ID_PROJECTION_COLUMN);
       long accountId = Policy.getAccountIdWithPolicyKey(context, policyId);
       if (accountId < 0) continue;
       Account account = Account.restoreAccountWithId(context, accountId);
       if (account != null) {
         // Mark the account as "on hold".
         setAccountHoldFlag(context, account, true);
         // Erase data
         Uri uri = EmailProvider.uiUri("uiaccountdata", accountId);
         context.getContentResolver().delete(uri, null, null);
         // Report one or more were found
         result = true;
       }
     }
   } finally {
     c.close();
   }
   return result;
 }
  @Override
  public void loadMore(long messageId) throws RemoteException {
    /// M: We Can't load more in low storage state @{
    if (StorageLowState.checkIfStorageLow(mContext)) {
      LogUtils.e(Logging.LOG_TAG, "Can't load more due to low storage");
      return;
    }
    /// @}
    // Load a message for view...
    try {
      // 1. Resample the message, in case it disappeared or synced while
      // this command was in queue
      final EmailContent.Message message =
          EmailContent.Message.restoreMessageWithId(mContext, messageId);
      if (message == null) {
        return;
      }
      if (message.mFlagLoaded == EmailContent.Message.FLAG_LOADED_COMPLETE) {
        // We should NEVER get here
        return;
      }

      // 2. Open the remote folder.
      // TODO combine with common code in loadAttachment
      final Account account = Account.restoreAccountWithId(mContext, message.mAccountKey);
      final Mailbox mailbox = Mailbox.restoreMailboxWithId(mContext, message.mMailboxKey);
      if (account == null || mailbox == null) {
        // mListeners.loadMessageForViewFailed(messageId, "null account or mailbox");
        return;
      }
      TrafficStats.setThreadStatsTag(TrafficFlags.getSyncFlags(mContext, account));

      final Store remoteStore = Store.getInstance(account, mContext);
      final String remoteServerId;
      // If this is a search result, use the protocolSearchInfo field to get the
      // correct remote location
      if (!TextUtils.isEmpty(message.mProtocolSearchInfo)) {
        remoteServerId = message.mProtocolSearchInfo;
      } else {
        remoteServerId = mailbox.mServerId;
      }
      final Folder remoteFolder = remoteStore.getFolder(remoteServerId);
      remoteFolder.open(OpenMode.READ_WRITE);

      // 3. Set up to download the entire message
      final Message remoteMessage = remoteFolder.getMessage(message.mServerId);
      final FetchProfile fp = new FetchProfile();
      fp.add(FetchProfile.Item.BODY);
      remoteFolder.fetch(new Message[] {remoteMessage}, fp, null);

      // 4. Write to provider
      Utilities.copyOneMessageToProvider(
          mContext, remoteMessage, account, mailbox, EmailContent.Message.FLAG_LOADED_COMPLETE);
    } catch (MessagingException me) {
      if (Logging.LOGD) LogUtils.v(Logging.LOG_TAG, "", me);

    } catch (RuntimeException rte) {
      LogUtils.d(Logging.LOG_TAG, "RTE During loadMore");
    }
  }
 private void handleError(final MessagingException result, final long accountId, int progress) {
   if (accountId == -1) {
     return;
   }
   if (result == null) {
     if (progress > 0) {
       // Connection now working; clear the error message banner
       if (mLastErrorAccountId == accountId) {
         dismissErrorMessage();
       }
     }
   } else {
     Account account = Account.restoreAccountWithId(EmailActivity.this, accountId);
     if (account == null) return;
     String message = MessagingExceptionStrings.getErrorString(EmailActivity.this, result);
     if (!TextUtils.isEmpty(account.mDisplayName)) {
       // TODO Use properly designed layout. Don't just concatenate strings;
       // which is generally poor for I18N.
       message = message + "   (" + account.mDisplayName + ")";
     }
     if (mErrorBanner.show(message)) {
       mLastErrorAccountId = accountId;
     }
   }
 }
 private static void syncAccount(final Context context, final Account account) {
   final EmailServiceUtils.EmailServiceInfo info =
       EmailServiceUtils.getServiceInfo(context, account.getProtocol(context));
   final android.accounts.Account amAccount =
       new android.accounts.Account(account.mEmailAddress, info.accountType);
   final Bundle extras = new Bundle(3);
   extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
   extras.putBoolean(ContentResolver.SYNC_EXTRAS_DO_NOT_RETRY, true);
   extras.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
   ContentResolver.requestSync(amAccount, EmailContent.AUTHORITY, extras);
   LogUtils.i(
       TAG,
       "requestSync SecurityPolicy syncAccount %s, %s",
       account.toString(),
       extras.toString());
 }
  @Override
  protected void onMessageShown(long messageId, Mailbox mailbox) {
    super.onMessageShown(messageId, mailbox);

    Account account = Account.restoreAccountWithId(mContext, getAccountId());
    boolean supportsMove = account.supportsMoveMessages(mContext) && mailbox.canHaveMessagesMoved();
    if (mSupportsMove != supportsMove) {
      mSupportsMove = supportsMove;
      Activity host = getActivity();
      if (host != null) {
        host.invalidateOptionsMenu();
      }
    }

    // Disable forward/reply buttons as necessary.
    enableReplyForwardButtons(Mailbox.isMailboxTypeReplyAndForwardable(mailbox.mType));
  }
  @Override
  public Integer buildFilter(Account account) {
    if (!Account.isNormalAccount(account.mId)) {
      // Shortcuts for combined accounts can only be for inboxes.
      return MailboxShortcutPickerFragment.FILTER_INBOX_ONLY;
    }

    return MailboxShortcutPickerFragment.FILTER_ALLOW_ALL;
  }
 /** Convenience method; see javadoc below */
 public static void setAccountHoldFlag(Context context, long accountId, boolean newState) {
   Account account = Account.restoreAccountWithId(context, accountId);
   if (account != null) {
     setAccountHoldFlag(context, account, newState);
     if (newState) {
       // Make sure there's a notification up
       final NotificationController nc = NotificationControllerCreatorHolder.getInstance(context);
       nc.showSecurityNeededNotification(account);
     }
   }
 }
 /**
  * This is the remote call from the Email app, currently unused. TODO: remove this when it's
  * been deleted from IEmailService.aidl.
  */
 @Deprecated
 @Override
 public void startSync(long mailboxId, boolean userRequest, int deltaMessageCount)
     throws RemoteException {
   SyncManager exchangeService = INSTANCE;
   if (exchangeService == null) return;
   checkExchangeServiceServiceRunning();
   Mailbox m = Mailbox.restoreMailboxWithId(exchangeService, mailboxId);
   if (m == null) return;
   Account acct = Account.restoreAccountWithId(exchangeService, m.mAccountKey);
   if (acct == null) return;
   // If this is a user request and we're being held, release the hold; this allows us to
   // try again (the hold might have been specific to this account and released already)
   if (userRequest) {
     if (onSyncDisabledHold(acct)) {
       releaseSyncHolds(exchangeService, AbstractSyncService.EXIT_ACCESS_DENIED, acct);
       log("User requested sync of account in sync disabled hold; releasing");
     } else if (onSecurityHold(acct)) {
       releaseSyncHolds(exchangeService, AbstractSyncService.EXIT_SECURITY_FAILURE, acct);
       log("User requested sync of account in security hold; releasing");
     }
     if (sConnectivityHold) {
       return;
     }
   }
   if (m.mType == Mailbox.TYPE_OUTBOX) {
     // We're using SERVER_ID to indicate an error condition (it has no other use for
     // sent mail)  Upon request to sync the Outbox, we clear this so that all messages
     // are candidates for sending.
     ContentValues cv = new ContentValues();
     cv.put(SyncColumns.SERVER_ID, 0);
     exchangeService
         .getContentResolver()
         .update(
             Message.CONTENT_URI,
             cv,
             WHERE_MAILBOX_KEY,
             new String[] {Long.toString(mailboxId)});
     // Clear the error state; the Outbox sync will be started from checkMailboxes
     exchangeService.mSyncErrorMap.remove(mailboxId);
     kick("start outbox");
     // Outbox can't be synced in EAS
     return;
   } else if (!isSyncable(m)) {
     return;
   }
   startManualSync(
       mailboxId,
       userRequest
           ? ExchangeService.SYNC_UI_REQUEST
           : ExchangeService.SYNC_SERVICE_START_SYNC,
       null);
 }
  /**
   * Create an intent to launch search activity.
   *
   * @param accountId ID of the account for the mailbox. Must not be {@link Account#NO_ACCOUNT}.
   * @param mailboxId ID of the mailbox to search, or {@link Mailbox#NO_MAILBOX} to perform global
   *     search.
   * @param query query string.
   */
  public static Intent createSearchIntent(
      Activity fromActivity, long accountId, long mailboxId, String query) {
    Preconditions.checkArgument(
        Account.isNormalAccount(accountId), "Can only search in normal accounts");

    // Note that a search doesn't use a restart intent, as we want another instance of
    // the activity to sit on the stack for search.
    Intent i = new Intent(fromActivity, EmailActivity.class);
    i.putExtra(EXTRA_ACCOUNT_ID, accountId);
    i.putExtra(EXTRA_MAILBOX_ID, mailboxId);
    i.putExtra(EXTRA_QUERY_STRING, query);
    i.setAction(Intent.ACTION_SEARCH);
    return i;
  }
  /**
   * Set the policy for an account atomically; this also removes any other policy associated with
   * the account and sets the policy key for the account. If policy is null, the policyKey is set to
   * 0 and the securitySyncKey to null. Also, update the account object to reflect the current
   * policyKey and securitySyncKey
   *
   * @param context the caller's context
   * @param account the account whose policy is to be set
   * @param policy the policy to set, or null if we're clearing the policy
   * @param securitySyncKey the security sync key for this account (ignored if policy is null)
   */
  public static void setAccountPolicy(
      Context context, Account account, Policy policy, String securitySyncKey) {
    ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();

    // Make sure this is a valid policy set
    if (policy != null) {
      policy.normalize();
      // Add the new policy (no account will yet reference this)
      ops.add(
          ContentProviderOperation.newInsert(Policy.CONTENT_URI)
              .withValues(policy.toContentValues())
              .build());
      // Make the policyKey of the account our newly created policy, and set the sync key
      ops.add(
          ContentProviderOperation.newUpdate(
                  ContentUris.withAppendedId(Account.CONTENT_URI, account.mId))
              .withValueBackReference(AccountColumns.POLICY_KEY, 0)
              .withValue(AccountColumns.SECURITY_SYNC_KEY, securitySyncKey)
              .build());
    } else {
      ops.add(
          ContentProviderOperation.newUpdate(
                  ContentUris.withAppendedId(Account.CONTENT_URI, account.mId))
              .withValue(AccountColumns.SECURITY_SYNC_KEY, null)
              .withValue(AccountColumns.POLICY_KEY, 0)
              .build());
    }

    // Delete the previous policy associated with this account, if any
    if (account.mPolicyKey > 0) {
      ops.add(
          ContentProviderOperation.newDelete(
                  ContentUris.withAppendedId(Policy.CONTENT_URI, account.mPolicyKey))
              .build());
    }

    try {
      context.getContentResolver().applyBatch(EmailContent.AUTHORITY, ops);
      account.refresh(context);
      syncAccount(context, account);
    } catch (RemoteException e) {
      // This is fatal to a remote process
      throw new IllegalStateException("Exception setting account policy.");
    } catch (OperationApplicationException e) {
      // Can't happen; our provider doesn't throw this exception
    }
  }
  /**
   * API: Sync service should call this any time a sync fails due to isActive() returning false.
   * This will kick off the notify-acquire-admin-state process and/or increase the security level.
   * The caller needs to write the required policies into this account before making this call.
   * Should not be called from UI thread - uses DB lookups to prepare new notifications
   *
   * @param accountId the account for which sync cannot proceed
   */
  public void policiesRequired(long accountId) {
    Account account = Account.restoreAccountWithId(mContext, accountId);
    // In case the account has been deleted, just return
    if (account == null) return;
    if (account.mPolicyKey == 0) return;
    Policy policy = Policy.restorePolicyWithId(mContext, account.mPolicyKey);
    if (policy == null) return;
    if (DebugUtils.DEBUG) {
      LogUtils.d(TAG, "policiesRequired for " + account.mDisplayName + ": " + policy);
    }

    // Mark the account as "on hold".
    setAccountHoldFlag(mContext, account, true);

    // Put up an appropriate notification
    final NotificationController nc = NotificationControllerCreatorHolder.getInstance(mContext);
    if (policy.mProtocolPoliciesUnsupported == null) {
      nc.showSecurityNeededNotification(account);
    } else {
      nc.showSecurityUnsupportedNotification(account);
    }
  }
  /**
   * Callback from EmailBroadcastProcessorService. This provides the workers for the
   * DeviceAdminReceiver calls. These should perform the work directly and not use async threads for
   * completion.
   */
  public static void onDeviceAdminReceiverMessage(Context context, int message) {
    SecurityPolicy instance = SecurityPolicy.getInstance(context);
    switch (message) {
      case DEVICE_ADMIN_MESSAGE_ENABLED:
        instance.onAdminEnabled(true);
        break;
      case DEVICE_ADMIN_MESSAGE_DISABLED:
        instance.onAdminEnabled(false);
        break;
      case DEVICE_ADMIN_MESSAGE_PASSWORD_CHANGED:
        // TODO make a small helper for this
        // Clear security holds (if any)
        Account.clearSecurityHoldOnAllAccounts(context);
        // Cancel any active notifications (if any are posted)
        final NotificationController nc = NotificationControllerCreatorHolder.getInstance(context);

        nc.cancelPasswordExpirationNotifications();
        break;
      case DEVICE_ADMIN_MESSAGE_PASSWORD_EXPIRING:
        instance.onPasswordExpiring(instance.mContext);
        break;
    }
  }
 /** Do DB access on a worker thread. */
 @Override
 protected Boolean doInBackground(Void... params) {
   mInboxId = Account.getInboxId(mContext, mAccountId);
   return Mailbox.isRefreshable(mContext, mMailboxId);
 }
  public static void updateAccountManagerType(
      Context context, android.accounts.Account amAccount, final Map<String, String> protocolMap) {
    final ContentResolver resolver = context.getContentResolver();
    final Cursor c =
        resolver.query(
            Account.CONTENT_URI,
            Account.CONTENT_PROJECTION,
            AccountColumns.EMAIL_ADDRESS + "=?",
            new String[] {amAccount.name},
            null);
    // That's odd, isn't it?
    if (c == null) return;
    try {
      if (c.moveToNext()) {
        // Get the EmailProvider Account/HostAuth
        final Account account = new Account();
        account.restore(c);
        final HostAuth hostAuth = HostAuth.restoreHostAuthWithId(context, account.mHostAuthKeyRecv);
        if (hostAuth == null) {
          return;
        }

        final String newProtocol = protocolMap.get(hostAuth.mProtocol);
        if (newProtocol == null) {
          // This account doesn't need updating.
          return;
        }

        LogUtils.w(Logging.LOG_TAG, "Converting " + amAccount.name + " to " + newProtocol);

        final ContentValues accountValues = new ContentValues();
        int oldFlags = account.mFlags;

        // Mark the provider account incomplete so it can't get reconciled away
        account.mFlags |= Account.FLAGS_INCOMPLETE;
        accountValues.put(AccountColumns.FLAGS, account.mFlags);
        final Uri accountUri = ContentUris.withAppendedId(Account.CONTENT_URI, account.mId);
        resolver.update(accountUri, accountValues, null, null);

        // Change the HostAuth to reference the new protocol; this has to be done before
        // trying to create the AccountManager account (below)
        final ContentValues hostValues = new ContentValues();
        hostValues.put(HostAuth.PROTOCOL, newProtocol);
        resolver.update(
            ContentUris.withAppendedId(HostAuth.CONTENT_URI, hostAuth.mId), hostValues, null, null);
        LogUtils.w(Logging.LOG_TAG, "Updated HostAuths");

        try {
          // Get current settings for the existing AccountManager account
          boolean email = ContentResolver.getSyncAutomatically(amAccount, EmailContent.AUTHORITY);
          if (!email) {
            // Try our old provider name
            email = ContentResolver.getSyncAutomatically(amAccount, "com.android.email.provider");
          }
          final boolean contacts =
              ContentResolver.getSyncAutomatically(amAccount, ContactsContract.AUTHORITY);
          final boolean calendar =
              ContentResolver.getSyncAutomatically(amAccount, CalendarContract.AUTHORITY);
          LogUtils.w(
              Logging.LOG_TAG,
              "Email: " + email + ", Contacts: " + contacts + "," + " Calendar: " + calendar);

          // Get sync keys for calendar/contacts
          final String amName = amAccount.name;
          final String oldType = amAccount.type;
          ContentProviderClient client =
              context
                  .getContentResolver()
                  .acquireContentProviderClient(CalendarContract.CONTENT_URI);
          byte[] calendarSyncKey = null;
          try {
            calendarSyncKey =
                SyncStateContract.Helpers.get(
                    client,
                    asCalendarSyncAdapter(SyncState.CONTENT_URI, amName, oldType),
                    new android.accounts.Account(amName, oldType));
          } catch (RemoteException e) {
            LogUtils.w(Logging.LOG_TAG, "Get calendar key FAILED");
          } finally {
            client.release();
          }
          client =
              context
                  .getContentResolver()
                  .acquireContentProviderClient(ContactsContract.AUTHORITY_URI);
          byte[] contactsSyncKey = null;
          try {
            contactsSyncKey =
                SyncStateContract.Helpers.get(
                    client,
                    ContactsContract.SyncState.CONTENT_URI,
                    new android.accounts.Account(amName, oldType));
          } catch (RemoteException e) {
            LogUtils.w(Logging.LOG_TAG, "Get contacts key FAILED");
          } finally {
            client.release();
          }
          if (calendarSyncKey != null) {
            LogUtils.w(Logging.LOG_TAG, "Got calendar key: " + new String(calendarSyncKey));
          }
          if (contactsSyncKey != null) {
            LogUtils.w(Logging.LOG_TAG, "Got contacts key: " + new String(contactsSyncKey));
          }

          // Set up a new AccountManager account with new type and old settings
          AccountManagerFuture<?> amFuture =
              setupAccountManagerAccount(context, account, email, calendar, contacts, null);
          finishAccountManagerBlocker(amFuture);
          LogUtils.w(Logging.LOG_TAG, "Created new AccountManager account");

          // TODO: Clean up how we determine the type.
          final String accountType = protocolMap.get(hostAuth.mProtocol + "_type");
          // Move calendar and contacts data from the old account to the new one.
          // We must do this before deleting the old account or the data is lost.
          moveCalendarData(context.getContentResolver(), amName, oldType, accountType);
          moveContactsData(context.getContentResolver(), amName, oldType, accountType);

          // Delete the AccountManager account
          amFuture = AccountManager.get(context).removeAccount(amAccount, null, null);
          finishAccountManagerBlocker(amFuture);
          LogUtils.w(Logging.LOG_TAG, "Deleted old AccountManager account");

          // Restore sync keys for contacts/calendar

          if (accountType != null && calendarSyncKey != null && calendarSyncKey.length != 0) {
            client =
                context
                    .getContentResolver()
                    .acquireContentProviderClient(CalendarContract.CONTENT_URI);
            try {
              SyncStateContract.Helpers.set(
                  client,
                  asCalendarSyncAdapter(SyncState.CONTENT_URI, amName, accountType),
                  new android.accounts.Account(amName, accountType),
                  calendarSyncKey);
              LogUtils.w(Logging.LOG_TAG, "Set calendar key...");
            } catch (RemoteException e) {
              LogUtils.w(Logging.LOG_TAG, "Set calendar key FAILED");
            } finally {
              client.release();
            }
          }
          if (accountType != null && contactsSyncKey != null && contactsSyncKey.length != 0) {
            client =
                context
                    .getContentResolver()
                    .acquireContentProviderClient(ContactsContract.AUTHORITY_URI);
            try {
              SyncStateContract.Helpers.set(
                  client,
                  ContactsContract.SyncState.CONTENT_URI,
                  new android.accounts.Account(amName, accountType),
                  contactsSyncKey);
              LogUtils.w(Logging.LOG_TAG, "Set contacts key...");
            } catch (RemoteException e) {
              LogUtils.w(Logging.LOG_TAG, "Set contacts key FAILED");
            }
          }

          // That's all folks!
          LogUtils.w(Logging.LOG_TAG, "Account update completed.");
        } finally {
          // Clear the incomplete flag on the provider account
          accountValues.put(AccountColumns.FLAGS, oldFlags);
          resolver.update(accountUri, accountValues, null, null);
          LogUtils.w(Logging.LOG_TAG, "[Incomplete flag cleared]");
        }
      }
    } finally {
      c.close();
    }
  }
 public static EmailServiceInfo getServiceInfoForAccount(Context context, long accountId) {
   String protocol = Account.getProtocol(context, accountId);
   return getServiceInfo(context, protocol);
 }
 /**
  * For a given account id, return a service proxy if applicable, or null.
  *
  * @param accountId the message of interest
  * @return service proxy, or null if n/a
  */
 public static EmailServiceProxy getServiceForAccount(Context context, long accountId) {
   return getService(context, Account.getProtocol(context, accountId));
 }
  public static void sendMailImpl(Context context, long accountId) {
    /// M: We Can't send mail in low storage state @{
    if (StorageLowState.checkIfStorageLow(context)) {
      LogUtils.e(Logging.LOG_TAG, "Can't send mail due to low storage");
      return;
    }
    /// @}

    /** M: Get the sendable mails count of the account and notify this sending @{ */
    final int count = getSendableMessageCount(context, accountId);
    LogUtils.logFeature(LogTag.SENDMAIL_TAG, "sendable message count [%d]", count);
    if (count <= 0) {
      return;
    }
    SendNotificationProxy.getInstance(context)
        .showSendingNotification(accountId, NotificationController.SEND_MAIL, count);
    /** @} */
    final Account account = Account.restoreAccountWithId(context, accountId);
    TrafficStats.setThreadStatsTag(TrafficFlags.getSmtpFlags(context, account));
    final NotificationController nc = NotificationController.getInstance(context);
    // 1.  Loop through all messages in the account's outbox
    final long outboxId = Mailbox.findMailboxOfType(context, account.mId, Mailbox.TYPE_OUTBOX);
    if (outboxId == Mailbox.NO_MAILBOX) {
      return;
    }
    final ContentResolver resolver = context.getContentResolver();
    final Cursor c =
        resolver.query(
            EmailContent.Message.CONTENT_URI,
            EmailContent.Message.ID_COLUMN_PROJECTION,
            EmailContent.Message.MAILBOX_KEY + "=?",
            new String[] {Long.toString(outboxId)},
            null);
    try {
      // 2.  exit early
      if (c.getCount() <= 0) {
        return;
      }
      final Sender sender = Sender.getInstance(context, account);
      final Store remoteStore = Store.getInstance(account, context);
      final ContentValues moveToSentValues;
      if (remoteStore.requireCopyMessageToSentFolder()) {
        Mailbox sentFolder = Mailbox.restoreMailboxOfType(context, accountId, Mailbox.TYPE_SENT);
        moveToSentValues = new ContentValues();
        moveToSentValues.put(MessageColumns.MAILBOX_KEY, sentFolder.mId);
      } else {
        moveToSentValues = null;
      }

      // 3.  loop through the available messages and send them
      /** M: mark should we cancel the Login Failed Notification. */
      boolean shouldCancelNf = false;
      while (c.moveToNext()) {
        long messageId = -1;
        if (moveToSentValues != null) {
          moveToSentValues.remove(EmailContent.MessageColumns.FLAGS);
        }
        try {
          messageId = c.getLong(0);
          // Don't send messages with unloaded attachments
          if (Utility.hasUnloadedAttachments(context, messageId)) {
            LogUtils.logFeature(
                LogTag.SENDMAIL_TAG, "Can't send #" + messageId + "; unloaded attachments");
            continue;
          }
          sender.sendMessage(messageId);
        } catch (MessagingException me) {
          LogUtils.logFeature(
              LogTag.SENDMAIL_TAG,
              "<<< Smtp send message failed id [%s], exception: %s",
              messageId,
              me);
          // report error for this message, but keep trying others
          if (me instanceof AuthenticationFailedException) {
            shouldCancelNf = false;
            nc.showLoginFailedNotification(account.mId);
          }
          /// M: One mail sent failed
          SendNotificationProxy.getInstance(context)
              .showSendingNotification(account.mId, NotificationController.SEND_FAILED, 1);
          continue;
        }

        /// M: One mail sent complete
        SendNotificationProxy.getInstance(context)
            .showSendingNotification(account.mId, NotificationController.SEND_COMPLETE, 1);

        // 4. move to sent, or delete
        final Uri syncedUri =
            ContentUris.withAppendedId(EmailContent.Message.SYNCED_CONTENT_URI, messageId);
        // Delete all cached files
        AttachmentUtilities.deleteAllCachedAttachmentFiles(context, account.mId, messageId);
        if (moveToSentValues != null) {
          // If this is a forwarded message and it has attachments, delete them, as they
          // duplicate information found elsewhere (on the server).  This saves storage.
          final EmailContent.Message msg =
              EmailContent.Message.restoreMessageWithId(context, messageId);
          if ((msg.mFlags & EmailContent.Message.FLAG_TYPE_FORWARD) != 0) {
            AttachmentUtilities.deleteAllAttachmentFiles(context, account.mId, messageId);
          }
          /// M: un-mark sending status after sending
          final int flags =
              msg.mFlags
                  & ~(EmailContent.Message.FLAG_TYPE_REPLY
                      | EmailContent.Message.FLAG_TYPE_FORWARD
                      | EmailContent.Message.FLAG_TYPE_REPLY_ALL
                      | EmailContent.Message.FLAG_TYPE_ORIGINAL
                      | EmailContent.Message.FLAG_STATUS_SENDING);

          moveToSentValues.put(EmailContent.MessageColumns.FLAGS, flags);
          resolver.update(syncedUri, moveToSentValues, null, null);
        } else {
          AttachmentUtilities.deleteAllAttachmentFiles(context, account.mId, messageId);
          final Uri uri = ContentUris.withAppendedId(EmailContent.Message.CONTENT_URI, messageId);
          resolver.delete(uri, null, null);
          resolver.delete(syncedUri, null, null);
        }
        shouldCancelNf = true;
      }
      if (shouldCancelNf) {
        nc.cancelLoginFailedNotification(account.mId);
      }
    } catch (MessagingException me) {
      if (me instanceof AuthenticationFailedException) {
        nc.showLoginFailedNotification(account.mId);
      }
      /// M: All mails failed to be sent, caused by fail to get instance of store
      SendNotificationProxy.getInstance(context)
          .showSendingNotification(account.mId, NotificationController.SEND_FAILED, c.getCount());
    } finally {
      c.close();
    }
  }
  /// M: The folder sync must be synchronized, otherwise the folders may be duplicate
  @Override
  public synchronized void updateFolderList(long accountId) throws RemoteException {
    /// M: We Can't updateFolderList in low storage state @{
    if (StorageLowState.checkIfStorageLow(mContext)) {
      LogUtils.e(Logging.LOG_TAG, "Can't updateFolderList due to low storage");
      return;
    }
    /// @}
    final Account account = Account.restoreAccountWithId(mContext, accountId);
    if (account == null) {
      return;
    }
    long inboxId = -1;
    TrafficStats.setThreadStatsTag(TrafficFlags.getSyncFlags(mContext, account));
    Cursor localFolderCursor = null;
    try {
      // Step 0: Make sure the default system mailboxes exist.
      for (final int type : Mailbox.REQUIRED_FOLDER_TYPES) {
        if (Mailbox.findMailboxOfType(mContext, accountId, type) == Mailbox.NO_MAILBOX) {
          final Mailbox mailbox = Mailbox.newSystemMailbox(mContext, accountId, type);
          mailbox.save(mContext);
          if (type == Mailbox.TYPE_INBOX) {
            inboxId = mailbox.mId;
          }
        }
      }

      // Step 1: Get remote mailboxes
      final Store store = Store.getInstance(account, mContext);
      final Folder[] remoteFolders = store.updateFolders();
      final HashSet<String> remoteFolderNames = new HashSet<String>();
      for (final Folder remoteFolder : remoteFolders) {
        remoteFolderNames.add(remoteFolder.getName());
      }

      // Step 2: Get local mailboxes
      localFolderCursor =
          mContext
              .getContentResolver()
              .query(
                  Mailbox.CONTENT_URI,
                  MAILBOX_PROJECTION,
                  EmailContent.MailboxColumns.ACCOUNT_KEY + "=?",
                  new String[] {String.valueOf(account.mId)},
                  null);

      // Step 3: Remove any local mailbox not on the remote list
      while (localFolderCursor.moveToNext()) {
        final String mailboxPath = localFolderCursor.getString(MAILBOX_COLUMN_SERVER_ID);
        // Short circuit if we have a remote mailbox with the same name
        if (remoteFolderNames.contains(mailboxPath)) {
          continue;
        }

        final int mailboxType = localFolderCursor.getInt(MAILBOX_COLUMN_TYPE);
        final long mailboxId = localFolderCursor.getLong(MAILBOX_COLUMN_ID);
        switch (mailboxType) {
          case Mailbox.TYPE_INBOX:
          case Mailbox.TYPE_DRAFTS:
          case Mailbox.TYPE_OUTBOX:
          case Mailbox.TYPE_SENT:
          case Mailbox.TYPE_TRASH:
          case Mailbox.TYPE_SEARCH:
            // Never, ever delete special mailboxes
            break;
          default:
            // Drop all attachment files related to this mailbox
            AttachmentUtilities.deleteAllMailboxAttachmentFiles(mContext, accountId, mailboxId);
            // Delete the mailbox; database triggers take care of related
            // Message, Body and Attachment records
            Uri uri = ContentUris.withAppendedId(Mailbox.CONTENT_URI, mailboxId);
            mContext.getContentResolver().delete(uri, null, null);
            break;
        }
      }
    } catch (MessagingException me) {
      LogUtils.i(Logging.LOG_TAG, me, "Error in updateFolderList");
      // We'll hope this is temporary
    } finally {
      if (localFolderCursor != null) {
        localFolderCursor.close();
      }
      // If we just created the inbox, sync it
      if (inboxId != -1) {
        startSync(inboxId, true, 0);
      }
    }
  }
  @Override
  public void loadAttachment(
      final IEmailServiceCallback cb, final long attachmentId, final boolean background)
      throws RemoteException {
    /// M: We Can't load attachment in low storage state @{
    if (StorageLowState.checkIfStorageLow(mContext)) {
      LogUtils.e(Logging.LOG_TAG, "Can't load attachment due to low storage");
      cb.loadAttachmentStatus(0, attachmentId, EmailServiceStatus.SUCCESS, 0);
      return;
    }
    /// @}
    Folder remoteFolder = null;
    try {
      // 1. Check if the attachment is already here and return early in that case
      Attachment attachment = Attachment.restoreAttachmentWithId(mContext, attachmentId);
      if (attachment == null) {
        cb.loadAttachmentStatus(0, attachmentId, EmailServiceStatus.ATTACHMENT_NOT_FOUND, 0);
        return;
      }
      final long messageId = attachment.mMessageKey;

      final EmailContent.Message message =
          EmailContent.Message.restoreMessageWithId(mContext, attachment.mMessageKey);
      if (message == null) {
        cb.loadAttachmentStatus(messageId, attachmentId, EmailServiceStatus.MESSAGE_NOT_FOUND, 0);
        return;
      }

      // If the message is loaded, just report that we're finished
      if (Utility.attachmentExists(mContext, attachment)
          && attachment.mUiState == UIProvider.AttachmentState.SAVED) {
        cb.loadAttachmentStatus(messageId, attachmentId, EmailServiceStatus.SUCCESS, 0);
        return;
      }

      // Say we're starting...
      cb.loadAttachmentStatus(messageId, attachmentId, EmailServiceStatus.IN_PROGRESS, 0);

      // 2. Open the remote folder.
      final Account account = Account.restoreAccountWithId(mContext, message.mAccountKey);
      Mailbox mailbox = Mailbox.restoreMailboxWithId(mContext, message.mMailboxKey);

      if (mailbox.mType == Mailbox.TYPE_OUTBOX
          /// M: View an attachment which comes from refMessage need sourceKey to identify
          || mailbox.mType == Mailbox.TYPE_DRAFTS) {
        long sourceId =
            Utility.getFirstRowLong(
                mContext,
                Body.CONTENT_URI,
                new String[] {BodyColumns.SOURCE_MESSAGE_KEY},
                BodyColumns.MESSAGE_KEY + "=?",
                new String[] {Long.toString(messageId)},
                null,
                0,
                -1L);
        if (sourceId != -1) {
          EmailContent.Message sourceMsg =
              EmailContent.Message.restoreMessageWithId(mContext, sourceId);
          if (sourceMsg != null) {
            mailbox = Mailbox.restoreMailboxWithId(mContext, sourceMsg.mMailboxKey);
            message.mServerId = sourceMsg.mServerId;
          }
        }
      } else if (mailbox.mType == Mailbox.TYPE_SEARCH && message.mMainMailboxKey != 0) {
        mailbox = Mailbox.restoreMailboxWithId(mContext, message.mMainMailboxKey);
      }

      if (account == null || mailbox == null) {
        // If the account/mailbox are gone, just report success; the UI handles this
        cb.loadAttachmentStatus(messageId, attachmentId, EmailServiceStatus.SUCCESS, 0);
        return;
      }
      TrafficStats.setThreadStatsTag(TrafficFlags.getAttachmentFlags(mContext, account));

      final Store remoteStore = Store.getInstance(account, mContext);
      remoteFolder = remoteStore.getFolder(mailbox.mServerId);
      remoteFolder.open(OpenMode.READ_WRITE);

      // 3. Generate a shell message in which to retrieve the attachment,
      // and a shell BodyPart for the attachment.  Then glue them together.
      final Message storeMessage = remoteFolder.createMessage(message.mServerId);
      final MimeBodyPart storePart = new MimeBodyPart();
      storePart.setSize((int) attachment.mSize);
      storePart.setHeader(MimeHeader.HEADER_ANDROID_ATTACHMENT_STORE_DATA, attachment.mLocation);
      storePart.setHeader(
          MimeHeader.HEADER_CONTENT_TYPE,
          String.format("%s;\n name=\"%s\"", attachment.mMimeType, attachment.mFileName));

      // TODO is this always true for attachments?  I think we dropped the
      // true encoding along the way
      /// M: set encoding type according to data base record.
      String encoding = attachment.mEncoding;
      if (TextUtils.isEmpty(encoding)) {
        encoding = "base64";
      }
      storePart.setHeader(MimeHeader.HEADER_CONTENT_TRANSFER_ENCODING, encoding);

      final MimeMultipart multipart = new MimeMultipart();
      multipart.setSubType("mixed");
      multipart.addBodyPart(storePart);

      storeMessage.setHeader(MimeHeader.HEADER_CONTENT_TYPE, "multipart/mixed");
      storeMessage.setBody(multipart);

      // 4. Now ask for the attachment to be fetched
      final FetchProfile fp = new FetchProfile();
      fp.add(storePart);
      remoteFolder.fetch(
          new Message[] {storeMessage},
          fp,
          new MessageRetrievalListenerBridge(messageId, attachmentId, cb));

      // If we failed to load the attachment, throw an Exception here, so that
      // AttachmentDownloadService knows that we failed
      if (storePart.getBody() == null) {
        throw new MessagingException("Attachment not loaded.");
      }

      // Save the attachment to wherever it's going
      AttachmentUtilities.saveAttachment(
          mContext, storePart.getBody().getInputStream(), attachment);

      // 6. Report success
      cb.loadAttachmentStatus(messageId, attachmentId, EmailServiceStatus.SUCCESS, 0);

    } catch (MessagingException me) {
      LogUtils.i(Logging.LOG_TAG, me, "Error loading attachment");

      final ContentValues cv = new ContentValues(1);
      cv.put(AttachmentColumns.UI_STATE, UIProvider.AttachmentState.FAILED);
      final Uri uri = ContentUris.withAppendedId(Attachment.CONTENT_URI, attachmentId);
      mContext.getContentResolver().update(uri, cv, null, null);

      cb.loadAttachmentStatus(0, attachmentId, EmailServiceStatus.CONNECTION_ERROR, 0);
    } finally {
      if (remoteFolder != null) {
        remoteFolder.close(false);
      }
    }
  }
Example #27
0
  public static int searchMessages(
      Context context, long accountId, SearchParams searchParams, long destMailboxId) {
    // Sanity check for arguments
    final int offset = searchParams.mOffset;
    final int limit = searchParams.mLimit;
    final String filter = searchParams.mFilter;
    if (limit < 0 || limit > MAX_SEARCH_RESULTS || offset < 0) return 0;
    // TODO Should this be checked in UI?  Are there guidelines for minimums?
    if (filter == null || filter.length() < MIN_QUERY_LENGTH) return 0;

    int res = 0;
    final Account account = Account.restoreAccountWithId(context, accountId);
    if (account == null) return res;
    final EasSyncService svc = EasSyncService.setupServiceForAccount(context, account);
    if (svc == null) return res;
    final Mailbox searchMailbox = Mailbox.restoreMailboxWithId(context, destMailboxId);
    // Sanity check; account might have been deleted?
    if (searchMailbox == null) return res;
    final ContentValues statusValues = new ContentValues(2);
    try {
      // Set the status of this mailbox to indicate query
      statusValues.put(Mailbox.UI_SYNC_STATUS, UIProvider.SyncStatus.LIVE_QUERY);
      searchMailbox.update(context, statusValues);

      svc.mMailbox = searchMailbox;
      svc.mAccount = account;
      final Serializer s = new Serializer();
      s.start(Tags.SEARCH_SEARCH).start(Tags.SEARCH_STORE);
      s.data(Tags.SEARCH_NAME, "Mailbox");
      s.start(Tags.SEARCH_QUERY).start(Tags.SEARCH_AND);
      s.data(Tags.SYNC_CLASS, "Email");

      // If this isn't an inbox search, then include the collection id
      final Mailbox inbox = Mailbox.restoreMailboxOfType(context, accountId, Mailbox.TYPE_INBOX);
      if (inbox == null) return 0;
      if (searchParams.mMailboxId != inbox.mId) {
        s.data(Tags.SYNC_COLLECTION_ID, inbox.mServerId);
      }

      s.data(Tags.SEARCH_FREE_TEXT, filter);

      // Add the date window if appropriate
      if (searchParams.mStartDate != null) {
        s.start(Tags.SEARCH_GREATER_THAN);
        s.tag(Tags.EMAIL_DATE_RECEIVED);
        s.data(Tags.SEARCH_VALUE, Eas.DATE_FORMAT.format(searchParams.mStartDate));
        s.end(); // SEARCH_GREATER_THAN
      }
      if (searchParams.mEndDate != null) {
        s.start(Tags.SEARCH_LESS_THAN);
        s.tag(Tags.EMAIL_DATE_RECEIVED);
        s.data(Tags.SEARCH_VALUE, Eas.DATE_FORMAT.format(searchParams.mEndDate));
        s.end(); // SEARCH_LESS_THAN
      }
      s.end().end(); // SEARCH_AND, SEARCH_QUERY
      s.start(Tags.SEARCH_OPTIONS);
      if (offset == 0) {
        s.tag(Tags.SEARCH_REBUILD_RESULTS);
      }
      if (searchParams.mIncludeChildren) {
        s.tag(Tags.SEARCH_DEEP_TRAVERSAL);
      }
      // Range is sent in the form first-last (e.g. 0-9)
      s.data(Tags.SEARCH_RANGE, offset + "-" + (offset + limit - 1));
      s.start(Tags.BASE_BODY_PREFERENCE);
      s.data(Tags.BASE_TYPE, Eas.BODY_PREFERENCE_HTML);
      s.data(Tags.BASE_TRUNCATION_SIZE, "20000");
      s.end(); // BASE_BODY_PREFERENCE
      s.end().end().end().done(); // SEARCH_OPTIONS, SEARCH_STORE, SEARCH_SEARCH
      final EasResponse resp = svc.sendHttpClientPost("Search", s.toByteArray());
      try {
        final int code = resp.getStatus();
        if (code == HttpStatus.SC_OK) {
          final InputStream is = resp.getInputStream();
          try {
            final SearchParser sp = new SearchParser(is, svc, filter);
            sp.parse();
            res = sp.getTotalResults();
            /** M: Set and update mailbox flag. @{ */
            int currentCount = offset + sp.getCurrentResults();
            boolean allMessagesLoaded = false;
            if (currentCount >= res) {
              allMessagesLoaded = true;
            }
            searchMailbox.updateAllMessageDownloadFlag(context, allMessagesLoaded);
            /** @} */
          } finally {
            is.close();
          }
        } else {
          svc.userLog("Search returned " + code);
        }
      } finally {
        resp.close();
      }
    } catch (IOException e) {
      svc.userLog("Search exception " + e);
    } finally {
      // TODO: Handle error states
      // Set the status of this mailbox to indicate query over
      statusValues.put(Mailbox.SYNC_TIME, System.currentTimeMillis());
      statusValues.put(Mailbox.UI_SYNC_STATUS, UIProvider.SyncStatus.NO_SYNC);
      searchMailbox.update(context, statusValues);
    }
    // Return the total count
    return res;
  }
  public static void reloadFolderList(Context context, long accountId, boolean force) {
    SyncManager exchangeService = INSTANCE;
    if (exchangeService == null) return;
    Cursor c =
        context
            .getContentResolver()
            .query(
                Mailbox.CONTENT_URI,
                Mailbox.CONTENT_PROJECTION,
                MailboxColumns.ACCOUNT_KEY + "=? AND " + MailboxColumns.TYPE + "=?",
                new String[] {
                  Long.toString(accountId), Long.toString(Mailbox.TYPE_EAS_ACCOUNT_MAILBOX)
                },
                null);
    try {
      if (c.moveToFirst()) {
        synchronized (sSyncLock) {
          Mailbox mailbox = new Mailbox();
          mailbox.restore(c);
          Account acct = Account.restoreAccountWithId(context, accountId);
          if (acct == null) {
            reloadFolderListFailed(accountId);
            return;
          }
          String syncKey = acct.mSyncKey;
          // No need to reload the list if we don't have one
          if (!force && (syncKey == null || syncKey.equals("0"))) {
            reloadFolderListFailed(accountId);
            return;
          }

          // Change all ping/push boxes to push/hold
          ContentValues cv = new ContentValues();
          cv.put(Mailbox.SYNC_INTERVAL, Mailbox.CHECK_INTERVAL_PUSH_HOLD);
          context
              .getContentResolver()
              .update(
                  Mailbox.CONTENT_URI,
                  cv,
                  WHERE_PUSH_OR_PING_NOT_ACCOUNT_MAILBOX,
                  new String[] {Long.toString(accountId)});
          log("Set push/ping boxes to push/hold");

          long id = mailbox.mId;
          AbstractSyncService svc = exchangeService.mServiceMap.get(id);
          // Tell the service we're done
          if (svc != null) {
            synchronized (svc.getSynchronizer()) {
              svc.stop();
              // Interrupt the thread so that it can stop
              Thread thread = svc.mThread;
              if (thread != null) {
                thread.setName(thread.getName() + " (Stopped)");
                thread.interrupt();
              }
            }
            // Abandon the service
            exchangeService.releaseMailbox(id);
            // And have it start naturally
            kick("reload folder list");
          }
        }
      }
    } finally {
      c.close();
    }
  }
  public void setAccountPolicy(long accountId, Policy policy, String securityKey, boolean notify) {
    Account account = Account.restoreAccountWithId(mContext, accountId);
    // In case the account has been deleted, just return
    if (account == null) {
      return;
    }
    Policy oldPolicy = null;
    if (account.mPolicyKey > 0) {
      oldPolicy = Policy.restorePolicyWithId(mContext, account.mPolicyKey);
    }

    // If attachment policies have changed, fix up any affected attachment records
    if (oldPolicy != null && securityKey != null) {
      if ((oldPolicy.mDontAllowAttachments != policy.mDontAllowAttachments)
          || (oldPolicy.mMaxAttachmentSize != policy.mMaxAttachmentSize)) {
        Policy.setAttachmentFlagsForNewPolicy(mContext, account, policy);
      }
    }

    boolean policyChanged = (oldPolicy == null) || !oldPolicy.equals(policy);
    if (!policyChanged
        && (TextUtilities.stringOrNullEquals(securityKey, account.mSecuritySyncKey))) {
      LogUtils.d(Logging.LOG_TAG, "setAccountPolicy; policy unchanged");
    } else {
      setAccountPolicy(mContext, account, policy, securityKey);
      policiesUpdated();
    }

    boolean setHold = false;
    final NotificationController nc = NotificationControllerCreatorHolder.getInstance(mContext);
    if (policy.mProtocolPoliciesUnsupported != null) {
      // We can't support this, reasons in unsupportedRemotePolicies
      LogUtils.d(
          Logging.LOG_TAG, "Notify policies for " + account.mDisplayName + " not supported.");
      setHold = true;
      if (notify) {
        nc.showSecurityUnsupportedNotification(account);
      }
      // Erase data
      Uri uri = EmailProvider.uiUri("uiaccountdata", accountId);
      mContext.getContentResolver().delete(uri, null, null);
    } else if (isActive(policy)) {
      if (policyChanged) {
        LogUtils.d(Logging.LOG_TAG, "Notify policies for " + account.mDisplayName + " changed.");
        if (notify) {
          // Notify that policies changed
          nc.showSecurityChangedNotification(account);
        }
      } else {
        LogUtils.d(Logging.LOG_TAG, "Policy is active and unchanged; do not notify.");
      }
    } else {
      setHold = true;
      LogUtils.d(
          Logging.LOG_TAG,
          "Notify policies for " + account.mDisplayName + " are not being enforced.");
      if (notify) {
        // Put up a notification
        nc.showSecurityNeededNotification(account);
      }
    }
    // Set/clear the account hold.
    setAccountHoldFlag(mContext, account, setHold);
  }