CursorBackedSuggestionCursor.java revision 3673ad4f2b3893f15675820ab2240ec0a64b7970
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 int getPosition() {
131        if (mClosed) {
132            throw new IllegalStateException("getPosition after close()");
133        }
134        try {
135            return mCursor.getPosition();
136        } catch (RuntimeException ex) {
137            // all operations on cross-process cursors can throw random exceptions
138            Log.e(TAG, "getPosition() failed, ", ex);
139            return -1;
140        }
141    }
142
143    public String getShortcutId() {
144        return getStringOrNull(SearchManager.SUGGEST_COLUMN_SHORTCUT_ID);
145    }
146
147    public String getSuggestionFormat() {
148        return getStringOrNull(mFormatCol);
149    }
150
151    public String getSuggestionText1() {
152        return getStringOrNull(mText1Col);
153    }
154
155    public String getSuggestionText2() {
156        return getStringOrNull(mText2Col);
157    }
158
159    public String getSuggestionText2Url() {
160        return getStringOrNull(mText2UrlCol);
161    }
162
163    public String getSuggestionIcon1() {
164        return getStringOrNull(mIcon1Col);
165    }
166
167    public String getSuggestionIcon2() {
168        return getStringOrNull(mIcon2Col);
169    }
170
171    public boolean isSpinnerWhileRefreshing() {
172        return "true".equals(getStringOrNull(mRefreshSpinnerCol));
173    }
174
175    /**
176     * Gets the intent action for the current suggestion.
177     */
178    public String getSuggestionIntentAction() {
179        return getStringOrNull(SearchManager.SUGGEST_COLUMN_INTENT_ACTION);
180    }
181
182    /**
183     * Gets the query for the current suggestion.
184     */
185    public String getSuggestionQuery() {
186        return getStringOrNull(SearchManager.SUGGEST_COLUMN_QUERY);
187    }
188
189    public String getSuggestionIntentDataString() {
190         // use specific data if supplied, or default data if supplied
191         String data = getStringOrNull(SearchManager.SUGGEST_COLUMN_INTENT_DATA);
192         if (data == null) {
193             data = getSuggestionSource().getDefaultIntentData();
194         }
195         // then, if an ID was provided, append it.
196         if (data != null) {
197             String id = getStringOrNull(SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID);
198             if (id != null) {
199                 data = data + "/" + Uri.encode(id);
200             }
201         }
202         return data;
203     }
204
205    /**
206     * Gets the intent extra data for the current suggestion.
207     */
208    public String getSuggestionIntentExtraData() {
209        return getStringOrNull(SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA);
210    }
211
212    /**
213     * Gets the index of a column in {@link #mCursor} by name.
214     *
215     * @return The index, or {@code -1} if the column was not found.
216     */
217    protected int getColumnIndex(String colName) {
218        if (mCursor == null) return -1;
219        try {
220            return mCursor.getColumnIndex(colName);
221        } catch (RuntimeException ex) {
222            // all operations on cross-process cursors can throw random exceptions
223            Log.e(TAG, "getColumnIndex() failed, ", ex);
224            return -1;
225        }
226    }
227
228    /**
229     * Gets the string value of a column in {@link #mCursor} by column index.
230     *
231     * @param col Column index.
232     * @return The string value, or {@code null}.
233     */
234    protected String getStringOrNull(int col) {
235        if (mCursor == null) return null;
236        if (col == -1) {
237            return null;
238        }
239        try {
240            return mCursor.getString(col);
241        } catch (RuntimeException ex) {
242            // all operations on cross-process cursors can throw random exceptions
243            Log.e(TAG, "getString() failed, ", ex);
244            return null;
245        }
246    }
247
248    /**
249     * Gets the string value of a column in {@link #mCursor} by column name.
250     *
251     * @param colName Column name.
252     * @return The string value, or {@code null}.
253     */
254    protected String getStringOrNull(String colName) {
255        int col = getColumnIndex(colName);
256        return getStringOrNull(col);
257    }
258
259    private String makeKeyComponent(String str) {
260        return str == null ? "" : str;
261    }
262
263    public String getSuggestionKey() {
264        String action = makeKeyComponent(getSuggestionIntentAction());
265        String data = makeKeyComponent(getSuggestionIntentDataString());
266        String query = makeKeyComponent(getSuggestionQuery());
267        // calculating accurate size of string builder avoids an allocation vs starting with
268        // the default size and having to expand.
269        int size = action.length() + 2 + data.length() + query.length();
270        return new StringBuilder(size)
271                .append(action)
272                .append('#')
273                .append(data)
274                .append('#')
275                .append(query)
276                .toString();
277    }
278
279    public void registerDataSetObserver(DataSetObserver observer) {
280        // We don't watch Cursor-backed SuggestionCursors for changes
281    }
282
283    public void unregisterDataSetObserver(DataSetObserver observer) {
284        // We don't watch Cursor-backed SuggestionCursors for changes
285    }
286}
287