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