Home | History | Annotate | Download | only in adapter
      1 /*
      2  * Copyright (C) 2011 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.exchange.adapter;
     18 
     19 import android.content.ContentProviderOperation;
     20 import android.content.Context;
     21 import android.content.OperationApplicationException;
     22 import android.os.RemoteException;
     23 import android.util.Log;
     24 
     25 import com.android.emailcommon.Logging;
     26 import com.android.emailcommon.provider.Account;
     27 import com.android.emailcommon.provider.EmailContent;
     28 import com.android.emailcommon.provider.EmailContent.Message;
     29 import com.android.emailcommon.provider.Mailbox;
     30 import com.android.emailcommon.service.EmailServiceStatus;
     31 import com.android.emailcommon.service.SearchParams;
     32 import com.android.emailcommon.utility.TextUtilities;
     33 import com.android.exchange.Eas;
     34 import com.android.exchange.EasResponse;
     35 import com.android.exchange.EasSyncService;
     36 import com.android.exchange.ExchangeService;
     37 import com.android.exchange.adapter.EmailSyncAdapter.EasEmailSyncParser;
     38 
     39 import org.apache.http.HttpStatus;
     40 
     41 import java.io.IOException;
     42 import java.io.InputStream;
     43 import java.util.ArrayList;
     44 
     45 /**
     46  * Implementation of server-side search for EAS using the EmailService API
     47  */
     48 public class Search {
     49     // The shortest search query we'll accept
     50     // TODO Check with UX whether this is correct
     51     private static final int MIN_QUERY_LENGTH = 3;
     52     // The largest number of results we'll ask for per server request
     53     private static final int MAX_SEARCH_RESULTS = 100;
     54 
     55     public static int searchMessages(Context context, long accountId, SearchParams searchParams,
     56             long destMailboxId) {
     57         // Sanity check for arguments
     58         int offset = searchParams.mOffset;
     59         int limit = searchParams.mLimit;
     60         String filter = searchParams.mFilter;
     61         if (limit < 0 || limit > MAX_SEARCH_RESULTS || offset < 0) return 0;
     62         // TODO Should this be checked in UI?  Are there guidelines for minimums?
     63         if (filter == null || filter.length() < MIN_QUERY_LENGTH) return 0;
     64 
     65         int res = 0;
     66         Account account = Account.restoreAccountWithId(context, accountId);
     67         if (account == null) return res;
     68         EasSyncService svc = EasSyncService.setupServiceForAccount(context, account);
     69         if (svc == null) return res;
     70         try {
     71             Mailbox searchMailbox = Mailbox.restoreMailboxWithId(context, destMailboxId);
     72             // Sanity check; account might have been deleted?
     73             if (searchMailbox == null) return res;
     74             svc.mMailbox = searchMailbox;
     75             svc.mAccount = account;
     76             Serializer s = new Serializer();
     77             s.start(Tags.SEARCH_SEARCH).start(Tags.SEARCH_STORE);
     78             s.data(Tags.SEARCH_NAME, "Mailbox");
     79             s.start(Tags.SEARCH_QUERY).start(Tags.SEARCH_AND);
     80             s.data(Tags.SYNC_CLASS, "Email");
     81 
     82             // If this isn't an inbox search, then include the collection id
     83             Mailbox inbox = Mailbox.restoreMailboxOfType(context, accountId, Mailbox.TYPE_INBOX);
     84             if (inbox == null) return 0;
     85             if (searchParams.mMailboxId != inbox.mId) {
     86                 s.data(Tags.SYNC_COLLECTION_ID, inbox.mServerId);
     87             }
     88 
     89             s.data(Tags.SEARCH_FREE_TEXT, filter);
     90             s.end().end();              // SEARCH_AND, SEARCH_QUERY
     91             s.start(Tags.SEARCH_OPTIONS);
     92             if (offset == 0) {
     93                 s.tag(Tags.SEARCH_REBUILD_RESULTS);
     94             }
     95             if (searchParams.mIncludeChildren) {
     96                 s.tag(Tags.SEARCH_DEEP_TRAVERSAL);
     97             }
     98             // Range is sent in the form first-last (e.g. 0-9)
     99             s.data(Tags.SEARCH_RANGE, offset + "-" + (offset + limit - 1));
    100             s.start(Tags.BASE_BODY_PREFERENCE);
    101             s.data(Tags.BASE_TYPE, Eas.BODY_PREFERENCE_HTML);
    102             s.data(Tags.BASE_TRUNCATION_SIZE, "20000");
    103             s.end();                    // BASE_BODY_PREFERENCE
    104             s.end().end().end().done(); // SEARCH_OPTIONS, SEARCH_STORE, SEARCH_SEARCH
    105             EasResponse resp = svc.sendHttpClientPost("Search", s.toByteArray());
    106             try {
    107                 int code = resp.getStatus();
    108                 if (code == HttpStatus.SC_OK) {
    109                     InputStream is = resp.getInputStream();
    110                     try {
    111                         SearchParser sp = new SearchParser(is, svc, filter);
    112                         sp.parse();
    113                         res = sp.getTotalResults();
    114                     } finally {
    115                         is.close();
    116                     }
    117                 } else {
    118                     svc.userLog("Search returned " + code);
    119                 }
    120             } finally {
    121                 resp.close();
    122             }
    123         } catch (IOException e) {
    124             svc.userLog("Search exception " + e);
    125         } finally {
    126             try {
    127                 ExchangeService.callback().syncMailboxStatus(destMailboxId,
    128                         EmailServiceStatus.SUCCESS, 100);
    129             } catch (RemoteException e) {
    130             }
    131         }
    132         // Return the total count
    133         return res;
    134     }
    135 
    136     /**
    137      * Parse the result of a Search command
    138      */
    139     static class SearchParser extends Parser {
    140         private final EasSyncService mService;
    141         private final String mQuery;
    142         private int mTotalResults;
    143 
    144         private SearchParser(InputStream in, EasSyncService service, String query)
    145                 throws IOException {
    146             super(in);
    147             mService = service;
    148             mQuery = query;
    149         }
    150 
    151         protected int getTotalResults() {
    152             return mTotalResults;
    153         }
    154 
    155         @Override
    156         public boolean parse() throws IOException {
    157             boolean res = false;
    158             if (nextTag(START_DOCUMENT) != Tags.SEARCH_SEARCH) {
    159                 throw new IOException();
    160             }
    161             while (nextTag(START_DOCUMENT) != END_DOCUMENT) {
    162                 if (tag == Tags.SEARCH_STATUS) {
    163                     String status = getValue();
    164                     if (Eas.USER_LOG) {
    165                         Log.d(Logging.LOG_TAG, "Search status: " + status);
    166                     }
    167                 } else if (tag == Tags.SEARCH_RESPONSE) {
    168                     parseResponse();
    169                 } else {
    170                     skipTag();
    171                 }
    172             }
    173             return res;
    174         }
    175 
    176         private boolean parseResponse() throws IOException {
    177             boolean res = false;
    178             while (nextTag(Tags.SEARCH_RESPONSE) != END) {
    179                 if (tag == Tags.SEARCH_STORE) {
    180                     parseStore();
    181                 } else {
    182                     skipTag();
    183                 }
    184             }
    185             return res;
    186         }
    187 
    188         private boolean parseStore() throws IOException {
    189             EmailSyncAdapter adapter = new EmailSyncAdapter(mService);
    190             EasEmailSyncParser parser = adapter.new EasEmailSyncParser(this, adapter);
    191             ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
    192             boolean res = false;
    193 
    194             while (nextTag(Tags.SEARCH_STORE) != END) {
    195                 if (tag == Tags.SEARCH_STATUS) {
    196                     String status = getValue();
    197                 } else if (tag == Tags.SEARCH_TOTAL) {
    198                     mTotalResults = getValueInt();
    199                 } else if (tag == Tags.SEARCH_RESULT) {
    200                     parseResult(parser, ops);
    201                 } else {
    202                     skipTag();
    203                 }
    204             }
    205 
    206             try {
    207                 adapter.mContentResolver.applyBatch(EmailContent.AUTHORITY, ops);
    208                 if (Eas.USER_LOG) {
    209                     mService.userLog("Saved " + ops.size() + " search results");
    210                 }
    211             } catch (RemoteException e) {
    212                 Log.d(Logging.LOG_TAG, "RemoteException while saving search results.");
    213             } catch (OperationApplicationException e) {
    214             }
    215 
    216             return res;
    217         }
    218 
    219         private boolean parseResult(EasEmailSyncParser parser,
    220                 ArrayList<ContentProviderOperation> ops) throws IOException {
    221             // Get an email sync parser for our incoming message data
    222             boolean res = false;
    223             Message msg = new Message();
    224             while (nextTag(Tags.SEARCH_RESULT) != END) {
    225                 if (tag == Tags.SYNC_CLASS) {
    226                     getValue();
    227                 } else if (tag == Tags.SYNC_COLLECTION_ID) {
    228                     getValue();
    229                 } else if (tag == Tags.SEARCH_LONG_ID) {
    230                     msg.mProtocolSearchInfo = getValue();
    231                 } else if (tag == Tags.SEARCH_PROPERTIES) {
    232                     msg.mAccountKey = mService.mAccount.mId;
    233                     msg.mMailboxKey = mService.mMailbox.mId;
    234                     msg.mFlagLoaded = Message.FLAG_LOADED_COMPLETE;
    235                     parser.pushTag(tag);
    236                     parser.addData(msg, tag);
    237                     if (msg.mHtml != null) {
    238                         msg.mHtml = TextUtilities.highlightTermsInHtml(msg.mHtml, mQuery);
    239                     }
    240                     msg.addSaveOps(ops);
    241                 } else {
    242                     skipTag();
    243                 }
    244             }
    245             return res;
    246         }
    247     }
    248 }
    249