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.database.DataSetObservable 19 import android.database.DataSetObserver 20 import android.util.Log 21 22 /** Collects all corpus results for a single query. */ 23 class Suggestions(val query: String, val source: Source) { 24 25 /** 26 * The observers that want notifications of changes to the published suggestions. This object may 27 * be accessed on any thread. 28 */ 29 private val mDataSetObservable: DataSetObservable = DataSetObservable() 30 31 private var mResult: SourceResult? = null 32 33 private var mRefCount = 0 34 35 private var mDone = false 36 37 /** True if [Suggestions.close] has been called. */ 38 var isClosed = false 39 private set 40 41 /** 42 * Gets the list of corpus results reported so far. Do not modify or hang on to the returned 43 * iterator. 44 */ getResultnull45 fun getResult(): SourceResult? { 46 return mResult 47 } 48 getWebResultnull49 fun getWebResult(): SourceResult? { 50 return mResult 51 } 52 acquirenull53 fun acquire() { 54 mRefCount++ 55 } 56 releasenull57 fun release() { 58 mRefCount-- 59 if (mRefCount <= 0) { 60 close() 61 } 62 } 63 64 /** Marks the suggestions set as complete, regardless of whether all corpora have returned. */ donenull65 fun done() { 66 mDone = true 67 } 68 69 /** 70 * Checks whether all sources have reported. Must be called on the UI thread, or before this 71 * object is seen by the UI thread. 72 */ 73 val isDone: Boolean 74 get() = mDone || mResult != null 75 76 /** 77 * Adds a list of corpus results. Must be called on the UI thread, or before this object is seen 78 * by the UI thread. 79 */ addResultsnull80 fun addResults(result: SourceResult?) { 81 if (isClosed) { 82 result?.close() 83 return 84 } 85 if (DBG) { 86 Log.d( 87 TAG, 88 "addResults[" + 89 hashCode().toString() + 90 "] source:" + 91 result?.source?.name.toString() + 92 " results:" + 93 result?.count 94 ) 95 } 96 if (query != result?.userQuery) { 97 throw IllegalArgumentException( 98 "Got result for wrong query: " + query + " != " + result?.userQuery 99 ) 100 } 101 mResult = result 102 notifyDataSetChanged() 103 } 104 105 /** 106 * Registers an observer that will be notified when the reported results or the done status 107 * changes. 108 */ registerDataSetObservernull109 fun registerDataSetObserver(observer: DataSetObserver?) { 110 if (isClosed) { 111 throw IllegalStateException("registerDataSetObserver() when closed") 112 } 113 mDataSetObservable.registerObserver(observer) 114 } 115 116 /** Unregisters an observer. */ unregisterDataSetObservernull117 fun unregisterDataSetObserver(observer: DataSetObserver?) { 118 mDataSetObservable.unregisterObserver(observer) 119 } 120 121 /** Calls [DataSetObserver.onChanged] on all observers. */ notifyDataSetChangednull122 protected fun notifyDataSetChanged() { 123 if (DBG) Log.d(TAG, "notifyDataSetChanged()") 124 mDataSetObservable.notifyChanged() 125 } 126 127 /** Closes all the source results and unregisters all observers. */ closenull128 private fun close() { 129 if (DBG) Log.d(TAG, "close() [" + hashCode().toString() + "]") 130 if (isClosed) { 131 throw IllegalStateException("Double close()") 132 } 133 isClosed = true 134 mDataSetObservable.unregisterAll() 135 mResult?.close() 136 mResult = null 137 } 138 139 @Override finalizenull140 protected fun finalize() { 141 if (!isClosed) { 142 Log.e(TAG, "LEAK! Finalized without being closed: Suggestions[$query]") 143 } 144 } 145 146 /** 147 * Gets the number of source results. Must be called on the UI thread, or before this object is 148 * seen by the UI thread. 149 */ 150 val resultCount: Int 151 get() { 152 if (isClosed) { 153 throw IllegalStateException("Called resultCount when closed.") 154 } 155 return mResult?.count ?: 0 156 } 157 158 @Override toStringnull159 override fun toString(): String { 160 return "Suggestions@" + 161 hashCode().toString() + 162 "{source=" + 163 source.toString() + 164 ",resultCount=" + 165 resultCount.toString() + 166 "}" 167 } 168 169 companion object { 170 private const val DBG = false 171 private const val TAG = "QSB.Suggestions" 172 } 173 } 174