Convert authorities, intents, and account manager types

* Tested ok on wiped Nexus
* Tested ok on S3
* Upgrade verified on Nexus

Change-Id: If5d4ce594f8a309cdb59589d10b1d33f3b79326c
This commit is contained in:
Marc Blank 2012-08-22 22:25:42 -07:00
parent 5ac8d38796
commit e714bb9d15
71 changed files with 979 additions and 5868 deletions

View File

@ -23,7 +23,7 @@ include $(CLEAR_VARS)
chips_dir := ../../../frameworks/ex/chips/res
unified_email_dir := ../UnifiedEmail
photo_dir := ../../../frameworks/ex/photoviewer/res
res_dir := $(chips_dir) res $(unified_email_dir)/res $(photo_dir)
res_dir := $(chips_dir) res $(unified_email_dir)/res $(photo_dir) build/res
LOCAL_MODULE_TAGS := optional

View File

@ -16,8 +16,8 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.email"
android:versionCode="500004"
android:versionName="4.2-004" >
android:versionCode="500005"
android:versionName="4.2-005" >
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
@ -31,7 +31,9 @@
<uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS"/>
<uses-permission android:name="android.permission.WRITE_CONTACTS"/>
<uses-permission android:name="android.permission.READ_CONTACTS"/>
<uses-permission android:name="android.permission.WRITE_CONTACTS"/>
<uses-permission android:name="android.permission.READ_CALENDAR"/>
<uses-permission android:name="android.permission.WRITE_CALENDAR"/>
<uses-permission android:name="android.permission.READ_PROFILE"/>
<uses-permission android:name="android.permission.NFC"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
@ -413,7 +415,7 @@
<!--Required stanza to register the PopImapAuthenticatorService with AccountManager -->
<service
android:name=".service.PopImapAuthenticatorService"
android:name=".service.Pop3AuthenticatorService"
android:exported="true"
android:enabled="true"
>
@ -423,20 +425,35 @@
</intent-filter>
<meta-data
android:name="android.accounts.AccountAuthenticator"
android:resource="@xml/pop_imap_authenticator"
android:resource="@xml/authenticator_pop3"
/>
</service>
<!--Required stanza to register the PopImapAuthenticatorService with AccountManager -->
<service
android:name=".service.ImapAuthenticatorService"
android:exported="true"
android:enabled="true"
>
<intent-filter>
<action
android:name="android.accounts.AccountAuthenticator" />
</intent-filter>
<meta-data
android:name="android.accounts.AccountAuthenticator"
android:resource="@xml/authenticator_imap"
/>
</service>
<!--Required stanza to register the PopImapSyncAdapterService with SyncManager -->
<service
android:name="com.android.email.service.PopImapSyncAdapterService"
android:name="com.android.email.service.Pop3SyncAdapterService"
android:exported="true">
<intent-filter>
<action
android:name="android.content.SyncAdapter" />
</intent-filter>
<meta-data android:name="android.content.SyncAdapter"
android:resource="@xml/syncadapter_pop_imap" />
android:resource="@xml/syncadapter_pop3" />
</service>
<!-- Require provider permission to use our Policy and Account services -->
@ -496,7 +513,7 @@
</intent-filter>
<meta-data
android:name="android.accounts.AccountAuthenticator"
android:resource="@xml/eas_authenticator"
android:resource="@xml/authenticator_eas"
/>
</service>
<!--Required stanza to register the EasTestAuthenticatorService with AccountManager -->
@ -533,7 +550,7 @@
/>
</service>
<service
android:name=".service.Imap2AuthenticatorService"
android:name=".service.ImapAuthenticatorService"
android:exported="true"
android:enabled="true"
>
@ -543,7 +560,7 @@
</intent-filter>
<meta-data
android:name="android.accounts.AccountAuthenticator"
android:resource="@xml/imap2_authenticator"
android:resource="@xml/authenticator_imap"
/>
</service>
@ -555,7 +572,7 @@
android:name="android.content.SyncAdapter" />
</intent-filter>
<meta-data android:name="android.content.SyncAdapter"
android:resource="@xml/syncadapter_imap2" />
android:resource="@xml/syncadapter_imap" />
</service>
<service
@ -583,5 +600,40 @@
android:label="@string/app_name"
/>
<!-- Legacy authenticators, etc. can be added below. OEMs may remove these -->
<service
android:name=".service.LegacyEmailAuthenticatorService"
android:exported="false"
android:enabled="true"
>
<intent-filter>
<action
android:name="android.accounts.AccountAuthenticator" />
</intent-filter>
<meta-data
android:name="android.accounts.AccountAuthenticator"
android:resource="@xml/authenticator_legacy_email"
/>
</service>
<service
android:name=".service.LegacyEasAuthenticatorService"
android:exported="false"
android:enabled="true"
>
<intent-filter>
<action
android:name="android.accounts.AccountAuthenticator" />
</intent-filter>
<meta-data
android:name="android.accounts.AccountAuthenticator"
android:resource="@xml/authenticator_legacy_eas"
/>
</service>
</application>
<!-- Legacy permissions, etc. can go here -->
</manifest>

View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2008 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="account_manager_type_exchange">com.android.exchange</string>
<string name="account_manager_type_pop3">com.android.pop3</string>
<string name="account_manager_type_imap">com.android.imap</string>
<string name="intent_exchange">com.android.email.EXCHANGE_INTENT</string>
<string name="authority_email_provider">com.android.email.provider</string>
<string name="protocol_imap">imap</string>
<string name="protocol_pop3">pop3</string>
</resources>

View File

@ -40,15 +40,6 @@ import java.util.UUID;
public final class Account extends EmailContent implements AccountColumns, Parcelable {
public static final String TABLE_NAME = "Account";
public static final Uri CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/account");
public static final Uri ADD_TO_FIELD_URI =
Uri.parse(EmailContent.CONTENT_URI + "/accountIdAddToField");
public static final Uri RESET_NEW_MESSAGE_COUNT_URI =
Uri.parse(EmailContent.CONTENT_URI + "/resetNewMessageCount");
public static final Uri NOTIFIER_URI =
Uri.parse(EmailContent.CONTENT_NOTIFIER_URI + "/account");
public static final Uri DEFAULT_ACCOUNT_ID_URI =
Uri.parse(EmailContent.CONTENT_URI + "/account/default");
// Define all pseudo account IDs here to avoid conflict with one another.
/**
@ -109,6 +100,19 @@ public final class Account extends EmailContent implements AccountColumns, Parce
public static final int CHECK_INTERVAL_NEVER = -1;
public static final int CHECK_INTERVAL_PUSH = -2;
public static Uri CONTENT_URI;
public static Uri ADD_TO_FIELD_URI;
public static Uri RESET_NEW_MESSAGE_COUNT_URI;
public static Uri NOTIFIER_URI;
public static Uri DEFAULT_ACCOUNT_ID_URI;
public static void initAccount() {
CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/account");
ADD_TO_FIELD_URI = Uri.parse(EmailContent.CONTENT_URI + "/accountIdAddToField");
RESET_NEW_MESSAGE_COUNT_URI = Uri.parse(EmailContent.CONTENT_URI + "/resetNewMessageCount");
NOTIFIER_URI = Uri.parse(EmailContent.CONTENT_NOTIFIER_URI + "/account");
DEFAULT_ACCOUNT_ID_URI = Uri.parse(EmailContent.CONTENT_URI + "/account/default");
}
public String mDisplayName;
public String mEmailAddress;
public String mSyncKey;
@ -495,7 +499,7 @@ public final class Account extends EmailContent implements AccountColumns, Parce
public static long getAccountIdFromShortcutSafeUri(Context context, Uri uri) {
// Make sure the URI is in the correct format.
if (!"content".equals(uri.getScheme())
|| !AUTHORITY.equals(uri.getAuthority())) {
|| !EmailContent.AUTHORITY.equals(uri.getAuthority())) {
return -1;
}
@ -699,7 +703,7 @@ public final class Account extends EmailContent implements AccountColumns, Parce
.newUpdate(ContentUris.withAppendedId(CONTENT_URI, mId))
.withValues(cv).build());
try {
context.getContentResolver().applyBatch(AUTHORITY, ops);
context.getContentResolver().applyBatch(EmailContent.AUTHORITY, ops);
return 1;
} catch (RemoteException e) {
// There is nothing to be done here; fail by returning 0
@ -779,7 +783,7 @@ public final class Account extends EmailContent implements AccountColumns, Parce
try {
ContentProviderResult[] results =
context.getContentResolver().applyBatch(AUTHORITY, ops);
context.getContentResolver().applyBatch(EmailContent.AUTHORITY, ops);
// If saving, set the mId's of the various saved objects
if (recvIndex >= 0) {
long newId = getId(results[recvIndex].uri);

View File

@ -29,6 +29,7 @@ import android.os.Environment;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.RemoteException;
import android.util.Log;
import com.android.emailcommon.utility.TextUtilities;
import com.android.emailcommon.utility.Utility;
@ -59,39 +60,12 @@ import java.util.ArrayList;
*
*/
public abstract class EmailContent {
public static final String AUTHORITY = "com.android.email.provider";
// The notifier authority is used to send notifications regarding changes to messages (insert,
// delete, or update) and is intended as an optimization for use by clients of message list
// cursors (initially, the email AppWidget).
public static final String NOTIFIER_AUTHORITY = "com.android.email.notifier";
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY);
public static final String PARAMETER_LIMIT = "limit";
public static final Uri CONTENT_NOTIFIER_URI = Uri.parse("content://" + NOTIFIER_AUTHORITY);
public static final Uri PICK_TRASH_FOLDER_URI =
Uri.parse("content://" + EmailContent.AUTHORITY + "/pickTrashFolder");
public static final Uri PICK_SENT_FOLDER_URI =
Uri.parse("content://" + EmailContent.AUTHORITY + "/pickSentFolder");
public static final Uri MAILBOX_NOTIFICATION_URI =
Uri.parse("content://" + EmailContent.AUTHORITY + "/mailboxNotification");
public static final String[] NOTIFICATION_PROJECTION =
new String[] {MailboxColumns.ID, MailboxColumns.UNREAD_COUNT, MailboxColumns.MESSAGE_COUNT};
public static final int NOTIFICATION_MAILBOX_ID_COLUMN = 0;
public static final int NOTIFICATION_MAILBOX_UNREAD_COUNT_COLUMN = 1;
public static final int NOTIFICATION_MAILBOX_MESSAGE_COUNT_COLUMN = 2;
public static final Uri MAILBOX_MOST_RECENT_MESSAGE_URI =
Uri.parse("content://" + EmailContent.AUTHORITY + "/mailboxMostRecentMessage");
public static final Uri ACCOUNT_CHECK_URI =
Uri.parse("content://" + EmailContent.AUTHORITY + "/accountCheck");
public static final String PROVIDER_PERMISSION = "com.android.email.permission.ACCESS_PROVIDER";
// All classes share this
public static final String RECORD_ID = "_id";
@ -139,6 +113,57 @@ public abstract class EmailContent {
// Read the Content from a ContentCursor
public abstract void restore (Cursor cursor);
public static String PACKAGE_NAME;
public static String EMAIL_PACKAGE_NAME;
public static String AUTHORITY;
// The notifier authority is used to send notifications regarding changes to messages (insert,
// delete, or update) and is intended as an optimization for use by clients of message list
// cursors (initially, the email AppWidget).
public static String NOTIFIER_AUTHORITY;
public static Uri CONTENT_URI;
public static final String PARAMETER_LIMIT = "limit";
public static Uri CONTENT_NOTIFIER_URI;
public static Uri PICK_TRASH_FOLDER_URI;
public static Uri PICK_SENT_FOLDER_URI;
public static Uri MAILBOX_NOTIFICATION_URI;
public static Uri MAILBOX_MOST_RECENT_MESSAGE_URI;
public static Uri ACCOUNT_CHECK_URI;
public static String PROVIDER_PERMISSION;
public static void init(Context context) {
if (AUTHORITY == null) {
PACKAGE_NAME = context.getPackageName();
EMAIL_PACKAGE_NAME = PACKAGE_NAME;
// If our package is com...exchange, the provider is com...email.provider
if (PACKAGE_NAME.endsWith("exchange")) {
int lastDot = EMAIL_PACKAGE_NAME.lastIndexOf('.');
EMAIL_PACKAGE_NAME = PACKAGE_NAME.substring(0, lastDot + 1) + "email";
}
AUTHORITY = EMAIL_PACKAGE_NAME + ".provider";
Log.d("EmailContent", "init for " + AUTHORITY);
NOTIFIER_AUTHORITY = EMAIL_PACKAGE_NAME + ".notifier";
CONTENT_URI = Uri.parse("content://" + AUTHORITY);
CONTENT_NOTIFIER_URI = Uri.parse("content://" + NOTIFIER_AUTHORITY);
PICK_TRASH_FOLDER_URI = Uri.parse("content://" + AUTHORITY + "/pickTrashFolder");
PICK_SENT_FOLDER_URI = Uri.parse("content://" + AUTHORITY + "/pickSendFolder");
MAILBOX_NOTIFICATION_URI = Uri.parse("content://" + AUTHORITY + "/mailboxNotification");
MAILBOX_MOST_RECENT_MESSAGE_URI = Uri.parse("content://" + AUTHORITY +
"/mailboxMostRecentMessage");
ACCOUNT_CHECK_URI = Uri.parse("content://" + AUTHORITY + "/accountCheck");
PROVIDER_PERMISSION = EMAIL_PACKAGE_NAME + ".permission.ACCESS_PROVIDER";
// Initialize subclasses
Account.initAccount();
Mailbox.initMailbox();
QuickResponse.initQuickResponse();
HostAuth.initHostAuth();
Policy.initPolicy();
Message.initMessage();
Body.initBody();
Attachment.initAttachment();
}
}
// The Uri is lazily initialized
public Uri getUri() {
if (mUri == null) {
@ -287,8 +312,11 @@ public abstract class EmailContent {
public static final class Body extends EmailContent implements BodyColumns {
public static final String TABLE_NAME = "Body";
@SuppressWarnings("hiding")
public static final Uri CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/body");
public static Uri CONTENT_URI;
public static void initBody() {
CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/body");
}
public static final int CONTENT_ID_COLUMN = 0;
public static final int CONTENT_MESSAGE_KEY_COLUMN = 1;
@ -330,7 +358,7 @@ public abstract class EmailContent {
public static final String[] COMMON_PROJECTION_SOURCE = new String[] {
RECORD_ID, BodyColumns.SOURCE_MESSAGE_KEY
};
public static final int COMMON_PROJECTION_COLUMN_TEXT = 1;
public static final int COMMON_PROJECTION_COLUMN_TEXT = 1;
private static final String[] PROJECTION_SOURCE_KEY =
new String[] { BodyColumns.SOURCE_MESSAGE_KEY };
@ -558,18 +586,28 @@ public abstract class EmailContent {
public static final String DELETED_TABLE_NAME = "Message_Deletes";
// To refer to a specific message, use ContentUris.withAppendedId(CONTENT_URI, id)
public static final Uri CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/message");
public static final Uri CONTENT_URI_LIMIT_1 = uriWithLimit(CONTENT_URI, 1);
public static final Uri SYNCED_CONTENT_URI =
Uri.parse(EmailContent.CONTENT_URI + "/syncedMessage");
public static final Uri SELECTED_MESSAGE_CONTENT_URI =
Uri.parse(EmailContent.CONTENT_URI + "/messageBySelection");
public static final Uri DELETED_CONTENT_URI =
Uri.parse(EmailContent.CONTENT_URI + "/deletedMessage");
public static final Uri UPDATED_CONTENT_URI =
Uri.parse(EmailContent.CONTENT_URI + "/updatedMessage");
public static final Uri NOTIFIER_URI =
Uri.parse(EmailContent.CONTENT_NOTIFIER_URI + "/message");
public static Uri CONTENT_URI;
public static Uri CONTENT_URI_LIMIT_1;
public static Uri SYNCED_CONTENT_URI;
public static Uri SELECTED_MESSAGE_CONTENT_URI ;
public static Uri DELETED_CONTENT_URI;
public static Uri UPDATED_CONTENT_URI;
public static Uri NOTIFIER_URI;
public static void initMessage() {
CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/message");
CONTENT_URI_LIMIT_1 = uriWithLimit(CONTENT_URI, 1);
SYNCED_CONTENT_URI =
Uri.parse(EmailContent.CONTENT_URI + "/syncedMessage");
SELECTED_MESSAGE_CONTENT_URI =
Uri.parse(EmailContent.CONTENT_URI + "/messageBySelection");
DELETED_CONTENT_URI =
Uri.parse(EmailContent.CONTENT_URI + "/deletedMessage");
UPDATED_CONTENT_URI =
Uri.parse(EmailContent.CONTENT_URI + "/updatedMessage");
NOTIFIER_URI =
Uri.parse(EmailContent.CONTENT_NOTIFIER_URI + "/message");
}
public static final String KEY_TIMESTAMP_DESC = MessageColumns.TIMESTAMP + " desc";
@ -1143,11 +1181,16 @@ public abstract class EmailContent {
public static final class Attachment extends EmailContent
implements AttachmentColumns, Parcelable {
public static final String TABLE_NAME = "Attachment";
@SuppressWarnings("hiding")
public static final Uri CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/attachment");
public static Uri CONTENT_URI;
// This must be used with an appended id: ContentUris.withAppendedId(MESSAGE_ID_URI, id)
public static final Uri MESSAGE_ID_URI = Uri.parse(
EmailContent.CONTENT_URI + "/attachment/message");
public static Uri MESSAGE_ID_URI;
public static void initAttachment() {
CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/attachment");
MESSAGE_ID_URI = Uri.parse(
EmailContent.CONTENT_URI + "/attachment/message");
}
public String mFileName;
public String mMimeType;

View File

@ -34,7 +34,11 @@ import java.net.URISyntaxException;
public final class HostAuth extends EmailContent implements HostAuthColumns, Parcelable {
public static final String TABLE_NAME = "HostAuth";
public static final Uri CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/hostauth");
public static Uri CONTENT_URI;
public static void initHostAuth() {
CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/hostauth");
}
// These legacy constants should be used in code created prior to Email2
public static final String LEGACY_SCHEME_IMAP = "imap";
@ -42,11 +46,6 @@ public final class HostAuth extends EmailContent implements HostAuthColumns, Par
public static final String LEGACY_SCHEME_EAS = "eas";
public static final String LEGACY_SCHEME_SMTP = "smtp";
// These constants should, over time, be replaced by information in services.xml
public static final String SCHEME_IMAP = "imap";
public static final String SCHEME_POP3 = "pop3";
public static final String SCHEME_EAS = "eas";
public static final String SCHEME_TRUST_ALL_CERTS = "trustallcerts";
public static final int PORT_UNKNOWN = -1;
@ -284,10 +283,6 @@ public final class HostAuth extends EmailContent implements HostAuthColumns, Par
mClientCertAlias = clientCertAlias;
}
/** Returns {@code true} if this is an EAS connection; otherwise, {@code false}. */
public boolean isEasConnection() {
return SCHEME_EAS.equals(mProtocol);
}
/** Convenience method to determine if SSL is used. */
public boolean shouldUseSsl() {

View File

@ -33,11 +33,17 @@ import com.android.emailcommon.utility.Utility;
public class Mailbox extends EmailContent implements SyncColumns, MailboxColumns, Parcelable {
public static final String TABLE_NAME = "Mailbox";
public static final Uri CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/mailbox");
public static final Uri ADD_TO_FIELD_URI =
Uri.parse(EmailContent.CONTENT_URI + "/mailboxIdAddToField");
public static final Uri FROM_ACCOUNT_AND_TYPE_URI =
Uri.parse(EmailContent.CONTENT_URI + "/mailboxIdFromAccountAndType");
public static Uri CONTENT_URI;
public static Uri ADD_TO_FIELD_URI;
public static Uri FROM_ACCOUNT_AND_TYPE_URI;
public static void initMailbox() {
CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/mailbox");
ADD_TO_FIELD_URI = Uri.parse(EmailContent.CONTENT_URI + "/mailboxIdAddToField");
FROM_ACCOUNT_AND_TYPE_URI = Uri.parse(EmailContent.CONTENT_URI +
"/mailboxIdFromAccountAndType");
}
public String mDisplayName;
public String mServerId;

View File

@ -40,8 +40,11 @@ public final class Policy extends EmailContent implements EmailContent.PolicyCol
public static final String TAG = "Email/Policy";
public static final String TABLE_NAME = "Policy";
@SuppressWarnings("hiding")
public static final Uri CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/policy");
public static Uri CONTENT_URI;
public static void initPolicy() {
CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/policy");
}
/* Convert days to mSec (used for password expiration) */
private static final long DAYS_TO_MSEC = 24 * 60 * 60 * 1000;

View File

@ -17,10 +17,6 @@
package com.android.emailcommon.provider;
import com.android.emailcommon.provider.EmailContent;
import com.android.emailcommon.provider.EmailContent.QuickResponseColumns;
import com.google.common.base.Objects;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
@ -29,6 +25,9 @@ import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
import com.android.emailcommon.provider.EmailContent.QuickResponseColumns;
import com.google.common.base.Objects;
/**
* A user-modifiable message that may be quickly inserted into the body while user is composing
* a message. Tied to a specific account.
@ -36,11 +35,13 @@ import android.os.Parcelable;
public final class QuickResponse extends EmailContent
implements QuickResponseColumns, Parcelable {
public static final String TABLE_NAME = "QuickResponse";
@SuppressWarnings("hiding")
public static final Uri CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI
+ "/quickresponse");
public static final Uri ACCOUNT_ID_URI = Uri.parse(
EmailContent.CONTENT_URI + "/quickresponse/account");
public static Uri CONTENT_URI;
public static Uri ACCOUNT_ID_URI;
public static void initQuickResponse() {
CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/quickresponse");
ACCOUNT_ID_URI = Uri.parse(EmailContent.CONTENT_URI + "/quickresponse/account");
}
private String mText;
private long mAccountKey;

View File

@ -24,14 +24,13 @@ import android.os.RemoteException;
public class AccountServiceProxy extends ServiceProxy implements IAccountService {
public static final String ACCOUNT_INTENT = "com.android.email.ACCOUNT_INTENT";
public static final int DEFAULT_ACCOUNT_COLOR = 0xFF0000FF;
private IAccountService mService = null;
private Object mReturn;
public AccountServiceProxy(Context _context) {
super(_context, new Intent(ACCOUNT_INTENT));
super(_context, getIntentForEmailPackage(_context, "ACCOUNT_INTENT"));
}
@Override

View File

@ -475,6 +475,23 @@ public class EmailServiceProxy extends ServiceProxy implements IEmailService {
return (Integer)mReturn;
}
}
/**
* Request that the account be updated for this service; this call is synchronous
*
* @param the email address of the account to be updated
*/
@Override
public void serviceUpdated(final String emailAddress) throws RemoteException {
setTask(new ProxyTask() {
@Override
public void run() throws RemoteException{
if (mCallback != null) mService.setCallback(mCallback);
mService.serviceUpdated(emailAddress);
}
}, "settingsUpdate");
waitForCompletion();
}
@Override
public IBinder asBinder() {

View File

@ -63,4 +63,6 @@ interface IEmailService {
// API level 3
int getCapabilities(in Account acct);
void serviceUpdated(String emailAddress);
}

View File

@ -17,7 +17,6 @@
package com.android.emailcommon.service;
import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
@ -29,14 +28,11 @@ public class PolicyServiceProxy extends ServiceProxy implements IPolicyService {
private static final boolean DEBUG_PROXY = false; // DO NOT CHECK THIS IN SET TO TRUE
private static final String TAG = "PolicyServiceProxy";
// The intent used by sync adapter services to connect to the PolicyService
public static final String POLICY_INTENT = "com.android.email.POLICY_INTENT";
private IPolicyService mService = null;
private Object mReturn = null;
public PolicyServiceProxy(Context _context) {
super(_context, new Intent(POLICY_INTENT));
super(_context, getIntentForEmailPackage(_context, "POLICY_INTENT"));
}
@Override

View File

@ -56,6 +56,16 @@ public abstract class ServiceProxy {
private long mStartTime;
private boolean mDead = false;
public static Intent getIntentForEmailPackage(Context context, String actionName) {
String packageName = context.getPackageName();
int lastDot = packageName.lastIndexOf('.');
String intentString =
packageName.substring(0, lastDot + 1) + "email." + actionName;
// STOPSHIP Remove logging
Log.d("ServiceProxy", actionName + " -> " + intentString);
return new Intent(intentString);
}
public abstract void onConnected(IBinder binder);
public ServiceProxy(Context _context, Intent _intent) {

View File

@ -30,6 +30,7 @@ import android.util.Log;
import android.webkit.MimeTypeMap;
import com.android.emailcommon.Logging;
import com.android.emailcommon.provider.EmailContent;
import com.android.emailcommon.provider.EmailContent.Attachment;
import com.android.emailcommon.provider.EmailContent.AttachmentColumns;
import com.android.emailcommon.provider.EmailContent.Body;
@ -47,8 +48,6 @@ import java.io.InputStream;
import java.io.OutputStream;
public class AttachmentUtilities {
public static final String AUTHORITY = "com.android.email.attachmentprovider";
public static final Uri CONTENT_URI = Uri.parse( "content://" + AUTHORITY);
public static final String FORMAT_RAW = "RAW";
public static final String FORMAT_THUMBNAIL = "THUMBNAIL";
@ -137,23 +136,17 @@ public class AttachmentUtilities {
*/
public static final int MAX_ATTACHMENT_UPLOAD_SIZE = (5 * 1024 * 1024);
private static Uri sUri;
public static Uri getAttachmentUri(long accountId, long id) {
return CONTENT_URI.buildUpon()
.appendPath(Long.toString(accountId))
.appendPath(Long.toString(id))
.appendPath(FORMAT_RAW)
.build();
}
public static Uri getAttachmentThumbnailUri(long accountId, long id,
int width, int height) {
return CONTENT_URI.buildUpon()
.appendPath(Long.toString(accountId))
.appendPath(Long.toString(id))
.appendPath(FORMAT_THUMBNAIL)
.appendPath(Integer.toString(width))
.appendPath(Integer.toString(height))
.build();
if (sUri == null) {
sUri = Uri.parse("content://" + EmailContent.EMAIL_PACKAGE_NAME +
".attachmentprovider");
}
return sUri.buildUpon()
.appendPath(Long.toString(accountId))
.appendPath(Long.toString(id))
.appendPath(FORMAT_RAW)
.build();
}
/**

View File

@ -17,16 +17,16 @@
package com.android.emailsync;
import com.android.emailcommon.provider.Account;
import com.android.emailcommon.provider.HostAuth;
import com.android.emailcommon.provider.Mailbox;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Bundle;
import android.util.Log;
import com.android.emailcommon.provider.Account;
import com.android.emailcommon.provider.HostAuth;
import com.android.emailcommon.provider.Mailbox;
import java.util.concurrent.LinkedBlockingQueue;
/**
@ -48,7 +48,6 @@ public abstract class AbstractSyncService implements Runnable {
public static final int CONNECT_TIMEOUT = 30*SECONDS;
public static final int NETWORK_WAIT = 15*SECONDS;
public static final String EAS_PROTOCOL = "eas";
public static final int EXIT_DONE = 0;
public static final int EXIT_IO_ERROR = 1;
public static final int EXIT_LOGIN_FAILURE = 2;
@ -289,7 +288,9 @@ public abstract class AbstractSyncService implements Runnable {
*/
public void addRequest(Request req) {
mRequestQueue.offer(req);
if (!mRequestQueue.contains(req)) {
mRequestQueue.offer(req);
}
}
public void removeRequest(Request req) {

View File

@ -1357,6 +1357,7 @@ public abstract class SyncManager extends Service implements Runnable {
@Override
public void onCreate() {
TAG = getClass().getSimpleName();
EmailContent.init(this);
Utility.runAsync(new Runnable() {
@Override
public void run() {

View File

@ -64,5 +64,6 @@
<enum name="mins60" value="60"/>
</attr>
<attr name="inferPrefix" format="string"/>
<attr name="requiresAccountUpdate" format="boolean"/>
</declare-styleable>
</resources>

View File

@ -1310,8 +1310,9 @@ as <xliff:g id="filename">%s</xliff:g>.</string>
<!-- Displayed in the middle of the screen when the inbox is empty [CHAR LIMIT 100]-->
<string name="no_conversations">No messages.</string>
<!-- Temporary; used by AccountManager -->
<string name="imap2_name">Push IMAP</string>
<!-- Used by AccountManager -->
<string name="imap2_name">IMAP</string>
<string name="pop3_name">POP3</string>
<string name="folder_picker_title">Picky, picky, picky!</string>
<!-- Displayed when the user must pick his server's trash folder from a list [CHAR LIMIT 30]-->
@ -1319,6 +1320,6 @@ as <xliff:g id="filename">%s</xliff:g>.</string>
<!-- Displayed when the user must pick his server's sent items folder from a list [CHAR LIMIT 30]-->
<string name="sent_folder_selection_title">Select server sent items folder</string>
<string name="create_new_folder">Create folder</string>
</resources>

View File

@ -23,9 +23,9 @@
<!-- The only difference from authenticator.xml is android:label. -->
<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
android:accountType="com.android.exchange"
android:icon="@drawable/ic_exchange_selected"
android:smallIcon="@drawable/ic_exchange_minitab_selected"
android:accountType="@string/account_manager_type_exchange"
android:icon="@mipmap/ic_launcher_mail"
android:smallIcon="@drawable/stat_notify_email_generic"
android:label="@string/exchange_name_alternate"
android:accountPreferences="@xml/account_preferences"
/>

View File

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
/**
* Copyright (c) 2009, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-->
<!-- The attributes in this XML file provide configuration information -->
<!-- for the Account Manager. -->
<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
android:accountType="@string/account_manager_type_exchange"
android:icon="@mipmap/ic_launcher_mail"
android:smallIcon="@drawable/stat_notify_email_generic"
android:label="@string/exchange_name"
android:accountPreferences="@xml/account_preferences"
/>

View File

@ -21,7 +21,7 @@
<!-- for the Account Manager. -->
<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
android:accountType="com.android.imap2"
android:accountType="@string/account_manager_type_imap"
android:icon="@mipmap/ic_launcher_mail"
android:smallIcon="@drawable/stat_notify_email_generic"
android:label="@string/imap2_name"

View File

@ -22,8 +22,8 @@
<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
android:accountType="com.android.exchange"
android:icon="@drawable/ic_exchange_selected"
android:smallIcon="@drawable/ic_exchange_minitab_selected"
android:icon="@mipmap/ic_launcher_mail"
android:smallIcon="@drawable/stat_notify_email_generic"
android:label="@string/exchange_name"
android:accountPreferences="@xml/account_preferences"
/>

View File

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
/**
* Copyright (c) 2009, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-->
<!-- The attributes in this XML file provide configuration information -->
<!-- for the Account Manager. -->
<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
android:accountType="com.android.email"
android:icon="@mipmap/ic_launcher_mail"
android:smallIcon="@drawable/stat_notify_email_generic"
android:label="@string/exchange_name"
android:accountPreferences="@xml/account_preferences"
/>

View File

@ -21,9 +21,9 @@
<!-- for the Account Manager. -->
<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
android:accountType="com.android.email"
android:accountType="@string/account_manager_type_pop3"
android:icon="@mipmap/ic_launcher_mail"
android:smallIcon="@drawable/stat_notify_email_generic"
android:label="@string/app_name"
android:label="@string/pop3_name"
android:accountPreferences="@xml/account_preferences"
/>

View File

@ -31,5 +31,4 @@
<senders>
<sender scheme="smtp" class="com.android.email.mail.transport.SmtpSender" />
<sender scheme="eas" class="com.android.email.mail.transport.ExchangeSender" />
</senders>

View File

@ -21,7 +21,7 @@
<!-- for the SyncAdapter. -->
<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
android:contentAuthority="com.android.email.provider"
android:accountType="com.android.imap2"
android:contentAuthority="@string/authority_email_provider"
android:accountType="@string/account_manager_type_imap"
android:supportsUploading="true"
/>

View File

@ -21,8 +21,8 @@
<!-- for the SyncAdapter. -->
<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
android:contentAuthority="com.android.email.provider"
android:accountType="com.android.email"
android:contentAuthority="@string/authority_email_provider"
android:accountType="@string/account_manager_type_pop3"
android:supportsUploading="true"
android:allowParallelSyncs="true"
/>

View File

@ -124,6 +124,7 @@ public class NotificationController {
@VisibleForTesting
NotificationController(Context context, Clock clock) {
mContext = context.getApplicationContext();
EmailContent.init(context);
mNotificationManager = (NotificationManager) context.getSystemService(
Context.NOTIFICATION_SERVICE);
mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);

View File

@ -33,6 +33,8 @@ import android.util.Log;
import com.android.email.R;
import com.android.email.mail.Sender;
import com.android.email.mail.Store;
import com.android.email.service.EmailServiceUtils;
import com.android.email.service.EmailServiceUtils.EmailServiceInfo;
import com.android.emailcommon.Logging;
import com.android.emailcommon.mail.MessagingException;
import com.android.emailcommon.provider.Account;
@ -473,6 +475,11 @@ public class AccountCheckSettingsFragment extends Fragment {
if (bundle != null) {
resultCode = bundle.getInt(
EmailServiceProxy.VALIDATE_BUNDLE_RESULT_CODE);
// Only show "policies required" if this is a new account setup
if (resultCode == MessagingException.SECURITY_POLICIES_REQUIRED &&
mAccount.isSaved()) {
resultCode = MessagingException.NO_ERROR;
}
}
if (resultCode == MessagingException.SECURITY_POLICIES_REQUIRED) {
SetupData.setPolicy((Policy)bundle.getParcelable(
@ -491,8 +498,11 @@ public class AccountCheckSettingsFragment extends Fragment {
}
}
String protocol = mAccount.mHostAuthRecv.mProtocol;
EmailServiceInfo info = EmailServiceUtils.getServiceInfo(mContext, protocol);
// Check Outgoing Settings
if ((mMode & SetupData.CHECK_OUTGOING) != 0) {
if (info.usesSmtp && (mMode & SetupData.CHECK_OUTGOING) != 0) {
if (isCancelled()) return null;
Log.d(Logging.LOG_TAG, "Begin check of outgoing email settings");
publishProgress(STATE_CHECK_OUTGOING);

View File

@ -41,7 +41,6 @@ import com.android.email.SecurityPolicy;
import com.android.email.service.EmailServiceUtils;
import com.android.email.service.EmailServiceUtils.EmailServiceInfo;
import com.android.email2.ui.MailActivityEmail;
import com.android.emailcommon.AccountManagerTypes;
import com.android.emailcommon.Logging;
import com.android.emailcommon.provider.Account;
import com.android.emailcommon.provider.EmailContent;
@ -727,7 +726,7 @@ public class AccountSettingsFragment extends EmailPreferenceFragment
EmailServiceUtils.getServiceInfo(mContext, mAccount.getProtocol(mContext));
if (info.syncContacts || info.syncCalendar) {
android.accounts.Account acct = new android.accounts.Account(mAccount.mEmailAddress,
AccountManagerTypes.TYPE_EXCHANGE);
info.accountType);
ContentResolver.setSyncAutomatically(acct, ContactsContract.AUTHORITY,
mSyncContacts.isChecked());
ContentResolver.setSyncAutomatically(acct, CalendarContract.AUTHORITY,

View File

@ -457,12 +457,6 @@ public class AccountSetupBasics extends AccountSetupActivity
HostAuth recvAuth = account.getOrCreateHostAuthRecv(this);
HostAuth.setHostAuthFromString(recvAuth, mProvider.incomingUri);
// STOPSHIP; Use for Imap2 testing
if (HostAuth.LEGACY_SCHEME_IMAP.equals(recvAuth.mProtocol) &&
EmailServiceUtils.isServiceAvailable(this, "imap2")) {
recvAuth.mProtocol = "imap2";
}
recvAuth.setLogin(mProvider.incomingUsername, password);
EmailServiceInfo info = EmailServiceUtils.getServiceInfo(this, recvAuth.mProtocol);
recvAuth.mPort =

View File

@ -42,10 +42,6 @@ public class SetupData implements Parcelable {
public static final int FLOW_MODE_RETURN_NO_ACCOUNTS_RESULT = 7;
public static final int FLOW_MODE_NO_ACCOUNTS = 8;
// For debug logging
private static final String[] FLOW_MODES = {"normal", "eas", "pop/imap", "edit", "force",
"rtc", "rtl"};
// Mode bits for AccountSetupCheckSettings, indicating the type of check requested
public static final int CHECK_INCOMING = 1;
public static final int CHECK_OUTGOING = 2;
@ -271,7 +267,6 @@ public class SetupData implements Parcelable {
public static String debugString() {
StringBuilder sb = new StringBuilder("SetupData");
SetupData data = getInstance();
sb.append(":flow=" + FLOW_MODES[data.mFlowMode]);
sb.append(":acct=" + (data.mAccount == null ? "none" : data.mAccount.mId));
if (data.mUsername != null) {
sb.append(":user=" + data.mUsername);

View File

@ -22,8 +22,8 @@ import com.android.email.imap2.Imap2SyncService.Connection;
import com.android.emailcommon.provider.EmailContent.Attachment;
import com.android.emailcommon.provider.EmailContent.Message;
import com.android.emailcommon.service.EmailServiceStatus;
import com.android.emailsync.PartRequest;
import com.android.emailcommon.utility.AttachmentUtilities;
import com.android.emailsync.PartRequest;
import com.android.mail.providers.UIProvider;
import org.apache.james.mime4j.decoder.Base64InputStream;
@ -177,7 +177,7 @@ public class AttachmentLoader {
OutputStream os = null;
File tmpFile = null;
try {
tmpFile = File.createTempFile("imap2_", "tmp", mContext.getCacheDir());
tmpFile = File.createTempFile("imap_", "tmp", mContext.getCacheDir());
os = new FileOutputStream(tmpFile);
String tag = mService.writeCommand(conn.writer, "uid fetch " + mMessage.mServerId +
" body[" + mAttachment.mLocation + ']');

View File

@ -16,11 +16,6 @@
package com.android.email.imap2;
import com.android.emailcommon.provider.EmailContent;
import com.android.emailcommon.provider.EmailContent.AccountColumns;
import com.android.emailcommon.provider.EmailContent.MailboxColumns;
import com.android.emailcommon.provider.Mailbox;
import android.accounts.Account;
import android.accounts.OperationCanceledException;
import android.app.Service;
@ -35,8 +30,13 @@ import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;
import com.android.emailcommon.provider.EmailContent;
import com.android.emailcommon.provider.EmailContent.AccountColumns;
import com.android.emailcommon.provider.EmailContent.MailboxColumns;
import com.android.emailcommon.provider.Mailbox;
public class EmailSyncAdapterService extends Service {
private static final String TAG = "Imap2 EmailSyncAdapterService";
private static final String TAG = "Imap EmailSyncAdapterService";
private static SyncAdapterImpl sSyncAdapter = null;
private static final Object sSyncAdapterLock = new Object();

View File

@ -28,9 +28,11 @@ import android.os.IBinder;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import com.android.email.R;
import com.android.emailcommon.Api;
import com.android.emailcommon.provider.Account;
import com.android.emailcommon.provider.EmailContent;
import com.android.emailcommon.provider.EmailContent.AccountColumns;
import com.android.emailcommon.provider.EmailContent.Attachment;
import com.android.emailcommon.provider.EmailContent.MailboxColumns;
import com.android.emailcommon.provider.HostAuth;
@ -42,10 +44,10 @@ import com.android.emailcommon.service.IEmailService;
import com.android.emailcommon.service.IEmailServiceCallback;
import com.android.emailcommon.service.IEmailServiceCallback.Stub;
import com.android.emailcommon.service.SearchParams;
import com.android.emailcommon.service.SyncWindow;
import com.android.emailsync.AbstractSyncService;
import com.android.emailsync.PartRequest;
import com.android.emailsync.SyncManager;
import com.android.email.R;
import com.android.mail.providers.UIProvider;
import com.android.mail.providers.UIProvider.AccountCapabilities;
import com.android.mail.providers.UIProvider.LastSyncResult;
@ -64,8 +66,7 @@ public class Imap2SyncManager extends SyncManager {
private Intent mIntent;
private static final String IMAP2_ACCOUNT_TYPE = "com.android.imap2";
private static final String PROTOCOL = "imap2";
private static String PROTOCOL;
/**
* Create our EmailService implementation here.
@ -221,6 +222,30 @@ public class Imap2SyncManager extends SyncManager {
AccountCapabilities.FOLDER_SERVER_SEARCH |
AccountCapabilities.UNDO;
}
@Override
public void serviceUpdated(String emailAddress) throws RemoteException {
SyncManager ssm = INSTANCE;
if (ssm == null) return;
log("serviceUpdated called for " + emailAddress);
Cursor c = ssm.getContentResolver().query(Account.CONTENT_URI, Account.ID_PROJECTION,
AccountColumns.EMAIL_ADDRESS + "=?", new String[] { emailAddress }, null);
if (c == null) return;
try {
if (c.moveToNext()) {
long accountId = c.getLong(0);
ContentValues values = new ContentValues();
values.put(AccountColumns.SYNC_INTERVAL, Account.CHECK_INTERVAL_PUSH);
values.put(AccountColumns.SYNC_LOOKBACK, SyncWindow.SYNC_WINDOW_AUTO);
// Say we can push (at least, we'll try)
mResolver.update(ContentUris.withAppendedId(Account.CONTENT_URI, accountId),
values, null, null);
log("Sync interval and lookback set for " + emailAddress);
}
} finally {
c.close();
}
}
};
static public IEmailServiceCallback callback() {
@ -305,11 +330,14 @@ public class Imap2SyncManager extends SyncManager {
// We must throw here; callers might use the information we provide for reconciliation, etc.
if (c == null) throw new ProviderUnavailableException();
try {
if (PROTOCOL == null) {
PROTOCOL = getString(R.string.protocol_imap);
}
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("imap2")) {
if (ha != null && ha.mProtocol.equals(PROTOCOL)) {
Account account = new Account();
account.restore(c);
account.mHostAuthRecv = ha;
@ -325,7 +353,7 @@ public class Imap2SyncManager extends SyncManager {
@Override
public String getAccountManagerType() {
return IMAP2_ACCOUNT_TYPE;
return getString(R.string.account_manager_type_imap);
}
@Override
@ -344,7 +372,8 @@ public class Imap2SyncManager extends SyncManager {
@Override
protected void runAccountReconcilerSync(Context context) {
alwaysLog("Reconciling accounts...");
new AccountServiceProxy(context).reconcileAccounts(PROTOCOL, getAccountManagerType());
new AccountServiceProxy(context).reconcileAccounts(
getString(R.string.protocol_imap), getAccountManagerType());
}
@Override

View File

@ -149,7 +149,7 @@ public class Imap2SyncService extends AbstractSyncService {
private final String[] MAILBOX_SERVER_ID_ARGS = new String[2];
public Imap2SyncService() {
this("Imap2 Validation");
this("Imap Validation");
}
private final ArrayList<Integer> SERVER_DELETES = new ArrayList<Integer>();
@ -186,7 +186,7 @@ public class Imap2SyncService extends AbstractSyncService {
}
public Imap2SyncService(Context _context, Account _account) {
this("Imap2 Account");
this("Imap Account");
mContext = _context;
mResolver = _context.getContentResolver();
mAccount = _account;
@ -1512,8 +1512,9 @@ public class Imap2SyncService extends AbstractSyncService {
if (andClause != null) {
ac = ac + andClause;
}
// Add "+0" to the sort order to coerce the text field to an integer
Cursor c = mResolver.query(Message.CONTENT_URI, UID_PROJECTION,
ac, new String[] {Long.toString(mMailboxId)}, SyncColumns.SERVER_ID);
ac, new String[] {Long.toString(mMailboxId)}, SyncColumns.SERVER_ID + "+0");
if (c != null) {
try {
int[] uids = new int[c.getCount()];
@ -1521,6 +1522,7 @@ public class Imap2SyncService extends AbstractSyncService {
do {
uids[offs++] = c.getInt(0);
} while (c.moveToNext());
System.err.println(uids);
return uids;
}
} finally {

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2008 The Android Open Source Project
* Copyright (C) 2008 The Android Open Source P-roject
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -20,7 +20,7 @@ import android.content.Context;
import android.os.Bundle;
import android.util.Log;
import com.android.email.mail.store.ImapStore;
import com.android.email.R;
import com.android.email.mail.store.Pop3Store;
import com.android.email.mail.store.ServiceStore;
import com.android.email2.ui.MailActivityEmail;
@ -58,11 +58,6 @@ public abstract class Store {
static final HashMap<String, Class<? extends Store>> sStoreClasses =
new HashMap<String, Class<? extends Store>>();
static {
sStoreClasses.put(HostAuth.LEGACY_SCHEME_IMAP, ImapStore.class);
sStoreClasses.put(HostAuth.LEGACY_SCHEME_POP3, Pop3Store.class);
}
/**
* Static named constructor. It should be overrode by extending class.
* Because this method will be called through reflection, it can not be protected.
@ -87,6 +82,9 @@ public abstract class Store {
*/
public synchronized static Store getInstance(Account account, Context context)
throws MessagingException {
if (sStores.isEmpty()) {
sStoreClasses.put(context.getString(R.string.protocol_pop3), Pop3Store.class);
}
HostAuth hostAuth = account.getOrCreateHostAuthRecv(context);
// An existing account might have been deleted
if (hostAuth == null) return null;

View File

@ -1,520 +0,0 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.email.mail.store;
import android.text.TextUtils;
import android.util.Log;
import com.android.email.mail.store.ImapStore.ImapException;
import com.android.email.mail.store.imap.ImapConstants;
import com.android.email.mail.store.imap.ImapList;
import com.android.email.mail.store.imap.ImapResponse;
import com.android.email.mail.store.imap.ImapResponseParser;
import com.android.email.mail.store.imap.ImapUtility;
import com.android.email.mail.transport.DiscourseLogger;
import com.android.email.mail.transport.MailTransport;
import com.android.email2.ui.MailActivityEmail;
import com.android.emailcommon.Logging;
import com.android.emailcommon.mail.AuthenticationFailedException;
import com.android.emailcommon.mail.CertificateValidationException;
import com.android.emailcommon.mail.MessagingException;
import com.android.emailcommon.mail.Transport;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import javax.net.ssl.SSLException;
/**
* A cacheable class that stores the details for a single IMAP connection.
*/
class ImapConnection {
// Always check in FALSE
private static final boolean DEBUG_FORCE_SEND_ID = false;
/** ID capability per RFC 2971*/
public static final int CAPABILITY_ID = 1 << 0;
/** NAMESPACE capability per RFC 2342 */
public static final int CAPABILITY_NAMESPACE = 1 << 1;
/** STARTTLS capability per RFC 3501 */
public static final int CAPABILITY_STARTTLS = 1 << 2;
/** UIDPLUS capability per RFC 4315 */
public static final int CAPABILITY_UIDPLUS = 1 << 3;
/** The capabilities supported; a set of CAPABILITY_* values. */
private int mCapabilities;
private static final String IMAP_REDACTED_LOG = "[IMAP command redacted]";
Transport mTransport;
private ImapResponseParser mParser;
private ImapStore mImapStore;
private String mUsername;
private String mLoginPhrase;
private String mIdPhrase = null;
/** # of command/response lines to log upon crash. */
private static final int DISCOURSE_LOGGER_SIZE = 64;
private final DiscourseLogger mDiscourse = new DiscourseLogger(DISCOURSE_LOGGER_SIZE);
/**
* Next tag to use. All connections associated to the same ImapStore instance share the same
* counter to make tests simpler.
* (Some of the tests involve multiple connections but only have a single counter to track the
* tag.)
*/
private final AtomicInteger mNextCommandTag = new AtomicInteger(0);
// Keep others from instantiating directly
ImapConnection(ImapStore store, String username, String password) {
setStore(store, username, password);
}
void setStore(ImapStore store, String username, String password) {
if (username != null && password != null) {
mUsername = username;
// build the LOGIN string once (instead of over-and-over again.)
// apply the quoting here around the built-up password
mLoginPhrase = ImapConstants.LOGIN + " " + mUsername + " "
+ ImapUtility.imapQuoted(password);
}
mImapStore = store;
}
void open() throws IOException, MessagingException {
if (mTransport != null && mTransport.isOpen()) {
return;
}
try {
// copy configuration into a clean transport, if necessary
if (mTransport == null) {
mTransport = mImapStore.cloneTransport();
}
mTransport.open();
mTransport.setSoTimeout(MailTransport.SOCKET_READ_TIMEOUT);
createParser();
// BANNER
mParser.readResponse();
// CAPABILITY
ImapResponse capabilities = queryCapabilities();
boolean hasStartTlsCapability =
capabilities.contains(ImapConstants.STARTTLS);
// TLS
ImapResponse newCapabilities = doStartTls(hasStartTlsCapability);
if (newCapabilities != null) {
capabilities = newCapabilities;
}
// NOTE: An IMAP response MUST be processed before issuing any new IMAP
// requests. Subsequent requests may destroy previous response data. As
// such, we save away capability information here for future use.
setCapabilities(capabilities);
String capabilityString = capabilities.flatten();
// ID
doSendId(isCapable(CAPABILITY_ID), capabilityString);
// LOGIN
doLogin();
// NAMESPACE (only valid in the Authenticated state)
doGetNamespace(isCapable(CAPABILITY_NAMESPACE));
// Gets the path separator from the server
doGetPathSeparator();
mImapStore.ensurePrefixIsValid();
} catch (SSLException e) {
if (MailActivityEmail.DEBUG) {
Log.d(Logging.LOG_TAG, e.toString());
}
throw new CertificateValidationException(e.getMessage(), e);
} catch (IOException ioe) {
// NOTE: Unlike similar code in POP3, I'm going to rethrow as-is. There is a lot
// of other code here that catches IOException and I don't want to break it.
// This catch is only here to enhance logging of connection-time issues.
if (MailActivityEmail.DEBUG) {
Log.d(Logging.LOG_TAG, ioe.toString());
}
throw ioe;
} finally {
destroyResponses();
}
}
/**
* Closes the connection and releases all resources. This connection can not be used again
* until {@link #setStore(ImapStore, String, String)} is called.
*/
void close() {
if (mTransport != null) {
mTransport.close();
mTransport = null;
}
destroyResponses();
mParser = null;
mImapStore = null;
}
/**
* Returns whether or not the specified capability is supported by the server.
*/
private boolean isCapable(int capability) {
return (mCapabilities & capability) != 0;
}
/**
* Sets the capability flags according to the response provided by the server.
* Note: We only set the capability flags that we are interested in. There are many IMAP
* capabilities that we do not track.
*/
private void setCapabilities(ImapResponse capabilities) {
if (capabilities.contains(ImapConstants.ID)) {
mCapabilities |= CAPABILITY_ID;
}
if (capabilities.contains(ImapConstants.NAMESPACE)) {
mCapabilities |= CAPABILITY_NAMESPACE;
}
if (capabilities.contains(ImapConstants.UIDPLUS)) {
mCapabilities |= CAPABILITY_UIDPLUS;
}
if (capabilities.contains(ImapConstants.STARTTLS)) {
mCapabilities |= CAPABILITY_STARTTLS;
}
}
/**
* Create an {@link ImapResponseParser} from {@code mTransport.getInputStream()} and
* set it to {@link #mParser}.
*
* If we already have an {@link ImapResponseParser}, we
* {@link #destroyResponses()} and throw it away.
*/
private void createParser() {
destroyResponses();
mParser = new ImapResponseParser(mTransport.getInputStream(), mDiscourse);
}
void destroyResponses() {
if (mParser != null) {
mParser.destroyResponses();
}
}
boolean isTransportOpenForTest() {
return mTransport != null ? mTransport.isOpen() : false;
}
ImapResponse readResponse() throws IOException, MessagingException {
return mParser.readResponse();
}
/**
* Send a single command to the server. The command will be preceded by an IMAP command
* tag and followed by \r\n (caller need not supply them).
*
* @param command The command to send to the server
* @param sensitive If true, the command will not be logged
* @return Returns the command tag that was sent
*/
String sendCommand(String command, boolean sensitive)
throws MessagingException, IOException {
open();
String tag = Integer.toString(mNextCommandTag.incrementAndGet());
String commandToSend = tag + " " + command;
mTransport.writeLine(commandToSend, sensitive ? IMAP_REDACTED_LOG : null);
mDiscourse.addSentCommand(sensitive ? IMAP_REDACTED_LOG : commandToSend);
return tag;
}
/**
* Send a single, complex command to the server. The command will be preceded by an IMAP
* command tag and followed by \r\n (caller need not supply them). After each piece of the
* command, a response will be read which MUST be a continuation request.
*
* @param commands An array of Strings comprising the command to be sent to the server
* @return Returns the command tag that was sent
*/
String sendComplexCommand(List<String> commands, boolean sensitive) throws MessagingException,
IOException {
open();
String tag = Integer.toString(mNextCommandTag.incrementAndGet());
int len = commands.size();
for (int i = 0; i < len; i++) {
String commandToSend = commands.get(i);
// The first part of the command gets the tag
if (i == 0) {
commandToSend = tag + " " + commandToSend;
} else {
// Otherwise, read the response from the previous part of the command
ImapResponse response = readResponse();
// If it isn't a continuation request, that's an error
if (!response.isContinuationRequest()) {
throw new MessagingException("Expected continuation request");
}
}
// Send the command
mTransport.writeLine(commandToSend, null);
mDiscourse.addSentCommand(sensitive ? IMAP_REDACTED_LOG : commandToSend);
}
return tag;
}
List<ImapResponse> executeSimpleCommand(String command) throws IOException,
MessagingException {
return executeSimpleCommand(command, false);
}
/**
* Read and return all of the responses from the most recent command sent to the server
*
* @return a list of ImapResponses
* @throws IOException
* @throws MessagingException
*/
List<ImapResponse> getCommandResponses() throws IOException, MessagingException {
ArrayList<ImapResponse> responses = new ArrayList<ImapResponse>();
ImapResponse response;
do {
response = mParser.readResponse();
responses.add(response);
} while (!response.isTagged());
if (!response.isOk()) {
final String toString = response.toString();
final String alert = response.getAlertTextOrEmpty().getString();
destroyResponses();
throw new ImapException(toString, alert);
}
return responses;
}
/**
* Execute a simple command at the server, a simple command being one that is sent in a single
* line of text
*
* @param command the command to send to the server
* @param sensitive whether the command should be redacted in logs (used for login)
* @return a list of ImapResponses
* @throws IOException
* @throws MessagingException
*/
List<ImapResponse> executeSimpleCommand(String command, boolean sensitive)
throws IOException, MessagingException {
sendCommand(command, sensitive);
return getCommandResponses();
}
/**
* Execute a complex command at the server, a complex command being one that must be sent in
* multiple lines due to the use of string literals
*
* @param commands a list of strings that comprise the command to be sent to the server
* @param sensitive whether the command should be redacted in logs (used for login)
* @return a list of ImapResponses
* @throws IOException
* @throws MessagingException
*/
List<ImapResponse> executeComplexCommand(List<String> commands, boolean sensitive)
throws IOException, MessagingException {
sendComplexCommand(commands, sensitive);
return getCommandResponses();
}
/**
* Query server for capabilities.
*/
private ImapResponse queryCapabilities() throws IOException, MessagingException {
ImapResponse capabilityResponse = null;
for (ImapResponse r : executeSimpleCommand(ImapConstants.CAPABILITY)) {
if (r.is(0, ImapConstants.CAPABILITY)) {
capabilityResponse = r;
break;
}
}
if (capabilityResponse == null) {
throw new MessagingException("Invalid CAPABILITY response received");
}
return capabilityResponse;
}
/**
* Sends client identification information to the IMAP server per RFC 2971. If
* the server does not support the ID command, this will perform no operation.
*
* Interoperability hack: Never send ID to *.secureserver.net, which sends back a
* malformed response that our parser can't deal with.
*/
private void doSendId(boolean hasIdCapability, String capabilities)
throws MessagingException {
if (!hasIdCapability) return;
// Never send ID to *.secureserver.net
String host = mTransport.getHost();
if (host.toLowerCase().endsWith(".secureserver.net")) return;
// Assign user-agent string (for RFC2971 ID command)
String mUserAgent =
ImapStore.getImapId(mImapStore.getContext(), mUsername, host, capabilities);
if (mUserAgent != null) {
mIdPhrase = ImapConstants.ID + " (" + mUserAgent + ")";
} else if (DEBUG_FORCE_SEND_ID) {
mIdPhrase = ImapConstants.ID + " " + ImapConstants.NIL;
}
// else: mIdPhrase = null, no ID will be emitted
// Send user-agent in an RFC2971 ID command
if (mIdPhrase != null) {
try {
executeSimpleCommand(mIdPhrase);
} catch (ImapException ie) {
// Log for debugging, but this is not a fatal problem.
if (MailActivityEmail.DEBUG) {
Log.d(Logging.LOG_TAG, ie.toString());
}
} catch (IOException ioe) {
// Special case to handle malformed OK responses and ignore them.
// A true IOException will recur on the following login steps
// This can go away after the parser is fixed - see bug 2138981
}
}
}
/**
* Gets the user's Personal Namespace from the IMAP server per RFC 2342. If the user
* explicitly sets a namespace (using setup UI) or if the server does not support the
* namespace command, this will perform no operation.
*/
private void doGetNamespace(boolean hasNamespaceCapability) throws MessagingException {
// user did not specify a hard-coded prefix; try to get it from the server
if (hasNamespaceCapability && !mImapStore.isUserPrefixSet()) {
List<ImapResponse> responseList = Collections.emptyList();
try {
responseList = executeSimpleCommand(ImapConstants.NAMESPACE);
} catch (ImapException ie) {
// Log for debugging, but this is not a fatal problem.
if (MailActivityEmail.DEBUG) {
Log.d(Logging.LOG_TAG, ie.toString());
}
} catch (IOException ioe) {
// Special case to handle malformed OK responses and ignore them.
}
for (ImapResponse response: responseList) {
if (response.isDataResponse(0, ImapConstants.NAMESPACE)) {
ImapList namespaceList = response.getListOrEmpty(1);
ImapList namespace = namespaceList.getListOrEmpty(0);
String namespaceString = namespace.getStringOrEmpty(0).getString();
if (!TextUtils.isEmpty(namespaceString)) {
mImapStore.setPathPrefix(ImapStore.decodeFolderName(namespaceString, null));
mImapStore.setPathSeparator(namespace.getStringOrEmpty(1).getString());
}
}
}
}
}
/**
* Logs into the IMAP server
*/
private void doLogin()
throws IOException, MessagingException, AuthenticationFailedException {
try {
// TODO eventually we need to add additional authentication
// options such as SASL
executeSimpleCommand(mLoginPhrase, true);
} catch (ImapException ie) {
if (MailActivityEmail.DEBUG) {
Log.d(Logging.LOG_TAG, ie.toString());
}
throw new AuthenticationFailedException(ie.getAlertText(), ie);
} catch (MessagingException me) {
throw new AuthenticationFailedException(null, me);
}
}
/**
* Gets the path separator per the LIST command in RFC 3501. If the path separator
* was obtained while obtaining the namespace or there is no prefix defined, this
* will perform no operation.
*/
private void doGetPathSeparator() throws MessagingException {
// user did not specify a hard-coded prefix; try to get it from the server
if (mImapStore.isUserPrefixSet()) {
List<ImapResponse> responseList = Collections.emptyList();
try {
responseList = executeSimpleCommand(ImapConstants.LIST + " \"\" \"\"");
} catch (ImapException ie) {
// Log for debugging, but this is not a fatal problem.
if (MailActivityEmail.DEBUG) {
Log.d(Logging.LOG_TAG, ie.toString());
}
} catch (IOException ioe) {
// Special case to handle malformed OK responses and ignore them.
}
for (ImapResponse response: responseList) {
if (response.isDataResponse(0, ImapConstants.LIST)) {
mImapStore.setPathSeparator(response.getStringOrEmpty(2).getString());
}
}
}
}
/**
* Starts a TLS session with the IMAP server per RFC 3501. If the user has not opted
* to use TLS or the server does not support the TLS capability, this will perform
* no operation.
*/
private ImapResponse doStartTls(boolean hasStartTlsCapability)
throws IOException, MessagingException {
if (mTransport.canTryTlsSecurity()) {
if (hasStartTlsCapability) {
// STARTTLS
executeSimpleCommand(ImapConstants.STARTTLS);
mTransport.reopenTls();
mTransport.setSoTimeout(MailTransport.SOCKET_READ_TIMEOUT);
createParser();
// Per RFC requirement (3501-6.2.1) gather new capabilities
return(queryCapabilities());
} else {
if (MailActivityEmail.DEBUG) {
Log.d(Logging.LOG_TAG, "TLS not supported but required");
}
throw new MessagingException(MessagingException.TLS_REQUIRED);
}
}
return null;
}
/** @see DiscourseLogger#logLastDiscourse() */
void logLastDiscourse() {
mDiscourse.logLastDiscourse();
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,618 +0,0 @@
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.email.mail.store;
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.Base64;
import android.util.Log;
import com.android.email.LegacyConversions;
import com.android.email.Preferences;
import com.android.email.R;
import com.android.email.mail.Store;
import com.android.email.mail.store.imap.ImapConstants;
import com.android.email.mail.store.imap.ImapResponse;
import com.android.email.mail.store.imap.ImapString;
import com.android.email.mail.transport.MailTransport;
import com.android.emailcommon.Logging;
import com.android.emailcommon.VendorPolicyLoader;
import com.android.emailcommon.internet.MimeMessage;
import com.android.emailcommon.mail.AuthenticationFailedException;
import com.android.emailcommon.mail.Flag;
import com.android.emailcommon.mail.Folder;
import com.android.emailcommon.mail.Message;
import com.android.emailcommon.mail.MessagingException;
import com.android.emailcommon.mail.Transport;
import com.android.emailcommon.provider.Account;
import com.android.emailcommon.provider.HostAuth;
import com.android.emailcommon.provider.Mailbox;
import com.android.emailcommon.service.EmailServiceProxy;
import com.android.emailcommon.utility.Utility;
import com.beetstra.jutf7.CharsetProvider;
import com.google.common.annotations.VisibleForTesting;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.regex.Pattern;
/**
* <pre>
* TODO Need to start keeping track of UIDVALIDITY
* TODO Need a default response handler for things like folder updates
* TODO In fetch(), if we need a ImapMessage and were given
* something else we can try to do a pre-fetch first.
* TODO Collect ALERT messages and show them to users.
*
* ftp://ftp.isi.edu/in-notes/rfc2683.txt When a client asks for
* certain information in a FETCH command, the server may return the requested
* information in any order, not necessarily in the order that it was requested.
* Further, the server may return the information in separate FETCH responses
* and may also return information that was not explicitly requested (to reflect
* to the client changes in the state of the subject message).
* </pre>
*/
public class ImapStore extends Store {
/** Charset used for converting folder names to and from UTF-7 as defined by RFC 3501. */
private static final Charset MODIFIED_UTF_7_CHARSET =
new CharsetProvider().charsetForName("X-RFC-3501");
@VisibleForTesting static String sImapId = null;
@VisibleForTesting String mPathPrefix;
@VisibleForTesting String mPathSeparator;
private final ConcurrentLinkedQueue<ImapConnection> mConnectionPool =
new ConcurrentLinkedQueue<ImapConnection>();
/**
* Static named constructor.
*/
public static Store newInstance(Account account, Context context) throws MessagingException {
return new ImapStore(context, account);
}
/**
* Creates a new store for the given account. Always use
* {@link #newInstance(Account, Context)} to create an IMAP store.
*/
private ImapStore(Context context, Account account) throws MessagingException {
mContext = context;
mAccount = account;
HostAuth recvAuth = account.getOrCreateHostAuthRecv(context);
if (recvAuth == null || !HostAuth.LEGACY_SCHEME_IMAP.equalsIgnoreCase(recvAuth.mProtocol)) {
throw new MessagingException("Unsupported protocol");
}
mTransport = new MailTransport(context, "IMAP", recvAuth);
String[] userInfo = recvAuth.getLogin();
if (userInfo != null) {
mUsername = userInfo[0];
mPassword = userInfo[1];
} else {
mUsername = null;
mPassword = null;
}
mPathPrefix = recvAuth.mDomain;
}
@VisibleForTesting
Collection<ImapConnection> getConnectionPoolForTest() {
return mConnectionPool;
}
/**
* For testing only. Injects a different root transport (it will be copied using
* newInstanceWithConfiguration() each time IMAP sets up a new channel). The transport
* should already be set up and ready to use. Do not use for real code.
* @param testTransport The Transport to inject and use for all future communication.
*/
@VisibleForTesting
void setTransportForTest(Transport testTransport) {
mTransport = testTransport;
}
/**
* Return, or create and return, an string suitable for use in an IMAP ID message.
* This is constructed similarly to the way the browser sets up its user-agent strings.
* See RFC 2971 for more details. The output of this command will be a series of key-value
* pairs delimited by spaces (there is no point in returning a structured result because
* this will be sent as-is to the IMAP server). No tokens, parenthesis or "ID" are included,
* because some connections may append additional values.
*
* The following IMAP ID keys may be included:
* name Android package name of the program
* os "android"
* os-version "version; model; build-id"
* vendor Vendor of the client/server
* x-android-device-model Model (only revealed if release build)
* x-android-net-operator Mobile network operator (if known)
* AGUID A device+account UID
*
* In addition, a vendor policy .apk can append key/value pairs.
*
* @param userName the username of the account
* @param host the host (server) of the account
* @param capabilities a list of the capabilities from the server
* @return a String for use in an IMAP ID message.
*/
public static String getImapId(Context context, String userName, String host,
String capabilities) {
// The first section is global to all IMAP connections, and generates the fixed
// values in any IMAP ID message
synchronized (ImapStore.class) {
if (sImapId == null) {
TelephonyManager tm =
(TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
String networkOperator = tm.getNetworkOperatorName();
if (networkOperator == null) networkOperator = "";
sImapId = makeCommonImapId(context.getPackageName(), Build.VERSION.RELEASE,
Build.VERSION.CODENAME, Build.MODEL, Build.ID, Build.MANUFACTURER,
networkOperator);
}
}
// This section is per Store, and adds in a dynamic elements like UID's.
// We don't cache the result of this work, because the caller does anyway.
StringBuilder id = new StringBuilder(sImapId);
// Optionally add any vendor-supplied id keys
String vendorId = VendorPolicyLoader.getInstance(context).getImapIdValues(userName, host,
capabilities);
if (vendorId != null) {
id.append(' ');
id.append(vendorId);
}
// Generate a UID that mixes a "stable" device UID with the email address
try {
String devUID = Preferences.getPreferences(context).getDeviceUID();
MessageDigest messageDigest;
messageDigest = MessageDigest.getInstance("SHA-1");
messageDigest.update(userName.getBytes());
messageDigest.update(devUID.getBytes());
byte[] uid = messageDigest.digest();
String hexUid = Base64.encodeToString(uid, Base64.NO_WRAP);
id.append(" \"AGUID\" \"");
id.append(hexUid);
id.append('\"');
} catch (NoSuchAlgorithmException e) {
Log.d(Logging.LOG_TAG, "couldn't obtain SHA-1 hash for device UID");
}
return id.toString();
}
/**
* Helper function that actually builds the static part of the IMAP ID string. This is
* separated from getImapId for testability. There is no escaping or encoding in IMAP ID so
* any rogue chars must be filtered here.
*
* @param packageName context.getPackageName()
* @param version Build.VERSION.RELEASE
* @param codeName Build.VERSION.CODENAME
* @param model Build.MODEL
* @param id Build.ID
* @param vendor Build.MANUFACTURER
* @param networkOperator TelephonyManager.getNetworkOperatorName()
* @return the static (never changes) portion of the IMAP ID
*/
@VisibleForTesting
static String makeCommonImapId(String packageName, String version,
String codeName, String model, String id, String vendor, String networkOperator) {
// Before building up IMAP ID string, pre-filter the input strings for "legal" chars
// This is using a fairly arbitrary char set intended to pass through most reasonable
// version, model, and vendor strings: a-z A-Z 0-9 - _ + = ; : . , / <space>
// The most important thing is *not* to pass parens, quotes, or CRLF, which would break
// the format of the IMAP ID list.
Pattern p = Pattern.compile("[^a-zA-Z0-9-_\\+=;:\\.,/ ]");
packageName = p.matcher(packageName).replaceAll("");
version = p.matcher(version).replaceAll("");
codeName = p.matcher(codeName).replaceAll("");
model = p.matcher(model).replaceAll("");
id = p.matcher(id).replaceAll("");
vendor = p.matcher(vendor).replaceAll("");
networkOperator = p.matcher(networkOperator).replaceAll("");
// "name" "com.android.email"
StringBuffer sb = new StringBuffer("\"name\" \"");
sb.append(packageName);
sb.append("\"");
// "os" "android"
sb.append(" \"os\" \"android\"");
// "os-version" "version; build-id"
sb.append(" \"os-version\" \"");
if (version.length() > 0) {
sb.append(version);
} else {
// default to "1.0"
sb.append("1.0");
}
// add the build ID or build #
if (id.length() > 0) {
sb.append("; ");
sb.append(id);
}
sb.append("\"");
// "vendor" "the vendor"
if (vendor.length() > 0) {
sb.append(" \"vendor\" \"");
sb.append(vendor);
sb.append("\"");
}
// "x-android-device-model" the device model (on release builds only)
if ("REL".equals(codeName)) {
if (model.length() > 0) {
sb.append(" \"x-android-device-model\" \"");
sb.append(model);
sb.append("\"");
}
}
// "x-android-mobile-net-operator" "name of network operator"
if (networkOperator.length() > 0) {
sb.append(" \"x-android-mobile-net-operator\" \"");
sb.append(networkOperator);
sb.append("\"");
}
return sb.toString();
}
@Override
public Folder getFolder(String name) {
return new ImapFolder(this, name);
}
/**
* Creates a mailbox hierarchy out of the flat data provided by the server.
*/
@VisibleForTesting
static void createHierarchy(HashMap<String, ImapFolder> mailboxes) {
Set<String> pathnames = mailboxes.keySet();
for (String path : pathnames) {
final ImapFolder folder = mailboxes.get(path);
final Mailbox mailbox = folder.mMailbox;
int delimiterIdx = mailbox.mServerId.lastIndexOf(mailbox.mDelimiter);
long parentKey = Mailbox.NO_MAILBOX;
if (delimiterIdx != -1) {
String parentPath = path.substring(0, delimiterIdx);
final ImapFolder parentFolder = mailboxes.get(parentPath);
final Mailbox parentMailbox = (parentFolder == null) ? null : parentFolder.mMailbox;
if (parentMailbox != null) {
parentKey = parentMailbox.mId;
parentMailbox.mFlags
|= (Mailbox.FLAG_HAS_CHILDREN | Mailbox.FLAG_CHILDREN_VISIBLE);
}
}
mailbox.mParentKey = parentKey;
}
}
/**
* Creates a {@link Folder} and associated {@link Mailbox}. If the folder does not already
* exist in the local database, a new row will immediately be created in the mailbox table.
* Otherwise, the existing row will be used. Any changes to existing rows, will not be stored
* to the database immediately.
* @param accountId The ID of the account the mailbox is to be associated with
* @param mailboxPath The path of the mailbox to add
* @param delimiter A path delimiter. May be {@code null} if there is no delimiter.
* @param selectable If {@code true}, the mailbox can be selected and used to store messages.
*/
private ImapFolder addMailbox(Context context, long accountId, String mailboxPath,
char delimiter, boolean selectable) {
ImapFolder folder = (ImapFolder) getFolder(mailboxPath);
Mailbox mailbox = Mailbox.getMailboxForPath(context, accountId, mailboxPath);
if (mailbox.isSaved()) {
// existing mailbox
// mailbox retrieved from database; save hash _before_ updating fields
folder.mHash = mailbox.getHashes();
}
updateMailbox(mailbox, accountId, mailboxPath, delimiter, selectable,
LegacyConversions.inferMailboxTypeFromName(context, mailboxPath));
if (folder.mHash == null) {
// new mailbox
// save hash after updating. allows tracking changes if the mailbox is saved
// outside of #saveMailboxList()
folder.mHash = mailbox.getHashes();
// We must save this here to make sure we have a valid ID for later
mailbox.save(mContext);
}
folder.mMailbox = mailbox;
return folder;
}
/**
* Persists the folders in the given list.
*/
private static void saveMailboxList(Context context, HashMap<String, ImapFolder> folderMap) {
for (ImapFolder imapFolder : folderMap.values()) {
imapFolder.save(context);
}
}
@Override
public Folder[] updateFolders() throws MessagingException {
ImapConnection connection = getConnection();
try {
HashMap<String, ImapFolder> mailboxes = new HashMap<String, ImapFolder>();
// Establish a connection to the IMAP server; if necessary
// This ensures a valid prefix if the prefix is automatically set by the server
connection.executeSimpleCommand(ImapConstants.NOOP);
String imapCommand = ImapConstants.LIST + " \"\" \"*\"";
if (mPathPrefix != null) {
imapCommand = ImapConstants.LIST + " \"\" \"" + mPathPrefix + "*\"";
}
List<ImapResponse> responses = connection.executeSimpleCommand(imapCommand);
for (ImapResponse response : responses) {
// S: * LIST (\Noselect) "/" ~/Mail/foo
if (response.isDataResponse(0, ImapConstants.LIST)) {
// Get folder name.
ImapString encodedFolder = response.getStringOrEmpty(3);
if (encodedFolder.isEmpty()) continue;
String folderName = decodeFolderName(encodedFolder.getString(), mPathPrefix);
if (ImapConstants.INBOX.equalsIgnoreCase(folderName)) continue;
// Parse attributes.
boolean selectable =
!response.getListOrEmpty(1).contains(ImapConstants.FLAG_NO_SELECT);
String delimiter = response.getStringOrEmpty(2).getString();
char delimiterChar = '\0';
if (!TextUtils.isEmpty(delimiter)) {
delimiterChar = delimiter.charAt(0);
}
ImapFolder folder =
addMailbox(mContext, mAccount.mId, folderName, delimiterChar, selectable);
mailboxes.put(folderName, folder);
}
}
String inboxName = mContext.getString(R.string.mailbox_name_display_inbox);
Folder newFolder =
addMailbox(mContext, mAccount.mId, inboxName, '\0', true /*selectable*/);
mailboxes.put(ImapConstants.INBOX, (ImapFolder)newFolder);
createHierarchy(mailboxes);
saveMailboxList(mContext, mailboxes);
return mailboxes.values().toArray(new Folder[] {});
} catch (IOException ioe) {
connection.close();
throw new MessagingException("Unable to get folder list.", ioe);
} catch (AuthenticationFailedException afe) {
// We do NOT want this connection pooled, or we will continue to send NOOP and SELECT
// commands to the server
connection.destroyResponses();
connection = null;
throw afe;
} finally {
if (connection != null) {
poolConnection(connection);
}
}
}
@Override
public Bundle checkSettings() throws MessagingException {
int result = MessagingException.NO_ERROR;
Bundle bundle = new Bundle();
ImapConnection connection = new ImapConnection(this, mUsername, mPassword);
try {
connection.open();
connection.close();
} catch (IOException ioe) {
bundle.putString(EmailServiceProxy.VALIDATE_BUNDLE_ERROR_MESSAGE, ioe.getMessage());
result = MessagingException.IOERROR;
} finally {
connection.destroyResponses();
}
bundle.putInt(EmailServiceProxy.VALIDATE_BUNDLE_RESULT_CODE, result);
return bundle;
}
/**
* Returns whether or not the prefix has been set by the user. This can be determined by
* the fact that the prefix is set, but, the path separator is not set.
*/
boolean isUserPrefixSet() {
return TextUtils.isEmpty(mPathSeparator) && !TextUtils.isEmpty(mPathPrefix);
}
/** Sets the path separator */
void setPathSeparator(String pathSeparator) {
mPathSeparator = pathSeparator;
}
/** Sets the prefix */
void setPathPrefix(String pathPrefix) {
mPathPrefix = pathPrefix;
}
/** Gets the context for this store */
Context getContext() {
return mContext;
}
/** Returns a clone of the transport associated with this store. */
Transport cloneTransport() {
return mTransport.clone();
}
/**
* Fixes the path prefix, if necessary. The path prefix must always end with the
* path separator.
*/
void ensurePrefixIsValid() {
// Make sure the path prefix ends with the path separator
if (!TextUtils.isEmpty(mPathPrefix) && !TextUtils.isEmpty(mPathSeparator)) {
if (!mPathPrefix.endsWith(mPathSeparator)) {
mPathPrefix = mPathPrefix + mPathSeparator;
}
}
}
/**
* Gets a connection if one is available from the pool, or creates a new one if not.
*/
ImapConnection getConnection() {
ImapConnection connection = null;
while ((connection = mConnectionPool.poll()) != null) {
try {
connection.setStore(this, mUsername, mPassword);
connection.executeSimpleCommand(ImapConstants.NOOP);
break;
} catch (MessagingException e) {
// Fall through
} catch (IOException e) {
// Fall through
}
connection.close();
connection = null;
}
if (connection == null) {
connection = new ImapConnection(this, mUsername, mPassword);
}
return connection;
}
/**
* Save a {@link ImapConnection} in the pool for reuse. Any responses associated with the
* connection are destroyed before adding the connection to the pool.
*/
void poolConnection(ImapConnection connection) {
if (connection != null) {
connection.destroyResponses();
mConnectionPool.add(connection);
}
}
/**
* Prepends the folder name with the given prefix and UTF-7 encodes it.
*/
static String encodeFolderName(String name, String prefix) {
// do NOT add the prefix to the special name "INBOX"
if (ImapConstants.INBOX.equalsIgnoreCase(name)) return name;
// Prepend prefix
if (prefix != null) {
name = prefix + name;
}
// TODO bypass the conversion if name doesn't have special char.
ByteBuffer bb = MODIFIED_UTF_7_CHARSET.encode(name);
byte[] b = new byte[bb.limit()];
bb.get(b);
return Utility.fromAscii(b);
}
/**
* UTF-7 decodes the folder name and removes the given path prefix.
*/
static String decodeFolderName(String name, String prefix) {
// TODO bypass the conversion if name doesn't have special char.
String folder;
folder = MODIFIED_UTF_7_CHARSET.decode(ByteBuffer.wrap(Utility.toAscii(name))).toString();
if ((prefix != null) && folder.startsWith(prefix)) {
folder = folder.substring(prefix.length());
}
return folder;
}
/**
* Returns UIDs of Messages joined with "," as the separator.
*/
static String joinMessageUids(Message[] messages) {
StringBuilder sb = new StringBuilder();
boolean notFirst = false;
for (Message m : messages) {
if (notFirst) {
sb.append(',');
}
sb.append(m.getUid());
notFirst = true;
}
return sb.toString();
}
static class ImapMessage extends MimeMessage {
ImapMessage(String uid, ImapFolder folder) {
mUid = uid;
mFolder = folder;
}
public void setSize(int size) {
mSize = size;
}
@Override
public void parse(InputStream in) throws IOException, MessagingException {
super.parse(in);
}
public void setFlagInternal(Flag flag, boolean set) throws MessagingException {
super.setFlag(flag, set);
}
@Override
public void setFlag(Flag flag, boolean set) throws MessagingException {
super.setFlag(flag, set);
mFolder.setFlags(new Message[] { this }, new Flag[] { flag }, set);
}
}
static class ImapException extends MessagingException {
private static final long serialVersionUID = 1L;
String mAlertText;
public ImapException(String message, String alertText, Throwable throwable) {
super(message, throwable);
mAlertText = alertText;
}
public ImapException(String message, String alertText) {
super(message);
mAlertText = alertText;
}
public String getAlertText() {
return mAlertText;
}
public void setAlertText(String alertText) {
mAlertText = alertText;
}
}
}

View File

@ -30,10 +30,10 @@ import com.android.emailcommon.mail.AuthenticationFailedException;
import com.android.emailcommon.mail.FetchProfile;
import com.android.emailcommon.mail.Flag;
import com.android.emailcommon.mail.Folder;
import com.android.emailcommon.mail.Transport;
import com.android.emailcommon.mail.Folder.OpenMode;
import com.android.emailcommon.mail.Message;
import com.android.emailcommon.mail.MessagingException;
import com.android.emailcommon.mail.Transport;
import com.android.emailcommon.provider.Account;
import com.android.emailcommon.provider.HostAuth;
import com.android.emailcommon.provider.Mailbox;
@ -77,9 +77,6 @@ public class Pop3Store extends Store {
mAccount = account;
HostAuth recvAuth = account.getOrCreateHostAuthRecv(context);
if (recvAuth == null || !HostAuth.LEGACY_SCHEME_POP3.equalsIgnoreCase(recvAuth.mProtocol)) {
throw new MessagingException("Unsupported protocol");
}
mTransport = new MailTransport(context, "POP3", recvAuth);
String[] userInfoParts = recvAuth.getLogin();
if (userInfoParts != null) {

View File

@ -1,95 +0,0 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.email.mail.store.imap;
import com.android.email.mail.Store;
public final class ImapConstants {
private ImapConstants() {}
public static final String FETCH_FIELD_BODY_PEEK_BARE = "BODY.PEEK";
public static final String FETCH_FIELD_BODY_PEEK = FETCH_FIELD_BODY_PEEK_BARE + "[]";
public static final String FETCH_FIELD_BODY_PEEK_SANE
= String.format("BODY.PEEK[]<0.%d>", Store.FETCH_BODY_SANE_SUGGESTED_SIZE);
public static final String FETCH_FIELD_HEADERS =
"BODY.PEEK[HEADER.FIELDS (date subject from content-type to cc message-id)]";
public static final String ALERT = "ALERT";
public static final String APPEND = "APPEND";
public static final String BAD = "BAD";
public static final String BADCHARSET = "BADCHARSET";
public static final String BODY = "BODY";
public static final String BODY_BRACKET_HEADER = "BODY[HEADER";
public static final String BODYSTRUCTURE = "BODYSTRUCTURE";
public static final String BYE = "BYE";
public static final String CAPABILITY = "CAPABILITY";
public static final String CHECK = "CHECK";
public static final String CLOSE = "CLOSE";
public static final String COPY = "COPY";
public static final String COPYUID = "COPYUID";
public static final String CREATE = "CREATE";
public static final String DELETE = "DELETE";
public static final String EXAMINE = "EXAMINE";
public static final String EXISTS = "EXISTS";
public static final String EXPUNGE = "EXPUNGE";
public static final String FETCH = "FETCH";
public static final String FLAG_ANSWERED = "\\ANSWERED";
public static final String FLAG_DELETED = "\\DELETED";
public static final String FLAG_FLAGGED = "\\FLAGGED";
public static final String FLAG_NO_SELECT = "\\NOSELECT";
public static final String FLAG_SEEN = "\\SEEN";
public static final String FLAGS = "FLAGS";
public static final String FLAGS_SILENT = "FLAGS.SILENT";
public static final String ID = "ID";
public static final String INBOX = "INBOX";
public static final String INTERNALDATE = "INTERNALDATE";
public static final String LIST = "LIST";
public static final String LOGIN = "LOGIN";
public static final String LOGOUT = "LOGOUT";
public static final String LSUB = "LSUB";
public static final String NAMESPACE = "NAMESPACE";
public static final String NO = "NO";
public static final String NOOP = "NOOP";
public static final String OK = "OK";
public static final String PARSE = "PARSE";
public static final String PERMANENTFLAGS = "PERMANENTFLAGS";
public static final String PREAUTH = "PREAUTH";
public static final String READ_ONLY = "READ-ONLY";
public static final String READ_WRITE = "READ-WRITE";
public static final String RENAME = "RENAME";
public static final String RFC822_SIZE = "RFC822.SIZE";
public static final String SEARCH = "SEARCH";
public static final String SELECT = "SELECT";
public static final String STARTTLS = "STARTTLS";
public static final String STATUS = "STATUS";
public static final String STORE = "STORE";
public static final String SUBSCRIBE = "SUBSCRIBE";
public static final String TEXT = "TEXT";
public static final String TRYCREATE = "TRYCREATE";
public static final String UID = "UID";
public static final String UID_COPY = "UID COPY";
public static final String UID_FETCH = "UID FETCH";
public static final String UID_SEARCH = "UID SEARCH";
public static final String UID_STORE = "UID STORE";
public static final String UIDNEXT = "UIDNEXT";
public static final String UIDPLUS = "UIDPLUS";
public static final String UIDVALIDITY = "UIDVALIDITY";
public static final String UNSEEN = "UNSEEN";
public static final String UNSUBSCRIBE = "UNSUBSCRIBE";
public static final String APPENDUID = "APPENDUID";
public static final String NIL = "NIL";
}

View File

@ -1,120 +0,0 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.email.mail.store.imap;
/**
* Class representing "element"s in IMAP responses.
*
* <p>Class hierarchy:
* <pre>
* ImapElement
* |
* |-- ImapElement.NONE (for 'index out of range')
* |
* |-- ImapList (isList() == true)
* | |
* | |-- ImapList.EMPTY
* | |
* | --- ImapResponse
* |
* --- ImapString (isString() == true)
* |
* |-- ImapString.EMPTY
* |
* |-- ImapSimpleString
* |
* |-- ImapMemoryLiteral
* |
* --- ImapTempFileLiteral
* </pre>
*/
public abstract class ImapElement {
/**
* An element that is returned by {@link ImapList#getElementOrNone} to indicate an index
* is out of range.
*/
public static final ImapElement NONE = new ImapElement() {
@Override public void destroy() {
// Don't call super.destroy().
// It's a shared object. We don't want the mDestroyed to be set on this.
}
@Override public boolean isList() {
return false;
}
@Override public boolean isString() {
return false;
}
@Override public String toString() {
return "[NO ELEMENT]";
}
@Override
public boolean equalsForTest(ImapElement that) {
return super.equalsForTest(that);
}
};
private boolean mDestroyed = false;
public abstract boolean isList();
public abstract boolean isString();
protected boolean isDestroyed() {
return mDestroyed;
}
/**
* Clean up the resources used by the instance.
* It's for removing a temp file used by {@link ImapTempFileLiteral}.
*/
public void destroy() {
mDestroyed = true;
}
/**
* Throws {@link RuntimeException} if it's already destroyed.
*/
protected final void checkNotDestroyed() {
if (mDestroyed) {
throw new RuntimeException("Already destroyed");
}
}
/**
* Return a string that represents this object; it's purely for the debug purpose. Don't
* mistake it for {@link ImapString#getString}.
*
* Abstract to force subclasses to implement it.
*/
@Override
public abstract String toString();
/**
* The equals implementation that is intended to be used only for unit testing.
* (Because it may be heavy and has a special sense of "equal" for testing.)
*/
public boolean equalsForTest(ImapElement that) {
if (that == null) {
return false;
}
return this.getClass() == that.getClass(); // Has to be the same class.
}
}

View File

@ -1,235 +0,0 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.email.mail.store.imap;
import java.util.ArrayList;
/**
* Class represents an IMAP list.
*/
public class ImapList extends ImapElement {
/**
* {@link ImapList} representing an empty list.
*/
public static final ImapList EMPTY = new ImapList() {
@Override public void destroy() {
// Don't call super.destroy().
// It's a shared object. We don't want the mDestroyed to be set on this.
}
@Override void add(ImapElement e) {
throw new RuntimeException();
}
};
private ArrayList<ImapElement> mList = new ArrayList<ImapElement>();
/* package */ void add(ImapElement e) {
if (e == null) {
throw new RuntimeException("Can't add null");
}
mList.add(e);
}
@Override
public final boolean isString() {
return false;
}
@Override
public final boolean isList() {
return true;
}
public final int size() {
return mList.size();
}
public final boolean isEmpty() {
return size() == 0;
}
/**
* Return true if the element at {@code index} exists, is string, and equals to {@code s}.
* (case insensitive)
*/
public final boolean is(int index, String s) {
return is(index, s, false);
}
/**
* Same as {@link #is(int, String)}, but does the prefix match if {@code prefixMatch}.
*/
public final boolean is(int index, String s, boolean prefixMatch) {
if (!prefixMatch) {
return getStringOrEmpty(index).is(s);
} else {
return getStringOrEmpty(index).startsWith(s);
}
}
/**
* Return the element at {@code index}.
* If {@code index} is out of range, returns {@link ImapElement#NONE}.
*/
public final ImapElement getElementOrNone(int index) {
return (index >= mList.size()) ? ImapElement.NONE : mList.get(index);
}
/**
* Return the element at {@code index} if it's a list.
* If {@code index} is out of range or not a list, returns {@link ImapList#EMPTY}.
*/
public final ImapList getListOrEmpty(int index) {
ImapElement el = getElementOrNone(index);
return el.isList() ? (ImapList) el : EMPTY;
}
/**
* Return the element at {@code index} if it's a string.
* If {@code index} is out of range or not a string, returns {@link ImapString#EMPTY}.
*/
public final ImapString getStringOrEmpty(int index) {
ImapElement el = getElementOrNone(index);
return el.isString() ? (ImapString) el : ImapString.EMPTY;
}
/**
* Return an element keyed by {@code key}. Return null if not found. {@code key} has to be
* at an even index.
*/
/* package */ final ImapElement getKeyedElementOrNull(String key, boolean prefixMatch) {
for (int i = 1; i < size(); i += 2) {
if (is(i-1, key, prefixMatch)) {
return mList.get(i);
}
}
return null;
}
/**
* Return an {@link ImapList} keyed by {@code key}.
* Return {@link ImapList#EMPTY} if not found.
*/
public final ImapList getKeyedListOrEmpty(String key) {
return getKeyedListOrEmpty(key, false);
}
/**
* Return an {@link ImapList} keyed by {@code key}.
* Return {@link ImapList#EMPTY} if not found.
*/
public final ImapList getKeyedListOrEmpty(String key, boolean prefixMatch) {
ImapElement e = getKeyedElementOrNull(key, prefixMatch);
return (e != null) ? ((ImapList) e) : ImapList.EMPTY;
}
/**
* Return an {@link ImapString} keyed by {@code key}.
* Return {@link ImapString#EMPTY} if not found.
*/
public final ImapString getKeyedStringOrEmpty(String key) {
return getKeyedStringOrEmpty(key, false);
}
/**
* Return an {@link ImapString} keyed by {@code key}.
* Return {@link ImapString#EMPTY} if not found.
*/
public final ImapString getKeyedStringOrEmpty(String key, boolean prefixMatch) {
ImapElement e = getKeyedElementOrNull(key, prefixMatch);
return (e != null) ? ((ImapString) e) : ImapString.EMPTY;
}
/**
* Return true if it contains {@code s}.
*/
public final boolean contains(String s) {
for (int i = 0; i < size(); i++) {
if (getStringOrEmpty(i).is(s)) {
return true;
}
}
return false;
}
@Override
public void destroy() {
if (mList != null) {
for (ImapElement e : mList) {
e.destroy();
}
mList = null;
}
super.destroy();
}
@Override
public String toString() {
return mList.toString();
}
/**
* Return the text representations of the contents concatenated with ",".
*/
public final String flatten() {
return flatten(new StringBuilder()).toString();
}
/**
* Returns text representations (i.e. getString()) of contents joined together with
* "," as the separator.
*
* Only used for building the capability string passed to vendor policies.
*
* We can't use toString(), because it's for debugging (meaning the format may change any time),
* and it won't expand literals.
*/
private final StringBuilder flatten(StringBuilder sb) {
sb.append('[');
for (int i = 0; i < mList.size(); i++) {
if (i > 0) {
sb.append(',');
}
final ImapElement e = getElementOrNone(i);
if (e.isList()) {
getListOrEmpty(i).flatten(sb);
} else if (e.isString()) {
sb.append(getStringOrEmpty(i).getString());
}
}
sb.append(']');
return sb;
}
@Override
public boolean equalsForTest(ImapElement that) {
if (!super.equalsForTest(that)) {
return false;
}
ImapList thatList = (ImapList) that;
if (size() != thatList.size()) {
return false;
}
for (int i = 0; i < size(); i++) {
if (!mList.get(i).equalsForTest(thatList.getElementOrNone(i))) {
return false;
}
}
return true;
}
}

View File

@ -1,72 +0,0 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.email.mail.store.imap;
import com.android.email.FixedLengthInputStream;
import com.android.emailcommon.Logging;
import com.android.emailcommon.utility.Utility;
import android.util.Log;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* Subclass of {@link ImapString} used for literals backed by an in-memory byte array.
*/
public class ImapMemoryLiteral extends ImapString {
private byte[] mData;
/* package */ ImapMemoryLiteral(FixedLengthInputStream in) throws IOException {
// We could use ByteArrayOutputStream and IOUtils.copy, but it'd perform an unnecessary
// copy....
mData = new byte[in.getLength()];
int pos = 0;
while (pos < mData.length) {
int read = in.read(mData, pos, mData.length - pos);
if (read < 0) {
break;
}
pos += read;
}
if (pos != mData.length) {
Log.w(Logging.LOG_TAG, "");
}
}
@Override
public void destroy() {
mData = null;
super.destroy();
}
@Override
public String getString() {
return Utility.fromAscii(mData);
}
@Override
public InputStream getAsStream() {
return new ByteArrayInputStream(mData);
}
@Override
public String toString() {
return String.format("{%d byte literal(memory)}", mData.length);
}
}

View File

@ -1,152 +0,0 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.email.mail.store.imap;
/**
* Class represents an IMAP response.
*/
public class ImapResponse extends ImapList {
private final String mTag;
private final boolean mIsContinuationRequest;
/* package */ ImapResponse(String tag, boolean isContinuationRequest) {
mTag = tag;
mIsContinuationRequest = isContinuationRequest;
}
/* package */ static boolean isStatusResponse(String symbol) {
return ImapConstants.OK.equalsIgnoreCase(symbol)
|| ImapConstants.NO.equalsIgnoreCase(symbol)
|| ImapConstants.BAD.equalsIgnoreCase(symbol)
|| ImapConstants.PREAUTH.equalsIgnoreCase(symbol)
|| ImapConstants.BYE.equalsIgnoreCase(symbol);
}
/**
* @return whether it's a tagged response.
*/
public boolean isTagged() {
return mTag != null;
}
/**
* @return whether it's a continuation request.
*/
public boolean isContinuationRequest() {
return mIsContinuationRequest;
}
public boolean isStatusResponse() {
return isStatusResponse(getStringOrEmpty(0).getString());
}
/**
* @return whether it's an OK response.
*/
public boolean isOk() {
return is(0, ImapConstants.OK);
}
/**
* @return whether it's an BAD response.
*/
public boolean isBad() {
return is(0, ImapConstants.BAD);
}
/**
* @return whether it's an NO response.
*/
public boolean isNo() {
return is(0, ImapConstants.NO);
}
/**
* @return whether it's an {@code responseType} data response. (i.e. not tagged).
* @param index where {@code responseType} should appear. e.g. 1 for "FETCH"
* @param responseType e.g. "FETCH"
*/
public final boolean isDataResponse(int index, String responseType) {
return !isTagged() && getStringOrEmpty(index).is(responseType);
}
/**
* @return Response code (RFC 3501 7.1) if it's a status response.
*
* e.g. "ALERT" for "* OK [ALERT] System shutdown in 10 minutes"
*/
public ImapString getResponseCodeOrEmpty() {
if (!isStatusResponse()) {
return ImapString.EMPTY; // Not a status response.
}
return getListOrEmpty(1).getStringOrEmpty(0);
}
/**
* @return Alert message it it has ALERT response code.
*
* e.g. "System shutdown in 10 minutes" for "* OK [ALERT] System shutdown in 10 minutes"
*/
public ImapString getAlertTextOrEmpty() {
if (!getResponseCodeOrEmpty().is(ImapConstants.ALERT)) {
return ImapString.EMPTY; // Not an ALERT
}
// The 3rd element contains all the rest of line.
return getStringOrEmpty(2);
}
/**
* @return Response text in a status response.
*/
public ImapString getStatusResponseTextOrEmpty() {
if (!isStatusResponse()) {
return ImapString.EMPTY;
}
return getStringOrEmpty(getElementOrNone(1).isList() ? 2 : 1);
}
@Override
public String toString() {
String tag = mTag;
if (isContinuationRequest()) {
tag = "+";
}
return "#" + tag + "# " + super.toString();
}
@Override
public boolean equalsForTest(ImapElement that) {
if (!super.equalsForTest(that)) {
return false;
}
final ImapResponse thatResponse = (ImapResponse) that;
if (mTag == null) {
if (thatResponse.mTag != null) {
return false;
}
} else {
if (!mTag.equals(thatResponse.mTag)) {
return false;
}
}
if (mIsContinuationRequest != thatResponse.mIsContinuationRequest) {
return false;
}
return true;
}
}

View File

@ -1,450 +0,0 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.email.mail.store.imap;
import android.text.TextUtils;
import android.util.Log;
import com.android.email.FixedLengthInputStream;
import com.android.email.PeekableInputStream;
import com.android.email.mail.transport.DiscourseLogger;
import com.android.email2.ui.MailActivityEmail;
import com.android.emailcommon.Logging;
import com.android.emailcommon.mail.MessagingException;
import com.android.emailcommon.utility.LoggingInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
/**
* IMAP response parser.
*/
public class ImapResponseParser {
private static final boolean DEBUG_LOG_RAW_STREAM = false; // DO NOT RELEASE AS 'TRUE'
/**
* Literal larger than this will be stored in temp file.
*/
public static final int LITERAL_KEEP_IN_MEMORY_THRESHOLD = 2 * 1024 * 1024;
/** Input stream */
private final PeekableInputStream mIn;
/**
* To log network activities when the parser crashes.
*
* <p>We log all bytes received from the server, except for the part sent as literals.
*/
private final DiscourseLogger mDiscourseLogger;
private final int mLiteralKeepInMemoryThreshold;
/** StringBuilder used by readUntil() */
private final StringBuilder mBufferReadUntil = new StringBuilder();
/** StringBuilder used by parseBareString() */
private final StringBuilder mParseBareString = new StringBuilder();
/**
* We store all {@link ImapResponse} in it. {@link #destroyResponses()} must be called from
* time to time to destroy them and clear it.
*/
private final ArrayList<ImapResponse> mResponsesToDestroy = new ArrayList<ImapResponse>();
/**
* Exception thrown when we receive BYE. It derives from IOException, so it'll be treated
* in the same way EOF does.
*/
public static class ByeException extends IOException {
public static final String MESSAGE = "Received BYE";
public ByeException() {
super(MESSAGE);
}
}
/**
* Public constructor for normal use.
*/
public ImapResponseParser(InputStream in, DiscourseLogger discourseLogger) {
this(in, discourseLogger, LITERAL_KEEP_IN_MEMORY_THRESHOLD);
}
/**
* Constructor for testing to override the literal size threshold.
*/
/* package for test */ ImapResponseParser(InputStream in, DiscourseLogger discourseLogger,
int literalKeepInMemoryThreshold) {
if (DEBUG_LOG_RAW_STREAM && MailActivityEmail.DEBUG) {
in = new LoggingInputStream(in);
}
mIn = new PeekableInputStream(in);
mDiscourseLogger = discourseLogger;
mLiteralKeepInMemoryThreshold = literalKeepInMemoryThreshold;
}
private static IOException newEOSException() {
final String message = "End of stream reached";
if (MailActivityEmail.DEBUG) {
Log.d(Logging.LOG_TAG, message);
}
return new IOException(message);
}
/**
* Peek next one byte.
*
* Throws IOException() if reaches EOF. As long as logical response lines end with \r\n,
* we shouldn't see EOF during parsing.
*/
private int peek() throws IOException {
final int next = mIn.peek();
if (next == -1) {
throw newEOSException();
}
return next;
}
/**
* Read and return one byte from {@link #mIn}, and put it in {@link #mDiscourseLogger}.
*
* Throws IOException() if reaches EOF. As long as logical response lines end with \r\n,
* we shouldn't see EOF during parsing.
*/
private int readByte() throws IOException {
int next = mIn.read();
if (next == -1) {
throw newEOSException();
}
mDiscourseLogger.addReceivedByte(next);
return next;
}
/**
* Destroy all the {@link ImapResponse}s stored in the internal storage and clear it.
*
* @see #readResponse()
*/
public void destroyResponses() {
for (ImapResponse r : mResponsesToDestroy) {
r.destroy();
}
mResponsesToDestroy.clear();
}
/**
* Reads the next response available on the stream and returns an
* {@link ImapResponse} object that represents it.
*
* <p>When this method successfully returns an {@link ImapResponse}, the {@link ImapResponse}
* is stored in the internal storage. When the {@link ImapResponse} is no longer used
* {@link #destroyResponses} should be called to destroy all the responses in the array.
*
* @return the parsed {@link ImapResponse} object.
* @exception ByeException when detects BYE.
*/
public ImapResponse readResponse() throws IOException, MessagingException {
ImapResponse response = null;
try {
response = parseResponse();
if (MailActivityEmail.DEBUG) {
Log.d(Logging.LOG_TAG, "<<< " + response.toString());
}
} catch (RuntimeException e) {
// Parser crash -- log network activities.
onParseError(e);
throw e;
} catch (IOException e) {
// Network error, or received an unexpected char.
onParseError(e);
throw e;
}
// Handle this outside of try-catch. We don't have to dump protocol log when getting BYE.
if (response.is(0, ImapConstants.BYE)) {
Log.w(Logging.LOG_TAG, ByeException.MESSAGE);
response.destroy();
throw new ByeException();
}
mResponsesToDestroy.add(response);
return response;
}
private void onParseError(Exception e) {
// Read a few more bytes, so that the log will contain some more context, even if the parser
// crashes in the middle of a response.
// This also makes sure the byte in question will be logged, no matter where it crashes.
// e.g. when parseAtom() peeks and finds at an unexpected char, it throws an exception
// before actually reading it.
// However, we don't want to read too much, because then it may get into an email message.
try {
for (int i = 0; i < 4; i++) {
int b = readByte();
if (b == -1 || b == '\n') {
break;
}
}
} catch (IOException ignore) {
}
Log.w(Logging.LOG_TAG, "Exception detected: " + e.getMessage());
mDiscourseLogger.logLastDiscourse();
}
/**
* Read next byte from stream and throw it away. If the byte is different from {@code expected}
* throw {@link MessagingException}.
*/
/* package for test */ void expect(char expected) throws IOException {
final int next = readByte();
if (expected != next) {
throw new IOException(String.format("Expected %04x (%c) but got %04x (%c)",
(int) expected, expected, next, (char) next));
}
}
/**
* Read bytes until we find {@code end}, and return all as string.
* The {@code end} will be read (rather than peeked) and won't be included in the result.
*/
/* package for test */ String readUntil(char end) throws IOException {
mBufferReadUntil.setLength(0);
for (;;) {
final int ch = readByte();
if (ch != end) {
mBufferReadUntil.append((char) ch);
} else {
return mBufferReadUntil.toString();
}
}
}
/**
* Read all bytes until \r\n.
*/
/* package */ String readUntilEol() throws IOException {
String ret = readUntil('\r');
expect('\n'); // TODO Should this really be error?
return ret;
}
/**
* Parse and return the response line.
*/
private ImapResponse parseResponse() throws IOException, MessagingException {
// We need to destroy the response if we get an exception.
// So, we first store the response that's being built in responseToDestroy, until it's
// completely built, at which point we copy it into responseToReturn and null out
// responseToDestroyt.
// If responseToDestroy is not null in finally, we destroy it because that means
// we got an exception somewhere.
ImapResponse responseToDestroy = null;
final ImapResponse responseToReturn;
try {
final int ch = peek();
if (ch == '+') { // Continuation request
readByte(); // skip +
expect(' ');
responseToDestroy = new ImapResponse(null, true);
// If it's continuation request, we don't really care what's in it.
responseToDestroy.add(new ImapSimpleString(readUntilEol()));
// Response has successfully been built. Let's return it.
responseToReturn = responseToDestroy;
responseToDestroy = null;
} else {
// Status response or response data
final String tag;
if (ch == '*') {
tag = null;
readByte(); // skip *
expect(' ');
} else {
tag = readUntil(' ');
}
responseToDestroy = new ImapResponse(tag, false);
final ImapString firstString = parseBareString();
responseToDestroy.add(firstString);
// parseBareString won't eat a space after the string, so we need to skip it,
// if exists.
// If the next char is not ' ', it should be EOL.
if (peek() == ' ') {
readByte(); // skip ' '
if (responseToDestroy.isStatusResponse()) { // It's a status response
// Is there a response code?
final int next = peek();
if (next == '[') {
responseToDestroy.add(parseList('[', ']'));
if (peek() == ' ') { // Skip following space
readByte();
}
}
String rest = readUntilEol();
if (!TextUtils.isEmpty(rest)) {
// The rest is free-form text.
responseToDestroy.add(new ImapSimpleString(rest));
}
} else { // It's a response data.
parseElements(responseToDestroy, '\0');
}
} else {
expect('\r');
expect('\n');
}
// Response has successfully been built. Let's return it.
responseToReturn = responseToDestroy;
responseToDestroy = null;
}
} finally {
if (responseToDestroy != null) {
// We get an exception.
responseToDestroy.destroy();
}
}
return responseToReturn;
}
private ImapElement parseElement() throws IOException, MessagingException {
final int next = peek();
switch (next) {
case '(':
return parseList('(', ')');
case '[':
return parseList('[', ']');
case '"':
readByte(); // Skip "
return new ImapSimpleString(readUntil('"'));
case '{':
return parseLiteral();
case '\r': // CR
readByte(); // Consume \r
expect('\n'); // Should be followed by LF.
return null;
case '\n': // LF // There shouldn't be a bare LF, but just in case.
readByte(); // Consume \n
return null;
default:
return parseBareString();
}
}
/**
* Parses an atom.
*
* Special case: If an atom contains '[', everything until the next ']' will be considered
* a part of the atom.
* (e.g. "BODY[HEADER.FIELDS ("DATE" ...)]" will become a single ImapString)
*
* If the value is "NIL", returns an empty string.
*/
private ImapString parseBareString() throws IOException, MessagingException {
mParseBareString.setLength(0);
for (;;) {
final int ch = peek();
// TODO Can we clean this up? (This condition is from the old parser.)
if (ch == '(' || ch == ')' || ch == '{' || ch == ' ' ||
// ']' is not part of atom (it's in resp-specials)
ch == ']' ||
// docs claim that flags are \ atom but atom isn't supposed to
// contain
// * and some flags contain *
// ch == '%' || ch == '*' ||
ch == '%' ||
// TODO probably should not allow \ and should recognize
// it as a flag instead
// ch == '"' || ch == '\' ||
ch == '"' || (0x00 <= ch && ch <= 0x1f) || ch == 0x7f) {
if (mParseBareString.length() == 0) {
throw new MessagingException("Expected string, none found.");
}
String s = mParseBareString.toString();
// NIL will be always converted into the empty string.
if (ImapConstants.NIL.equalsIgnoreCase(s)) {
return ImapString.EMPTY;
}
return new ImapSimpleString(s);
} else if (ch == '[') {
// Eat all until next ']'
mParseBareString.append((char) readByte());
mParseBareString.append(readUntil(']'));
mParseBareString.append(']'); // readUntil won't include the end char.
} else {
mParseBareString.append((char) readByte());
}
}
}
private void parseElements(ImapList list, char end)
throws IOException, MessagingException {
for (;;) {
for (;;) {
final int next = peek();
if (next == end) {
return;
}
if (next != ' ') {
break;
}
// Skip space
readByte();
}
final ImapElement el = parseElement();
if (el == null) { // EOL
return;
}
list.add(el);
}
}
private ImapList parseList(char opening, char closing)
throws IOException, MessagingException {
expect(opening);
final ImapList list = new ImapList();
parseElements(list, closing);
expect(closing);
return list;
}
private ImapString parseLiteral() throws IOException, MessagingException {
expect('{');
final int size;
try {
size = Integer.parseInt(readUntil('}'));
} catch (NumberFormatException nfe) {
throw new MessagingException("Invalid length in literal");
}
expect('\r');
expect('\n');
FixedLengthInputStream in = new FixedLengthInputStream(mIn, size);
if (size > mLiteralKeepInMemoryThreshold) {
return new ImapTempFileLiteral(in);
} else {
return new ImapMemoryLiteral(in);
}
}
}

View File

@ -1,55 +0,0 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.email.mail.store.imap;
import com.android.emailcommon.utility.Utility;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
/**
* Subclass of {@link ImapString} used for non literals.
*/
public class ImapSimpleString extends ImapString {
private String mString;
/* package */ ImapSimpleString(String string) {
mString = (string != null) ? string : "";
}
@Override
public void destroy() {
mString = null;
super.destroy();
}
@Override
public String getString() {
return mString;
}
@Override
public InputStream getAsStream() {
return new ByteArrayInputStream(Utility.toAscii(mString));
}
@Override
public String toString() {
// Purposefully not return just mString, in order to prevent using it instead of getString.
return "\"" + mString + "\"";
}
}

View File

@ -1,187 +0,0 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.email.mail.store.imap;
import com.android.emailcommon.Logging;
import android.util.Log;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
/**
* Class represents an IMAP "element" that is not a list.
*
* An atom, quoted string, literal, are all represented by this. Values like OK, STATUS are too.
* Also, this class class may contain more arbitrary value like "BODY[HEADER.FIELDS ("DATE")]".
* See {@link ImapResponseParser}.
*/
public abstract class ImapString extends ImapElement {
private static final byte[] EMPTY_BYTES = new byte[0];
public static final ImapString EMPTY = new ImapString() {
@Override public void destroy() {
// Don't call super.destroy().
// It's a shared object. We don't want the mDestroyed to be set on this.
}
@Override public String getString() {
return "";
}
@Override public InputStream getAsStream() {
return new ByteArrayInputStream(EMPTY_BYTES);
}
@Override public String toString() {
return "";
}
};
// This is used only for parsing IMAP's FETCH ENVELOPE command, in which
// en_US-like date format is used like "01-Jan-2009 11:20:39 -0800", so this should be
// handled by Locale.US
private final static SimpleDateFormat DATE_TIME_FORMAT =
new SimpleDateFormat("dd-MMM-yyyy HH:mm:ss Z", Locale.US);
private boolean mIsInteger;
private int mParsedInteger;
private Date mParsedDate;
@Override
public final boolean isList() {
return false;
}
@Override
public final boolean isString() {
return true;
}
/**
* @return true if and only if the length of the string is larger than 0.
*
* Note: IMAP NIL is considered an empty string. See {@link ImapResponseParser
* #parseBareString}.
* On the other hand, a quoted/literal string with value NIL (i.e. "NIL" and {3}\r\nNIL) is
* treated literally.
*/
public final boolean isEmpty() {
return getString().length() == 0;
}
public abstract String getString();
public abstract InputStream getAsStream();
/**
* @return whether it can be parsed as a number.
*/
public final boolean isNumber() {
if (mIsInteger) {
return true;
}
try {
mParsedInteger = Integer.parseInt(getString());
mIsInteger = true;
return true;
} catch (NumberFormatException e) {
return false;
}
}
/**
* @return value parsed as a number.
*/
public final int getNumberOrZero() {
if (!isNumber()) {
return 0;
}
return mParsedInteger;
}
/**
* @return whether it can be parsed as a date using {@link #DATE_TIME_FORMAT}.
*/
public final boolean isDate() {
if (mParsedDate != null) {
return true;
}
if (isEmpty()) {
return false;
}
try {
mParsedDate = DATE_TIME_FORMAT.parse(getString());
return true;
} catch (ParseException e) {
Log.w(Logging.LOG_TAG, getString() + " can't be parsed as a date.");
return false;
}
}
/**
* @return value it can be parsed as a {@link Date}, or null otherwise.
*/
public final Date getDateOrNull() {
if (!isDate()) {
return null;
}
return mParsedDate;
}
/**
* @return whether the value case-insensitively equals to {@code s}.
*/
public final boolean is(String s) {
if (s == null) {
return false;
}
return getString().equalsIgnoreCase(s);
}
/**
* @return whether the value case-insensitively starts with {@code s}.
*/
public final boolean startsWith(String prefix) {
if (prefix == null) {
return false;
}
final String me = this.getString();
if (me.length() < prefix.length()) {
return false;
}
return me.substring(0, prefix.length()).equalsIgnoreCase(prefix);
}
// To force subclasses to implement it.
@Override
public abstract String toString();
@Override
public final boolean equalsForTest(ImapElement that) {
if (!super.equalsForTest(that)) {
return false;
}
ImapString thatString = (ImapString) that;
return getString().equals(thatString.getString());
}
}

View File

@ -1,124 +0,0 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.email.mail.store.imap;
import android.util.Log;
import com.android.email.FixedLengthInputStream;
import com.android.emailcommon.Logging;
import com.android.emailcommon.TempDirectory;
import com.android.emailcommon.utility.Utility;
import org.apache.commons.io.IOUtils;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
/**
* Subclass of {@link ImapString} used for literals backed by a temp file.
*/
public class ImapTempFileLiteral extends ImapString {
/* package for test */ final File mFile;
/** Size is purely for toString() */
private final int mSize;
/* package */ ImapTempFileLiteral(FixedLengthInputStream stream) throws IOException {
mSize = stream.getLength();
mFile = File.createTempFile("imap", ".tmp", TempDirectory.getTempDirectory());
// Unfortunately, we can't really use deleteOnExit(), because temp filenames are random
// so it'd simply cause a memory leak.
// deleteOnExit() simply adds filenames to a static list and the list will never shrink.
// mFile.deleteOnExit();
OutputStream out = new FileOutputStream(mFile);
IOUtils.copy(stream, out);
out.close();
}
/**
* Make sure we delete the temp file.
*
* We should always be calling {@link ImapResponse#destroy()}, but it's here as a last resort.
*/
@Override
protected void finalize() throws Throwable {
try {
destroy();
} finally {
super.finalize();
}
}
@Override
public InputStream getAsStream() {
checkNotDestroyed();
try {
return new FileInputStream(mFile);
} catch (FileNotFoundException e) {
// It's probably possible if we're low on storage and the system clears the cache dir.
Log.w(Logging.LOG_TAG, "ImapTempFileLiteral: Temp file not found");
// Return 0 byte stream as a dummy...
return new ByteArrayInputStream(new byte[0]);
}
}
@Override
public String getString() {
checkNotDestroyed();
try {
byte[] bytes = IOUtils.toByteArray(getAsStream());
// Prevent crash from OOM; we've seen this, but only rarely and not reproducibly
if (bytes.length > ImapResponseParser.LITERAL_KEEP_IN_MEMORY_THRESHOLD) {
throw new IOException();
}
return Utility.fromAscii(bytes);
} catch (IOException e) {
Log.w(Logging.LOG_TAG, "ImapTempFileLiteral: Error while reading temp file", e);
return "";
}
}
@Override
public void destroy() {
try {
if (!isDestroyed() && mFile.exists()) {
mFile.delete();
}
} catch (RuntimeException re) {
// Just log and ignore.
Log.w(Logging.LOG_TAG, "Failed to remove temp file: " + re.getMessage());
}
super.destroy();
}
@Override
public String toString() {
return String.format("{%d byte literal(file)}", mSize);
}
public boolean tempFileExistsForTest() {
return mFile.exists();
}
}

View File

@ -1,127 +0,0 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.email.mail.store.imap;
import com.android.emailcommon.Logging;
import android.util.Log;
import java.util.ArrayList;
/**
* Utility methods for use with IMAP.
*/
public class ImapUtility {
/**
* Apply quoting rules per IMAP RFC,
* quoted = DQUOTE *QUOTED-CHAR DQUOTE
* QUOTED-CHAR = <any TEXT-CHAR except quoted-specials> / "\" quoted-specials
* quoted-specials = DQUOTE / "\"
*
* This is used primarily for IMAP login, but might be useful elsewhere.
*
* NOTE: Not very efficient - you may wish to preflight this, or perhaps it should check
* for trouble chars before calling the replace functions.
*
* @param s The string to be quoted.
* @return A copy of the string, having undergone quoting as described above
*/
public static String imapQuoted(String s) {
// First, quote any backslashes by replacing \ with \\
// regex Pattern: \\ (Java string const = \\\\)
// Substitute: \\\\ (Java string const = \\\\\\\\)
String result = s.replaceAll("\\\\", "\\\\\\\\");
// Then, quote any double-quotes by replacing " with \"
// regex Pattern: " (Java string const = \")
// Substitute: \\" (Java string const = \\\\\")
result = result.replaceAll("\"", "\\\\\"");
// return string with quotes around it
return "\"" + result + "\"";
}
/**
* Gets all of the values in a sequence set per RFC 3501. Any ranges are expanded into a
* list of individual numbers. If the set is invalid, an empty array is returned.
* <pre>
* sequence-number = nz-number / "*"
* sequence-range = sequence-number ":" sequence-number
* sequence-set = (sequence-number / sequence-range) *("," sequence-set)
* </pre>
*/
public static String[] getImapSequenceValues(String set) {
ArrayList<String> list = new ArrayList<String>();
if (set != null) {
String[] setItems = set.split(",");
for (String item : setItems) {
if (item.indexOf(':') == -1) {
// simple item
try {
Integer.parseInt(item); // Don't need the value; just ensure it's valid
list.add(item);
} catch (NumberFormatException e) {
Log.d(Logging.LOG_TAG, "Invalid UID value", e);
}
} else {
// range
for (String rangeItem : getImapRangeValues(item)) {
list.add(rangeItem);
}
}
}
}
String[] stringList = new String[list.size()];
return list.toArray(stringList);
}
/**
* Expand the given number range into a list of individual numbers. If the range is not valid,
* an empty array is returned.
* <pre>
* sequence-number = nz-number / "*"
* sequence-range = sequence-number ":" sequence-number
* sequence-set = (sequence-number / sequence-range) *("," sequence-set)
* </pre>
*/
public static String[] getImapRangeValues(String range) {
ArrayList<String> list = new ArrayList<String>();
try {
if (range != null) {
int colonPos = range.indexOf(':');
if (colonPos > 0) {
int first = Integer.parseInt(range.substring(0, colonPos));
int second = Integer.parseInt(range.substring(colonPos + 1));
if (first < second) {
for (int i = first; i <= second; i++) {
list.add(Integer.toString(i));
}
} else {
for (int i = first; i >= second; i--) {
list.add(Integer.toString(i));
}
}
}
}
} catch (NumberFormatException e) {
Log.d(Logging.LOG_TAG, "Invalid range value", e);
}
String[] stringList = new String[list.size()];
return list.toArray(stringList);
}
}

View File

@ -49,10 +49,10 @@ import java.util.List;
* A simple ContentProvider that allows file access to Email's attachments.
*
* The URI scheme is as follows. For raw file access:
* content://com.android.email.attachmentprovider/acct#/attach#/RAW
* content://com.android.mail.attachmentprovider/acct#/attach#/RAW
*
* And for access to thumbnails:
* content://com.android.email.attachmentprovider/acct#/attach#/THUMBNAIL/width#/height#
* content://com.android.mail.attachmentprovider/acct#/attach#/THUMBNAIL/width#/height#
*
* The on-disk (storage) schema is as follows.
*

View File

@ -29,7 +29,6 @@ import android.provider.ContactsContract;
import android.util.Log;
import com.android.email2.ui.MailActivityEmail;
import com.android.emailcommon.AccountManagerTypes;
import com.android.emailcommon.mail.Address;
import com.android.emailcommon.provider.Account;
import com.android.emailcommon.provider.EmailContent;
@ -586,7 +585,7 @@ public final class DBHelper {
// Versions >= 5 require that data be preserved!
if (oldVersion < 5) {
android.accounts.Account[] accounts = AccountManager.get(mContext)
.getAccountsByType(AccountManagerTypes.TYPE_EXCHANGE);
.getAccountsByType("eas");
for (android.accounts.Account account: accounts) {
AccountManager.get(mContext).removeAccount(account, null, null);
}
@ -1092,8 +1091,9 @@ public final class DBHelper {
static private void createAccountManagerAccount(Context context, String login,
String password) {
AccountManager accountManager = AccountManager.get(context);
// STOPSHIP
android.accounts.Account amAccount =
new android.accounts.Account(login, AccountManagerTypes.TYPE_POP_IMAP);
new android.accounts.Account(login, "com.android.email");
accountManager.addAccountExplicitly(amAccount, password, null);
ContentResolver.setIsSyncable(amAccount, EmailContent.AUTHORITY, 1);
ContentResolver.setSyncAutomatically(amAccount, EmailContent.AUTHORITY, true);
@ -1135,7 +1135,7 @@ public final class DBHelper {
android.accounts.Account amAccount =
new android.accounts.Account(
accountCursor.getString(V21_ACCOUNT_EMAIL),
AccountManagerTypes.TYPE_EXCHANGE);
"eas");
ContentResolver.setIsSyncable(amAccount, EmailContent.AUTHORITY, 1);
ContentResolver.setSyncAutomatically(amAccount,
EmailContent.AUTHORITY, true);

View File

@ -136,15 +136,6 @@ public class EmailProvider extends ContentProvider {
public static final String EMAIL_ATTACHMENT_MIME_TYPE =
"vnd.android.cursor.item/email-attachment";
public static final Uri INTEGRITY_CHECK_URI =
Uri.parse("content://" + EmailContent.AUTHORITY + "/integrityCheck");
public static final Uri ACCOUNT_BACKUP_URI =
Uri.parse("content://" + EmailContent.AUTHORITY + "/accountBackup");
public static final Uri FOLDER_STATUS_URI =
Uri.parse("content://" + EmailContent.AUTHORITY + "/status");
public static final Uri FOLDER_REFRESH_URI =
Uri.parse("content://" + EmailContent.AUTHORITY + "/refresh");
/** Appended to the notification URI for delete operations */
public static final String NOTIFICATION_OP_DELETE = "delete";
/** Appended to the notification URI for insert operations */
@ -310,7 +301,7 @@ public class EmailProvider extends ContentProvider {
null // UI
};
private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);
private static UriMatcher sURIMatcher = null;
private static final String MAILBOX_PRE_CACHE_SELECTION = MailboxColumns.TYPE + " IN (" +
Mailbox.TYPE_INBOX + "," + Mailbox.TYPE_DRAFTS + "," + Mailbox.TYPE_TRASH + "," +
@ -341,7 +332,7 @@ public class EmailProvider extends ContentProvider {
private static final String ID_EQUALS = EmailContent.RECORD_ID + "=?";
private static final ContentValues CONTENT_VALUES_RESET_NEW_MESSAGE_COUNT;
private static ContentValues CONTENT_VALUES_RESET_NEW_MESSAGE_COUNT;
private static final ContentValues EMPTY_CONTENT_VALUES = new ContentValues();
public static final String MESSAGE_URI_PARAMETER_MAILBOX_ID = "mailboxId";
@ -357,130 +348,6 @@ public class EmailProvider extends ContentProvider {
private static final String SWIPE_DELETE = Integer.toString(Swipe.DELETE);
private static final String SWIPE_DISABLED = Integer.toString(Swipe.DISABLED);
static {
// Email URI matching table
UriMatcher matcher = sURIMatcher;
// All accounts
matcher.addURI(EmailContent.AUTHORITY, "account", ACCOUNT);
// A specific account
// insert into this URI causes a mailbox to be added to the account
matcher.addURI(EmailContent.AUTHORITY, "account/#", ACCOUNT_ID);
matcher.addURI(EmailContent.AUTHORITY, "account/default", ACCOUNT_DEFAULT_ID);
matcher.addURI(EmailContent.AUTHORITY, "accountCheck/#", ACCOUNT_CHECK);
// Special URI to reset the new message count. Only update works, and content values
// will be ignored.
matcher.addURI(EmailContent.AUTHORITY, "resetNewMessageCount",
ACCOUNT_RESET_NEW_COUNT);
matcher.addURI(EmailContent.AUTHORITY, "resetNewMessageCount/#",
ACCOUNT_RESET_NEW_COUNT_ID);
// All mailboxes
matcher.addURI(EmailContent.AUTHORITY, "mailbox", MAILBOX);
// A specific mailbox
// insert into this URI causes a message to be added to the mailbox
// ** NOTE For now, the accountKey must be set manually in the values!
matcher.addURI(EmailContent.AUTHORITY, "mailbox/#", MAILBOX_ID);
matcher.addURI(EmailContent.AUTHORITY, "mailboxIdFromAccountAndType/#/#",
MAILBOX_ID_FROM_ACCOUNT_AND_TYPE);
matcher.addURI(EmailContent.AUTHORITY, "mailboxNotification/#", MAILBOX_NOTIFICATION);
matcher.addURI(EmailContent.AUTHORITY, "mailboxMostRecentMessage/#",
MAILBOX_MOST_RECENT_MESSAGE);
// All messages
matcher.addURI(EmailContent.AUTHORITY, "message", MESSAGE);
// A specific message
// insert into this URI causes an attachment to be added to the message
matcher.addURI(EmailContent.AUTHORITY, "message/#", MESSAGE_ID);
// A specific attachment
matcher.addURI(EmailContent.AUTHORITY, "attachment", ATTACHMENT);
// A specific attachment (the header information)
matcher.addURI(EmailContent.AUTHORITY, "attachment/#", ATTACHMENT_ID);
// The attachments of a specific message (query only) (insert & delete TBD)
matcher.addURI(EmailContent.AUTHORITY, "attachment/message/#",
ATTACHMENTS_MESSAGE_ID);
// All mail bodies
matcher.addURI(EmailContent.AUTHORITY, "body", BODY);
// A specific mail body
matcher.addURI(EmailContent.AUTHORITY, "body/#", BODY_ID);
// All hostauth records
matcher.addURI(EmailContent.AUTHORITY, "hostauth", HOSTAUTH);
// A specific hostauth
matcher.addURI(EmailContent.AUTHORITY, "hostauth/*", HOSTAUTH_ID);
// Atomically a constant value to a particular field of a mailbox/account
matcher.addURI(EmailContent.AUTHORITY, "mailboxIdAddToField/#",
MAILBOX_ID_ADD_TO_FIELD);
matcher.addURI(EmailContent.AUTHORITY, "accountIdAddToField/#",
ACCOUNT_ID_ADD_TO_FIELD);
/**
* THIS URI HAS SPECIAL SEMANTICS
* ITS USE IS INTENDED FOR THE UI APPLICATION TO MARK CHANGES THAT NEED TO BE SYNCED BACK
* TO A SERVER VIA A SYNC ADAPTER
*/
matcher.addURI(EmailContent.AUTHORITY, "syncedMessage/#", SYNCED_MESSAGE_ID);
matcher.addURI(EmailContent.AUTHORITY, "messageBySelection", MESSAGE_SELECTION);
/**
* THE URIs BELOW THIS POINT ARE INTENDED TO BE USED BY SYNC ADAPTERS ONLY
* THEY REFER TO DATA CREATED AND MAINTAINED BY CALLS TO THE SYNCED_MESSAGE_ID URI
* BY THE UI APPLICATION
*/
// All deleted messages
matcher.addURI(EmailContent.AUTHORITY, "deletedMessage", DELETED_MESSAGE);
// A specific deleted message
matcher.addURI(EmailContent.AUTHORITY, "deletedMessage/#", DELETED_MESSAGE_ID);
// All updated messages
matcher.addURI(EmailContent.AUTHORITY, "updatedMessage", UPDATED_MESSAGE);
// A specific updated message
matcher.addURI(EmailContent.AUTHORITY, "updatedMessage/#", UPDATED_MESSAGE_ID);
CONTENT_VALUES_RESET_NEW_MESSAGE_COUNT = new ContentValues();
CONTENT_VALUES_RESET_NEW_MESSAGE_COUNT.put(Account.NEW_MESSAGE_COUNT, 0);
matcher.addURI(EmailContent.AUTHORITY, "policy", POLICY);
matcher.addURI(EmailContent.AUTHORITY, "policy/#", POLICY_ID);
// All quick responses
matcher.addURI(EmailContent.AUTHORITY, "quickresponse", QUICK_RESPONSE);
// A specific quick response
matcher.addURI(EmailContent.AUTHORITY, "quickresponse/#", QUICK_RESPONSE_ID);
// All quick responses associated with a particular account id
matcher.addURI(EmailContent.AUTHORITY, "quickresponse/account/#",
QUICK_RESPONSE_ACCOUNT_ID);
matcher.addURI(EmailContent.AUTHORITY, "uifolders/#", UI_FOLDERS);
matcher.addURI(EmailContent.AUTHORITY, "uiallfolders/#", UI_ALL_FOLDERS);
matcher.addURI(EmailContent.AUTHORITY, "uisubfolders/#", UI_SUBFOLDERS);
matcher.addURI(EmailContent.AUTHORITY, "uimessages/#", UI_MESSAGES);
matcher.addURI(EmailContent.AUTHORITY, "uimessage/#", UI_MESSAGE);
matcher.addURI(EmailContent.AUTHORITY, "uisendmail/#", UI_SENDMAIL);
matcher.addURI(EmailContent.AUTHORITY, "uiundo", UI_UNDO);
matcher.addURI(EmailContent.AUTHORITY, "uisavedraft/#", UI_SAVEDRAFT);
matcher.addURI(EmailContent.AUTHORITY, "uiupdatedraft/#", UI_UPDATEDRAFT);
matcher.addURI(EmailContent.AUTHORITY, "uisenddraft/#", UI_SENDDRAFT);
matcher.addURI(EmailContent.AUTHORITY, "uirefresh/#", UI_FOLDER_REFRESH);
matcher.addURI(EmailContent.AUTHORITY, "uifolder/#", UI_FOLDER);
matcher.addURI(EmailContent.AUTHORITY, "uiaccount/#", UI_ACCOUNT);
matcher.addURI(EmailContent.AUTHORITY, "uiaccts", UI_ACCTS);
matcher.addURI(EmailContent.AUTHORITY, "uiattachments/#", UI_ATTACHMENTS);
matcher.addURI(EmailContent.AUTHORITY, "uiattachment/#", UI_ATTACHMENT);
matcher.addURI(EmailContent.AUTHORITY, "uisearch/#", UI_SEARCH);
matcher.addURI(EmailContent.AUTHORITY, "uiaccountdata/#", UI_ACCOUNT_DATA);
matcher.addURI(EmailContent.AUTHORITY, "uiloadmore/#", UI_FOLDER_LOAD_MORE);
matcher.addURI(EmailContent.AUTHORITY, "uiconversation/#", UI_CONVERSATION);
matcher.addURI(EmailContent.AUTHORITY, "uirecentfolders/#", UI_RECENT_FOLDERS);
matcher.addURI(EmailContent.AUTHORITY, "uidefaultrecentfolders/#",
UI_DEFAULT_RECENT_FOLDERS);
matcher.addURI(EmailContent.AUTHORITY, "pickTrashFolder/#", ACCOUNT_PICK_TRASH_FOLDER);
matcher.addURI(EmailContent.AUTHORITY, "pickSentFolder/#", ACCOUNT_PICK_SENT_FOLDER);
}
/**
* Wrap the UriMatcher call so we can throw a runtime exception if an unknown Uri is passed in
@ -497,6 +364,11 @@ public class EmailProvider extends ContentProvider {
return match;
}
public static Uri INTEGRITY_CHECK_URI;
public static Uri ACCOUNT_BACKUP_URI;
public static Uri FOLDER_STATUS_URI;
public static Uri FOLDER_REFRESH_URI;
private SQLiteDatabase mDatabase;
private SQLiteDatabase mBodyDatabase;
@ -1171,10 +1043,147 @@ public class EmailProvider extends ContentProvider {
return resultUri;
}
@Override
public boolean onCreate() {
MailActivityEmail.setServicesEnabledAsync(getContext());
checkDatabases();
@Override
public boolean onCreate() {
Context context = getContext();
EmailContent.init(context);
if (INTEGRITY_CHECK_URI == null) {
INTEGRITY_CHECK_URI = Uri.parse("content://" + EmailContent.AUTHORITY +
"/integrityCheck");
ACCOUNT_BACKUP_URI =
Uri.parse("content://" + EmailContent.AUTHORITY + "/accountBackup");
FOLDER_STATUS_URI =
Uri.parse("content://" + EmailContent.AUTHORITY + "/status");
FOLDER_REFRESH_URI =
Uri.parse("content://" + EmailContent.AUTHORITY + "/refresh");
}
MailActivityEmail.setServicesEnabledAsync(context);
checkDatabases();
if (sURIMatcher == null) {
sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);
// Email URI matching table
UriMatcher matcher = sURIMatcher;
// All accounts
matcher.addURI(EmailContent.AUTHORITY, "account", ACCOUNT);
// A specific account
// insert into this URI causes a mailbox to be added to the account
matcher.addURI(EmailContent.AUTHORITY, "account/#", ACCOUNT_ID);
matcher.addURI(EmailContent.AUTHORITY, "account/default", ACCOUNT_DEFAULT_ID);
matcher.addURI(EmailContent.AUTHORITY, "accountCheck/#", ACCOUNT_CHECK);
// Special URI to reset the new message count. Only update works, and content values
// will be ignored.
matcher.addURI(EmailContent.AUTHORITY, "resetNewMessageCount",
ACCOUNT_RESET_NEW_COUNT);
matcher.addURI(EmailContent.AUTHORITY, "resetNewMessageCount/#",
ACCOUNT_RESET_NEW_COUNT_ID);
// All mailboxes
matcher.addURI(EmailContent.AUTHORITY, "mailbox", MAILBOX);
// A specific mailbox
// insert into this URI causes a message to be added to the mailbox
// ** NOTE For now, the accountKey must be set manually in the values!
matcher.addURI(EmailContent.AUTHORITY, "mailbox/#", MAILBOX_ID);
matcher.addURI(EmailContent.AUTHORITY, "mailboxIdFromAccountAndType/#/#",
MAILBOX_ID_FROM_ACCOUNT_AND_TYPE);
matcher.addURI(EmailContent.AUTHORITY, "mailboxNotification/#", MAILBOX_NOTIFICATION);
matcher.addURI(EmailContent.AUTHORITY, "mailboxMostRecentMessage/#",
MAILBOX_MOST_RECENT_MESSAGE);
// All messages
matcher.addURI(EmailContent.AUTHORITY, "message", MESSAGE);
// A specific message
// insert into this URI causes an attachment to be added to the message
matcher.addURI(EmailContent.AUTHORITY, "message/#", MESSAGE_ID);
// A specific attachment
matcher.addURI(EmailContent.AUTHORITY, "attachment", ATTACHMENT);
// A specific attachment (the header information)
matcher.addURI(EmailContent.AUTHORITY, "attachment/#", ATTACHMENT_ID);
// The attachments of a specific message (query only) (insert & delete TBD)
matcher.addURI(EmailContent.AUTHORITY, "attachment/message/#",
ATTACHMENTS_MESSAGE_ID);
// All mail bodies
matcher.addURI(EmailContent.AUTHORITY, "body", BODY);
// A specific mail body
matcher.addURI(EmailContent.AUTHORITY, "body/#", BODY_ID);
// All hostauth records
matcher.addURI(EmailContent.AUTHORITY, "hostauth", HOSTAUTH);
// A specific hostauth
matcher.addURI(EmailContent.AUTHORITY, "hostauth/*", HOSTAUTH_ID);
// Atomically a constant value to a particular field of a mailbox/account
matcher.addURI(EmailContent.AUTHORITY, "mailboxIdAddToField/#",
MAILBOX_ID_ADD_TO_FIELD);
matcher.addURI(EmailContent.AUTHORITY, "accountIdAddToField/#",
ACCOUNT_ID_ADD_TO_FIELD);
/**
* THIS URI HAS SPECIAL SEMANTICS
* ITS USE IS INTENDED FOR THE UI TO MARK CHANGES THAT NEED TO BE SYNCED BACK
* TO A SERVER VIA A SYNC ADAPTER
*/
matcher.addURI(EmailContent.AUTHORITY, "syncedMessage/#", SYNCED_MESSAGE_ID);
matcher.addURI(EmailContent.AUTHORITY, "messageBySelection", MESSAGE_SELECTION);
/**
* THE URIs BELOW THIS POINT ARE INTENDED TO BE USED BY SYNC ADAPTERS ONLY
* THEY REFER TO DATA CREATED AND MAINTAINED BY CALLS TO THE SYNCED_MESSAGE_ID URI
* BY THE UI APPLICATION
*/
// All deleted messages
matcher.addURI(EmailContent.AUTHORITY, "deletedMessage", DELETED_MESSAGE);
// A specific deleted message
matcher.addURI(EmailContent.AUTHORITY, "deletedMessage/#", DELETED_MESSAGE_ID);
// All updated messages
matcher.addURI(EmailContent.AUTHORITY, "updatedMessage", UPDATED_MESSAGE);
// A specific updated message
matcher.addURI(EmailContent.AUTHORITY, "updatedMessage/#", UPDATED_MESSAGE_ID);
CONTENT_VALUES_RESET_NEW_MESSAGE_COUNT = new ContentValues();
CONTENT_VALUES_RESET_NEW_MESSAGE_COUNT.put(Account.NEW_MESSAGE_COUNT, 0);
matcher.addURI(EmailContent.AUTHORITY, "policy", POLICY);
matcher.addURI(EmailContent.AUTHORITY, "policy/#", POLICY_ID);
// All quick responses
matcher.addURI(EmailContent.AUTHORITY, "quickresponse", QUICK_RESPONSE);
// A specific quick response
matcher.addURI(EmailContent.AUTHORITY, "quickresponse/#", QUICK_RESPONSE_ID);
// All quick responses associated with a particular account id
matcher.addURI(EmailContent.AUTHORITY, "quickresponse/account/#",
QUICK_RESPONSE_ACCOUNT_ID);
matcher.addURI(EmailContent.AUTHORITY, "uifolders/#", UI_FOLDERS);
matcher.addURI(EmailContent.AUTHORITY, "uiallfolders/#", UI_ALL_FOLDERS);
matcher.addURI(EmailContent.AUTHORITY, "uisubfolders/#", UI_SUBFOLDERS);
matcher.addURI(EmailContent.AUTHORITY, "uimessages/#", UI_MESSAGES);
matcher.addURI(EmailContent.AUTHORITY, "uimessage/#", UI_MESSAGE);
matcher.addURI(EmailContent.AUTHORITY, "uisendmail/#", UI_SENDMAIL);
matcher.addURI(EmailContent.AUTHORITY, "uiundo", UI_UNDO);
matcher.addURI(EmailContent.AUTHORITY, "uisavedraft/#", UI_SAVEDRAFT);
matcher.addURI(EmailContent.AUTHORITY, "uiupdatedraft/#", UI_UPDATEDRAFT);
matcher.addURI(EmailContent.AUTHORITY, "uisenddraft/#", UI_SENDDRAFT);
matcher.addURI(EmailContent.AUTHORITY, "uirefresh/#", UI_FOLDER_REFRESH);
matcher.addURI(EmailContent.AUTHORITY, "uifolder/#", UI_FOLDER);
matcher.addURI(EmailContent.AUTHORITY, "uiaccount/#", UI_ACCOUNT);
matcher.addURI(EmailContent.AUTHORITY, "uiaccts", UI_ACCTS);
matcher.addURI(EmailContent.AUTHORITY, "uiattachments/#", UI_ATTACHMENTS);
matcher.addURI(EmailContent.AUTHORITY, "uiattachment/#", UI_ATTACHMENT);
matcher.addURI(EmailContent.AUTHORITY, "uisearch/#", UI_SEARCH);
matcher.addURI(EmailContent.AUTHORITY, "uiaccountdata/#", UI_ACCOUNT_DATA);
matcher.addURI(EmailContent.AUTHORITY, "uiloadmore/#", UI_FOLDER_LOAD_MORE);
matcher.addURI(EmailContent.AUTHORITY, "uiconversation/#", UI_CONVERSATION);
matcher.addURI(EmailContent.AUTHORITY, "uirecentfolders/#", UI_RECENT_FOLDERS);
matcher.addURI(EmailContent.AUTHORITY, "uidefaultrecentfolders/#",
UI_DEFAULT_RECENT_FOLDERS);
matcher.addURI(EmailContent.AUTHORITY, "pickTrashFolder/#", ACCOUNT_PICK_TRASH_FOLDER);
matcher.addURI(EmailContent.AUTHORITY, "pickSentFolder/#", ACCOUNT_PICK_SENT_FOLDER);
}
return false;
}
@ -2121,28 +2130,34 @@ outer:
* Mapping of UIProvider columns to EmailProvider columns for the message list (called the
* conversation list in UnifiedEmail)
*/
private static final ProjectionMap sMessageListMap = ProjectionMap.builder()
.add(BaseColumns._ID, MessageColumns.ID)
.add(UIProvider.ConversationColumns.URI, uriWithId("uimessage"))
.add(UIProvider.ConversationColumns.MESSAGE_LIST_URI, uriWithId("uimessage"))
.add(UIProvider.ConversationColumns.SUBJECT, MessageColumns.SUBJECT)
.add(UIProvider.ConversationColumns.SNIPPET, MessageColumns.SNIPPET)
.add(UIProvider.ConversationColumns.CONVERSATION_INFO, null)
.add(UIProvider.ConversationColumns.DATE_RECEIVED_MS, MessageColumns.TIMESTAMP)
.add(UIProvider.ConversationColumns.HAS_ATTACHMENTS, MessageColumns.FLAG_ATTACHMENT)
.add(UIProvider.ConversationColumns.NUM_MESSAGES, "1")
.add(UIProvider.ConversationColumns.NUM_DRAFTS, "0")
.add(UIProvider.ConversationColumns.SENDING_STATE,
Integer.toString(ConversationSendingState.OTHER))
.add(UIProvider.ConversationColumns.PRIORITY, Integer.toString(ConversationPriority.LOW))
.add(UIProvider.ConversationColumns.READ, MessageColumns.FLAG_READ)
.add(UIProvider.ConversationColumns.STARRED, MessageColumns.FLAG_FAVORITE)
.add(UIProvider.ConversationColumns.FLAGS, CONVERSATION_FLAGS)
.add(UIProvider.ConversationColumns.ACCOUNT_URI,
"'content://" + EmailContent.AUTHORITY + "/uiaccount/' || "
+ MessageColumns.ACCOUNT_KEY)
.add(UIProvider.ConversationColumns.SENDER_INFO, MessageColumns.FROM_LIST)
.build();
private ProjectionMap getMessageListMap() {
if (sMessageListMap == null) {
sMessageListMap = ProjectionMap.builder()
.add(BaseColumns._ID, MessageColumns.ID)
.add(UIProvider.ConversationColumns.URI, uriWithId("uimessage"))
.add(UIProvider.ConversationColumns.MESSAGE_LIST_URI, uriWithId("uimessage"))
.add(UIProvider.ConversationColumns.SUBJECT, MessageColumns.SUBJECT)
.add(UIProvider.ConversationColumns.SNIPPET, MessageColumns.SNIPPET)
.add(UIProvider.ConversationColumns.CONVERSATION_INFO, null)
.add(UIProvider.ConversationColumns.DATE_RECEIVED_MS, MessageColumns.TIMESTAMP)
.add(UIProvider.ConversationColumns.HAS_ATTACHMENTS, MessageColumns.FLAG_ATTACHMENT)
.add(UIProvider.ConversationColumns.NUM_MESSAGES, "1")
.add(UIProvider.ConversationColumns.NUM_DRAFTS, "0")
.add(UIProvider.ConversationColumns.SENDING_STATE,
Integer.toString(ConversationSendingState.OTHER))
.add(UIProvider.ConversationColumns.PRIORITY,
Integer.toString(ConversationPriority.LOW))
.add(UIProvider.ConversationColumns.READ, MessageColumns.FLAG_READ)
.add(UIProvider.ConversationColumns.STARRED, MessageColumns.FLAG_FAVORITE)
.add(UIProvider.ConversationColumns.FLAGS, CONVERSATION_FLAGS)
.add(UIProvider.ConversationColumns.ACCOUNT_URI,
uriWithColumn("uiaccount", MessageColumns.ACCOUNT_KEY))
.add(UIProvider.ConversationColumns.SENDER_INFO, MessageColumns.FROM_LIST)
.build();
}
return sMessageListMap;
}
private static ProjectionMap sMessageListMap;
/**
* Generate UIProvider draft type; note the test for "reply all" must come before "reply"
@ -2167,45 +2182,53 @@ outer:
* Mapping of UIProvider columns to EmailProvider columns for a detailed message view in
* UnifiedEmail
*/
private static final ProjectionMap sMessageViewMap = ProjectionMap.builder()
.add(BaseColumns._ID, Message.TABLE_NAME + "." + EmailContent.MessageColumns.ID)
.add(UIProvider.MessageColumns.SERVER_ID, SyncColumns.SERVER_ID)
.add(UIProvider.MessageColumns.URI, uriWithFQId("uimessage", Message.TABLE_NAME))
.add(UIProvider.MessageColumns.CONVERSATION_ID,
uriWithFQId("uimessage", Message.TABLE_NAME))
.add(UIProvider.MessageColumns.SUBJECT, EmailContent.MessageColumns.SUBJECT)
.add(UIProvider.MessageColumns.SNIPPET, EmailContent.MessageColumns.SNIPPET)
.add(UIProvider.MessageColumns.FROM, EmailContent.MessageColumns.FROM_LIST)
.add(UIProvider.MessageColumns.TO, EmailContent.MessageColumns.TO_LIST)
.add(UIProvider.MessageColumns.CC, EmailContent.MessageColumns.CC_LIST)
.add(UIProvider.MessageColumns.BCC, EmailContent.MessageColumns.BCC_LIST)
.add(UIProvider.MessageColumns.REPLY_TO, EmailContent.MessageColumns.REPLY_TO_LIST)
.add(UIProvider.MessageColumns.DATE_RECEIVED_MS, EmailContent.MessageColumns.TIMESTAMP)
.add(UIProvider.MessageColumns.BODY_HTML, Body.HTML_CONTENT)
.add(UIProvider.MessageColumns.BODY_TEXT, Body.TEXT_CONTENT)
.add(UIProvider.MessageColumns.REF_MESSAGE_ID, "0")
.add(UIProvider.MessageColumns.DRAFT_TYPE, NOT_A_DRAFT_STRING)
.add(UIProvider.MessageColumns.APPEND_REF_MESSAGE_CONTENT, "0")
.add(UIProvider.MessageColumns.HAS_ATTACHMENTS, EmailContent.MessageColumns.FLAG_ATTACHMENT)
.add(UIProvider.MessageColumns.ATTACHMENT_LIST_URI,
uriWithFQId("uiattachments", Message.TABLE_NAME))
.add(UIProvider.MessageColumns.MESSAGE_FLAGS, MESSAGE_FLAGS)
.add(UIProvider.MessageColumns.SAVE_MESSAGE_URI,
uriWithFQId("uiupdatedraft", Message.TABLE_NAME))
.add(UIProvider.MessageColumns.SEND_MESSAGE_URI,
uriWithFQId("uisenddraft", Message.TABLE_NAME))
.add(UIProvider.MessageColumns.DRAFT_TYPE, MESSAGE_DRAFT_TYPE)
.add(UIProvider.MessageColumns.MESSAGE_ACCOUNT_URI,
uriWithColumn("account", MessageColumns.ACCOUNT_KEY))
.add(UIProvider.MessageColumns.STARRED, EmailContent.MessageColumns.FLAG_FAVORITE)
.add(UIProvider.MessageColumns.READ, EmailContent.MessageColumns.FLAG_READ)
.add(UIProvider.MessageColumns.SPAM_WARNING_STRING, null)
.add(UIProvider.MessageColumns.SPAM_WARNING_LEVEL,
Integer.toString(UIProvider.SpamWarningLevel.NO_WARNING))
.add(UIProvider.MessageColumns.SPAM_WARNING_LINK_TYPE,
Integer.toString(UIProvider.SpamWarningLinkType.NO_LINK))
.add(UIProvider.MessageColumns.VIA_DOMAIN, null)
.build();
private ProjectionMap getMessageViewMap() {
if (sMessageViewMap == null) {
sMessageViewMap = ProjectionMap.builder()
.add(BaseColumns._ID, Message.TABLE_NAME + "." + EmailContent.MessageColumns.ID)
.add(UIProvider.MessageColumns.SERVER_ID, SyncColumns.SERVER_ID)
.add(UIProvider.MessageColumns.URI, uriWithFQId("uimessage", Message.TABLE_NAME))
.add(UIProvider.MessageColumns.CONVERSATION_ID,
uriWithFQId("uimessage", Message.TABLE_NAME))
.add(UIProvider.MessageColumns.SUBJECT, EmailContent.MessageColumns.SUBJECT)
.add(UIProvider.MessageColumns.SNIPPET, EmailContent.MessageColumns.SNIPPET)
.add(UIProvider.MessageColumns.FROM, EmailContent.MessageColumns.FROM_LIST)
.add(UIProvider.MessageColumns.TO, EmailContent.MessageColumns.TO_LIST)
.add(UIProvider.MessageColumns.CC, EmailContent.MessageColumns.CC_LIST)
.add(UIProvider.MessageColumns.BCC, EmailContent.MessageColumns.BCC_LIST)
.add(UIProvider.MessageColumns.REPLY_TO, EmailContent.MessageColumns.REPLY_TO_LIST)
.add(UIProvider.MessageColumns.DATE_RECEIVED_MS,
EmailContent.MessageColumns.TIMESTAMP)
.add(UIProvider.MessageColumns.BODY_HTML, Body.HTML_CONTENT)
.add(UIProvider.MessageColumns.BODY_TEXT, Body.TEXT_CONTENT)
.add(UIProvider.MessageColumns.REF_MESSAGE_ID, "0")
.add(UIProvider.MessageColumns.DRAFT_TYPE, NOT_A_DRAFT_STRING)
.add(UIProvider.MessageColumns.APPEND_REF_MESSAGE_CONTENT, "0")
.add(UIProvider.MessageColumns.HAS_ATTACHMENTS,
EmailContent.MessageColumns.FLAG_ATTACHMENT)
.add(UIProvider.MessageColumns.ATTACHMENT_LIST_URI,
uriWithFQId("uiattachments", Message.TABLE_NAME))
.add(UIProvider.MessageColumns.MESSAGE_FLAGS, MESSAGE_FLAGS)
.add(UIProvider.MessageColumns.SAVE_MESSAGE_URI,
uriWithFQId("uiupdatedraft", Message.TABLE_NAME))
.add(UIProvider.MessageColumns.SEND_MESSAGE_URI,
uriWithFQId("uisenddraft", Message.TABLE_NAME))
.add(UIProvider.MessageColumns.DRAFT_TYPE, MESSAGE_DRAFT_TYPE)
.add(UIProvider.MessageColumns.MESSAGE_ACCOUNT_URI,
uriWithColumn("account", MessageColumns.ACCOUNT_KEY))
.add(UIProvider.MessageColumns.STARRED, EmailContent.MessageColumns.FLAG_FAVORITE)
.add(UIProvider.MessageColumns.READ, EmailContent.MessageColumns.FLAG_READ)
.add(UIProvider.MessageColumns.SPAM_WARNING_STRING, null)
.add(UIProvider.MessageColumns.SPAM_WARNING_LEVEL,
Integer.toString(UIProvider.SpamWarningLevel.NO_WARNING))
.add(UIProvider.MessageColumns.SPAM_WARNING_LINK_TYPE,
Integer.toString(UIProvider.SpamWarningLinkType.NO_LINK))
.add(UIProvider.MessageColumns.VIA_DOMAIN, null)
.build();
}
return sMessageViewMap;
}
private static ProjectionMap sMessageViewMap;
/**
* Generate UIProvider folder capabilities from mailbox flags
@ -2236,50 +2259,62 @@ outer:
+ " WHEN " + Mailbox.TYPE_STARRED + " THEN " + R.drawable.ic_menu_star_holo_light
+ " ELSE -1 END";
private static final ProjectionMap sFolderListMap = ProjectionMap.builder()
.add(BaseColumns._ID, MailboxColumns.ID)
.add(UIProvider.FolderColumns.URI, uriWithId("uifolder"))
.add(UIProvider.FolderColumns.NAME, "displayName")
.add(UIProvider.FolderColumns.HAS_CHILDREN,
MailboxColumns.FLAGS + "&" + Mailbox.FLAG_HAS_CHILDREN)
.add(UIProvider.FolderColumns.CAPABILITIES, FOLDER_CAPABILITIES)
.add(UIProvider.FolderColumns.SYNC_WINDOW, "3")
.add(UIProvider.FolderColumns.CONVERSATION_LIST_URI, uriWithId("uimessages"))
.add(UIProvider.FolderColumns.CHILD_FOLDERS_LIST_URI, uriWithId("uisubfolders"))
.add(UIProvider.FolderColumns.UNREAD_COUNT, MailboxColumns.UNREAD_COUNT)
.add(UIProvider.FolderColumns.TOTAL_COUNT, MailboxColumns.MESSAGE_COUNT)
.add(UIProvider.FolderColumns.REFRESH_URI, uriWithId("uirefresh"))
.add(UIProvider.FolderColumns.SYNC_STATUS, MailboxColumns.UI_SYNC_STATUS)
.add(UIProvider.FolderColumns.LAST_SYNC_RESULT, MailboxColumns.UI_LAST_SYNC_RESULT)
.add(UIProvider.FolderColumns.TYPE, FOLDER_TYPE)
.add(UIProvider.FolderColumns.ICON_RES_ID, FOLDER_ICON)
.add(UIProvider.FolderColumns.HIERARCHICAL_DESC, MailboxColumns.HIERARCHICAL_NAME)
.build();
private ProjectionMap getFolderListMap() {
if (sFolderListMap == null) {
sFolderListMap = ProjectionMap.builder()
.add(BaseColumns._ID, MailboxColumns.ID)
.add(UIProvider.FolderColumns.URI, uriWithId("uifolder"))
.add(UIProvider.FolderColumns.NAME, "displayName")
.add(UIProvider.FolderColumns.HAS_CHILDREN,
MailboxColumns.FLAGS + "&" + Mailbox.FLAG_HAS_CHILDREN)
.add(UIProvider.FolderColumns.CAPABILITIES, FOLDER_CAPABILITIES)
.add(UIProvider.FolderColumns.SYNC_WINDOW, "3")
.add(UIProvider.FolderColumns.CONVERSATION_LIST_URI, uriWithId("uimessages"))
.add(UIProvider.FolderColumns.CHILD_FOLDERS_LIST_URI, uriWithId("uisubfolders"))
.add(UIProvider.FolderColumns.UNREAD_COUNT, MailboxColumns.UNREAD_COUNT)
.add(UIProvider.FolderColumns.TOTAL_COUNT, MailboxColumns.MESSAGE_COUNT)
.add(UIProvider.FolderColumns.REFRESH_URI, uriWithId("uirefresh"))
.add(UIProvider.FolderColumns.SYNC_STATUS, MailboxColumns.UI_SYNC_STATUS)
.add(UIProvider.FolderColumns.LAST_SYNC_RESULT, MailboxColumns.UI_LAST_SYNC_RESULT)
.add(UIProvider.FolderColumns.TYPE, FOLDER_TYPE)
.add(UIProvider.FolderColumns.ICON_RES_ID, FOLDER_ICON)
.add(UIProvider.FolderColumns.HIERARCHICAL_DESC, MailboxColumns.HIERARCHICAL_NAME)
.build();
}
return sFolderListMap;
}
private static ProjectionMap sFolderListMap;
private static final ProjectionMap sAccountListMap = ProjectionMap.builder()
.add(BaseColumns._ID, AccountColumns.ID)
.add(UIProvider.AccountColumns.FOLDER_LIST_URI, uriWithId("uifolders"))
.add(UIProvider.AccountColumns.FULL_FOLDER_LIST_URI, uriWithId("uiallfolders"))
.add(UIProvider.AccountColumns.NAME, AccountColumns.DISPLAY_NAME)
.add(UIProvider.AccountColumns.SAVE_DRAFT_URI, uriWithId("uisavedraft"))
.add(UIProvider.AccountColumns.SEND_MAIL_URI, uriWithId("uisendmail"))
.add(UIProvider.AccountColumns.UNDO_URI,
("'content://" + UIProvider.AUTHORITY + "/uiundo'"))
.add(UIProvider.AccountColumns.URI, uriWithId("uiaccount"))
.add(UIProvider.AccountColumns.SEARCH_URI, uriWithId("uisearch"))
// TODO: Is provider version used?
.add(UIProvider.AccountColumns.PROVIDER_VERSION, "1")
.add(UIProvider.AccountColumns.SYNC_STATUS, "0")
.add(UIProvider.AccountColumns.RECENT_FOLDER_LIST_URI, uriWithId("uirecentfolders"))
.add(UIProvider.AccountColumns.DEFAULT_RECENT_FOLDER_LIST_URI,
uriWithId("uidefaultrecentfolders"))
.add(UIProvider.AccountColumns.SettingsColumns.SIGNATURE, AccountColumns.SIGNATURE)
.add(UIProvider.AccountColumns.SettingsColumns.SNAP_HEADERS,
Integer.toString(UIProvider.SnapHeaderValue.ALWAYS))
.add(UIProvider.AccountColumns.SettingsColumns.REPLY_BEHAVIOR,
Integer.toString(UIProvider.DefaultReplyBehavior.REPLY))
.add(UIProvider.AccountColumns.SettingsColumns.CONFIRM_ARCHIVE, "0")
.build();
private ProjectionMap getAccountListMap() {
if (sAccountListMap == null) {
sAccountListMap = ProjectionMap.builder()
.add(BaseColumns._ID, AccountColumns.ID)
.add(UIProvider.AccountColumns.FOLDER_LIST_URI, uriWithId("uifolders"))
.add(UIProvider.AccountColumns.FULL_FOLDER_LIST_URI, uriWithId("uiallfolders"))
.add(UIProvider.AccountColumns.NAME, AccountColumns.DISPLAY_NAME)
.add(UIProvider.AccountColumns.SAVE_DRAFT_URI, uriWithId("uisavedraft"))
.add(UIProvider.AccountColumns.SEND_MAIL_URI, uriWithId("uisendmail"))
.add(UIProvider.AccountColumns.UNDO_URI,
("'content://" + UIProvider.AUTHORITY + "/uiundo'"))
.add(UIProvider.AccountColumns.URI, uriWithId("uiaccount"))
.add(UIProvider.AccountColumns.SEARCH_URI, uriWithId("uisearch"))
// TODO: Is provider version used?
.add(UIProvider.AccountColumns.PROVIDER_VERSION, "1")
.add(UIProvider.AccountColumns.SYNC_STATUS, "0")
.add(UIProvider.AccountColumns.RECENT_FOLDER_LIST_URI, uriWithId("uirecentfolders"))
.add(UIProvider.AccountColumns.DEFAULT_RECENT_FOLDER_LIST_URI,
uriWithId("uidefaultrecentfolders"))
.add(UIProvider.AccountColumns.SettingsColumns.SIGNATURE, AccountColumns.SIGNATURE)
.add(UIProvider.AccountColumns.SettingsColumns.SNAP_HEADERS,
Integer.toString(UIProvider.SnapHeaderValue.ALWAYS))
.add(UIProvider.AccountColumns.SettingsColumns.REPLY_BEHAVIOR,
Integer.toString(UIProvider.DefaultReplyBehavior.REPLY))
.add(UIProvider.AccountColumns.SettingsColumns.CONFIRM_ARCHIVE, "0")
.build();
}
return sAccountListMap;
}
private static ProjectionMap sAccountListMap;
/**
* The "ORDER BY" clause for top level folders
@ -2298,16 +2333,23 @@ outer:
/**
* Mapping of UIProvider columns to EmailProvider columns for a message's attachments
*/
private static final ProjectionMap sAttachmentMap = ProjectionMap.builder()
.add(UIProvider.AttachmentColumns.NAME, AttachmentColumns.FILENAME)
.add(UIProvider.AttachmentColumns.SIZE, AttachmentColumns.SIZE)
.add(UIProvider.AttachmentColumns.URI, uriWithId("uiattachment"))
.add(UIProvider.AttachmentColumns.CONTENT_TYPE, AttachmentColumns.MIME_TYPE)
.add(UIProvider.AttachmentColumns.STATE, AttachmentColumns.UI_STATE)
.add(UIProvider.AttachmentColumns.DESTINATION, AttachmentColumns.UI_DESTINATION)
.add(UIProvider.AttachmentColumns.DOWNLOADED_SIZE, AttachmentColumns.UI_DOWNLOADED_SIZE)
.add(UIProvider.AttachmentColumns.CONTENT_URI, AttachmentColumns.CONTENT_URI)
.build();
private ProjectionMap getAttachmentMap() {
if (sAttachmentMap == null) {
sAttachmentMap = ProjectionMap.builder()
.add(UIProvider.AttachmentColumns.NAME, AttachmentColumns.FILENAME)
.add(UIProvider.AttachmentColumns.SIZE, AttachmentColumns.SIZE)
.add(UIProvider.AttachmentColumns.URI, uriWithId("uiattachment"))
.add(UIProvider.AttachmentColumns.CONTENT_TYPE, AttachmentColumns.MIME_TYPE)
.add(UIProvider.AttachmentColumns.STATE, AttachmentColumns.UI_STATE)
.add(UIProvider.AttachmentColumns.DESTINATION, AttachmentColumns.UI_DESTINATION)
.add(UIProvider.AttachmentColumns.DOWNLOADED_SIZE,
AttachmentColumns.UI_DOWNLOADED_SIZE)
.add(UIProvider.AttachmentColumns.CONTENT_URI, AttachmentColumns.CONTENT_URI)
.build();
}
return sAttachmentMap;
}
private static ProjectionMap sAttachmentMap;
/**
* Generate the SELECT clause using a specified mapping and the original UI projection
@ -2469,7 +2511,7 @@ outer:
"content://ui.email2.android.com/event/" + msg.mId);
}
}
StringBuilder sb = genSelect(sMessageViewMap, uiProjection, values);
StringBuilder sb = genSelect(getMessageViewMap(), uiProjection, values);
sb.append(" FROM " + Message.TABLE_NAME + "," + Body.TABLE_NAME + " WHERE " +
Body.MESSAGE_KEY + "=" + Message.TABLE_NAME + "." + Message.RECORD_ID + " AND " +
Message.TABLE_NAME + "." + Message.RECORD_ID + "=?");
@ -2484,7 +2526,7 @@ outer:
* @return the SQLite query to be executed on the EmailProvider database
*/
private String genQueryMailboxMessages(String[] uiProjection) {
StringBuilder sb = genSelect(sMessageListMap, uiProjection);
StringBuilder sb = genSelect(getMessageListMap(), uiProjection);
sb.append(" FROM " + Message.TABLE_NAME + " WHERE " + Message.MAILBOX_KEY + "=? ORDER BY " +
MessageColumns.TIMESTAMP + " DESC");
return sb.toString();
@ -2501,7 +2543,7 @@ outer:
long mailboxId) {
ContentValues values = new ContentValues();
values.put(UIProvider.ConversationColumns.COLOR, CONVERSATION_COLOR);
StringBuilder sb = genSelect(sMessageListMap, uiProjection, values);
StringBuilder sb = genSelect(getMessageListMap(), uiProjection, values);
if (isCombinedMailbox(mailboxId)) {
switch (getVirtualMailboxType(mailboxId)) {
case Mailbox.TYPE_INBOX:
@ -2543,7 +2585,7 @@ outer:
* @return the SQLite query to be executed on the EmailProvider database
*/
private String genQueryConversation(String[] uiProjection) {
StringBuilder sb = genSelect(sMessageListMap, uiProjection);
StringBuilder sb = genSelect(getMessageListMap(), uiProjection);
sb.append(" FROM " + Message.TABLE_NAME + " WHERE " + Message.RECORD_ID + "=?");
return sb.toString();
}
@ -2555,7 +2597,7 @@ outer:
* @return the SQLite query to be executed on the EmailProvider database
*/
private String genQueryAccountMailboxes(String[] uiProjection) {
StringBuilder sb = genSelect(sFolderListMap, uiProjection);
StringBuilder sb = genSelect(getFolderListMap(), uiProjection);
sb.append(" FROM " + Mailbox.TABLE_NAME + " WHERE " + MailboxColumns.ACCOUNT_KEY +
"=? AND " + MailboxColumns.TYPE + " < " + Mailbox.TYPE_NOT_EMAIL +
" AND " + MailboxColumns.PARENT_KEY + " < 0 ORDER BY ");
@ -2571,7 +2613,7 @@ outer:
* @return the SQLite query to be executed on the EmailProvider database
*/
private String genQueryAccountAllMailboxes(String[] uiProjection) {
StringBuilder sb = genSelect(sFolderListMap, uiProjection);
StringBuilder sb = genSelect(getFolderListMap(), uiProjection);
// Use a derived column to choose either hierarchicalName or displayName
sb.append(", case when " + MailboxColumns.HIERARCHICAL_NAME + " is null then " +
MailboxColumns.DISPLAY_NAME + " else " + MailboxColumns.HIERARCHICAL_NAME +
@ -2590,7 +2632,7 @@ outer:
* @return the SQLite query to be executed on the EmailProvider database
*/
private String genQueryRecentMailboxes(String[] uiProjection) {
StringBuilder sb = genSelect(sFolderListMap, uiProjection);
StringBuilder sb = genSelect(getFolderListMap(), uiProjection);
sb.append(" FROM " + Mailbox.TABLE_NAME + " WHERE " + MailboxColumns.ACCOUNT_KEY +
"=? AND " + MailboxColumns.TYPE + " < " + Mailbox.TYPE_NOT_EMAIL +
" AND " + MailboxColumns.PARENT_KEY + " < 0 AND " +
@ -2656,7 +2698,7 @@ outer:
getFolderCapabilities(info, mailbox.mFlags, mailbox.mType, mailboxId));
}
}
StringBuilder sb = genSelect(sFolderListMap, uiProjection, values);
StringBuilder sb = genSelect(getFolderListMap(), uiProjection, values);
sb.append(" FROM " + Mailbox.TABLE_NAME + " WHERE " + MailboxColumns.ID + "=?");
return sb.toString();
}
@ -2778,7 +2820,7 @@ outer:
values.put(UIProvider.AccountColumns.SettingsColumns.PRIORITY_ARROWS_ENABLED, "0");
}
final StringBuilder sb = genSelect(sAccountListMap, uiProjection, values);
final StringBuilder sb = genSelect(getAccountListMap(), uiProjection, values);
sb.append(" FROM " + Account.TABLE_NAME + " WHERE " + AccountColumns.ID + "=?");
return sb.toString();
}
@ -3037,7 +3079,7 @@ outer:
*/
private String genQueryAttachments(String[] uiProjection,
List<String> contentTypeQueryParameters) {
StringBuilder sb = genSelect(sAttachmentMap, uiProjection);
StringBuilder sb = genSelect(getAttachmentMap(), uiProjection);
sb.append(" FROM " + Attachment.TABLE_NAME + " WHERE " + AttachmentColumns.MESSAGE_KEY +
" =? ");
@ -3069,7 +3111,7 @@ outer:
* @return the SQLite query to be executed on the EmailProvider database
*/
private String genQueryAttachment(String[] uiProjection) {
StringBuilder sb = genSelect(sAttachmentMap, uiProjection);
StringBuilder sb = genSelect(getAttachmentMap(), uiProjection);
sb.append(" FROM " + Attachment.TABLE_NAME + " WHERE " + AttachmentColumns.ID + " =? ");
return sb.toString();
}
@ -3081,7 +3123,7 @@ outer:
* @return the SQLite query to be executed on the EmailProvider database
*/
private String genQuerySubfolders(String[] uiProjection) {
StringBuilder sb = genSelect(sFolderListMap, uiProjection);
StringBuilder sb = genSelect(getFolderListMap(), uiProjection);
sb.append(" FROM " + Mailbox.TABLE_NAME + " WHERE " + MailboxColumns.PARENT_KEY +
" =? ORDER BY ");
sb.append(MAILBOX_ORDER_BY);
@ -3157,7 +3199,7 @@ outer:
final String[] idAndType = { BaseColumns._ID, UIProvider.FolderColumns.TYPE };
// Sent, Drafts, and Starred are the default recents.
final StringBuilder sb = genSelect(sFolderListMap, idAndType);
final StringBuilder sb = genSelect(getFolderListMap(), idAndType);
sb.append(" FROM " + Mailbox.TABLE_NAME
+ " WHERE " + MailboxColumns.ACCOUNT_KEY + " = " + id
+ " AND "

View File

@ -203,7 +203,7 @@ public class EmailBroadcastProcessorService extends IntentService {
private void onSystemAccountChanged() {
Log.i(Logging.LOG_TAG, "System accounts updated.");
MailService.reconcilePopImapAccountsSync(this);
MailService.reconcilePopAccountsSync(this);
// Start any remote services
EmailServiceUtils.startRemoteServices(this);

View File

@ -28,11 +28,11 @@ import android.text.TextUtils;
import android.util.Log;
import com.android.email.NotificationController;
import com.android.email.R;
import com.android.email.mail.Sender;
import com.android.email.mail.Store;
import com.android.email.provider.Utilities;
import com.android.email2.ui.MailActivityEmail;
import com.android.emailcommon.AccountManagerTypes;
import com.android.emailcommon.Api;
import com.android.emailcommon.Logging;
import com.android.emailcommon.TrafficFlags;
@ -108,7 +108,7 @@ public abstract class EmailServiceStub extends IEmailService.Stub implements IEm
Account account = Account.restoreAccountWithId(mContext, mailbox.mAccountKey);
if (account == null) return;
android.accounts.Account acct = new android.accounts.Account(account.mEmailAddress,
AccountManagerTypes.TYPE_POP_IMAP);
mContext.getString(R.string.account_manager_type_pop3));
Bundle extras = new Bundle();
extras.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
extras.putLong(SYNC_EXTRA_MAILBOX_ID, mailboxId);
@ -450,7 +450,7 @@ public abstract class EmailServiceStub extends IEmailService.Stub implements IEm
@Override
public void deleteAccountPIMData(long accountId) throws RemoteException {
MailService.reconcilePopImapAccountsSync(mContext);
MailService.reconcilePopAccountsSync(mContext);
}
@Override

View File

@ -21,21 +21,29 @@ import android.accounts.AccountManagerFuture;
import android.accounts.AuthenticatorException;
import android.accounts.OperationCanceledException;
import android.app.Service;
import android.content.ComponentName;
import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
import android.database.Cursor;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Debug;
import android.os.IBinder;
import android.os.RemoteException;
import android.provider.CalendarContract;
import android.provider.CalendarContract.Calendars;
import android.provider.CalendarContract.SyncState;
import android.provider.ContactsContract;
import android.provider.SyncStateContract;
import android.util.Log;
import com.android.email.R;
@ -43,8 +51,8 @@ import com.android.emailcommon.Api;
import com.android.emailcommon.Logging;
import com.android.emailcommon.provider.Account;
import com.android.emailcommon.provider.EmailContent;
import com.android.emailcommon.provider.HostAuth;
import com.android.emailcommon.provider.EmailContent.AccountColumns;
import com.android.emailcommon.provider.HostAuth;
import com.android.emailcommon.service.EmailServiceProxy;
import com.android.emailcommon.service.IEmailService;
import com.android.emailcommon.service.IEmailServiceCallback;
@ -167,6 +175,7 @@ public class EmailServiceUtils {
public CharSequence[] syncIntervals;
public int defaultSyncInterval;
public String inferPrefix;
public boolean requiresAccountUpdate;
public String toString() {
StringBuilder sb = new StringBuilder("Protocol: ");
@ -187,7 +196,14 @@ public class EmailServiceUtils {
if (info == null) {
Log.w(Logging.LOG_TAG, "Returning NullService for " + protocol);
return new EmailServiceProxy(context, NullService.class, null);
} else if (info.klass != null) {
} else {
return getServiceFromInfo(context, callback, info);
}
}
public static EmailServiceProxy getServiceFromInfo(Context context,
IEmailServiceCallback callback, EmailServiceInfo info) {
if (info.klass != null) {
return new EmailServiceProxy(context, info.klass, callback);
} else {
return new EmailServiceProxy(context, info.intentAction, callback);
@ -229,30 +245,47 @@ public class EmailServiceUtils {
}
}
/**
* "Change" the account manager type of the account; this entails deleting the account
* and adding a new one. We can't call into AccountManager on the UI thread, but we might
* well be on it (currently no clean way of guaranteeing that we're not).
*
* @param context the caller's context
* @param amAccount the AccountManager account we're changing
* @param newType the new AccountManager type for this account
* @param newProtocol the protocol now being used
*/
private static void updateAccountManagerType(final Context context,
final android.accounts.Account amAccount, final String newType,
final String newProtocol) {
// STOPSHIP There must be a better way
Thread amThread = new Thread(new Runnable() {
@Override
public void run() {
updateAccountManagerTypeImpl(context, amAccount, newType, newProtocol);
}});
amThread.start();
private static class UpdateAccountManagerTask extends AsyncTask<Void, Void, Void> {
private final Context mContext;
private final android.accounts.Account mAccount;
private final EmailServiceInfo mOldInfo;
private final EmailServiceInfo mNewInfo;
public UpdateAccountManagerTask(Context context, android.accounts.Account amAccount,
EmailServiceInfo oldInfo, EmailServiceInfo newInfo) {
super();
mContext = context;
mAccount = amAccount;
mOldInfo = oldInfo;
mNewInfo = newInfo;
}
@Override
protected Void doInBackground(Void... params) {
updateAccountManagerType(mContext, mAccount, mOldInfo, mNewInfo);
return null;
}
}
private static void updateAccountManagerTypeImpl(Context context,
android.accounts.Account amAccount, String newType, String newProtocol) {
private static class DisableComponentsTask extends AsyncTask<Void, Void, Void> {
private final Context mContext;
public DisableComponentsTask(Context context) {
super();
mContext = context;
}
@Override
protected Void doInBackground(Void... params) {
disableComponent(mContext, LegacyEmailAuthenticatorService.class);
disableComponent(mContext, LegacyEasAuthenticatorService.class);
return null;
}
}
private static void updateAccountManagerType(Context context,
android.accounts.Account amAccount, EmailServiceInfo oldInfo,
EmailServiceInfo newInfo) {
ContentResolver resolver = context.getContentResolver();
Cursor c = resolver.query(Account.CONTENT_URI, Account.CONTENT_PROJECTION,
AccountColumns.EMAIL_ADDRESS + "=?", new String[] { amAccount.name }, null);
@ -260,7 +293,6 @@ public class EmailServiceUtils {
if (c == null) return;
try {
if (c.moveToNext()) {
Log.w(Logging.LOG_TAG, "Converting " + amAccount.name + " to " + newProtocol);
// Get the EmailProvider Account/HostAuth
Account account = new Account();
account.restore(c);
@ -268,6 +300,14 @@ public class EmailServiceUtils {
HostAuth.restoreHostAuthWithId(context, account.mHostAuthKeyRecv);
if (hostAuth == null) return;
// Make sure this email address is using the expected protocol; our query to
// AccountManager doesn't know which protocol was being used (com.android.email
// was used for both pop3 and imap
if (!hostAuth.mProtocol.equals(oldInfo.protocol)) {
return;
}
Log.w(Logging.LOG_TAG, "Converting " + amAccount.name + " to " + newInfo.protocol);
ContentValues accountValues = new ContentValues();
int oldFlags = account.mFlags;
@ -280,33 +320,122 @@ public class EmailServiceUtils {
// Change the HostAuth to reference the new protocol; this has to be done before
// trying to create the AccountManager account (below)
ContentValues hostValues = new ContentValues();
hostValues.put(HostAuth.PROTOCOL, newProtocol);
hostValues.put(HostAuth.PROTOCOL, newInfo.protocol);
resolver.update(ContentUris.withAppendedId(HostAuth.CONTENT_URI, hostAuth.mId),
hostValues, null, null);
Log.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");
}
boolean contacts = ContentResolver.getSyncAutomatically(amAccount,
ContactsContract.AUTHORITY);
boolean calendar = ContentResolver.getSyncAutomatically(amAccount,
CalendarContract.AUTHORITY);
Log.w(Logging.LOG_TAG, "Email: " + email + ", Contacts: " + contacts + "," +
" Calendar: " + calendar);
// Delete the AccountManager account
AccountManagerFuture<?> amFuture = AccountManager.get(context)
.removeAccount(amAccount, null, null);
finishAccountManagerBlocker(amFuture);
// Get sync keys for calendar/contacts
String amName = amAccount.name;
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) {
Log.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) {
Log.w(Logging.LOG_TAG, "Get contacts key FAILED");
} finally {
client.release();
}
if (calendarSyncKey != null) {
Log.w(Logging.LOG_TAG, "Got calendar key: " + new String(calendarSyncKey));
}
if (contactsSyncKey != null) {
Log.w(Logging.LOG_TAG, "Got contacts key: " + new String(contactsSyncKey));
}
// Set up a new AccountManager account with new type and old settings
amFuture = MailService.setupAccountManagerAccount(context, account, email,
calendar, contacts, null);
AccountManagerFuture<?> amFuture = MailService.setupAccountManagerAccount(
context, account, email, calendar, contacts, null);
finishAccountManagerBlocker(amFuture);
Log.w(Logging.LOG_TAG, "Conversion complete!");
Log.w(Logging.LOG_TAG, "Created new AccountManager account");
// Delete the AccountManager account
amFuture = AccountManager.get(context)
.removeAccount(amAccount, null, null);
finishAccountManagerBlocker(amFuture);
Log.w(Logging.LOG_TAG, "Deleted old AccountManager account");
// Restore sync keys for contacts/calendar
if (calendarSyncKey != null && calendarSyncKey.length != 0) {
client = context.getContentResolver()
.acquireContentProviderClient(CalendarContract.CONTENT_URI);
try {
SyncStateContract.Helpers.set(client,
asCalendarSyncAdapter(SyncState.CONTENT_URI, amName,
newInfo.accountType),
new android.accounts.Account(amName, newInfo.accountType),
calendarSyncKey);
Log.w(Logging.LOG_TAG, "Set calendar key...");
} catch (RemoteException e) {
Log.w(Logging.LOG_TAG, "Set calendar key FAILED");
} finally {
client.release();
}
}
if (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, newInfo.accountType),
contactsSyncKey);
Log.w(Logging.LOG_TAG, "Set contacts key...");
} catch (RemoteException e) {
Log.w(Logging.LOG_TAG, "Set contacts key FAILED");
}
}
if (oldInfo.requiresAccountUpdate) {
EmailServiceProxy service =
EmailServiceUtils.getServiceFromInfo(context, null, newInfo);
try {
service.serviceUpdated(amAccount.name);
Log.w(Logging.LOG_TAG, "Updated account settings");
} catch (RemoteException e) {
// Old settings won't hurt anyone
}
}
// That's all folks!
Log.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);
Log.w(Logging.LOG_TAG, "[Incomplete flag cleared]");
}
}
} finally {
@ -314,6 +443,14 @@ public class EmailServiceUtils {
}
}
private static void disableComponent(Context context, Class<?> klass) {
Log.w(Logging.LOG_TAG, "Disabling legacy authenticator " + klass.getSimpleName());
final ComponentName c = new ComponentName(context, klass);
context.getPackageManager().setComponentEnabledSetting(c,
PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
PackageManager.DONT_KILL_APP);
}
/**
* Parse services.xml file to find our available email services
*/
@ -339,12 +476,14 @@ public class EmailServiceUtils {
throw new IllegalStateException(
"Replacement service not found: " + newProtocol);
}
info.requiresAccountUpdate = ta.getBoolean(
R.styleable.EmailServiceInfo_requiresAccountUpdate, false);
AccountManager am = AccountManager.get(context);
android.accounts.Account[] amAccounts =
am.getAccountsByType(info.accountType);
for (android.accounts.Account amAccount: amAccounts) {
updateAccountManagerType(context, amAccount, newInfo.accountType,
newProtocol);
new UpdateAccountManagerTask(context, amAccount, info, newInfo)
.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR);
}
continue;
}
@ -407,6 +546,8 @@ public class EmailServiceUtils {
sServiceList.add(info);
}
}
// Disable our legacy components
new DisableComponentsTask(context).executeOnExecutor(AsyncTask.SERIAL_EXECUTOR);
} catch (XmlPullParserException e) {
// ignore
} catch (IOException e) {
@ -414,6 +555,12 @@ public class EmailServiceUtils {
}
}
private static Uri asCalendarSyncAdapter(Uri uri, String account, String accountType) {
return uri.buildUpon().appendQueryParameter(CalendarContract.CALLER_IS_SYNCADAPTER, "true")
.appendQueryParameter(Calendars.ACCOUNT_NAME, account)
.appendQueryParameter(Calendars.ACCOUNT_TYPE, accountType).build();
}
/**
* A no-op service that can be returned for non-existent/null protocols
*/
@ -504,6 +651,10 @@ public class EmailServiceUtils {
public void sendMail(long accountId) throws RemoteException {
}
@Override
public void serviceUpdated(String emailAddress) throws RemoteException {
}
@Override
public int getCapabilities(Account acct) throws RemoteException {
return 0;

View File

@ -19,5 +19,5 @@ package com.android.email.service;
/**
* This service needs to be declared separately from the base service
*/
public class Imap2AuthenticatorService extends AuthenticatorService {
public class ImapAuthenticatorService extends AuthenticatorService {
}

File diff suppressed because it is too large Load Diff

View File

@ -1,127 +0,0 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.email.service;
import android.util.Log;
import com.android.email.FixedLengthInputStream;
import com.android.email.mail.store.imap.ImapResponse;
import com.android.email.mail.store.imap.ImapResponseParser;
import com.android.email.mail.store.imap.ImapString;
import com.android.emailcommon.Logging;
import com.android.emailcommon.TempDirectory;
import com.android.emailcommon.utility.Utility;
import org.apache.commons.io.IOUtils;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
/**
* Subclass of {@link ImapString} used for literals backed by a temp file.
*/
public class ImapTempFileLiteral extends ImapString {
/* package for test */ final File mFile;
/** Size is purely for toString() */
private final int mSize;
/* package */ ImapTempFileLiteral(FixedLengthInputStream stream) throws IOException {
mSize = stream.getLength();
mFile = File.createTempFile("imap", ".tmp", TempDirectory.getTempDirectory());
// Unfortunately, we can't really use deleteOnExit(), because temp filenames are random
// so it'd simply cause a memory leak.
// deleteOnExit() simply adds filenames to a static list and the list will never shrink.
// mFile.deleteOnExit();
OutputStream out = new FileOutputStream(mFile);
IOUtils.copy(stream, out);
out.close();
}
/**
* Make sure we delete the temp file.
*
* We should always be calling {@link ImapResponse#destroy()}, but it's here as a last resort.
*/
@Override
protected void finalize() throws Throwable {
try {
destroy();
} finally {
super.finalize();
}
}
@Override
public InputStream getAsStream() {
checkNotDestroyed();
try {
return new FileInputStream(mFile);
} catch (FileNotFoundException e) {
// It's probably possible if we're low on storage and the system clears the cache dir.
Log.w(Logging.LOG_TAG, "ImapTempFileLiteral: Temp file not found");
// Return 0 byte stream as a dummy...
return new ByteArrayInputStream(new byte[0]);
}
}
@Override
public String getString() {
checkNotDestroyed();
try {
byte[] bytes = IOUtils.toByteArray(getAsStream());
// Prevent crash from OOM; we've seen this, but only rarely and not reproducibly
if (bytes.length > ImapResponseParser.LITERAL_KEEP_IN_MEMORY_THRESHOLD) {
throw new IOException();
}
return Utility.fromAscii(bytes);
} catch (IOException e) {
Log.w(Logging.LOG_TAG, "ImapTempFileLiteral: Error while reading temp file", e);
return "";
}
}
@Override
public void destroy() {
try {
if (!isDestroyed() && mFile.exists()) {
mFile.delete();
}
} catch (RuntimeException re) {
// Just log and ignore.
Log.w(Logging.LOG_TAG, "Failed to remove temp file: " + re.getMessage());
}
super.destroy();
}
@Override
public String toString() {
return String.format("{%d byte literal(file)}", mSize);
}
public boolean tempFileExistsForTest() {
return mFile.exists();
}
}

View File

@ -1,6 +1,5 @@
/*
/*
* Copyright (C) 2011 The Android Open Source Project
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -15,9 +14,10 @@
* limitations under the License.
*/
package com.android.emailcommon;
package com.android.email.service;
public class AccountManagerTypes {
public static final String TYPE_EXCHANGE = "com.android.exchange";
public static final String TYPE_POP_IMAP = "com.android.email";
/**
* This service needs to be declared separately from the base service
*/
public class LegacyEasAuthenticatorService extends AuthenticatorService {
}

View File

@ -0,0 +1,23 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.email.service;
/**
* This service needs to be declared separately from the base service
*/
public class LegacyEmailAuthenticatorService extends AuthenticatorService {
}

View File

@ -26,11 +26,11 @@ import android.database.Cursor;
import android.os.Bundle;
import android.os.IBinder;
import com.android.email.R;
import com.android.email.SingleRunningTask;
import com.android.email.provider.AccountReconciler;
import com.android.email.service.EmailServiceUtils.EmailServiceInfo;
import com.android.email2.ui.MailActivityEmail;
import com.android.emailcommon.AccountManagerTypes;
import com.android.emailcommon.provider.Account;
import com.android.emailcommon.provider.HostAuth;
import com.android.emailcommon.utility.EmailAsyncTask;
@ -51,7 +51,7 @@ public class MailService extends Service {
EmailAsyncTask.runAsyncParallel(new Runnable() {
@Override
public void run() {
reconcilePopImapAccountsSync(MailService.this);
reconcilePopAccountsSync(MailService.this);
}
});
@ -70,7 +70,7 @@ public class MailService extends Service {
return null;
}
public static ArrayList<Account> getPopImapAccountList(Context context) {
public static ArrayList<Account> getPopAccountList(Context context) {
ArrayList<Account> providerAccounts = new ArrayList<Account>();
Cursor c = context.getContentResolver().query(Account.CONTENT_URI, Account.ID_PROJECTION,
null, null, null);
@ -79,7 +79,8 @@ public class MailService extends Service {
long accountId = c.getLong(Account.CONTENT_ID_COLUMN);
String protocol = Account.getProtocol(context, accountId);
EmailServiceInfo info = EmailServiceUtils.getServiceInfo(context, protocol);
if ((info != null) && info.accountType.equals(AccountManagerTypes.TYPE_POP_IMAP)) {
if ((info != null) && info.accountType.equals(
context.getString(R.string.account_manager_type_pop3))) {
Account account = Account.restoreAccountWithId(context, accountId);
if (account != null) {
providerAccounts.add(account);
@ -92,13 +93,14 @@ public class MailService extends Service {
return providerAccounts;
}
private static final SingleRunningTask<Context> sReconcilePopImapAccountsSyncExecutor =
private static final SingleRunningTask<Context> sReconcilePopAccountsSyncExecutor =
new SingleRunningTask<Context>("ReconcilePopImapAccountsSync") {
@Override
protected void runInternal(Context context) {
android.accounts.Account[] accountManagerAccounts = AccountManager.get(context)
.getAccountsByType(AccountManagerTypes.TYPE_POP_IMAP);
ArrayList<Account> providerAccounts = getPopImapAccountList(context);
.getAccountsByType(
context.getString(R.string.account_manager_type_pop3));
ArrayList<Account> providerAccounts = getPopAccountList(context);
MailService.reconcileAccountsWithAccountManager(context, providerAccounts,
accountManagerAccounts, context);
@ -108,20 +110,8 @@ public class MailService extends Service {
/**
* Reconcile POP/IMAP accounts.
*/
public static void reconcilePopImapAccountsSync(Context context) {
sReconcilePopImapAccountsSyncExecutor.run(context);
}
/**
* Determines whether or not POP/IMAP accounts need reconciling or not. This is a safe operation
* to perform on the UI thread.
*/
public static boolean hasMismatchInPopImapAccounts(Context context) {
android.accounts.Account[] accountManagerAccounts = AccountManager.get(context)
.getAccountsByType(AccountManagerTypes.TYPE_POP_IMAP);
ArrayList<Account> providerAccounts = getPopImapAccountList(context);
return AccountReconciler.accountsNeedReconciling(
context, providerAccounts, accountManagerAccounts);
public static void reconcilePopAccountsSync(Context context) {
sReconcilePopAccountsSyncExecutor.run(context);
}
/**

View File

@ -19,5 +19,5 @@ package com.android.email.service;
/**
* This service needs to be declared separately from the base service
*/
public class PopImapAuthenticatorService extends AuthenticatorService {
public class Pop3AuthenticatorService extends AuthenticatorService {
}

View File

@ -105,6 +105,11 @@ public class Pop3Service extends Service {
// We load attachments during a sync
startSync(inboxId, true);
}
@Override
public void serviceUpdated(String emailAddress) throws RemoteException {
// Not required for POP3
}
};
@Override

View File

@ -38,18 +38,17 @@ import com.android.emailcommon.provider.Account;
import com.android.emailcommon.provider.EmailContent;
import com.android.emailcommon.provider.EmailContent.AccountColumns;
import com.android.emailcommon.provider.EmailContent.Message;
import com.android.emailcommon.provider.HostAuth;
import com.android.emailcommon.provider.Mailbox;
import com.android.emailcommon.service.EmailServiceProxy;
import java.util.ArrayList;
public class PopImapSyncAdapterService extends Service {
private static final String TAG = "PopImapSyncAdapterService";
public class Pop3SyncAdapterService extends Service {
private static final String TAG = "Pop3SyncAdapterService";
private static SyncAdapterImpl sSyncAdapter = null;
private static final Object sSyncAdapterLock = new Object();
public PopImapSyncAdapterService() {
public Pop3SyncAdapterService() {
super();
}
@ -65,7 +64,7 @@ public class PopImapSyncAdapterService extends Service {
public void onPerformSync(android.accounts.Account account, Bundle extras,
String authority, ContentProviderClient provider, SyncResult syncResult) {
try {
PopImapSyncAdapterService.performSync(mContext, account, extras,
Pop3SyncAdapterService.performSync(mContext, account, extras,
authority, provider, syncResult);
} catch (OperationCanceledException e) {
}
@ -87,25 +86,6 @@ public class PopImapSyncAdapterService extends Service {
return sSyncAdapter.getSyncAdapterBinder();
}
/**
* @return whether or not this mailbox retrieves its data from the server (as opposed to just
* a local mailbox that is never synced).
*/
public static boolean loadsFromServer(Mailbox m, String protocol) {
if (HostAuth.LEGACY_SCHEME_IMAP.equals(protocol)) {
// TODO: actually use a sync flag when creating the mailboxes. Right now we use an
// approximation for IMAP.
return m.mType != Mailbox.TYPE_DRAFTS
&& m.mType != Mailbox.TYPE_OUTBOX
&& m.mType != Mailbox.TYPE_SEARCH;
} else if (HostAuth.LEGACY_SCHEME_POP3.equals(protocol)) {
return Mailbox.TYPE_INBOX == m.mType;
}
return false;
}
private static void sync(Context context, long mailboxId, SyncResult syncResult,
boolean uiRefresh) {
TempDirectory.setTempDirectory(context);
@ -114,8 +94,7 @@ public class PopImapSyncAdapterService extends Service {
Account account = Account.restoreAccountWithId(context, mailbox.mAccountKey);
if (account == null) return;
ContentResolver resolver = context.getContentResolver();
String protocol = account.getProtocol(context);
if ((mailbox.mType != Mailbox.TYPE_OUTBOX) && !loadsFromServer(mailbox, protocol)) {
if ((mailbox.mType != Mailbox.TYPE_OUTBOX) && (mailbox.mType != Mailbox.TYPE_INBOX)) {
// This is an update to a message in a non-syncing mailbox; delete this from the
// updates table and return
resolver.delete(Message.UPDATED_CONTENT_URI, Message.MAILBOX_KEY + "=?",
@ -134,8 +113,6 @@ public class PopImapSyncAdapterService extends Service {
try {
if (mailbox.mType == Mailbox.TYPE_OUTBOX) {
EmailServiceStub.sendMailImpl(context, account.mId);
} else if (protocol.equals(HostAuth.LEGACY_SCHEME_IMAP)) {
ImapService.synchronizeMailboxSynchronous(context, account, mailbox);
} else {
Pop3Service.synchronizeMailboxSynchronous(context, account, mailbox);
}

View File

@ -16,6 +16,7 @@
package com.android.mail.providers.protos.boot;
import com.android.emailcommon.provider.EmailContent;
import com.android.mail.providers.MailAppProvider;
import android.content.BroadcastReceiver;
@ -30,12 +31,9 @@ public class AccountReceiver extends BroadcastReceiver {
public static final String ACTION_PROVIDER_CREATED
= "com.android.email2.providers.protos.boot.intent.ACTION_PROVIDER_CREATED";
private static final Uri ACCOUNTS_URI =
Uri.parse("content://com.android.email.provider/uiaccts");
@Override
public void onReceive(Context context, Intent intent) {
MailAppProvider.addAccountsForUriAsync(ACCOUNTS_URI);
EmailContent.init(context);
MailAppProvider.addAccountsForUriAsync(Uri.parse(EmailContent.CONTENT_URI + "/uiaccts"));
}
}

View File

@ -134,12 +134,12 @@ public class UtilityMediumTests extends ProviderTestCase2<EmailProvider> {
public void testBuildLimitOneUri() {
// EmailProvider supports "?limit="
assertEquals(Uri.parse("content://com.android.email.provider?limit=1"),
Utility.buildLimitOneUri(Uri.parse("content://com.android.email.provider")));
assertEquals(Uri.parse("content://com.android.mail.provider?limit=1"),
Utility.buildLimitOneUri(Uri.parse("content://com.android.mail.provider")));
// Others don't -- so don't add it.
assertEquals(Uri.parse("content://com.android.email.attachmentprovider"),
Utility.buildLimitOneUri(Uri.parse("content://com.android.email.attachmentprovider"
assertEquals(Uri.parse("content://com.android.mail.attachmentprovider"),
Utility.buildLimitOneUri(Uri.parse("content://com.android.mail.attachmentprovider"
)));
assertEquals(Uri.parse("content://gmail-ls/android@gmail.com"),
Utility.buildLimitOneUri(Uri.parse("content://gmail-ls/android@gmail.com"