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