Implement initial UIProvider operations (UnifiedEmail)

* Add support for folder list, conversation list, and message view
  queries
* Open up EmailProvider (temporarily) to allow access from
  UnifiedEmail (signatures don't currently match)
* Modify make file so that we can reference definitions in
  UIProvider

Change-Id: If73c59aa9edfdac5619ff2c6b9cedfdfe4e93d6f
This commit is contained in:
Marc Blank 2012-01-11 17:46:00 -08:00
parent 23dd801d5d
commit 3594ac8146
3 changed files with 261 additions and 11 deletions

View File

@ -22,11 +22,18 @@ include $(CLEAR_VARS)
chips_dir := ../../../frameworks/ex/chips/res
mail_common_dir := ../../../frameworks/opt/mailcommon/res
res_dir := $(chips_dir) $(mail_common_dir) res
unified_email_src_dir := ../UnifiedEmail/src
imported_unified_email_files := \
$(unified_email_src_dir)/com/android/mail/providers/UIProviderValidator.java \
$(unified_email_src_dir)/com/android/mail/providers/UIProvider.java
LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES := $(call all-java-files-under, src/com/android/email)
LOCAL_SRC_FILES += $(call all-java-files-under, src/com/beetstra)
LOCAL_SRC_FILES += $(imported_unified_email_files)
LOCAL_RESOURCE_DIR := $(addprefix $(LOCAL_PATH)/, $(res_dir))
LOCAL_AAPT_FLAGS := --auto-add-overlay
LOCAL_AAPT_FLAGS += --extra-packages com.android.ex.chips

View File

@ -75,9 +75,10 @@
android:name="android.permission.USE_CREDENTIALS"/>
<!-- Grant permission to system apps to access provider (see provider below) -->
<!-- STOPSHIP: Temporarily set protection level to "dangerous" (from "signature") -->
<permission
android:name="com.android.email.permission.ACCESS_PROVIDER"
android:protectionLevel="signature"
android:protectionLevel="dangerous"
android:label="@string/permission_access_provider_label"
android:description="@string/permission_access_provider_desc"/>
<uses-permission
@ -483,8 +484,7 @@
<receiver
android:name=".provider.WidgetProvider" >
<intent-filter>
<action
android:name="android.appwidget.action.APPWIDGET_UPDATE" />
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<intent-filter>
<action android:name="com.android.email.MESSAGE_LIST_DATASET_CHANGED" />

View File

@ -23,8 +23,8 @@ import android.content.ContentProviderResult;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Intent;
import android.content.Context;
import android.content.Intent;
import android.content.OperationApplicationException;
import android.content.UriMatcher;
import android.database.ContentObserver;
@ -35,10 +35,13 @@ import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteOpenHelper;
import android.net.Uri;
import android.os.Debug;
import android.provider.BaseColumns;
import android.provider.ContactsContract;
import android.text.TextUtils;
import android.util.Log;
import com.android.common.content.ProjectionMap;
import com.android.email.Email;
import com.android.email.Preferences;
import com.android.email.provider.ContentCache.CacheToken;
@ -65,6 +68,7 @@ import com.android.emailcommon.provider.Mailbox;
import com.android.emailcommon.provider.Policy;
import com.android.emailcommon.provider.QuickResponse;
import com.android.emailcommon.service.LegacyPolicySet;
import com.android.mail.providers.UIProvider;
import com.google.common.annotations.VisibleForTesting;
import java.io.File;
@ -93,7 +97,7 @@ public class EmailProvider extends ContentProvider {
* is NOT the preferred way of getting notification.
*/
public static final String ACTION_NOTIFY_MESSAGE_LIST_DATASET_CHANGED =
"com.android.email.MESSAGE_LIST_DATASET_CHANGED";
"com.android.email.MESSAGE_LIST_DATASET_CHANGED";
public static final String EMAIL_MESSAGE_MIME_TYPE =
"vnd.android.cursor.item/email-message";
@ -226,8 +230,13 @@ public class EmailProvider extends ContentProvider {
private static final int QUICK_RESPONSE_ID = QUICK_RESPONSE_BASE + 1;
private static final int QUICK_RESPONSE_ACCOUNT_ID = QUICK_RESPONSE_BASE + 2;
private static final int UI_BASE = 0x9000;
private static final int UI_FOLDERS = UI_BASE;
private static final int UI_MESSAGES = UI_BASE + 1;
private static final int UI_MESSAGE = UI_BASE + 2;
// MUST ALWAYS EQUAL THE LAST OF THE PREVIOUS BASE CONSTANTS
private static final int LAST_EMAIL_PROVIDER_DB_BASE = QUICK_RESPONSE_BASE;
private static final int LAST_EMAIL_PROVIDER_DB_BASE = UI_BASE;
// DO NOT CHANGE BODY_BASE!!
private static final int BODY_BASE = LAST_EMAIL_PROVIDER_DB_BASE + 0x1000;
@ -248,7 +257,8 @@ public class EmailProvider extends ContentProvider {
Message.DELETED_TABLE_NAME,
Policy.TABLE_NAME,
QuickResponse.TABLE_NAME,
Body.TABLE_NAME
Body.TABLE_NAME,
null
};
// CONTENT_CACHES MUST remain in the order of the BASE constants above
@ -262,7 +272,8 @@ public class EmailProvider extends ContentProvider {
null, // Deleted message
mCachePolicy,
null, // Quick response
null // Body
null, // Body
null // UI
};
// CACHE_PROJECTIONS MUST remain in the order of the BASE constants above
@ -276,7 +287,8 @@ public class EmailProvider extends ContentProvider {
null, // Deleted message
Policy.CONTENT_PROJECTION,
null, // Quick response
null // Body
null, // Body
null // UI
};
private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);
@ -428,6 +440,10 @@ public class EmailProvider extends ContentProvider {
// 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, "uimessages/#", UI_MESSAGES);
matcher.addURI(EmailContent.AUTHORITY, "uimessage/#", UI_MESSAGE);
}
/**
@ -1744,7 +1760,6 @@ public class EmailProvider extends ContentProvider {
bodyFile.delete();
}
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
@ -1798,6 +1813,15 @@ public class EmailProvider extends ContentProvider {
try {
switch (match) {
// First, dispatch queries from UnfiedEmail
case UI_FOLDERS:
case UI_MESSAGES:
case UI_MESSAGE:
// For now, we don't allow selection criteria within these queries
if (selection != null || selectionArgs != null) {
throw new IllegalArgumentException("UI queries can't have selection/args");
}
return uiQuery(match, uri, projection);
case ACCOUNT_DEFAULT_ID:
// Start with a snapshot of the cache
Map<String, Cursor> accountCache = mCacheAccount.getSnapshot();
@ -2641,7 +2665,7 @@ outer:
}
}
/**
/**
* For testing purposes, check whether a given row is cached
* @param baseUri the base uri of the EmailContent
* @param id the row id of the EmailContent
@ -2680,4 +2704,223 @@ outer:
public void injectAttachmentService(AttachmentService as) {
mAttachmentService = (as == null) ? DEFAULT_ATTACHMENT_SERVICE : as;
}
/**
* Support for UnifiedEmail below
*/
/**
* 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.SENDER_INFO, MessageColumns.FROM_LIST)
.add(UIProvider.ConversationColumns.DATE_RECEIVED_MS, MessageColumns.TIMESTAMP)
.add(UIProvider.ConversationColumns.HAS_ATTACHMENTS, MessageColumns.FLAG_ATTACHMENT)
.build();
/**
* 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.EMBEDS_EXTERNAL_RESOURCES, "0")
.add(UIProvider.MessageColumns.REF_MESSAGE_ID, "0")
.add(UIProvider.MessageColumns.DRAFT_TYPE, "0")
.add(UIProvider.MessageColumns.INCLUDE_QUOTED_TEXT, "0")
.add(UIProvider.MessageColumns.QUOTE_START_POS, "0")
.add(UIProvider.MessageColumns.CLIENT_CREATED, "0")
.add(UIProvider.MessageColumns.CUSTOM_FROM_ADDRESS, "0")
.add(UIProvider.MessageColumns.HAS_ATTACHMENTS, EmailContent.MessageColumns.FLAG_ATTACHMENT)
.add(UIProvider.MessageColumns.INCLUDE_QUOTED_TEXT, "0")
.add(UIProvider.MessageColumns.ATTACHMENT_LIST_URI,
uriWithFQId("uiattachments", Message.TABLE_NAME))
.add(UIProvider.MessageColumns.MESSAGE_FLAGS, "0")
.build();
/**
* Mapping of UIProvider columns to EmailProvider columns for the folder list in UnifiedEmail
*/
private static final ProjectionMap sFolderListMap = ProjectionMap.builder()
.add(BaseColumns._ID, MessageColumns.ID)
.add(UIProvider.FolderColumns.URI, uriWithId("uifolder"))
.add(UIProvider.FolderColumns.NAME, "displayName")
.add(UIProvider.FolderColumns.HAS_CHILDREN, "0")
.add(UIProvider.FolderColumns.CAPABILITIES, "0")
.add(UIProvider.FolderColumns.SYNC_FREQUENCY, "0")
.add(UIProvider.FolderColumns.SYNC_WINDOW, "3")
.add(UIProvider.FolderColumns.CONVERSATION_LIST_URI, uriWithId("uimessages"))
.add(UIProvider.FolderColumns.CHILD_FOLDERS_LIST_URI, uriWithId("uichildren"))
.add(UIProvider.FolderColumns.UNREAD_COUNT, "7")
.add(UIProvider.FolderColumns.TOTAL_COUNT, "77")
.build();
/**
* The "ORDER BY" clause for top level folders
*/
private static final String MAILBOX_ORDER_BY = "CASE " + MailboxColumns.TYPE
+ " WHEN " + Mailbox.TYPE_INBOX + " THEN 0"
+ " WHEN " + Mailbox.TYPE_DRAFTS + " THEN 1"
+ " WHEN " + Mailbox.TYPE_OUTBOX + " THEN 2"
+ " WHEN " + Mailbox.TYPE_SENT + " THEN 3"
+ " WHEN " + Mailbox.TYPE_TRASH + " THEN 4"
+ " WHEN " + Mailbox.TYPE_JUNK + " THEN 5"
// Other mailboxes (i.e. of Mailbox.TYPE_MAIL) are shown in alphabetical order.
+ " ELSE 10 END"
+ " ," + MailboxColumns.DISPLAY_NAME + " COLLATE LOCALIZED ASC";
/**
* Generate the SELECT clause using a specified mapping and the original UI projection
* @param map the ProjectionMap to use for this projection
* @param projection the projection as sent by UnifiedEmail
* @return a StringBuilder containing the SELECT expression for a SQLite query
*/
private StringBuilder genSelect(ProjectionMap map, String[] projection) {
StringBuilder sb = new StringBuilder("SELECT ");
boolean first = true;
for (String column: projection) {
if (first) {
first = false;
} else {
sb.append(',');
}
sb.append(map.get(column));
}
return sb;
}
/**
* Convenience method to create a Uri string given the "type" of query; we append the type
* of the query and the id column name (_id)
*
* @param type the "type" of the query, as defined by our UriMatcher definitions
* @return a Uri string
*/
private static String uriWithId(String type) {
return "'content://" + EmailContent.AUTHORITY + "/" + type + "/' || _id";
}
/**
* Convenience method to create a Uri string given the "type" of query and the table name to
* which it applies; we append the type of the query and the fully qualified (FQ) id column
* (i.e. including the table name); we need this for join queries where _id would otherwise
* be ambiguous
*
* @param type the "type" of the query, as defined by our UriMatcher definitions
* @param tableName the name of the table whose _id is referred to
* @return a Uri string
*/
private static String uriWithFQId(String type, String tableName) {
return "'content://" + EmailContent.AUTHORITY + "/" + type + "/' || " + tableName + "._id";
}
/**
* Generate the "view message" SQLite query, given a projection from UnifiedEmail
*
* @param uiProjection as passed from UnifiedEmail
* @return the SQLite query to be executed on the EmailProvider database
*/
private String genQueryViewMessage(String[] uiProjection) {
StringBuilder sb = genSelect(sMessageViewMap, uiProjection);
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 + "=?");
return sb.toString();
}
/**
* Generate the "message list" SQLite query, given a projection from UnifiedEmail
*
* @param uiProjection as passed from UnifiedEmail
* @return the SQLite query to be executed on the EmailProvider database
*/
private String genQueryMailboxMessages(String[] uiProjection) {
StringBuilder sb = genSelect(sMessageListMap, uiProjection);
// Make constant
sb.append(" FROM " + Message.TABLE_NAME + " WHERE " + Message.MAILBOX_KEY + "=?");
return sb.toString();
}
/**
* Generate the "folder list" SQLite query, given a projection from UnifiedEmail
*
* @param uiProjection as passed from UnifiedEmail
* @return the SQLite query to be executed on the EmailProvider database
*/
private String genQueryAccountMailboxes(String[] uiProjection) {
StringBuilder sb = genSelect(sFolderListMap, uiProjection);
// Make constant
sb.append(" FROM " + Mailbox.TABLE_NAME + " WHERE " + Mailbox.ACCOUNT_KEY + "=? ORDER BY ");
sb.append(MAILBOX_ORDER_BY);
return sb.toString();
}
/**
* Given the email address of an account, return its account id (the _id row in the Account
* table), or NO_ACCOUNT (-1) if not found
*
* @param email the email address of the account
* @return the account id for this account, or NO_ACCOUNT if not found
*/
private long findAccountIdByName(String email) {
Map<String, Cursor> accountCache = mCacheAccount.getSnapshot();
Collection<Cursor> accounts = accountCache.values();
for (Cursor accountCursor: accounts) {
if (accountCursor.getString(Account.CONTENT_EMAIL_ADDRESS_COLUMN).equals(email)) {
return accountCursor.getLong(Account.CONTENT_ID_COLUMN);
}
}
return Account.NO_ACCOUNT;
}
/**
* Handle UnifiedEmail queries here (dispatched from query())
*
* @param match the UriMatcher match for the original uri passed in from UnifiedEmail
* @param uri the original uri passed in from UnifiedEmail
* @param uiProjection the projection passed in from UnifiedEmail
* @return the result Cursor
*/
private Cursor uiQuery(int match, Uri uri, String[] uiProjection) {
Context context = getContext();
SQLiteDatabase db = getDatabase(context);
switch(match) {
case UI_FOLDERS:
// We are passed the email address (unique account identifier) in the uri; we
// need to turn this into the _id of the Account row in the EmailProvider db
String accountName = uri.getPathSegments().get(1);
long acctId = findAccountIdByName(accountName);
if (acctId == Account.NO_ACCOUNT) return null;
return db.rawQuery(genQueryAccountMailboxes(uiProjection),
new String[] {Long.toString(acctId)});
case UI_MESSAGES:
String id = uri.getPathSegments().get(1);
return db.rawQuery(genQueryMailboxMessages(uiProjection), new String[] {id});
case UI_MESSAGE:
id = uri.getPathSegments().get(1);
return db.rawQuery(genQueryViewMessage(uiProjection), new String[] {id});
}
// Not sure whether to throw an exception here, but we return null for now
return null;
}
}