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