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