/** * 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); } } }
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); }