diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 524062f34..835f28009 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -108,6 +108,7 @@ /> + + android:name=".activity.MessageListXL"> + + + + 0x00FFFF00) /*package*/ static final String HIGHLIGHT_COLOR_STRING = '#' + Integer.toHexString(HIGHLIGHT_COLOR_INT & 0x00FFFFFF); diff --git a/res/drawable-mdpi/ic_menu_search_holo_light.png b/res/drawable-mdpi/ic_menu_search_holo_light.png new file mode 100644 index 000000000..101eeae45 Binary files /dev/null and b/res/drawable-mdpi/ic_menu_search_holo_light.png differ diff --git a/res/menu/message_list_xl_option.xml b/res/menu/message_list_xl_option.xml index 339b5abe1..0d1ad4fd5 100644 --- a/res/menu/message_list_xl_option.xml +++ b/res/menu/message_list_xl_option.xml @@ -16,8 +16,16 @@ + This should not be altered if the original string ("999+") makes sense in the target language. Typical alternatives include "+999" and ">999". [CHAR_LIMIT=4] --> 999+ + + + Email + + Search email diff --git a/res/xml/searchable.xml b/res/xml/searchable.xml new file mode 100644 index 000000000..e716bbbbb --- /dev/null +++ b/res/xml/searchable.xml @@ -0,0 +1,21 @@ + + + + diff --git a/src/com/android/email/Controller.java b/src/com/android/email/Controller.java index ffeb2987b..7a3d3926c 100644 --- a/src/com/android/email/Controller.java +++ b/src/com/android/email/Controller.java @@ -886,7 +886,7 @@ public class Controller { } catch (RemoteException e) { // TODO Change exception handling to be consistent with however this method // is implemented for other protocols - Log.e("onDownloadAttachment", "RemoteException", e); + Log.e("searchMessages", "RemoteException", e); } } } diff --git a/src/com/android/email/activity/MessageListFragment.java b/src/com/android/email/activity/MessageListFragment.java index 6a78ff3a0..17f1fe93a 100644 --- a/src/com/android/email/activity/MessageListFragment.java +++ b/src/com/android/email/activity/MessageListFragment.java @@ -1180,6 +1180,11 @@ public class MessageListFragment extends ListFragment lss = new Utility.ListStateSaver(lv); } + // If this is a search mailbox, set the query + if (mMailbox != null && mMailbox.mType == Mailbox.TYPE_SEARCH) { + mListAdapter.setQuery(mMailbox.mDisplayName); + } + // Update the list mListAdapter.swapCursor(cursor); // Show chips if combined view. diff --git a/src/com/android/email/activity/MessageListItem.java b/src/com/android/email/activity/MessageListItem.java index bb33be100..29de275f0 100644 --- a/src/com/android/email/activity/MessageListItem.java +++ b/src/com/android/email/activity/MessageListItem.java @@ -17,6 +17,7 @@ package com.android.email.activity; import com.android.email.R; +import com.android.emailcommon.utility.TextUtilities; import android.content.Context; import android.content.res.Resources; @@ -37,6 +38,7 @@ import android.text.TextPaint; import android.text.TextUtils; import android.text.TextUtils.TruncateAt; import android.text.format.DateUtils; +import android.text.style.BackgroundColorSpan; import android.text.style.StyleSpan; import android.util.AttributeSet; import android.view.MotionEvent; @@ -89,6 +91,7 @@ public class MessageListItem extends View { private static final TextPaint sDefaultPaint = new TextPaint(); private static final TextPaint sBoldPaint = new TextPaint(); private static final TextPaint sDatePaint = new TextPaint(); + private static final TextPaint sHighlightPaint = new TextPaint(); private static Bitmap sAttachmentIcon; private static Bitmap sInviteIcon; private static Bitmap sFavoriteIconOff; @@ -100,7 +103,7 @@ public class MessageListItem extends View { public String mSender; public CharSequence mText; - public String mSnippet; + public CharSequence mSnippet; public String mSubject; public boolean mRead; public long mTimestamp; @@ -208,6 +211,7 @@ public class MessageListItem extends View { sBoldPaint.setTypeface(Typeface.DEFAULT_BOLD); sBoldPaint.setTextSize(sTextSize); sBoldPaint.setAntiAlias(true); + sHighlightPaint.setColor(TextUtilities.HIGHLIGHT_COLOR_INT); sAttachmentIcon = BitmapFactory.decodeResource(r, R.drawable.ic_badge_attachment); sInviteIcon = BitmapFactory.decodeResource(r, R.drawable.ic_badge_invite); sFavoriteIconOff = @@ -444,28 +448,35 @@ public class MessageListItem extends View { mRead ? sDefaultPaint : sBoldPaint); // Draw each of the snippet lines - int subjectEnd = (mSubject == null) ? 0 : mSubject.length(); - int lineStart = 0; - TextPaint subjectPaint = mRead ? sDefaultPaint : sBoldPaint; for (int i = 0; i < MAX_SUBJECT_SNIPPET_LINES && i < mSnippetLineCount; i++) { CharSequence line = mSnippetLines[i]; int drawX = snippetX; if (line != null) { - int defaultPaintStart = 0; - if (lineStart <= subjectEnd) { - int boldPaintEnd = subjectEnd - lineStart; - if (boldPaintEnd > line.length()) { - boldPaintEnd = line.length(); + SpannableStringBuilder ssb = (SpannableStringBuilder)line; + Object[] spans = ssb.getSpans(0, line.length(), Object.class); + int curr = 0; + for (Object span: spans) { + if (span != null) { + int spanStart = ssb.getSpanStart(span); + int spanEnd = ssb.getSpanEnd(span); + if (curr < spanStart) { + canvas.drawText(line, curr, spanStart, drawX, snippetY, sDefaultPaint); + drawX += sDefaultPaint.measureText(line, curr, spanStart); + } + TextPaint spanPaint = + (span instanceof StyleSpan) ? sBoldPaint : sDefaultPaint; + float textWidth = spanPaint.measureText(line, spanStart, spanEnd); + if (span instanceof BackgroundColorSpan) { + canvas.drawRect(drawX, snippetY + ascent, drawX + textWidth, + snippetY + descent, sHighlightPaint); + } + canvas.drawText(line, spanStart, spanEnd, drawX, snippetY, spanPaint); + drawX += textWidth; + curr = spanEnd; } - // From 0 to end, do in bold or default depending on the read flag - canvas.drawText(line, 0, boldPaintEnd, drawX, snippetY, subjectPaint); - defaultPaintStart = boldPaintEnd; - drawX += subjectPaint.measureText(line, 0, boldPaintEnd); } - canvas.drawText(line, defaultPaintStart, line.length(), drawX, snippetY, - sDefaultPaint); + canvas.drawText(line, curr, line.length(), drawX, snippetY, sDefaultPaint); snippetY += lineHeight; - lineStart += line.length(); } } diff --git a/src/com/android/email/activity/MessageListXL.java b/src/com/android/email/activity/MessageListXL.java index 4fd381265..387e69594 100644 --- a/src/com/android/email/activity/MessageListXL.java +++ b/src/com/android/email/activity/MessageListXL.java @@ -29,11 +29,17 @@ import com.android.emailcommon.Logging; import com.android.emailcommon.mail.MessagingException; import com.android.emailcommon.provider.EmailContent.Account; import com.android.emailcommon.provider.EmailContent.Mailbox; +import com.android.emailcommon.provider.EmailContent.Message; import com.android.emailcommon.utility.EmailAsyncTask; +import com.android.emailcommon.utility.Utility; import android.app.ActionBar; import android.app.Activity; import android.app.LoaderManager.LoaderCallbacks; +import android.app.SearchManager; +import android.content.ContentResolver; +import android.content.ContentUris; +import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.content.Loader; @@ -227,6 +233,47 @@ public class MessageListXL extends Activity implements MessageListXLFragmentMana if (Email.DEBUG_LIFECYCLE && Email.DEBUG) Log.d(Logging.LOG_TAG, "MessageListXL onStart"); super.onStart(); mFragmentManager.onStart(); + + // STOPSHIP Temporary search UI + Intent intent = getIntent(); + if (Intent.ACTION_SEARCH.equals(intent.getAction())) { + // TODO Very temporary (e.g. no database access in UI thread) + Bundle appData = getIntent().getBundleExtra(SearchManager.APP_DATA); + if (appData == null) return; // ?? + final long accountId = appData.getLong(EXTRA_ACCOUNT_ID); + final long mailboxId = appData.getLong(EXTRA_MAILBOX_ID); + final String queryString = intent.getStringExtra(SearchManager.QUERY); + Log.d(Logging.LOG_TAG, queryString); + // Switch to search mailbox + // TODO How to handle search from within the search mailbox?? + final Controller controller = Controller.getInstance(mContext); + final Mailbox searchMailbox = controller.getSearchMailbox(accountId); + if (searchMailbox == null) return; + + // Delete contents, add a placeholder + ContentResolver resolver = mContext.getContentResolver(); + resolver.delete(Message.CONTENT_URI, Message.MAILBOX_KEY + "=" + searchMailbox.mId, + null); + ContentValues cv = new ContentValues(); + cv.put(Mailbox.DISPLAY_NAME, queryString); + resolver.update(ContentUris.withAppendedId(Mailbox.CONTENT_URI, searchMailbox.mId), cv, + null, null); + Message msg = new Message(); + msg.mMailboxKey = searchMailbox.mId; + msg.mAccountKey = accountId; + msg.mDisplayName = "Searching for " + queryString; + msg.mTimeStamp = Long.MAX_VALUE; // Sort on top + msg.save(mContext); + + actionOpenMessage(MessageListXL.this, accountId, searchMailbox.mId, msg.mId); + Utility.runAsync(new Runnable() { + @Override + public void run() { + controller.searchMessages(accountId, mailboxId, true, queryString, 10, 0, + searchMailbox.mId); + }}); + return; + } } @Override @@ -455,6 +502,17 @@ public class MessageListXL extends Activity implements MessageListXLFragmentMana @Override public boolean onPrepareOptionsMenu(Menu menu) { + // STOPSHIP Temporary search UI + // Only show search for EAS + boolean showSearch = false; + long accountId = mFragmentManager.getActualAccountId(); + if (accountId > 0) { + if ("eas".equals(Account.getProtocol(mContext, accountId))) { + showSearch = true; + } + } + menu.findItem(R.id.search).setVisible(showSearch); + ActivityHelper.updateRefreshMenuIcon( menu.findItem(R.id.refresh), shouldShowRefreshButton(), isProgressActive()); return super.onPrepareOptionsMenu(menu); @@ -466,6 +524,15 @@ public class MessageListXL extends Activity implements MessageListXLFragmentMana return -1 != mFragmentManager.getActualAccountId(); } + @Override + public boolean onSearchRequested() { + Bundle bundle = new Bundle(); + bundle.putLong(EXTRA_ACCOUNT_ID, mFragmentManager.getActualAccountId()); + bundle.putLong(EXTRA_MAILBOX_ID, mFragmentManager.getMailboxId()); + startSearch(null, false, bundle, false); + return true; + } + @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { @@ -478,6 +545,9 @@ public class MessageListXL extends Activity implements MessageListXLFragmentMana case R.id.refresh: onRefresh(); return true; + case R.id.search: + onSearchRequested(); + return true; case R.id.account_settings: return onAccountSettings(); } diff --git a/src/com/android/email/activity/MessagesAdapter.java b/src/com/android/email/activity/MessagesAdapter.java index ba1741ca0..9747fed03 100644 --- a/src/com/android/email/activity/MessagesAdapter.java +++ b/src/com/android/email/activity/MessagesAdapter.java @@ -23,6 +23,7 @@ import com.android.emailcommon.Logging; import com.android.emailcommon.provider.EmailContent; import com.android.emailcommon.provider.EmailContent.Message; import com.android.emailcommon.provider.EmailContent.MessageColumns; +import com.android.emailcommon.utility.TextUtilities; import com.android.emailcommon.utility.Utility; import android.content.Context; @@ -69,6 +70,9 @@ import java.util.Set; /** If true, show color chips. */ private boolean mShowColorChips; + /** If not null, the query represented by this group of messages */ + private String mQuery; + /** * Set of seleced message IDs. */ @@ -118,6 +122,10 @@ import java.util.Set; mShowColorChips = show; } + public void setQuery(String query) { + mQuery = query; + } + public Set getSelectedSet() { return mSelectedSet; } @@ -149,6 +157,11 @@ import java.util.Set; itemView.mSnippetLineCount = MessageListItem.NEEDS_LAYOUT; itemView.mColorChipPaint = mShowColorChips ? mResourceHelper.getAccountColorPaint(accountId) : null; + + if (mQuery != null && itemView.mSnippet != null) { + itemView.mSnippet = + TextUtilities.highlightTermsInText(cursor.getString(COLUMN_SNIPPET), mQuery); + } } @Override @@ -209,7 +222,7 @@ import java.util.Set; } - private static class MessagesCursorLoader extends ThrottlingCursorLoader { + static private class MessagesCursorLoader extends ThrottlingCursorLoader { private final Context mContext; private final long mMailboxId;