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