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