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 com.google.common.annotations.VisibleForTesting
21 import kotlin.collections.ArrayList
22 import kotlin.collections.HashSet
23 
24 /** A SuggestionCursor that is backed by a list of Suggestions. */
25 open class ListSuggestionCursor(userQuery: String?, capacity: Int) :
26   AbstractSuggestionCursorWrapper(userQuery!!) {
27   private val mDataSetObservable: DataSetObservable = DataSetObservable()
28 
29   private val mSuggestions: ArrayList<Entry>
30 
31   private var mExtraColumns: HashSet<String>? = null
32 
33   override var position = 0
34 
35   constructor(userQuery: String?) : this(userQuery, DEFAULT_CAPACITY)
36 
37   @VisibleForTesting
38   constructor(
39     userQuery: String?,
40     vararg suggestions: Suggestion?
41   ) : this(userQuery, suggestions.size) {
42     for (suggestion in suggestions) {
43       add(suggestion!!)
44     }
45   }
46 
47   /**
48    * Adds a suggestion from another suggestion cursor.
49    *
50    * @return `true` if the suggestion was added.
51    */
addnull52   open fun add(suggestion: Suggestion): Boolean {
53     mSuggestions.add(Entry(suggestion))
54     return true
55   }
56 
closenull57   override fun close() {
58     mSuggestions.clear()
59   }
60 
moveTonull61   override fun moveTo(pos: Int) {
62     position = pos
63   }
64 
moveToNextnull65   override fun moveToNext(): Boolean {
66     val size: Int = mSuggestions.size
67     if (position >= size) {
68       // Already past the end
69       return false
70     }
71     position++
72     return position < size
73   }
74 
removeRownull75   fun removeRow() {
76     mSuggestions.removeAt(position)
77   }
78 
replaceRownull79   fun replaceRow(suggestion: Suggestion) {
80     mSuggestions.set(position, Entry(suggestion))
81   }
82 
83   override val count: Int
84     get() = mSuggestions.size
85 
86   @Override
currentnull87   override fun current(): Suggestion {
88     return mSuggestions.get(position).get()
89   }
90 
91   @Override
toStringnull92   override fun toString(): String {
93     return this::class.simpleName.toString() + "{[" + userQuery + "] " + mSuggestions + "}"
94   }
95 
96   /**
97    * Register an observer that is called when changes happen to this data set.
98    *
99    * @param observer gets notified when the data set changes.
100    */
registerDataSetObservernull101   override fun registerDataSetObserver(observer: DataSetObserver?) {
102     mDataSetObservable.registerObserver(observer)
103   }
104 
105   /**
106    * Unregister an observer that has previously been registered with [.registerDataSetObserver]
107    *
108    * @param observer the observer to unregister.
109    */
unregisterDataSetObservernull110   override fun unregisterDataSetObserver(observer: DataSetObserver?) {
111     mDataSetObservable.unregisterObserver(observer)
112   }
113 
notifyDataSetChangednull114   protected fun notifyDataSetChanged() {
115     mDataSetObservable.notifyChanged()
116   }
117 
118   // override with caching to avoid re-parsing the extras
119   @get:Override
120   override val extras: SuggestionExtras?
121     // override with caching to avoid re-parsing the extras
122     get() = mSuggestions.get(position).getExtras()
123 
124   override val extraColumns: Collection<String>?
125     get() {
126       if (mExtraColumns == null) {
127         mExtraColumns = HashSet<String>()
128         for (e in mSuggestions) {
129           val extras: SuggestionExtras? = e.getExtras()
130           val extraColumns: Collection<String>? =
131             if (extras == null) null else extras.extraColumnNames
132           if (extraColumns != null) {
133             for (column in extras!!.extraColumnNames) {
134               mExtraColumns?.add(column)
135             }
136           }
137         }
138       }
139       return if (mExtraColumns!!.isEmpty()) null else mExtraColumns
140     }
141 
142   /** This class exists purely to cache the suggestion extras. */
143   private class Entry(private val mSuggestion: Suggestion) {
144     private var mExtras: SuggestionExtras? = null
getnull145     fun get(): Suggestion {
146       return mSuggestion
147     }
148 
getExtrasnull149     fun getExtras(): SuggestionExtras? {
150       if (mExtras == null) {
151         mExtras = mSuggestion.extras
152       }
153       return mExtras
154     }
155   }
156 
157   companion object {
158     private const val DEFAULT_CAPACITY = 16
159   }
160 
161   init {
162     mSuggestions = ArrayList<Entry>(capacity)
163   }
164 }
165