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