| /* |
| * Copyright (C) 2011 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.exchange.adapter; |
| |
| import android.content.ContentProviderOperation; |
| import android.content.Context; |
| import android.content.OperationApplicationException; |
| import android.os.RemoteException; |
| import android.util.Log; |
| |
| import com.android.emailcommon.Logging; |
| import com.android.emailcommon.provider.Account; |
| import com.android.emailcommon.provider.EmailContent; |
| import com.android.emailcommon.provider.EmailContent.Message; |
| import com.android.emailcommon.provider.Mailbox; |
| import com.android.emailcommon.service.EmailServiceStatus; |
| import com.android.emailcommon.service.SearchParams; |
| import com.android.emailcommon.utility.TextUtilities; |
| import com.android.exchange.Eas; |
| import com.android.exchange.EasResponse; |
| import com.android.exchange.EasSyncService; |
| import com.android.exchange.ExchangeService; |
| import com.android.exchange.adapter.EmailSyncAdapter.EasEmailSyncParser; |
| |
| import org.apache.http.HttpStatus; |
| |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.util.ArrayList; |
| |
| /** |
| * Implementation of server-side search for EAS using the EmailService API |
| */ |
| public class Search { |
| // The shortest search query we'll accept |
| // TODO Check with UX whether this is correct |
| private static final int MIN_QUERY_LENGTH = 3; |
| // The largest number of results we'll ask for per server request |
| private static final int MAX_SEARCH_RESULTS = 100; |
| |
| public static int searchMessages(Context context, long accountId, SearchParams searchParams, |
| long destMailboxId) { |
| // Sanity check for arguments |
| int offset = searchParams.mOffset; |
| int limit = searchParams.mLimit; |
| String filter = searchParams.mFilter; |
| if (limit < 0 || limit > MAX_SEARCH_RESULTS || offset < 0) return 0; |
| // TODO Should this be checked in UI? Are there guidelines for minimums? |
| if (filter == null || filter.length() < MIN_QUERY_LENGTH) return 0; |
| |
| int res = 0; |
| Account account = Account.restoreAccountWithId(context, accountId); |
| if (account == null) return res; |
| EasSyncService svc = EasSyncService.setupServiceForAccount(context, account); |
| if (svc == null) return res; |
| try { |
| Mailbox searchMailbox = Mailbox.restoreMailboxWithId(context, destMailboxId); |
| // Sanity check; account might have been deleted? |
| if (searchMailbox == null) return res; |
| svc.mMailbox = searchMailbox; |
| svc.mAccount = account; |
| Serializer s = new Serializer(); |
| s.start(Tags.SEARCH_SEARCH).start(Tags.SEARCH_STORE); |
| s.data(Tags.SEARCH_NAME, "Mailbox"); |
| s.start(Tags.SEARCH_QUERY).start(Tags.SEARCH_AND); |
| s.data(Tags.SYNC_CLASS, "Email"); |
| |
| // If this isn't an inbox search, then include the collection id |
| Mailbox inbox = Mailbox.restoreMailboxOfType(context, accountId, Mailbox.TYPE_INBOX); |
| if (inbox == null) return 0; |
| if (searchParams.mMailboxId != inbox.mId) { |
| s.data(Tags.SYNC_COLLECTION_ID, inbox.mServerId); |
| } |
| |
| s.data(Tags.SEARCH_FREE_TEXT, filter); |
| s.end().end(); // SEARCH_AND, SEARCH_QUERY |
| s.start(Tags.SEARCH_OPTIONS); |
| if (offset == 0) { |
| s.tag(Tags.SEARCH_REBUILD_RESULTS); |
| } |
| if (searchParams.mIncludeChildren) { |
| s.tag(Tags.SEARCH_DEEP_TRAVERSAL); |
| } |
| // Range is sent in the form first-last (e.g. 0-9) |
| s.data(Tags.SEARCH_RANGE, offset + "-" + (offset + limit - 1)); |
| s.start(Tags.BASE_BODY_PREFERENCE); |
| s.data(Tags.BASE_TYPE, Eas.BODY_PREFERENCE_HTML); |
| s.data(Tags.BASE_TRUNCATION_SIZE, "20000"); |
| s.end(); // BASE_BODY_PREFERENCE |
| s.end().end().end().done(); // SEARCH_OPTIONS, SEARCH_STORE, SEARCH_SEARCH |
| EasResponse resp = svc.sendHttpClientPost("Search", s.toByteArray()); |
| try { |
| int code = resp.getStatus(); |
| if (code == HttpStatus.SC_OK) { |
| InputStream is = resp.getInputStream(); |
| try { |
| SearchParser sp = new SearchParser(is, svc, filter); |
| sp.parse(); |
| res = sp.getTotalResults(); |
| } finally { |
| is.close(); |
| } |
| } else { |
| svc.userLog("Search returned " + code); |
| } |
| } finally { |
| resp.close(); |
| } |
| } catch (IOException e) { |
| svc.userLog("Search exception " + e); |
| } finally { |
| try { |
| ExchangeService.callback().syncMailboxStatus(destMailboxId, |
| EmailServiceStatus.SUCCESS, 100); |
| } catch (RemoteException e) { |
| } |
| } |
| // Return the total count |
| return res; |
| } |
| |
| /** |
| * Parse the result of a Search command |
| */ |
| static class SearchParser extends Parser { |
| private final EasSyncService mService; |
| private final String mQuery; |
| private int mTotalResults; |
| |
| private SearchParser(InputStream in, EasSyncService service, String query) |
| throws IOException { |
| super(in); |
| mService = service; |
| mQuery = query; |
| } |
| |
| protected int getTotalResults() { |
| return mTotalResults; |
| } |
| |
| @Override |
| public boolean parse() throws IOException { |
| boolean res = false; |
| if (nextTag(START_DOCUMENT) != Tags.SEARCH_SEARCH) { |
| throw new IOException(); |
| } |
| while (nextTag(START_DOCUMENT) != END_DOCUMENT) { |
| if (tag == Tags.SEARCH_STATUS) { |
| String status = getValue(); |
| if (Eas.DEBUG) { |
| Log.d(Logging.LOG_TAG, "Search status: " + status); |
| } |
| } else if (tag == Tags.SEARCH_RESPONSE) { |
| parseResponse(); |
| } else { |
| skipTag(); |
| } |
| } |
| return res; |
| } |
| |
| private boolean parseResponse() throws IOException { |
| boolean res = false; |
| while (nextTag(Tags.SEARCH_RESPONSE) != END) { |
| if (tag == Tags.SEARCH_STORE) { |
| parseStore(); |
| } else { |
| skipTag(); |
| } |
| } |
| return res; |
| } |
| |
| private boolean parseStore() throws IOException { |
| EmailSyncAdapter adapter = new EmailSyncAdapter(mService); |
| EasEmailSyncParser parser = adapter.new EasEmailSyncParser(this, adapter); |
| ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>(); |
| boolean res = false; |
| |
| while (nextTag(Tags.SEARCH_STORE) != END) { |
| if (tag == Tags.SEARCH_STATUS) { |
| String status = getValue(); |
| if (Eas.DEBUG) { |
| Log.d(Logging.LOG_TAG, "Store status: " + status); |
| } |
| } else if (tag == Tags.SEARCH_TOTAL) { |
| mTotalResults = getValueInt(); |
| } else if (tag == Tags.SEARCH_RESULT) { |
| parseResult(parser, ops); |
| } else { |
| skipTag(); |
| } |
| } |
| |
| try { |
| adapter.mContentResolver.applyBatch(EmailContent.AUTHORITY, ops); |
| if (Eas.DEBUG) { |
| mService.userLog("Saved " + ops.size() + " search results"); |
| } |
| } catch (RemoteException e) { |
| Log.d(Logging.LOG_TAG, "RemoteException while saving search results."); |
| } catch (OperationApplicationException e) { |
| } |
| |
| return res; |
| } |
| |
| private boolean parseResult(EasEmailSyncParser parser, |
| ArrayList<ContentProviderOperation> ops) throws IOException { |
| // Get an email sync parser for our incoming message data |
| boolean res = false; |
| Message msg = new Message(); |
| while (nextTag(Tags.SEARCH_RESULT) != END) { |
| if (tag == Tags.SYNC_CLASS) { |
| getValue(); |
| } else if (tag == Tags.SYNC_COLLECTION_ID) { |
| getValue(); |
| } else if (tag == Tags.SEARCH_LONG_ID) { |
| msg.mProtocolSearchInfo = getValue(); |
| } else if (tag == Tags.SEARCH_PROPERTIES) { |
| msg.mAccountKey = mService.mAccount.mId; |
| msg.mMailboxKey = mService.mMailbox.mId; |
| msg.mFlagLoaded = Message.FLAG_LOADED_COMPLETE; |
| parser.pushTag(tag); |
| parser.addData(msg, tag); |
| if (msg.mHtml != null) { |
| msg.mHtml = TextUtilities.highlightTermsInHtml(msg.mHtml, mQuery); |
| } |
| msg.addSaveOps(ops); |
| } else { |
| skipTag(); |
| } |
| } |
| return res; |
| } |
| } |
| } |