Home | History | Annotate | Download | only in eas
      1 package com.android.exchange.eas;
      2 
      3 import android.content.ContentValues;
      4 import android.content.Context;
      5 import android.content.SyncResult;
      6 
      7 import com.android.emailcommon.Logging;
      8 import com.android.emailcommon.provider.Account;
      9 import com.android.emailcommon.provider.Mailbox;
     10 import com.android.emailcommon.service.SearchParams;
     11 import com.android.exchange.CommandStatusException;
     12 import com.android.exchange.Eas;
     13 import com.android.exchange.EasResponse;
     14 import com.android.exchange.adapter.Serializer;
     15 import com.android.exchange.adapter.Tags;
     16 import com.android.exchange.adapter.SearchParser;
     17 import com.android.mail.providers.UIProvider;
     18 import com.android.mail.utils.LogUtils;
     19 
     20 import org.apache.http.HttpEntity;
     21 import org.apache.http.entity.ByteArrayEntity;
     22 
     23 import java.io.BufferedOutputStream;
     24 import java.io.ByteArrayOutputStream;
     25 import java.io.IOException;
     26 import java.io.InputStream;
     27 
     28 public class EasSearch extends EasOperation {
     29 
     30     public final static int RESULT_NO_MESSAGES = 0;
     31     public final static int RESULT_OK = 1;
     32     public final static int RESULT_EMPTY_RESPONSE = 2;
     33 
     34     // The shortest search query we'll accept
     35     // TODO Check with UX whether this is correct
     36     private static final int MIN_QUERY_LENGTH = 3;
     37     // The largest number of results we'll ask for per server request
     38     private static final int MAX_SEARCH_RESULTS = 100;
     39 
     40     final SearchParams mSearchParams;
     41     final long mDestMailboxId;
     42     int mTotalResults;
     43     Mailbox mSearchMailbox;
     44 
     45     public EasSearch(final Context context, final Account account, final SearchParams searchParams,
     46         final long destMailboxId) {
     47         super(context, account);
     48         mSearchParams = searchParams;
     49         mDestMailboxId = destMailboxId;
     50     }
     51 
     52     public int getTotalResults() {
     53         return mTotalResults;
     54     }
     55 
     56     @Override
     57     protected String getCommand() {
     58         return "Search";
     59     }
     60 
     61     @Override
     62     protected HttpEntity getRequestEntity() throws IOException {
     63         // Sanity check for arguments
     64         final int offset = mSearchParams.mOffset;
     65         final int limit = mSearchParams.mLimit;
     66         final String filter = mSearchParams.mFilter;
     67         if (limit < 0 || limit > MAX_SEARCH_RESULTS || offset < 0) {
     68             return null;
     69         }
     70         // TODO Should this be checked in UI?  Are there guidelines for minimums?
     71         if (filter == null || filter.length() < MIN_QUERY_LENGTH) {
     72             LogUtils.w(LOG_TAG, "filter too short");
     73             return null;
     74         }
     75 
     76         int res = 0;
     77         mSearchMailbox = Mailbox.restoreMailboxWithId(mContext, mDestMailboxId);
     78         // Sanity check; account might have been deleted?
     79         if (mSearchMailbox == null) {
     80             LogUtils.i(LOG_TAG, "search mailbox ceased to exist");
     81             return null;
     82         }
     83         try {
     84             // Set the status of this mailbox to indicate query
     85             final ContentValues statusValues = new ContentValues(1);
     86             statusValues.put(Mailbox.UI_SYNC_STATUS, UIProvider.SyncStatus.LIVE_QUERY);
     87             mSearchMailbox.update(mContext, statusValues);
     88 
     89             final Serializer s = new Serializer();
     90             s.start(Tags.SEARCH_SEARCH).start(Tags.SEARCH_STORE);
     91             s.data(Tags.SEARCH_NAME, "Mailbox");
     92             s.start(Tags.SEARCH_QUERY).start(Tags.SEARCH_AND);
     93             s.data(Tags.SYNC_CLASS, "Email");
     94 
     95             // If this isn't an inbox search, then include the collection id
     96             final Mailbox inbox =
     97                     Mailbox.restoreMailboxOfType(mContext, mAccount.mId, Mailbox.TYPE_INBOX);
     98             if (inbox == null) {
     99                 LogUtils.i(LOG_TAG, "Inbox ceased to exist");
    100                 return null;
    101             }
    102             if (mSearchParams.mMailboxId != inbox.mId) {
    103                 s.data(Tags.SYNC_COLLECTION_ID, inbox.mServerId);
    104             }
    105             s.data(Tags.SEARCH_FREE_TEXT, filter);
    106 
    107             // Add the date window if appropriate
    108             if (mSearchParams.mStartDate != null) {
    109                 s.start(Tags.SEARCH_GREATER_THAN);
    110                 s.tag(Tags.EMAIL_DATE_RECEIVED);
    111                 s.data(Tags.SEARCH_VALUE, Eas.DATE_FORMAT.format(mSearchParams.mStartDate));
    112                 s.end(); // SEARCH_GREATER_THAN
    113             }
    114             if (mSearchParams.mEndDate != null) {
    115                 s.start(Tags.SEARCH_LESS_THAN);
    116                 s.tag(Tags.EMAIL_DATE_RECEIVED);
    117                 s.data(Tags.SEARCH_VALUE, Eas.DATE_FORMAT.format(mSearchParams.mEndDate));
    118                 s.end(); // SEARCH_LESS_THAN
    119             }
    120             s.end().end(); // SEARCH_AND, SEARCH_QUERY
    121             s.start(Tags.SEARCH_OPTIONS);
    122             if (offset == 0) {
    123                 s.tag(Tags.SEARCH_REBUILD_RESULTS);
    124             }
    125             if (mSearchParams.mIncludeChildren) {
    126                 s.tag(Tags.SEARCH_DEEP_TRAVERSAL);
    127             }
    128             // Range is sent in the form first-last (e.g. 0-9)
    129             s.data(Tags.SEARCH_RANGE, offset + "-" + (offset + limit - 1));
    130             s.start(Tags.BASE_BODY_PREFERENCE);
    131             s.data(Tags.BASE_TYPE, Eas.BODY_PREFERENCE_HTML);
    132             s.data(Tags.BASE_TRUNCATION_SIZE, "20000");
    133             s.end();                    // BASE_BODY_PREFERENCE
    134             s.end().end().end().done(); // SEARCH_OPTIONS, SEARCH_STORE, SEARCH_SEARCH
    135             return makeEntity(s);
    136         } catch (IOException e) {
    137             LogUtils.d(LOG_TAG, e, "Search exception");
    138         }
    139         LogUtils.i(LOG_TAG, "end returning null");
    140         return null;
    141     }
    142 
    143     @Override
    144     protected int handleResponse(final EasResponse response)
    145         throws IOException, CommandStatusException {
    146         if (response.isEmpty()) {
    147             return RESULT_EMPTY_RESPONSE;
    148         }
    149         final InputStream is = response.getInputStream();
    150         try {
    151             final Mailbox searchMailbox = Mailbox.restoreMailboxWithId(mContext, mDestMailboxId);
    152             final SearchParser sp = new SearchParser(mContext, mContext.getContentResolver(),
    153                     is, searchMailbox, mAccount, mSearchParams.mFilter);
    154             sp.parse();
    155             mTotalResults = sp.getTotalResults();
    156         } finally {
    157             is.close();
    158         }
    159         return RESULT_OK;
    160     }
    161 
    162     protected void onRequestComplete() {
    163         if (mSearchMailbox != null) {
    164             // TODO: Handle error states
    165             final ContentValues statusValues = new ContentValues(2);
    166             statusValues.put(Mailbox.UI_SYNC_STATUS, UIProvider.SyncStatus.NO_SYNC);
    167             statusValues.put(Mailbox.SYNC_TIME, System.currentTimeMillis());
    168             mSearchMailbox.update(mContext, statusValues);
    169         }
    170     }
    171 }
    172