/* * 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 android.util.Log /** Collects all corpus results for a single query. */ class Suggestions(val query: String, val source: Source) { /** * The observers that want notifications of changes to the published suggestions. This object may * be accessed on any thread. */ private val mDataSetObservable: DataSetObservable = DataSetObservable() private var mResult: SourceResult? = null private var mRefCount = 0 private var mDone = false /** True if [Suggestions.close] has been called. */ var isClosed = false private set /** * Gets the list of corpus results reported so far. Do not modify or hang on to the returned * iterator. */ fun getResult(): SourceResult? { return mResult } fun getWebResult(): SourceResult? { return mResult } fun acquire() { mRefCount++ } fun release() { mRefCount-- if (mRefCount <= 0) { close() } } /** Marks the suggestions set as complete, regardless of whether all corpora have returned. */ fun done() { mDone = true } /** * Checks whether all sources have reported. Must be called on the UI thread, or before this * object is seen by the UI thread. */ val isDone: Boolean get() = mDone || mResult != null /** * Adds a list of corpus results. Must be called on the UI thread, or before this object is seen * by the UI thread. */ fun addResults(result: SourceResult?) { if (isClosed) { result?.close() return } if (DBG) { Log.d( TAG, "addResults[" + hashCode().toString() + "] source:" + result?.source?.name.toString() + " results:" + result?.count ) } if (query != result?.userQuery) { throw IllegalArgumentException( "Got result for wrong query: " + query + " != " + result?.userQuery ) } mResult = result notifyDataSetChanged() } /** * Registers an observer that will be notified when the reported results or the done status * changes. */ fun registerDataSetObserver(observer: DataSetObserver?) { if (isClosed) { throw IllegalStateException("registerDataSetObserver() when closed") } mDataSetObservable.registerObserver(observer) } /** Unregisters an observer. */ fun unregisterDataSetObserver(observer: DataSetObserver?) { mDataSetObservable.unregisterObserver(observer) } /** Calls [DataSetObserver.onChanged] on all observers. */ protected fun notifyDataSetChanged() { if (DBG) Log.d(TAG, "notifyDataSetChanged()") mDataSetObservable.notifyChanged() } /** Closes all the source results and unregisters all observers. */ private fun close() { if (DBG) Log.d(TAG, "close() [" + hashCode().toString() + "]") if (isClosed) { throw IllegalStateException("Double close()") } isClosed = true mDataSetObservable.unregisterAll() mResult?.close() mResult = null } @Override protected fun finalize() { if (!isClosed) { Log.e(TAG, "LEAK! Finalized without being closed: Suggestions[$query]") } } /** * Gets the number of source results. Must be called on the UI thread, or before this object is * seen by the UI thread. */ val resultCount: Int get() { if (isClosed) { throw IllegalStateException("Called resultCount when closed.") } return mResult?.count ?: 0 } @Override override fun toString(): String { return "Suggestions@" + hashCode().toString() + "{source=" + source.toString() + ",resultCount=" + resultCount.toString() + "}" } companion object { private const val DBG = false private const val TAG = "QSB.Suggestions" } }