CursorBackedSuggestionCursor.java revision 04a180b52fb4100a2f3747e795fb5d26e3207a4a
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 /** The suggestions, or {@code null} if the suggestions query failed. */ 31 protected final Cursor mCursor; 32 33 /** Column index of {@link SearchManager#SUGGEST_COLUMN_FORMAT} in @{link mCursor}. */ 34 private final int mFormatCol; 35 36 /** Column index of {@link SearchManager#SUGGEST_COLUMN_TEXT_1} in @{link mCursor}. */ 37 private final int mText1Col; 38 39 /** Column index of {@link SearchManager#SUGGEST_COLUMN_TEXT_2} in @{link mCursor}. */ 40 private final int mText2Col; 41 42 /** Column index of {@link SearchManager#SUGGEST_COLUMN_ICON_1} in @{link mCursor}. */ 43 private final int mIcon1Col; 44 45 /** Column index of {@link SearchManager#SUGGEST_COLUMN_ICON_1} in @{link mCursor}. */ 46 private final int mIcon2Col; 47 48 /** Column index of {@link SearchManager#SUGGEST_COLUMN_SPINNER_WHILE_REFRESHING} 49 * in @{link mCursor}. 50 **/ 51 private final int mRefreshSpinnerCol; 52 53 /** True if this result has been closed. */ 54 private boolean mClosed = false; 55 56 public CursorBackedSuggestionCursor(String userQuery, Cursor cursor) { 57 super(userQuery); 58 mCursor = cursor; 59 mFormatCol = getColumnIndex(SearchManager.SUGGEST_COLUMN_FORMAT); 60 mText1Col = getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_1); 61 mText2Col = getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_2); 62 mIcon1Col = getColumnIndex(SearchManager.SUGGEST_COLUMN_ICON_1); 63 mIcon2Col = getColumnIndex(SearchManager.SUGGEST_COLUMN_ICON_2); 64 mRefreshSpinnerCol = getColumnIndex(SearchManager.SUGGEST_COLUMN_SPINNER_WHILE_REFRESHING); 65 } 66 67 public abstract Source getSuggestionSource(); 68 69 public String getSuggestionLogType() { 70 return getSuggestionSource().getLogName(); 71 } 72 73 public void close() { 74 if (DBG) Log.d(TAG, "close()"); 75 if (mClosed) { 76 throw new IllegalStateException("Double close()"); 77 } 78 mClosed = true; 79 if (mCursor != null) { 80 // TODO: all operations on cross-process cursors can throw random exceptions 81 mCursor.close(); 82 } 83 } 84 85 @Override 86 protected void finalize() { 87 if (!mClosed) { 88 Log.e(TAG, "LEAK! Finalized without being closed: " + toString()); 89 close(); 90 } 91 } 92 93 public int getCount() { 94 if (mClosed) { 95 throw new IllegalStateException("getCount() after close()"); 96 } 97 if (mCursor == null) return 0; 98 // TODO: all operations on cross-process cursors can throw random exceptions 99 return mCursor.getCount(); 100 } 101 102 public void moveTo(int pos) { 103 if (mClosed) { 104 throw new IllegalStateException("moveTo(" + pos + ") after close()"); 105 } 106 // TODO: all operations on cross-process cursors can throw random exceptions 107 if (!mCursor.moveToPosition(pos)) { 108 throw new IllegalArgumentException("Move to " + pos + ", count=" + getCount()); 109 } 110 } 111 112 public int getPosition() { 113 if (mClosed) { 114 throw new IllegalStateException("getPosition after close()"); 115 } 116 return mCursor.getPosition(); 117 } 118 119 public String getShortcutId() { 120 return getStringOrNull(SearchManager.SUGGEST_COLUMN_SHORTCUT_ID); 121 } 122 123 public String getSuggestionFormat() { 124 return getStringOrNull(mFormatCol); 125 } 126 127 public String getSuggestionText1() { 128 return getStringOrNull(mText1Col); 129 } 130 131 public String getSuggestionText2() { 132 return getStringOrNull(mText2Col); 133 } 134 135 public String getSuggestionIcon1() { 136 return getStringOrNull(mIcon1Col); 137 } 138 139 public String getSuggestionIcon2() { 140 return getStringOrNull(mIcon2Col); 141 } 142 143 public boolean isSpinnerWhileRefreshing() { 144 return "true".equals(getStringOrNull(mRefreshSpinnerCol)); 145 } 146 147 /** 148 * Gets the intent action for the current suggestion. 149 */ 150 public String getSuggestionIntentAction() { 151 return getStringOrNull(SearchManager.SUGGEST_COLUMN_INTENT_ACTION); 152 } 153 154 /** 155 * Gets the query for the current suggestion. 156 */ 157 public String getSuggestionQuery() { 158 return getStringOrNull(SearchManager.SUGGEST_COLUMN_QUERY); 159 } 160 161 public String getSuggestionIntentDataString() { 162 // use specific data if supplied, or default data if supplied 163 String data = getStringOrNull(SearchManager.SUGGEST_COLUMN_INTENT_DATA); 164 if (data == null) { 165 data = getSuggestionSource().getDefaultIntentData(); 166 } 167 // then, if an ID was provided, append it. 168 if (data != null) { 169 String id = getStringOrNull(SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID); 170 if (id != null) { 171 data = data + "/" + Uri.encode(id); 172 } 173 } 174 return data; 175 } 176 177 /** 178 * Gets the intent extra data for the current suggestion. 179 */ 180 public String getSuggestionIntentExtraData() { 181 return getStringOrNull(SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA); 182 } 183 184 /** 185 * Gets the index of a column in {@link #mCursor} by name. 186 * 187 * @return The index, or {@code -1} if the column was not found. 188 */ 189 protected int getColumnIndex(String colName) { 190 if (mCursor == null) return -1; 191 // TODO: all operations on cross-process cursors can throw random exceptions 192 return mCursor.getColumnIndex(colName); 193 } 194 195 /** 196 * Gets the string value of a column in {@link #mCursor} by column index. 197 * 198 * @param col Column index. 199 * @return The string value, or {@code null}. 200 */ 201 protected String getStringOrNull(int col) { 202 if (mCursor == null) return null; 203 if (col == -1) { 204 return null; 205 } 206 try { 207 // TODO: all operations on cross-process cursors can throw random exceptions 208 return mCursor.getString(col); 209 } catch (Exception e) { 210 Log.e(TAG, 211 "unexpected error retrieving valid column from cursor, " 212 + "did the remote process die?", e); 213 return null; 214 } 215 } 216 217 /** 218 * Gets the string value of a column in {@link #mCursor} by column name. 219 * 220 * @param colName Column name. 221 * @return The string value, or {@code null}. 222 */ 223 protected String getStringOrNull(String colName) { 224 int col = getColumnIndex(colName); 225 return getStringOrNull(col); 226 } 227 228 private String makeKeyComponent(String str) { 229 return str == null ? "" : str; 230 } 231 232 public String getSuggestionKey() { 233 String action = makeKeyComponent(getSuggestionIntentAction()); 234 String data = makeKeyComponent(getSuggestionIntentDataString()); 235 String query = makeKeyComponent(getSuggestionQuery()); 236 // calculating accurate size of string builder avoids an allocation vs starting with 237 // the default size and having to expand. 238 int size = action.length() + 2 + data.length() + query.length(); 239 return new StringBuilder(size) 240 .append(action) 241 .append('#') 242 .append(data) 243 .append('#') 244 .append(query) 245 .toString(); 246 } 247 248 public void registerDataSetObserver(DataSetObserver observer) { 249 // We don't watch Cursor-backed SuggestionCursors for changes 250 } 251 252 public void unregisterDataSetObserver(DataSetObserver observer) { 253 // We don't watch Cursor-backed SuggestionCursors for changes 254 } 255} 256