/* * 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.database.DataSetObservable import android.database.DataSetObserver import com.google.common.annotations.VisibleForTesting import kotlin.collections.ArrayList import kotlin.collections.HashSet /** A SuggestionCursor that is backed by a list of Suggestions. */ open class ListSuggestionCursor(userQuery: String?, capacity: Int) : AbstractSuggestionCursorWrapper(userQuery!!) { private val mDataSetObservable: DataSetObservable = DataSetObservable() private val mSuggestions: ArrayList private var mExtraColumns: HashSet? = null override var position = 0 constructor(userQuery: String?) : this(userQuery, DEFAULT_CAPACITY) @VisibleForTesting constructor( userQuery: String?, vararg suggestions: Suggestion? ) : this(userQuery, suggestions.size) { for (suggestion in suggestions) { add(suggestion!!) } } /** * Adds a suggestion from another suggestion cursor. * * @return `true` if the suggestion was added. */ open fun add(suggestion: Suggestion): Boolean { mSuggestions.add(Entry(suggestion)) return true } override fun close() { mSuggestions.clear() } override fun moveTo(pos: Int) { position = pos } override fun moveToNext(): Boolean { val size: Int = mSuggestions.size if (position >= size) { // Already past the end return false } position++ return position < size } fun removeRow() { mSuggestions.removeAt(position) } fun replaceRow(suggestion: Suggestion) { mSuggestions.set(position, Entry(suggestion)) } override val count: Int get() = mSuggestions.size @Override override fun current(): Suggestion { return mSuggestions.get(position).get() } @Override override fun toString(): String { return this::class.simpleName.toString() + "{[" + userQuery + "] " + mSuggestions + "}" } /** * Register an observer that is called when changes happen to this data set. * * @param observer gets notified when the data set changes. */ override fun registerDataSetObserver(observer: DataSetObserver?) { mDataSetObservable.registerObserver(observer) } /** * Unregister an observer that has previously been registered with [.registerDataSetObserver] * * @param observer the observer to unregister. */ override fun unregisterDataSetObserver(observer: DataSetObserver?) { mDataSetObservable.unregisterObserver(observer) } protected fun notifyDataSetChanged() { mDataSetObservable.notifyChanged() } // override with caching to avoid re-parsing the extras @get:Override override val extras: SuggestionExtras? // override with caching to avoid re-parsing the extras get() = mSuggestions.get(position).getExtras() override val extraColumns: Collection? get() { if (mExtraColumns == null) { mExtraColumns = HashSet() for (e in mSuggestions) { val extras: SuggestionExtras? = e.getExtras() val extraColumns: Collection? = if (extras == null) null else extras.extraColumnNames if (extraColumns != null) { for (column in extras!!.extraColumnNames) { mExtraColumns?.add(column) } } } } return if (mExtraColumns!!.isEmpty()) null else mExtraColumns } /** This class exists purely to cache the suggestion extras. */ private class Entry(private val mSuggestion: Suggestion) { private var mExtras: SuggestionExtras? = null fun get(): Suggestion { return mSuggestion } fun getExtras(): SuggestionExtras? { if (mExtras == null) { mExtras = mSuggestion.extras } return mExtras } } companion object { private const val DEFAULT_CAPACITY = 16 } init { mSuggestions = ArrayList(capacity) } }