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.content.ComponentName; 21import android.content.Intent; 22import android.database.Cursor; 23import android.database.DataSetObserver; 24import android.net.Uri; 25import android.util.Log; 26 27public abstract class CursorBackedSuggestionCursor implements SuggestionCursor { 28 29 private static final boolean DBG = false; 30 protected static final String TAG = "QSB.CursorBackedSuggestionCursor"; 31 32 public static final String SUGGEST_COLUMN_LOG_TYPE = "suggest_log_type"; 33 34 private final String mUserQuery; 35 36 /** The suggestions, or {@code null} if the suggestions query failed. */ 37 protected final Cursor mCursor; 38 39 /** Column index of {@link SearchManager#SUGGEST_COLUMN_FORMAT} in @{link mCursor}. */ 40 private final int mFormatCol; 41 42 /** Column index of {@link SearchManager#SUGGEST_COLUMN_TEXT_1} in @{link mCursor}. */ 43 private final int mText1Col; 44 45 /** Column index of {@link SearchManager#SUGGEST_COLUMN_TEXT_2} in @{link mCursor}. */ 46 private final int mText2Col; 47 48 /** Column index of {@link SearchManager#SUGGEST_COLUMN_TEXT_2_URL} in @{link mCursor}. */ 49 private final int mText2UrlCol; 50 51 /** Column index of {@link SearchManager#SUGGEST_COLUMN_ICON_1} in @{link mCursor}. */ 52 private final int mIcon1Col; 53 54 /** Column index of {@link SearchManager#SUGGEST_COLUMN_ICON_1} in @{link mCursor}. */ 55 private final int mIcon2Col; 56 57 /** Column index of {@link SearchManager#SUGGEST_COLUMN_SPINNER_WHILE_REFRESHING} 58 * in @{link mCursor}. 59 **/ 60 private final int mRefreshSpinnerCol; 61 62 /** True if this result has been closed. */ 63 private boolean mClosed = false; 64 65 public CursorBackedSuggestionCursor(String userQuery, Cursor cursor) { 66 mUserQuery = userQuery; 67 mCursor = cursor; 68 mFormatCol = getColumnIndex(SearchManager.SUGGEST_COLUMN_FORMAT); 69 mText1Col = getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_1); 70 mText2Col = getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_2); 71 mText2UrlCol = getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_2_URL); 72 mIcon1Col = getColumnIndex(SearchManager.SUGGEST_COLUMN_ICON_1); 73 mIcon2Col = getColumnIndex(SearchManager.SUGGEST_COLUMN_ICON_2); 74 mRefreshSpinnerCol = getColumnIndex(SearchManager.SUGGEST_COLUMN_SPINNER_WHILE_REFRESHING); 75 } 76 77 public String getUserQuery() { 78 return mUserQuery; 79 } 80 81 public abstract Source getSuggestionSource(); 82 83 public String getSuggestionLogType() { 84 return getStringOrNull(SUGGEST_COLUMN_LOG_TYPE); 85 } 86 87 public void close() { 88 if (DBG) Log.d(TAG, "close()"); 89 if (mClosed) { 90 throw new IllegalStateException("Double close()"); 91 } 92 mClosed = true; 93 if (mCursor != null) { 94 try { 95 mCursor.close(); 96 } catch (RuntimeException ex) { 97 // all operations on cross-process cursors can throw random exceptions 98 Log.e(TAG, "close() failed, ", ex); 99 } 100 } 101 } 102 103 @Override 104 protected void finalize() { 105 if (!mClosed) { 106 Log.e(TAG, "LEAK! Finalized without being closed: " + toString()); 107 } 108 } 109 110 public int getCount() { 111 if (mClosed) { 112 throw new IllegalStateException("getCount() after close()"); 113 } 114 if (mCursor == null) return 0; 115 try { 116 return mCursor.getCount(); 117 } catch (RuntimeException ex) { 118 // all operations on cross-process cursors can throw random exceptions 119 Log.e(TAG, "getCount() failed, ", ex); 120 return 0; 121 } 122 } 123 124 public void moveTo(int pos) { 125 if (mClosed) { 126 throw new IllegalStateException("moveTo(" + pos + ") after close()"); 127 } 128 try { 129 if (!mCursor.moveToPosition(pos)) { 130 Log.e(TAG, "moveToPosition(" + pos + ") failed, count=" + getCount()); 131 } 132 } catch (RuntimeException ex) { 133 // all operations on cross-process cursors can throw random exceptions 134 Log.e(TAG, "moveToPosition() failed, ", ex); 135 } 136 } 137 138 public boolean moveToNext() { 139 if (mClosed) { 140 throw new IllegalStateException("moveToNext() after close()"); 141 } 142 try { 143 return mCursor.moveToNext(); 144 } catch (RuntimeException ex) { 145 // all operations on cross-process cursors can throw random exceptions 146 Log.e(TAG, "moveToNext() failed, ", ex); 147 return false; 148 } 149 } 150 151 public int getPosition() { 152 if (mClosed) { 153 throw new IllegalStateException("getPosition after close()"); 154 } 155 try { 156 return mCursor.getPosition(); 157 } catch (RuntimeException ex) { 158 // all operations on cross-process cursors can throw random exceptions 159 Log.e(TAG, "getPosition() failed, ", ex); 160 return -1; 161 } 162 } 163 164 public String getShortcutId() { 165 return getStringOrNull(SearchManager.SUGGEST_COLUMN_SHORTCUT_ID); 166 } 167 168 public String getSuggestionFormat() { 169 return getStringOrNull(mFormatCol); 170 } 171 172 public String getSuggestionText1() { 173 return getStringOrNull(mText1Col); 174 } 175 176 public String getSuggestionText2() { 177 return getStringOrNull(mText2Col); 178 } 179 180 public String getSuggestionText2Url() { 181 return getStringOrNull(mText2UrlCol); 182 } 183 184 public String getSuggestionIcon1() { 185 return getStringOrNull(mIcon1Col); 186 } 187 188 public String getSuggestionIcon2() { 189 return getStringOrNull(mIcon2Col); 190 } 191 192 public boolean isSpinnerWhileRefreshing() { 193 return "true".equals(getStringOrNull(mRefreshSpinnerCol)); 194 } 195 196 /** 197 * Gets the intent action for the current suggestion. 198 */ 199 public String getSuggestionIntentAction() { 200 String action = getStringOrNull(SearchManager.SUGGEST_COLUMN_INTENT_ACTION); 201 if (action != null) return action; 202 return getSuggestionSource().getDefaultIntentAction(); 203 } 204 205 public abstract ComponentName getSuggestionIntentComponent(); 206 207 /** 208 * Gets the query for the current suggestion. 209 */ 210 public String getSuggestionQuery() { 211 return getStringOrNull(SearchManager.SUGGEST_COLUMN_QUERY); 212 } 213 214 public String getSuggestionIntentDataString() { 215 // use specific data if supplied, or default data if supplied 216 String data = getStringOrNull(SearchManager.SUGGEST_COLUMN_INTENT_DATA); 217 if (data == null) { 218 data = getSuggestionSource().getDefaultIntentData(); 219 } 220 // then, if an ID was provided, append it. 221 if (data != null) { 222 String id = getStringOrNull(SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID); 223 if (id != null) { 224 data = data + "/" + Uri.encode(id); 225 } 226 } 227 return data; 228 } 229 230 /** 231 * Gets the intent extra data for the current suggestion. 232 */ 233 public String getSuggestionIntentExtraData() { 234 return getStringOrNull(SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA); 235 } 236 237 public boolean isWebSearchSuggestion() { 238 return Intent.ACTION_WEB_SEARCH.equals(getSuggestionIntentAction()); 239 } 240 241 /** 242 * Gets the index of a column in {@link #mCursor} by name. 243 * 244 * @return The index, or {@code -1} if the column was not found. 245 */ 246 protected int getColumnIndex(String colName) { 247 if (mCursor == null) return -1; 248 try { 249 return mCursor.getColumnIndex(colName); 250 } catch (RuntimeException ex) { 251 // all operations on cross-process cursors can throw random exceptions 252 Log.e(TAG, "getColumnIndex() failed, ", ex); 253 return -1; 254 } 255 } 256 257 /** 258 * Gets the string value of a column in {@link #mCursor} by column index. 259 * 260 * @param col Column index. 261 * @return The string value, or {@code null}. 262 */ 263 protected String getStringOrNull(int col) { 264 if (mCursor == null) return null; 265 if (col == -1) { 266 return null; 267 } 268 try { 269 return mCursor.getString(col); 270 } catch (RuntimeException ex) { 271 // all operations on cross-process cursors can throw random exceptions 272 Log.e(TAG, "getString() failed, ", ex); 273 return null; 274 } 275 } 276 277 /** 278 * Gets the string value of a column in {@link #mCursor} by column name. 279 * 280 * @param colName Column name. 281 * @return The string value, or {@code null}. 282 */ 283 protected String getStringOrNull(String colName) { 284 int col = getColumnIndex(colName); 285 return getStringOrNull(col); 286 } 287 288 public void registerDataSetObserver(DataSetObserver observer) { 289 // We don't watch Cursor-backed SuggestionCursors for changes 290 } 291 292 public void unregisterDataSetObserver(DataSetObserver observer) { 293 // We don't watch Cursor-backed SuggestionCursors for changes 294 } 295 296 @Override 297 public String toString() { 298 return getClass().getSimpleName() + "[" + mUserQuery + "]"; 299 } 300 301} 302