1 /* 2 * Copyright (C) 2022 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 package com.android.quicksearchbox 17 18 import android.app.SearchManager 19 import android.content.ComponentName 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 abstract class CursorBackedSuggestionCursor(override val userQuery: String?, cursor: Cursor?) : 27 SuggestionCursor { 28 29 /** The suggestions, or `null` if the suggestions query failed. */ 30 @JvmField protected val mCursor: Cursor? 31 32 /** Column index of [SearchManager.SUGGEST_COLUMN_FORMAT] in @{link mCursor}. */ 33 private val mFormatCol: Int 34 35 /** Column index of [SearchManager.SUGGEST_COLUMN_TEXT_1] in @{link mCursor}. */ 36 private val mText1Col: Int 37 38 /** Column index of [SearchManager.SUGGEST_COLUMN_TEXT_2] in @{link mCursor}. */ 39 private val mText2Col: Int 40 41 /** Column index of [SearchManager.SUGGEST_COLUMN_TEXT_2_URL] in @{link mCursor}. */ 42 private val mText2UrlCol: Int 43 44 /** Column index of [SearchManager.SUGGEST_COLUMN_ICON_1] in @{link mCursor}. */ 45 private val mIcon1Col: Int 46 47 /** Column index of [SearchManager.SUGGEST_COLUMN_ICON_1] in @{link mCursor}. */ 48 private val mIcon2Col: Int 49 50 /** Column index of [SearchManager.SUGGEST_COLUMN_SPINNER_WHILE_REFRESHING] in @{link mCursor}. */ 51 private val mRefreshSpinnerCol: Int 52 53 /** True if this result has been closed. */ 54 private var mClosed = false 55 abstract override val suggestionSource: Source? 56 override val suggestionLogType: String? 57 get() = getStringOrNull(SUGGEST_COLUMN_LOG_TYPE) 58 closenull59 override fun close() { 60 if (DBG) Log.d(TAG, "close()") 61 if (mClosed) { 62 throw IllegalStateException("Double close()") 63 } 64 mClosed = true 65 if (mCursor != null) { 66 try { 67 mCursor.close() 68 } catch (ex: RuntimeException) { 69 // all operations on cross-process cursors can throw random exceptions 70 Log.e(TAG, "close() failed, ", ex) 71 } 72 } 73 } 74 75 @Override finalizenull76 protected fun finalize() { 77 if (!mClosed) { 78 Log.e(TAG, "LEAK! Finalized without being closed: " + toString()) 79 } 80 } 81 82 override val count: Int 83 get() { 84 if (mClosed) { 85 throw IllegalStateException("getCount() after close()") 86 } 87 return if (mCursor == null) 0 88 else 89 try { 90 mCursor.getCount() 91 } catch (ex: RuntimeException) { 92 // all operations on cross-process cursors can throw random exceptions 93 Log.e(TAG, "getCount() failed, ", ex) 94 0 95 } 96 } 97 moveTonull98 override fun moveTo(pos: Int) { 99 if (mClosed) { 100 throw IllegalStateException("moveTo($pos) after close()") 101 } 102 try { 103 if (!mCursor!!.moveToPosition(pos)) { 104 Log.e(TAG, "moveToPosition($pos) failed, count=$count") 105 } 106 } catch (ex: RuntimeException) { 107 // all operations on cross-process cursors can throw random exceptions 108 Log.e(TAG, "moveToPosition() failed, ", ex) 109 } 110 } 111 moveToNextnull112 override fun moveToNext(): Boolean { 113 if (mClosed) { 114 throw IllegalStateException("moveToNext() after close()") 115 } 116 return try { 117 mCursor!!.moveToNext() 118 } catch (ex: RuntimeException) { 119 // all operations on cross-process cursors can throw random exceptions 120 Log.e(TAG, "moveToNext() failed, ", ex) 121 false 122 } 123 } 124 125 override val position: Int 126 get() { 127 if (mClosed) { 128 throw IllegalStateException("get() on position after close()") 129 } 130 return try { 131 mCursor!!.position 132 } catch (ex: RuntimeException) { 133 // all operations on cross-process cursors can throw random exceptions 134 Log.e(TAG, "get() on position failed, ", ex) 135 -1 136 } 137 } 138 override val shortcutId: String? 139 get() = getStringOrNull(SearchManager.SUGGEST_COLUMN_SHORTCUT_ID) 140 override val suggestionFormat: String? 141 get() = getStringOrNull(mFormatCol) 142 override val suggestionText1: String? 143 get() = getStringOrNull(mText1Col) 144 override val suggestionText2: String? 145 get() = getStringOrNull(mText2Col) 146 override val suggestionText2Url: String? 147 get() = getStringOrNull(mText2UrlCol) 148 override val suggestionIcon1: String? 149 get() = getStringOrNull(mIcon1Col) 150 override val suggestionIcon2: String? 151 get() = getStringOrNull(mIcon2Col) 152 override val isSpinnerWhileRefreshing: Boolean 153 get() = "true".equals(getStringOrNull(mRefreshSpinnerCol)) 154 155 /** Gets the intent action for the current suggestion. */ 156 override val suggestionIntentAction: String? 157 get() { 158 val action: String? = getStringOrNull(SearchManager.SUGGEST_COLUMN_INTENT_ACTION) 159 return action 160 } 161 abstract override val suggestionIntentComponent: ComponentName? 162 163 /** Gets the query for the current suggestion. */ 164 override val suggestionQuery: String? 165 get() = getStringOrNull(SearchManager.SUGGEST_COLUMN_QUERY) 166 167 override val suggestionIntentDataString: String? 168 get() { 169 // use specific data if supplied, or default data if supplied 170 var data: String? = getStringOrNull(SearchManager.SUGGEST_COLUMN_INTENT_DATA) 171 if (data == null) { 172 data = suggestionSource?.defaultIntentData 173 } 174 // then, if an ID was provided, append it. 175 if (data != null) { 176 val id: String? = getStringOrNull(SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID) 177 if (id != null) { 178 data = data.toString() + "/" + Uri.encode(id) 179 } 180 } 181 return data 182 } 183 184 /** Gets the intent extra data for the current suggestion. */ 185 override val suggestionIntentExtraData: String? 186 get() = getStringOrNull(SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA) 187 override val isWebSearchSuggestion: Boolean 188 get() = Intent.ACTION_WEB_SEARCH.equals(suggestionIntentAction) 189 190 /** 191 * Gets the index of a column in [.mCursor] by name. 192 * 193 * @return The index, or `-1` if the column was not found. 194 */ getColumnIndexnull195 protected fun getColumnIndex(colName: String?): Int { 196 return if (mCursor == null) -1 197 else 198 try { 199 mCursor.getColumnIndex(colName) 200 } catch (ex: RuntimeException) { 201 // all operations on cross-process cursors can throw random exceptions 202 Log.e(TAG, "getColumnIndex() failed, ", ex) 203 -1 204 } 205 } 206 207 /** 208 * Gets the string value of a column in [.mCursor] by column index. 209 * 210 * @param col Column index. 211 * @return The string value, or `null`. 212 */ getStringOrNullnull213 protected fun getStringOrNull(col: Int): String? { 214 if (mCursor == null) return null 215 return if (col == -1) { 216 null 217 } else 218 try { 219 mCursor.getString(col) 220 } catch (ex: RuntimeException) { 221 // all operations on cross-process cursors can throw random exceptions 222 Log.e(TAG, "getString() failed, ", ex) 223 null 224 } 225 } 226 227 /** 228 * Gets the string value of a column in [.mCursor] by column name. 229 * 230 * @param colName Column name. 231 * @return The string value, or `null`. 232 */ getStringOrNullnull233 protected fun getStringOrNull(colName: String?): String? { 234 val col = getColumnIndex(colName) 235 return getStringOrNull(col) 236 } 237 registerDataSetObservernull238 override fun registerDataSetObserver(observer: DataSetObserver?) { 239 // We don't watch Cursor-backed SuggestionCursors for changes 240 } 241 unregisterDataSetObservernull242 override fun unregisterDataSetObserver(observer: DataSetObserver?) { 243 // We don't watch Cursor-backed SuggestionCursors for changes 244 } 245 246 @Override toStringnull247 override fun toString(): String { 248 return this::class.simpleName.toString() + "[" + userQuery + "]" 249 } 250 251 companion object { 252 private const val DBG = false 253 protected const val TAG = "QSB.CursorBackedSuggestionCursor" 254 const val SUGGEST_COLUMN_LOG_TYPE = "suggest_log_type" 255 } 256 257 init { 258 mCursor = cursor 259 mFormatCol = getColumnIndex(SearchManager.SUGGEST_COLUMN_FORMAT) 260 mText1Col = getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_1) 261 mText2Col = getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_2) 262 mText2UrlCol = getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_2_URL) 263 mIcon1Col = getColumnIndex(SearchManager.SUGGEST_COLUMN_ICON_1) 264 mIcon2Col = getColumnIndex(SearchManager.SUGGEST_COLUMN_ICON_2) 265 mRefreshSpinnerCol = getColumnIndex(SearchManager.SUGGEST_COLUMN_SPINNER_WHILE_REFRESHING) 266 } 267 } 268