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