1331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner/**
2331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner * Copyright (c) 2009, Google Inc.
3331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner *
4331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner * Licensed under the Apache License, Version 2.0 (the "License");
5331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner * you may not use this file except in compliance with the License.
6331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner * You may obtain a copy of the License at
7331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner *
8331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner *     http://www.apache.org/licenses/LICENSE-2.0
9331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner *
10331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner * Unless required by applicable law or agreed to in writing, software
11331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner * distributed under the License is distributed on an "AS IS" BASIS,
12331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner * See the License for the specific language governing permissions and
14331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner * limitations under the License.
15331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner */
16331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner
17331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagnerpackage com.android.mms.ui;
18331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner
19ea5e915b6104ff0e30c313bcb5a96f838997e57bMark Wagnerimport java.util.HashMap;
20f114492537753bc68640d4a0d403861387296bcbMark Wagnerimport java.util.regex.Matcher;
21f114492537753bc68640d4a0d403861387296bcbMark Wagnerimport java.util.regex.Pattern;
22f114492537753bc68640d4a0d403861387296bcbMark Wagner
238447aa1d84a469e8938b53a60b0763633b08ae98Tom Taylorimport com.android.mms.MmsApp;
24331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagnerimport com.android.mms.R;
25331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagnerimport android.app.ListActivity;
26331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagnerimport android.app.SearchManager;
27331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagnerimport android.content.AsyncQueryHandler;
28331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagnerimport android.content.ContentResolver;
29331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagnerimport android.content.Context;
30331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagnerimport android.content.Intent;
31331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagnerimport android.database.Cursor;
32331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagnerimport android.graphics.Color;
33331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagnerimport android.graphics.Typeface;
34331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagnerimport android.net.Uri;
35331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagnerimport android.os.Bundle;
36f114492537753bc68640d4a0d403861387296bcbMark Wagnerimport android.provider.SearchRecentSuggestions;
37f114492537753bc68640d4a0d403861387296bcbMark Wagner
38f7e8281a223af6228e6399055a6197a1edd9bc3aTom Taylorimport android.provider.Telephony;
39331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagnerimport android.text.SpannableString;
40331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagnerimport android.text.TextPaint;
41331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagnerimport android.text.style.ForegroundColorSpan;
42331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagnerimport android.text.style.StyleSpan;
43331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagnerimport android.util.AttributeSet;
44331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagnerimport android.view.LayoutInflater;
45331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagnerimport android.view.View;
46331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagnerimport android.view.ViewGroup;
47331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagnerimport android.widget.CursorAdapter;
48bc878528ed57d19bec31553cd75efaac1e0d3a5bMark Wagnerimport android.widget.ListView;
49331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagnerimport android.widget.TextView;
50331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner
51331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagnerimport com.android.mms.data.Contact;
52ea5e915b6104ff0e30c313bcb5a96f838997e57bMark Wagnerimport com.android.mms.data.Contact.UpdateListener;
53331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagnerimport com.android.mms.ui.ComposeMessageActivity;
54331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner
55331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner/***
56331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner * Presents a List of search results.  Each item in the list represents a thread which
57331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner * matches.  The item contains the contact (or phone number) as the "title" and a
58331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner * snippet of what matches, below.  The snippet is taken from the most recent part of
59331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner * the conversation that has a match.  Each match within the visible portion of the
60331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner * snippet is highlighted.
61331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner */
62331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner
63331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagnerpublic class SearchActivity extends ListActivity
64331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner{
65ea5e915b6104ff0e30c313bcb5a96f838997e57bMark Wagner    private AsyncQueryHandler mQueryHandler;
66ea5e915b6104ff0e30c313bcb5a96f838997e57bMark Wagner
67ea5e915b6104ff0e30c313bcb5a96f838997e57bMark Wagner    // Track which TextView's show which Contact objects so that we can update
68ea5e915b6104ff0e30c313bcb5a96f838997e57bMark Wagner    // appropriately when the Contact gets fully loaded.
69ea5e915b6104ff0e30c313bcb5a96f838997e57bMark Wagner    private HashMap<Contact, TextView> mContactMap = new HashMap<Contact, TextView>();
70ea5e915b6104ff0e30c313bcb5a96f838997e57bMark Wagner
71331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner
72331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner    /*
73331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner     * Subclass of TextView which displays a snippet of text which matches the full text and
74331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner     * highlights the matches within the snippet.
75331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner     */
76331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner    public static class TextViewSnippet extends TextView {
77331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner        private static String sEllipsis = "\u2026";
78bc878528ed57d19bec31553cd75efaac1e0d3a5bMark Wagner
79331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner        private static int sTypefaceHighlight = Typeface.BOLD;
80331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner
81331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner        private String mFullText;
82331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner        private String mTargetString;
83f114492537753bc68640d4a0d403861387296bcbMark Wagner        private Pattern mPattern;
84331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner
85331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner        public TextViewSnippet(Context context, AttributeSet attrs) {
86331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner            super(context, attrs);
87331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner        }
88331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner
89331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner        public TextViewSnippet(Context context) {
90331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner            super(context);
91331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner        }
92331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner
93331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner        public TextViewSnippet(Context context, AttributeSet attrs, int defStyle) {
94331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner            super(context, attrs, defStyle);
95331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner        }
96331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner
97331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner        /**
98331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner         * We have to know our width before we can compute the snippet string.  Do that
99331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner         * here and then defer to super for whatever work is normally done.
100331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner         */
101331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner        @Override
102331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner        protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
103331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner            String fullTextLower = mFullText.toLowerCase();
104331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner            String targetStringLower = mTargetString.toLowerCase();
105b2de23b71c774412d228d20ff4b941f082df3725Mark Wagner
106f114492537753bc68640d4a0d403861387296bcbMark Wagner            int startPos = 0;
107331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner            int searchStringLength = targetStringLower.length();
108331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner            int bodyLength = fullTextLower.length();
109331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner
110f114492537753bc68640d4a0d403861387296bcbMark Wagner            Matcher m = mPattern.matcher(mFullText);
111f114492537753bc68640d4a0d403861387296bcbMark Wagner            if (m.find(0)) {
112f114492537753bc68640d4a0d403861387296bcbMark Wagner                startPos = m.start();
113f114492537753bc68640d4a0d403861387296bcbMark Wagner            }
114f114492537753bc68640d4a0d403861387296bcbMark Wagner
115331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner            TextPaint tp = getPaint();
116331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner
117331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner            float searchStringWidth = tp.measureText(mTargetString);
118331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner            float textFieldWidth = getWidth();
119331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner
120331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner            String snippetString = null;
121331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner            if (searchStringWidth > textFieldWidth) {
122331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner                snippetString = mFullText.substring(startPos, startPos + searchStringLength);
123331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner            } else {
124331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner                float ellipsisWidth = tp.measureText(sEllipsis);
125331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner                textFieldWidth -= (2F * ellipsisWidth); // assume we'll need one on both ends
126331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner
127331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner                int offset = -1;
128331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner                int start = -1;
129331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner                int end = -1;
130331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner                /* TODO: this code could be made more efficient by only measuring the additional
131331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner                 * characters as we widen the string rather than measuring the whole new
132331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner                 * string each time.
133331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner                 */
134331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner                while (true) {
135331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner                    offset += 1;
136331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner
137331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner                    int newstart = Math.max(0, startPos - offset);
138331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner                    int newend = Math.min(bodyLength, startPos + searchStringLength + offset);
139331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner
140331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner                    if (newstart == start && newend == end) {
141331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner                        // if we couldn't expand out any further then we're done
142331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner                        break;
143331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner                    }
144331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner                    start = newstart;
145331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner                    end = newend;
146331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner
147331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner                    // pull the candidate string out of the full text rather than body
148331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner                    // because body has been toLower()'ed
149331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner                    String candidate = mFullText.substring(start, end);
150331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner                    if (tp.measureText(candidate) > textFieldWidth) {
151331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner                        // if the newly computed width would exceed our bounds then we're done
152331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner                        // do not use this "candidate"
153331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner                        break;
154331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner                    }
155331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner
156331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner                    snippetString = String.format(
157331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner                            "%s%s%s",
158331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner                            start == 0 ? "" : sEllipsis,
159331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner                            candidate,
160331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner                            end == bodyLength ? "" : sEllipsis);
161331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner                }
162331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner            }
163331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner
164331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner            SpannableString spannable = new SpannableString(snippetString);
165331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner            int start = 0;
166f114492537753bc68640d4a0d403861387296bcbMark Wagner
167f114492537753bc68640d4a0d403861387296bcbMark Wagner            m = mPattern.matcher(snippetString);
168f114492537753bc68640d4a0d403861387296bcbMark Wagner            while (m.find(start)) {
169f114492537753bc68640d4a0d403861387296bcbMark Wagner                spannable.setSpan(new StyleSpan(sTypefaceHighlight), m.start(), m.end(), 0);
170f114492537753bc68640d4a0d403861387296bcbMark Wagner                start = m.end();
171331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner            }
172331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner            setText(spannable);
173331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner
174331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner            // do this after the call to setText() above
175331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner            super.onLayout(changed, left, top, right, bottom);
176331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner        }
177331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner
178331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner        public void setText(String fullText, String target) {
179f114492537753bc68640d4a0d403861387296bcbMark Wagner            // Use a regular expression to locate the target string
180f114492537753bc68640d4a0d403861387296bcbMark Wagner            // within the full text.  The target string must be
181f114492537753bc68640d4a0d403861387296bcbMark Wagner            // found as a word start so we use \b which matches
182f114492537753bc68640d4a0d403861387296bcbMark Wagner            // word boundaries.
183459ad1ea05610f7c071f65da6842e00e21bd5eafMark Wagner            String patternString = "\\b" + Pattern.quote(target);
184f114492537753bc68640d4a0d403861387296bcbMark Wagner            mPattern = Pattern.compile(patternString, Pattern.CASE_INSENSITIVE);
185f114492537753bc68640d4a0d403861387296bcbMark Wagner
186331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner            mFullText = fullText;
187331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner            mTargetString = target;
188331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner            requestLayout();
189331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner        }
190331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner    }
191331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner
192ea5e915b6104ff0e30c313bcb5a96f838997e57bMark Wagner    Contact.UpdateListener mContactListener = new Contact.UpdateListener() {
193ea5e915b6104ff0e30c313bcb5a96f838997e57bMark Wagner        public void onUpdate(Contact updated) {
194ea5e915b6104ff0e30c313bcb5a96f838997e57bMark Wagner            TextView tv = mContactMap.get(updated);
195ea5e915b6104ff0e30c313bcb5a96f838997e57bMark Wagner            if (tv != null) {
196ea5e915b6104ff0e30c313bcb5a96f838997e57bMark Wagner                tv.setText(updated.getNameAndNumber());
197ea5e915b6104ff0e30c313bcb5a96f838997e57bMark Wagner            }
198ea5e915b6104ff0e30c313bcb5a96f838997e57bMark Wagner        }
199ea5e915b6104ff0e30c313bcb5a96f838997e57bMark Wagner    };
200ea5e915b6104ff0e30c313bcb5a96f838997e57bMark Wagner
201ea5e915b6104ff0e30c313bcb5a96f838997e57bMark Wagner    @Override
202ea5e915b6104ff0e30c313bcb5a96f838997e57bMark Wagner    public void onStop() {
203ea5e915b6104ff0e30c313bcb5a96f838997e57bMark Wagner        super.onStop();
204ea5e915b6104ff0e30c313bcb5a96f838997e57bMark Wagner        Contact.removeListener(mContactListener);
205ea5e915b6104ff0e30c313bcb5a96f838997e57bMark Wagner    }
206ea5e915b6104ff0e30c313bcb5a96f838997e57bMark Wagner
207ea5e915b6104ff0e30c313bcb5a96f838997e57bMark Wagner    @Override
208331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner    public void onCreate(Bundle icicle)
209331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner    {
210331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner        super.onCreate(icicle);
211d808dd7a449839282d13426d353b7007122ddb64Mark Wagner        setContentView(R.layout.search_activity);
212331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner
213f114492537753bc68640d4a0d403861387296bcbMark Wagner        String searchStringParameter = getIntent().getStringExtra(SearchManager.QUERY);
214f114492537753bc68640d4a0d403861387296bcbMark Wagner        if (searchStringParameter == null) {
215f114492537753bc68640d4a0d403861387296bcbMark Wagner            searchStringParameter = getIntent().getStringExtra("intent_extra_data_key" /*SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA*/);
216f114492537753bc68640d4a0d403861387296bcbMark Wagner        }
2178447aa1d84a469e8938b53a60b0763633b08ae98Tom Taylor        final String searchString =
2183e48eec660aa5eaf3dfd186f75a648ea188d1f7bMark Wagner        	searchStringParameter != null ? searchStringParameter.trim() : searchStringParameter;
219331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner        ContentResolver cr = getContentResolver();
220331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner
221f114492537753bc68640d4a0d403861387296bcbMark Wagner        searchStringParameter = searchStringParameter.trim();
222bc878528ed57d19bec31553cd75efaac1e0d3a5bMark Wagner        final ListView listView = getListView();
223bc878528ed57d19bec31553cd75efaac1e0d3a5bMark Wagner        listView.setItemsCanFocus(true);
224bc878528ed57d19bec31553cd75efaac1e0d3a5bMark Wagner        listView.setFocusable(true);
225bc878528ed57d19bec31553cd75efaac1e0d3a5bMark Wagner        listView.setClickable(true);
226bc878528ed57d19bec31553cd75efaac1e0d3a5bMark Wagner
227b2de23b71c774412d228d20ff4b941f082df3725Mark Wagner        // I considered something like "searching..." but typically it will
228b2de23b71c774412d228d20ff4b941f082df3725Mark Wagner        // flash on the screen briefly which I found to be more distracting
229b2de23b71c774412d228d20ff4b941f082df3725Mark Wagner        // than beneficial.
230b2de23b71c774412d228d20ff4b941f082df3725Mark Wagner        // This gets updated when the query completes.
231b2de23b71c774412d228d20ff4b941f082df3725Mark Wagner        setTitle("");
232b2de23b71c774412d228d20ff4b941f082df3725Mark Wagner
233ea5e915b6104ff0e30c313bcb5a96f838997e57bMark Wagner        Contact.addListener(mContactListener);
234ea5e915b6104ff0e30c313bcb5a96f838997e57bMark Wagner
235f06d26b3491ea4708e8e1b8214f2ef1cf9d4575bMark Wagner        // When the query completes cons up a new adapter and set our list adapter to that.
236331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner        mQueryHandler = new AsyncQueryHandler(cr) {
237331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner            protected void onQueryComplete(int token, Object cookie, Cursor c) {
23869f9edb80d24a50a0f98d7fcaccdc3e19cc903beTom Taylor                if (c == null) {
23969f9edb80d24a50a0f98d7fcaccdc3e19cc903beTom Taylor                    return;
24069f9edb80d24a50a0f98d7fcaccdc3e19cc903beTom Taylor                }
241331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner                final int threadIdPos = c.getColumnIndex("thread_id");
242331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner                final int addressPos  = c.getColumnIndex("address");
243331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner                final int bodyPos     = c.getColumnIndex("body");
244331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner                final int rowidPos    = c.getColumnIndex("_id");
245331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner
246b2de23b71c774412d228d20ff4b941f082df3725Mark Wagner                int cursorCount = c.getCount();
247b2de23b71c774412d228d20ff4b941f082df3725Mark Wagner                setTitle(getResources().getQuantityString(
248b2de23b71c774412d228d20ff4b941f082df3725Mark Wagner                        R.plurals.search_results_title,
249b2de23b71c774412d228d20ff4b941f082df3725Mark Wagner                        cursorCount,
250b2de23b71c774412d228d20ff4b941f082df3725Mark Wagner                        cursorCount,
251b2de23b71c774412d228d20ff4b941f082df3725Mark Wagner                        searchString));
252b2de23b71c774412d228d20ff4b941f082df3725Mark Wagner
253f1ab045ff991e1ec3e4161d213627b8629971c0eTom Taylor                // Note that we're telling the CursorAdapter not to do auto-requeries. If we
254f1ab045ff991e1ec3e4161d213627b8629971c0eTom Taylor                // want to dynamically respond to changes in the search results,
255f1ab045ff991e1ec3e4161d213627b8629971c0eTom Taylor                // we'll have have to add a setOnDataSetChangedListener().
256f1ab045ff991e1ec3e4161d213627b8629971c0eTom Taylor                setListAdapter(new CursorAdapter(SearchActivity.this,
257f1ab045ff991e1ec3e4161d213627b8629971c0eTom Taylor                        c, false /* no auto-requery */) {
258331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner                    @Override
259331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner                    public void bindView(View view, Context context, Cursor cursor) {
26081c7152ef1638673d215e36a126b4bf8db236abeMark Wagner                        final TextView title = (TextView)(view.findViewById(R.id.title));
26181c7152ef1638673d215e36a126b4bf8db236abeMark Wagner                        final TextViewSnippet snippet = (TextViewSnippet)(view.findViewById(R.id.subtitle));
262331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner
263331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner                        String address = cursor.getString(addressPos);
2646fb305d028ceaf74e4237687a1337396bb01b056Mark Wagner                        Contact contact = address != null ? Contact.get(address, false) : null;
26581c7152ef1638673d215e36a126b4bf8db236abeMark Wagner
2666fb305d028ceaf74e4237687a1337396bb01b056Mark Wagner                        String titleString = contact != null ? contact.getNameAndNumber() : "";
26781c7152ef1638673d215e36a126b4bf8db236abeMark Wagner                        title.setText(titleString);
268331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner
269331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner                        snippet.setText(cursor.getString(bodyPos), searchString);
270331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner
271331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner                        // if the user touches the item then launch the compose message
272331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner                        // activity with some extra parameters to highlight the search
273331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner                        // results and scroll to the latest part of the conversation
274331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner                        // that has a match.
275331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner                        final long threadId = cursor.getLong(threadIdPos);
276331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner                        final long rowid = cursor.getLong(rowidPos);
277331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner
278331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner                        view.setOnClickListener(new View.OnClickListener() {
279331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner                            public void onClick(View v) {
280331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner                                final Intent onClickIntent = new Intent(SearchActivity.this, ComposeMessageActivity.class);
281331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner                                onClickIntent.putExtra("thread_id", threadId);
282331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner                                onClickIntent.putExtra("highlight", searchString);
283331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner                                onClickIntent.putExtra("select_id", rowid);
284331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner                                startActivity(onClickIntent);
285331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner                            }
286331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner                        });
287331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner                    }
288331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner
289331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner                    @Override
290331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner                    public View newView(Context context, Cursor cursor, ViewGroup parent) {
291331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner                        LayoutInflater inflater = LayoutInflater.from(context);
292bc878528ed57d19bec31553cd75efaac1e0d3a5bMark Wagner                        View v = inflater.inflate(R.layout.search_item, parent, false);
293331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner                        return v;
294331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner                    }
295331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner
296331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner                });
297417d3ee9970f4cef1a42650c49d97c6d85c35204Mark Wagner
298417d3ee9970f4cef1a42650c49d97c6d85c35204Mark Wagner                // ListView seems to want to reject the setFocusable until such time
299417d3ee9970f4cef1a42650c49d97c6d85c35204Mark Wagner                // as the list is not empty.  Set it here and request focus.  Without
300417d3ee9970f4cef1a42650c49d97c6d85c35204Mark Wagner                // this the arrow keys (and trackball) fail to move the selection.
301417d3ee9970f4cef1a42650c49d97c6d85c35204Mark Wagner                listView.setFocusable(true);
302417d3ee9970f4cef1a42650c49d97c6d85c35204Mark Wagner                listView.setFocusableInTouchMode(true);
303417d3ee9970f4cef1a42650c49d97c6d85c35204Mark Wagner                listView.requestFocus();
3048447aa1d84a469e8938b53a60b0763633b08ae98Tom Taylor
3058447aa1d84a469e8938b53a60b0763633b08ae98Tom Taylor                // Remember the query if there are actual results
3068447aa1d84a469e8938b53a60b0763633b08ae98Tom Taylor                if (cursorCount > 0) {
307f114492537753bc68640d4a0d403861387296bcbMark Wagner                    SearchRecentSuggestions recent = ((MmsApp)getApplication()).getRecentSuggestions();
308f114492537753bc68640d4a0d403861387296bcbMark Wagner                    if (recent != null) {
309f114492537753bc68640d4a0d403861387296bcbMark Wagner                        recent.saveRecentQuery(
310f114492537753bc68640d4a0d403861387296bcbMark Wagner                                searchString,
311f114492537753bc68640d4a0d403861387296bcbMark Wagner                                getString(R.string.search_history,
312f114492537753bc68640d4a0d403861387296bcbMark Wagner                                        cursorCount, searchString));
313f114492537753bc68640d4a0d403861387296bcbMark Wagner                    }
3148447aa1d84a469e8938b53a60b0763633b08ae98Tom Taylor                }
315331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner            }
316331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner        };
317331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner
318331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner        // don't pass a projection since the search uri ignores it
319f7e8281a223af6228e6399055a6197a1edd9bc3aTom Taylor        Uri uri = Telephony.MmsSms.SEARCH_URI.buildUpon()
3206be18bedb5b87dbbcdb54f37d5a0945bd0f71377Tom Taylor                    .appendQueryParameter("pattern", searchString).build();
321331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner
322331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner        // kick off a query for the threads which match the search string
323f06d26b3491ea4708e8e1b8214f2ef1cf9d4575bMark Wagner        mQueryHandler.startQuery(0, null, uri, null, null, null, null);
324331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner
325331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner    }
326331864544ec51ba6807fc5471cc6d537b7fef198Mark Wagner}
327