blob: 43776d6b8aa5f4cb919396171b350c89ac6fa163 [file] [log] [blame]
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.quicksearchbox
import android.app.SearchManager
import android.content.ComponentName
import android.content.Intent
import android.database.Cursor
import android.database.DataSetObserver
import android.net.Uri
import android.util.Log
abstract class CursorBackedSuggestionCursor(override val userQuery: String?, cursor: Cursor?) :
SuggestionCursor {
/** The suggestions, or `null` if the suggestions query failed. */
@JvmField protected val mCursor: Cursor?
/** Column index of [SearchManager.SUGGEST_COLUMN_FORMAT] in @{link mCursor}. */
private val mFormatCol: Int
/** Column index of [SearchManager.SUGGEST_COLUMN_TEXT_1] in @{link mCursor}. */
private val mText1Col: Int
/** Column index of [SearchManager.SUGGEST_COLUMN_TEXT_2] in @{link mCursor}. */
private val mText2Col: Int
/** Column index of [SearchManager.SUGGEST_COLUMN_TEXT_2_URL] in @{link mCursor}. */
private val mText2UrlCol: Int
/** Column index of [SearchManager.SUGGEST_COLUMN_ICON_1] in @{link mCursor}. */
private val mIcon1Col: Int
/** Column index of [SearchManager.SUGGEST_COLUMN_ICON_1] in @{link mCursor}. */
private val mIcon2Col: Int
/** Column index of [SearchManager.SUGGEST_COLUMN_SPINNER_WHILE_REFRESHING] in @{link mCursor}. */
private val mRefreshSpinnerCol: Int
/** True if this result has been closed. */
private var mClosed = false
abstract override val suggestionSource: Source?
override val suggestionLogType: String?
get() = getStringOrNull(SUGGEST_COLUMN_LOG_TYPE)
override fun close() {
if (DBG) Log.d(TAG, "close()")
if (mClosed) {
throw IllegalStateException("Double close()")
}
mClosed = true
if (mCursor != null) {
try {
mCursor.close()
} catch (ex: RuntimeException) {
// all operations on cross-process cursors can throw random exceptions
Log.e(TAG, "close() failed, ", ex)
}
}
}
@Override
protected fun finalize() {
if (!mClosed) {
Log.e(TAG, "LEAK! Finalized without being closed: " + toString())
}
}
override val count: Int
get() {
if (mClosed) {
throw IllegalStateException("getCount() after close()")
}
return if (mCursor == null) 0
else
try {
mCursor.getCount()
} catch (ex: RuntimeException) {
// all operations on cross-process cursors can throw random exceptions
Log.e(TAG, "getCount() failed, ", ex)
0
}
}
override fun moveTo(pos: Int) {
if (mClosed) {
throw IllegalStateException("moveTo($pos) after close()")
}
try {
if (!mCursor!!.moveToPosition(pos)) {
Log.e(TAG, "moveToPosition($pos) failed, count=$count")
}
} catch (ex: RuntimeException) {
// all operations on cross-process cursors can throw random exceptions
Log.e(TAG, "moveToPosition() failed, ", ex)
}
}
override fun moveToNext(): Boolean {
if (mClosed) {
throw IllegalStateException("moveToNext() after close()")
}
return try {
mCursor!!.moveToNext()
} catch (ex: RuntimeException) {
// all operations on cross-process cursors can throw random exceptions
Log.e(TAG, "moveToNext() failed, ", ex)
false
}
}
override val position: Int
get() {
if (mClosed) {
throw IllegalStateException("get() on position after close()")
}
return try {
mCursor!!.position
} catch (ex: RuntimeException) {
// all operations on cross-process cursors can throw random exceptions
Log.e(TAG, "get() on position failed, ", ex)
-1
}
}
override val shortcutId: String?
get() = getStringOrNull(SearchManager.SUGGEST_COLUMN_SHORTCUT_ID)
override val suggestionFormat: String?
get() = getStringOrNull(mFormatCol)
override val suggestionText1: String?
get() = getStringOrNull(mText1Col)
override val suggestionText2: String?
get() = getStringOrNull(mText2Col)
override val suggestionText2Url: String?
get() = getStringOrNull(mText2UrlCol)
override val suggestionIcon1: String?
get() = getStringOrNull(mIcon1Col)
override val suggestionIcon2: String?
get() = getStringOrNull(mIcon2Col)
override val isSpinnerWhileRefreshing: Boolean
get() = "true".equals(getStringOrNull(mRefreshSpinnerCol))
/** Gets the intent action for the current suggestion. */
override val suggestionIntentAction: String?
get() {
val action: String? = getStringOrNull(SearchManager.SUGGEST_COLUMN_INTENT_ACTION)
return action
}
abstract override val suggestionIntentComponent: ComponentName?
/** Gets the query for the current suggestion. */
override val suggestionQuery: String?
get() = getStringOrNull(SearchManager.SUGGEST_COLUMN_QUERY)
override val suggestionIntentDataString: String?
get() {
// use specific data if supplied, or default data if supplied
var data: String? = getStringOrNull(SearchManager.SUGGEST_COLUMN_INTENT_DATA)
if (data == null) {
data = suggestionSource?.defaultIntentData
}
// then, if an ID was provided, append it.
if (data != null) {
val id: String? = getStringOrNull(SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID)
if (id != null) {
data = data.toString() + "/" + Uri.encode(id)
}
}
return data
}
/** Gets the intent extra data for the current suggestion. */
override val suggestionIntentExtraData: String?
get() = getStringOrNull(SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA)
override val isWebSearchSuggestion: Boolean
get() = Intent.ACTION_WEB_SEARCH.equals(suggestionIntentAction)
/**
* Gets the index of a column in [.mCursor] by name.
*
* @return The index, or `-1` if the column was not found.
*/
protected fun getColumnIndex(colName: String?): Int {
return if (mCursor == null) -1
else
try {
mCursor.getColumnIndex(colName)
} catch (ex: RuntimeException) {
// all operations on cross-process cursors can throw random exceptions
Log.e(TAG, "getColumnIndex() failed, ", ex)
-1
}
}
/**
* Gets the string value of a column in [.mCursor] by column index.
*
* @param col Column index.
* @return The string value, or `null`.
*/
protected fun getStringOrNull(col: Int): String? {
if (mCursor == null) return null
return if (col == -1) {
null
} else
try {
mCursor.getString(col)
} catch (ex: RuntimeException) {
// all operations on cross-process cursors can throw random exceptions
Log.e(TAG, "getString() failed, ", ex)
null
}
}
/**
* Gets the string value of a column in [.mCursor] by column name.
*
* @param colName Column name.
* @return The string value, or `null`.
*/
protected fun getStringOrNull(colName: String?): String? {
val col = getColumnIndex(colName)
return getStringOrNull(col)
}
override fun registerDataSetObserver(observer: DataSetObserver?) {
// We don't watch Cursor-backed SuggestionCursors for changes
}
override fun unregisterDataSetObserver(observer: DataSetObserver?) {
// We don't watch Cursor-backed SuggestionCursors for changes
}
@Override
override fun toString(): String {
return this::class.simpleName.toString() + "[" + userQuery + "]"
}
companion object {
private const val DBG = false
protected const val TAG = "QSB.CursorBackedSuggestionCursor"
const val SUGGEST_COLUMN_LOG_TYPE = "suggest_log_type"
}
init {
mCursor = cursor
mFormatCol = getColumnIndex(SearchManager.SUGGEST_COLUMN_FORMAT)
mText1Col = getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_1)
mText2Col = getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_2)
mText2UrlCol = getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_2_URL)
mIcon1Col = getColumnIndex(SearchManager.SUGGEST_COLUMN_ICON_1)
mIcon2Col = getColumnIndex(SearchManager.SUGGEST_COLUMN_ICON_2)
mRefreshSpinnerCol = getColumnIndex(SearchManager.SUGGEST_COLUMN_SPINNER_WHILE_REFRESHING)
}
}