• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
searchMessages(Context context, long accountId, SearchParams searchParams, long destMailboxId)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 
SearchParser(InputStream in, EasSyncService service, String query)155         private SearchParser(InputStream in, EasSyncService service, String query)
156                 throws IOException {
157             super(in);
158             mService = service;
159             mQuery = query;
160         }
161 
getTotalResults()162         protected int getTotalResults() {
163             return mTotalResults;
164         }
165 
166         @Override
parse()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 
parseResponse()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 
parseStore()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 
parseResult(EasEmailSyncParser parser, ArrayList<ContentProviderOperation> ops)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