CursorBackedSuggestionCursor.java revision 9038d65a5a8ebcfada1ec3067f81a26f05622088
1/*
2 * Copyright (C) 2009 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
17package com.android.quicksearchbox;
18
19import android.app.SearchManager;
20import android.content.Intent;
21import android.database.Cursor;
22import android.database.DataSetObserver;
23import android.net.Uri;
24import android.util.Log;
25
26public abstract class CursorBackedSuggestionCursor implements SuggestionCursor {
27
28    private static final boolean DBG = false;
29    protected static final String TAG = "QSB.CursorBackedSuggestionCursor";
30
31    public static final String SUGGEST_COLUMN_LOG_TYPE = "suggest_log_type";
32
33    private final String mUserQuery;
34
35    /** The suggestions, or {@code null} if the suggestions query failed. */
36    protected final Cursor mCursor;
37
38    /** Column index of {@link SearchManager#SUGGEST_COLUMN_FORMAT} in @{link mCursor}. */
39    private final int mFormatCol;
40
41    /** Column index of {@link SearchManager#SUGGEST_COLUMN_TEXT_1} in @{link mCursor}. */
42    private final int mText1Col;
43
44    /** Column index of {@link SearchManager#SUGGEST_COLUMN_TEXT_2} in @{link mCursor}. */
45    private final int mText2Col;
46
47    /** Column index of {@link SearchManager#SUGGEST_COLUMN_TEXT_2_URL} in @{link mCursor}. */
48    private final int mText2UrlCol;
49
50    /** Column index of {@link SearchManager#SUGGEST_COLUMN_ICON_1} in @{link mCursor}. */
51    private final int mIcon1Col;
52
53    /** Column index of {@link SearchManager#SUGGEST_COLUMN_ICON_1} in @{link mCursor}. */
54    private final int mIcon2Col;
55
56    /** Column index of {@link SearchManager#SUGGEST_COLUMN_SPINNER_WHILE_REFRESHING}
57     * in @{link mCursor}.
58     **/
59    private final int mRefreshSpinnerCol;
60
61    /** True if this result has been closed. */
62    private boolean mClosed = false;
63
64    public CursorBackedSuggestionCursor(String userQuery, Cursor cursor) {
65        mUserQuery = userQuery;
66        mCursor = cursor;
67        mFormatCol = getColumnIndex(SearchManager.SUGGEST_COLUMN_FORMAT);
68        mText1Col = getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_1);
69        mText2Col = getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_2);
70        mText2UrlCol = getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_2_URL);
71        mIcon1Col = getColumnIndex(SearchManager.SUGGEST_COLUMN_ICON_1);
72        mIcon2Col = getColumnIndex(SearchManager.SUGGEST_COLUMN_ICON_2);
73        mRefreshSpinnerCol = getColumnIndex(SearchManager.SUGGEST_COLUMN_SPINNER_WHILE_REFRESHING);
74    }
75
76    public String getUserQuery() {
77        return mUserQuery;
78    }
79
80    public abstract Source getSuggestionSource();
81
82    public String getSuggestionLogType() {
83        return getStringOrNull(SUGGEST_COLUMN_LOG_TYPE);
84    }
85
86    public void close() {
87        if (DBG) Log.d(TAG, "close()");
88        if (mClosed) {
89            throw new IllegalStateException("Double close()");
90        }
91        mClosed = true;
92        if (mCursor != null) {
93            try {
94                mCursor.close();
95            } catch (RuntimeException ex) {
96                // all operations on cross-process cursors can throw random exceptions
97                Log.e(TAG, "close() failed, ", ex);
98            }
99        }
100    }
101
102    @Override
103    protected void finalize() {
104        if (!mClosed) {
105            Log.e(TAG, "LEAK! Finalized without being closed: " + toString());
106        }
107    }
108
109    public int getCount() {
110        if (mClosed) {
111            throw new IllegalStateException("getCount() after close()");
112        }
113        if (mCursor == null) return 0;
114        try {
115            return mCursor.getCount();
116        } catch (RuntimeException ex) {
117            // all operations on cross-process cursors can throw random exceptions
118            Log.e(TAG, "getCount() failed, ", ex);
119            return 0;
120        }
121    }
122
123    public void moveTo(int pos) {
124        if (mClosed) {
125            throw new IllegalStateException("moveTo(" + pos + ") after close()");
126        }
127        try {
128            if (!mCursor.moveToPosition(pos)) {
129                Log.e(TAG, "moveToPosition(" + pos + ") failed, count=" + getCount());
130            }
131        } catch (RuntimeException ex) {
132            // all operations on cross-process cursors can throw random exceptions
133            Log.e(TAG, "moveToPosition() failed, ", ex);
134        }
135    }
136
137    public boolean moveToNext() {
138        if (mClosed) {
139            throw new IllegalStateException("moveToNext() after close()");
140        }
141        try {
142            return mCursor.moveToNext();
143        } catch (RuntimeException ex) {
144            // all operations on cross-process cursors can throw random exceptions
145            Log.e(TAG, "moveToNext() failed, ", ex);
146            return false;
147        }
148    }
149
150    public int getPosition() {
151        if (mClosed) {
152            throw new IllegalStateException("getPosition after close()");
153        }
154        try {
155            return mCursor.getPosition();
156        } catch (RuntimeException ex) {
157            // all operations on cross-process cursors can throw random exceptions
158            Log.e(TAG, "getPosition() failed, ", ex);
159            return -1;
160        }
161    }
162
163    public String getShortcutId() {
164        return getStringOrNull(SearchManager.SUGGEST_COLUMN_SHORTCUT_ID);
165    }
166
167    public String getSuggestionFormat() {
168        return getStringOrNull(mFormatCol);
169    }
170
171    public String getSuggestionText1() {
172        return getStringOrNull(mText1Col);
173    }
174
175    public String getSuggestionText2() {
176        return getStringOrNull(mText2Col);
177    }
178
179    public String getSuggestionText2Url() {
180        return getStringOrNull(mText2UrlCol);
181    }
182
183    public String getSuggestionIcon1() {
184        return getStringOrNull(mIcon1Col);
185    }
186
187    public String getSuggestionIcon2() {
188        return getStringOrNull(mIcon2Col);
189    }
190
191    public boolean isSpinnerWhileRefreshing() {
192        return "true".equals(getStringOrNull(mRefreshSpinnerCol));
193    }
194
195    /**
196     * Gets the intent action for the current suggestion.
197     */
198    public String getSuggestionIntentAction() {
199        String action = getStringOrNull(SearchManager.SUGGEST_COLUMN_INTENT_ACTION);
200        if (action != null) return action;
201        return getSuggestionSource().getDefaultIntentAction();
202    }
203
204    /**
205     * Gets the query for the current suggestion.
206     */
207    public String getSuggestionQuery() {
208        return getStringOrNull(SearchManager.SUGGEST_COLUMN_QUERY);
209    }
210
211    public String getSuggestionIntentDataString() {
212         // use specific data if supplied, or default data if supplied
213         String data = getStringOrNull(SearchManager.SUGGEST_COLUMN_INTENT_DATA);
214         if (data == null) {
215             data = getSuggestionSource().getDefaultIntentData();
216         }
217         // then, if an ID was provided, append it.
218         if (data != null) {
219             String id = getStringOrNull(SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID);
220             if (id != null) {
221                 data = data + "/" + Uri.encode(id);
222             }
223         }
224         return data;
225     }
226
227    /**
228     * Gets the intent extra data for the current suggestion.
229     */
230    public String getSuggestionIntentExtraData() {
231        return getStringOrNull(SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA);
232    }
233
234    public boolean isWebSearchSuggestion() {
235        return Intent.ACTION_WEB_SEARCH.equals(getSuggestionIntentAction());
236    }
237
238    /**
239     * Gets the index of a column in {@link #mCursor} by name.
240     *
241     * @return The index, or {@code -1} if the column was not found.
242     */
243    protected int getColumnIndex(String colName) {
244        if (mCursor == null) return -1;
245        try {
246            return mCursor.getColumnIndex(colName);
247        } catch (RuntimeException ex) {
248            // all operations on cross-process cursors can throw random exceptions
249            Log.e(TAG, "getColumnIndex() failed, ", ex);
250            return -1;
251        }
252    }
253
254    /**
255     * Gets the string value of a column in {@link #mCursor} by column index.
256     *
257     * @param col Column index.
258     * @return The string value, or {@code null}.
259     */
260    protected String getStringOrNull(int col) {
261        if (mCursor == null) return null;
262        if (col == -1) {
263            return null;
264        }
265        try {
266            return mCursor.getString(col);
267        } catch (RuntimeException ex) {
268            // all operations on cross-process cursors can throw random exceptions
269            Log.e(TAG, "getString() failed, ", ex);
270            return null;
271        }
272    }
273
274    /**
275     * Gets the string value of a column in {@link #mCursor} by column name.
276     *
277     * @param colName Column name.
278     * @return The string value, or {@code null}.
279     */
280    protected String getStringOrNull(String colName) {
281        int col = getColumnIndex(colName);
282        return getStringOrNull(col);
283    }
284
285    public void registerDataSetObserver(DataSetObserver observer) {
286        // We don't watch Cursor-backed SuggestionCursors for changes
287    }
288
289    public void unregisterDataSetObserver(DataSetObserver observer) {
290        // We don't watch Cursor-backed SuggestionCursors for changes
291    }
292
293    @Override
294    public String toString() {
295        return getClass().getSimpleName() + "[" + mUserQuery + "]";
296    }
297
298}
299