summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTreehugger Robot <treehugger-gerrit@google.com>2022-08-29 20:35:11 +0000
committerAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>2022-08-29 20:35:11 +0000
commit19fee172cc1afd33aa393a6ea9829f47b7ad1afc (patch)
tree6339bf594a247ef3370db610cac4dc318e7a5bdb
parentc6f67a487a0af47ff4c974cb30585e735c72d1c1 (diff)
parent6d1e8b50a2afda7e290f28401ff8766d3d5f6c8c (diff)
downloadQuickSearchBox-19fee172cc1afd33aa393a6ea9829f47b7ad1afc.tar.gz
Merge changes from topics "DelayingSuggestionsAdapter", "SearchActivityViewSinglePane", "SuggestionViewInflater", "SuggestionsListAdapter", "SuggestionsView", "WebSearchSuggestionView" am: af2d414b1c am: fa5bc6533d am: 55bb94f3be am: 6d1e8b50a2
Original change: https://android-review.googlesource.com/c/platform/packages/apps/QuickSearchBox/+/2197991 Change-Id: I81fb633d1f942dae34a1accb9aaa265d875cd7df Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
-rw-r--r--Android.bp7
-rw-r--r--src/com/android/quicksearchbox/AbstractSuggestionWrapper.kt40
-rw-r--r--src/com/android/quicksearchbox/SearchActivity.kt4
-rw-r--r--src/com/android/quicksearchbox/SuggestionPosition.kt8
-rw-r--r--src/com/android/quicksearchbox/SuggestionUtils.kt14
-rw-r--r--src/com/android/quicksearchbox/ui/DelayingSuggestionsAdapter.kt158
-rw-r--r--src/com/android/quicksearchbox/ui/SearchActivityView.kt17
-rw-r--r--src/com/android/quicksearchbox/ui/SearchActivityViewSinglePane.kt43
-rw-r--r--src/com/android/quicksearchbox/ui/SuggestionViewInflater.kt81
-rw-r--r--src/com/android/quicksearchbox/ui/SuggestionsAdapterBase.kt223
-rw-r--r--src/com/android/quicksearchbox/ui/SuggestionsListAdapter.kt97
-rw-r--r--src/com/android/quicksearchbox/ui/SuggestionsListView.kt4
-rw-r--r--src/com/android/quicksearchbox/ui/SuggestionsView.kt68
-rw-r--r--src/com/android/quicksearchbox/ui/WebSearchSuggestionView.kt101
14 files changed, 820 insertions, 45 deletions
diff --git a/Android.bp b/Android.bp
index 8dc773a..89cc425 100644
--- a/Android.bp
+++ b/Android.bp
@@ -39,13 +39,20 @@ android_app {
"src/**/quicksearchbox/ui/CorpusView.java",
"src/**/quicksearchbox/ui/DefaultSuggestionView.java",
"src/**/quicksearchbox/ui/DefaultSuggestionViewFactory.java",
+ "src/**/quicksearchbox/ui/DelayingSuggestionsAdapter.java",
"src/**/quicksearchbox/ui/QueryTextView.java",
"src/**/quicksearchbox/ui/SearchActivityView.java",
+ "src/**/quicksearchbox/ui/SearchActivityViewSinglePane.java",
"src/**/quicksearchbox/ui/SuggestionClickListener.java",
"src/**/quicksearchbox/ui/SuggestionsAdapter.java",
+ "src/**/quicksearchbox/ui/SuggestionsAdapterBase.java",
+ "src/**/quicksearchbox/ui/SuggestionsListAdapter.java",
"src/**/quicksearchbox/ui/SuggestionsListView.java",
+ "src/**/quicksearchbox/ui/SuggestionsView.java",
"src/**/quicksearchbox/ui/SuggestionView.java",
"src/**/quicksearchbox/ui/SuggestionViewFactory.java",
+ "src/**/quicksearchbox/ui/SuggestionViewInflater.java",
+ "src/**/quicksearchbox/ui/WebSearchSuggestionView.java",
"src/**/quicksearchbox/util/AsyncDataSetObservable.java",
"src/**/quicksearchbox/util/BarrierConsumer.java",
"src/**/quicksearchbox/util/BatchingNamedTaskExecutor.java",
diff --git a/src/com/android/quicksearchbox/AbstractSuggestionWrapper.kt b/src/com/android/quicksearchbox/AbstractSuggestionWrapper.kt
index ce43cc0..cac1db9 100644
--- a/src/com/android/quicksearchbox/AbstractSuggestionWrapper.kt
+++ b/src/com/android/quicksearchbox/AbstractSuggestionWrapper.kt
@@ -24,43 +24,43 @@ abstract class AbstractSuggestionWrapper : Suggestion {
/**
* Gets the current suggestion.
*/
- protected abstract fun current(): Suggestion
+ protected abstract fun current(): Suggestion?
override val shortcutId: String?
- get() = current().shortcutId
+ get() = current()?.shortcutId
override val suggestionFormat: String?
- get() = current().suggestionFormat
+ get() = current()?.suggestionFormat
override val suggestionIcon1: String?
- get() = current().suggestionIcon1
+ get() = current()?.suggestionIcon1
override val suggestionIcon2: String?
- get() = current().suggestionIcon2
+ get() = current()?.suggestionIcon2
override val suggestionIntentAction: String?
- get() = current().suggestionIntentAction
+ get() = current()?.suggestionIntentAction
override val suggestionIntentComponent: ComponentName?
- get() = current().suggestionIntentComponent
+ get() = current()?.suggestionIntentComponent
override val suggestionIntentDataString: String?
- get() = current().suggestionIntentDataString
+ get() = current()?.suggestionIntentDataString
override val suggestionIntentExtraData: String?
- get() = current().suggestionIntentExtraData
+ get() = current()?.suggestionIntentExtraData
override val suggestionLogType: String?
- get() = current().suggestionLogType
+ get() = current()?.suggestionLogType
override val suggestionQuery: String?
- get() = current().suggestionQuery
+ get() = current()?.suggestionQuery
override val suggestionSource: Source?
- get() = current().suggestionSource
+ get() = current()?.suggestionSource
override val suggestionText1: String?
- get() = current().suggestionText1
+ get() = current()?.suggestionText1
override val suggestionText2: String?
- get() = current().suggestionText2
+ get() = current()?.suggestionText2
override val suggestionText2Url: String?
- get() = current().suggestionText2Url
+ get() = current()?.suggestionText2Url
override val isSpinnerWhileRefreshing: Boolean
- get() = current().isSpinnerWhileRefreshing
+ get() = current()?.isSpinnerWhileRefreshing == true
override val isSuggestionShortcut: Boolean
- get() = current().isSuggestionShortcut
+ get() = current()?.isSuggestionShortcut == true
override val isWebSearchSuggestion: Boolean
- get() = current().isWebSearchSuggestion
+ get() = current()?.isWebSearchSuggestion == true
override val isHistorySuggestion: Boolean
- get() = current().isHistorySuggestion
+ get() = current()?.isHistorySuggestion == true
override val extras: SuggestionExtras?
- get() = current().extras
+ get() = current()?.extras
} \ No newline at end of file
diff --git a/src/com/android/quicksearchbox/SearchActivity.kt b/src/com/android/quicksearchbox/SearchActivity.kt
index cc5cfd0..0620b97 100644
--- a/src/com/android/quicksearchbox/SearchActivity.kt
+++ b/src/com/android/quicksearchbox/SearchActivity.kt
@@ -382,8 +382,8 @@ class SearchActivity : Activity() {
return true
}
- protected fun launchSuggestion(suggestions: SuggestionCursor, position: Int) {
- suggestions.moveTo(position)
+ protected fun launchSuggestion(suggestions: SuggestionCursor?, position: Int) {
+ suggestions?.moveTo(position)
val intent: Intent = SuggestionUtils.getSuggestionIntent(suggestions, mAppSearchData)
launchIntent(intent)
}
diff --git a/src/com/android/quicksearchbox/SuggestionPosition.kt b/src/com/android/quicksearchbox/SuggestionPosition.kt
index 783ab4b..43b6895 100644
--- a/src/com/android/quicksearchbox/SuggestionPosition.kt
+++ b/src/com/android/quicksearchbox/SuggestionPosition.kt
@@ -20,16 +20,16 @@ package com.android.quicksearchbox
*
*/
class SuggestionPosition @JvmOverloads constructor(
- val cursor: SuggestionCursor,
- val position: Int = cursor.position
+ val cursor: SuggestionCursor?,
+ val position: Int = cursor!!.position
) : AbstractSuggestionWrapper() {
/**
* Gets the suggestion cursor, moved to point to the right suggestion.
*/
@Override
- override fun current(): Suggestion {
- cursor.moveTo(position)
+ override fun current(): Suggestion? {
+ cursor?.moveTo(position)
return cursor
}
diff --git a/src/com/android/quicksearchbox/SuggestionUtils.kt b/src/com/android/quicksearchbox/SuggestionUtils.kt
index f7e7c5b..0c3f806 100644
--- a/src/com/android/quicksearchbox/SuggestionUtils.kt
+++ b/src/com/android/quicksearchbox/SuggestionUtils.kt
@@ -29,12 +29,12 @@ import kotlin.text.StringBuilder
*/
object SuggestionUtils {
@JvmStatic
- fun getSuggestionIntent(suggestion: SuggestionCursor, appSearchData: Bundle?): Intent {
- val action: String? = suggestion.suggestionIntentAction
- val data: String? = suggestion.suggestionIntentDataString
- val query: String? = suggestion.suggestionQuery
- val userQuery: String? = suggestion.userQuery
- val extraData: String? = suggestion.suggestionIntentExtraData
+ fun getSuggestionIntent(suggestion: SuggestionCursor?, appSearchData: Bundle?): Intent {
+ val action: String? = suggestion?.suggestionIntentAction
+ val data: String? = suggestion?.suggestionIntentDataString
+ val query: String? = suggestion?.suggestionQuery
+ val userQuery: String? = suggestion?.userQuery
+ val extraData: String? = suggestion?.suggestionIntentExtraData
// Now build the Intent
val intent = Intent(action)
@@ -55,7 +55,7 @@ object SuggestionUtils {
if (appSearchData != null) {
intent.putExtra(SearchManager.APP_DATA, appSearchData)
}
- intent.setComponent(suggestion.suggestionIntentComponent)
+ intent.setComponent(suggestion?.suggestionIntentComponent)
return intent
}
diff --git a/src/com/android/quicksearchbox/ui/DelayingSuggestionsAdapter.kt b/src/com/android/quicksearchbox/ui/DelayingSuggestionsAdapter.kt
new file mode 100644
index 0000000..be4e98a
--- /dev/null
+++ b/src/com/android/quicksearchbox/ui/DelayingSuggestionsAdapter.kt
@@ -0,0 +1,158 @@
+/*
+ * 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.ui
+
+import android.database.DataSetObserver
+import android.util.Log
+import android.view.View.OnFocusChangeListener
+
+import com.android.quicksearchbox.SuggestionCursor
+import com.android.quicksearchbox.SuggestionPosition
+import com.android.quicksearchbox.Suggestions
+
+/**
+ * A [SuggestionsListAdapter] that doesn't expose the new suggestions until there are some results
+ * to show.
+ */
+class DelayingSuggestionsAdapter<A>(private val mDelayedAdapter: SuggestionsAdapterBase<A>) :
+ SuggestionsAdapter<A> {
+ private var mPendingDataSetObserver: DataSetObserver? = null
+ private var mPendingSuggestions: Suggestions? = null
+ fun close() {
+ setPendingSuggestions(null)
+ mDelayedAdapter.close()
+ }
+
+ /** Gets whether the given suggestions are non-empty for the selected source. */
+ private fun shouldPublish(suggestions: Suggestions?): Boolean {
+ if (suggestions!!.isDone) return true
+ val cursor: SuggestionCursor? = suggestions.getResult()
+ return cursor != null && cursor.count > 0
+ }
+
+ private fun setPendingSuggestions(suggestions: Suggestions?) {
+ if (mPendingSuggestions === suggestions) {
+ return
+ }
+ if (mDelayedAdapter.isClosed) {
+ suggestions?.release()
+ return
+ }
+ if (mPendingDataSetObserver == null) {
+ mPendingDataSetObserver = PendingSuggestionsObserver()
+ }
+ if (mPendingSuggestions != null) {
+ mPendingSuggestions!!.unregisterDataSetObserver(mPendingDataSetObserver)
+ // Close old suggestions, but only if they are not also the current
+ // suggestions.
+ if (mPendingSuggestions !== this.suggestions) {
+ mPendingSuggestions!!.release()
+ }
+ }
+ mPendingSuggestions = suggestions
+ if (mPendingSuggestions != null) {
+ mPendingSuggestions!!.registerDataSetObserver(mPendingDataSetObserver)
+ }
+ }
+
+ protected fun onPendingSuggestionsChanged() {
+ if (DBG) Log.d(TAG, "onPendingSuggestionsChanged(), mPendingSuggestions=" + mPendingSuggestions)
+ if (shouldPublish(mPendingSuggestions)) {
+ if (DBG) Log.d(TAG, "Suggestions now available, publishing: $mPendingSuggestions")
+ mDelayedAdapter.suggestions = mPendingSuggestions
+ // The suggestions are no longer pending.
+ setPendingSuggestions(null)
+ }
+ }
+
+ private inner class PendingSuggestionsObserver : DataSetObserver() {
+ @Override
+ override fun onChanged() {
+ onPendingSuggestionsChanged()
+ }
+ }
+
+ @get:Override
+ override val listAdapter: A
+ get() = mDelayedAdapter.listAdapter
+ val currentPromotedSuggestions: SuggestionCursor?
+ get() = mDelayedAdapter.currentSuggestions
+
+ // Clear any old pending suggestions.
+ @get:Override
+ @set:Override
+ override var suggestions: Suggestions?
+ get() = mDelayedAdapter.suggestions
+ set(suggestions) {
+ if (suggestions == null) {
+ mDelayedAdapter.suggestions = null
+ setPendingSuggestions(null)
+ return
+ }
+ if (shouldPublish(suggestions)) {
+ if (DBG)
+ Log.d(
+ TAG,
+ "Publishing suggestions immediately: $suggestions"
+ )
+ mDelayedAdapter.suggestions = suggestions
+ // Clear any old pending suggestions.
+ setPendingSuggestions(null)
+ } else {
+ if (DBG)
+ Log.d(
+ TAG,
+ "Delaying suggestions publishing: $suggestions"
+ )
+ setPendingSuggestions(suggestions)
+ }
+ }
+
+ @Override
+ override fun getSuggestion(suggestionId: Long): SuggestionPosition? {
+ return mDelayedAdapter.getSuggestion(suggestionId)
+ }
+
+ @Override
+ override fun onSuggestionClicked(suggestionId: Long) {
+ mDelayedAdapter.onSuggestionClicked(suggestionId)
+ }
+
+ @Override
+ override fun onSuggestionQueryRefineClicked(suggestionId: Long) {
+ mDelayedAdapter.onSuggestionQueryRefineClicked(suggestionId)
+ }
+
+ @Override
+ override fun setOnFocusChangeListener(l: OnFocusChangeListener?) {
+ mDelayedAdapter.setOnFocusChangeListener(l)
+ }
+
+ @Override
+ override fun setSuggestionClickListener(listener: SuggestionClickListener?) {
+ mDelayedAdapter.setSuggestionClickListener(listener)
+ }
+
+ @get:Override
+ override val isEmpty: Boolean
+ get() = mDelayedAdapter.isEmpty
+
+ companion object {
+ private const val DBG = false
+ private const val TAG = "QSB.DelayingSuggestionsAdapter"
+ }
+}
diff --git a/src/com/android/quicksearchbox/ui/SearchActivityView.kt b/src/com/android/quicksearchbox/ui/SearchActivityView.kt
index 64852dd..8e8dcac 100644
--- a/src/com/android/quicksearchbox/ui/SearchActivityView.kt
+++ b/src/com/android/quicksearchbox/ui/SearchActivityView.kt
@@ -34,13 +34,10 @@ import android.widget.ListAdapter
import android.widget.RelativeLayout
import android.widget.TextView
import android.widget.TextView.OnEditorActionListener
-
import com.android.quicksearchbox.*
import com.android.quicksearchbox.R
-
-import kotlin.collections.ArrayList
-
import java.util.Arrays
+import kotlin.collections.ArrayList
abstract class SearchActivityView : RelativeLayout {
@JvmField protected var mQueryTextView: QueryTextView? = null
@@ -49,8 +46,8 @@ abstract class SearchActivityView : RelativeLayout {
@JvmField protected var mQueryWasEmpty = true
@JvmField protected var mQueryTextEmptyBg: Drawable? = null
protected var mQueryTextNotEmptyBg: Drawable? = null
- @JvmField protected var mSuggestionsView: SuggestionsListView<ListAdapter>? = null
- @JvmField protected var mSuggestionsAdapter: SuggestionsAdapter<ListAdapter>? = null
+ @JvmField protected var mSuggestionsView: SuggestionsListView<ListAdapter?>? = null
+ @JvmField protected var mSuggestionsAdapter: SuggestionsAdapter<ListAdapter?>? = null
@JvmField protected var mSearchGoButton: ImageButton? = null
@JvmField protected var mVoiceSearchButton: ImageButton? = null
@JvmField protected var mButtonsKeyListener: ButtonsKeyListener? = null
@@ -60,14 +57,14 @@ abstract class SearchActivityView : RelativeLayout {
@JvmField protected var mExitClickListener: View.OnClickListener? = null
constructor(context: Context?) : super(context)
- constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
- constructor(
+ constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
+ constructor(
context: Context?,
attrs: AttributeSet?,
defStyle: Int
) : super(context, attrs, defStyle)
- @Override
+ @Override
protected override fun onFinishInflate() {
mQueryTextView = findViewById(R.id.search_src_text) as QueryTextView?
mSuggestionsView = findViewById(R.id.suggestions) as SuggestionsView?
@@ -116,7 +113,7 @@ abstract class SearchActivityView : RelativeLayout {
protected val voiceSearch: VoiceSearch?
get() = qsbApplication.voiceSearch
- protected fun createSuggestionsAdapter(): SuggestionsAdapter<ListAdapter> {
+ protected fun createSuggestionsAdapter(): SuggestionsAdapter<ListAdapter?> {
return DelayingSuggestionsAdapter(SuggestionsListAdapter(qsbApplication.suggestionViewFactory))
}
diff --git a/src/com/android/quicksearchbox/ui/SearchActivityViewSinglePane.kt b/src/com/android/quicksearchbox/ui/SearchActivityViewSinglePane.kt
new file mode 100644
index 0000000..a245234
--- /dev/null
+++ b/src/com/android/quicksearchbox/ui/SearchActivityViewSinglePane.kt
@@ -0,0 +1,43 @@
+/*
+ * 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.ui
+
+import android.content.Context
+import android.util.AttributeSet
+
+/** Finishes the containing activity on BACK, even if input method is showing. */
+class SearchActivityViewSinglePane : SearchActivityView {
+ constructor(context: Context?) : super(context)
+ constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
+ constructor(
+ context: Context?,
+ attrs: AttributeSet?,
+ defStyle: Int
+ ) : super(context, attrs, defStyle)
+
+ @Override
+ override fun onResume() {
+ focusQueryTextView()
+ }
+
+ @Override
+ override fun considerHidingInputMethod() {
+ mQueryTextView!!.hideInputMethod()
+ }
+
+ @Override override fun onStop() {}
+}
diff --git a/src/com/android/quicksearchbox/ui/SuggestionViewInflater.kt b/src/com/android/quicksearchbox/ui/SuggestionViewInflater.kt
new file mode 100644
index 0000000..a71745a
--- /dev/null
+++ b/src/com/android/quicksearchbox/ui/SuggestionViewInflater.kt
@@ -0,0 +1,81 @@
+/*
+ * 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.ui
+
+import android.content.Context
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+
+import com.android.quicksearchbox.Suggestion
+import com.android.quicksearchbox.SuggestionCursor
+
+/** Suggestion view factory that inflates views from XML. */
+open class SuggestionViewInflater(
+ private val mViewType: String,
+ viewClass: Class<out SuggestionView?>,
+ layoutId: Int,
+ context: Context?
+) : SuggestionViewFactory {
+ private val mViewClass: Class<*>
+ private val mLayoutId: Int
+ private val mContext: Context?
+
+ protected val inflater: LayoutInflater
+ get() = mContext?.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
+
+ override val suggestionViewTypes: Collection<String>
+ get() = listOf(mViewType)
+
+ override fun getView(
+ suggestion: SuggestionCursor?,
+ userQuery: String?,
+ convertView: View?,
+ parent: ViewGroup?
+ ): View? {
+ var mConvertView: View? = convertView
+ if (mConvertView == null || !mConvertView::class.equals(mViewClass)) {
+ val layoutId = mLayoutId
+ mConvertView = inflater.inflate(layoutId, parent, false)
+ }
+ if (mConvertView !is SuggestionView) {
+ throw IllegalArgumentException("Not a SuggestionView: $mConvertView")
+ }
+ (mConvertView as SuggestionView).bindAsSuggestion(suggestion, userQuery)
+ return mConvertView
+ }
+
+ override fun getViewType(suggestion: Suggestion?): String {
+ return mViewType
+ }
+
+ override fun canCreateView(suggestion: Suggestion?): Boolean {
+ return true
+ }
+
+ /**
+ * @param viewType The unique type of views inflated by this factory
+ * @param viewClass The expected type of view classes.
+ * @param layoutId resource ID of layout to use.
+ * @param context Context to use for inflating the views.
+ */
+ init {
+ mViewClass = viewClass
+ mLayoutId = layoutId
+ mContext = context
+ }
+}
diff --git a/src/com/android/quicksearchbox/ui/SuggestionsAdapterBase.kt b/src/com/android/quicksearchbox/ui/SuggestionsAdapterBase.kt
new file mode 100644
index 0000000..f334d5d
--- /dev/null
+++ b/src/com/android/quicksearchbox/ui/SuggestionsAdapterBase.kt
@@ -0,0 +1,223 @@
+/*
+ * 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.ui
+
+import com.android.quicksearchbox.Suggestion
+import com.android.quicksearchbox.SuggestionCursor
+import com.android.quicksearchbox.SuggestionPosition
+import com.android.quicksearchbox.Suggestions
+
+import android.database.DataSetObserver
+import android.util.Log
+import android.view.View
+import android.view.View.OnFocusChangeListener
+import android.view.ViewGroup
+
+import kotlin.collections.HashMap
+
+/** Base class for suggestions adapters. The templated class A is the list adapter class. */
+abstract class SuggestionsAdapterBase<A>
+protected constructor(private val mViewFactory: SuggestionViewFactory) : SuggestionsAdapter<A> {
+ private var mDataSetObserver: DataSetObserver? = null
+ var currentSuggestions: SuggestionCursor? = null
+ private set
+ private val mViewTypeMap: HashMap<String, Int>
+ private var mSuggestions: Suggestions? = null
+ private var mSuggestionClickListener: SuggestionClickListener? = null
+ private var mOnFocusChangeListener: OnFocusChangeListener? = null
+ var isClosed = false
+ private set
+
+ @get:Override abstract override val isEmpty: Boolean
+ fun close() {
+ suggestions = null
+ isClosed = true
+ }
+
+ @Override
+ override fun setSuggestionClickListener(listener: SuggestionClickListener?) {
+ mSuggestionClickListener = listener
+ }
+
+ @Override
+ override fun setOnFocusChangeListener(l: OnFocusChangeListener?) {
+ mOnFocusChangeListener = l
+ }
+
+ // TODO: delay the change if there are no suggestions for the currently visible tab.
+ @get:Override
+ @set:Override
+ override var suggestions: Suggestions?
+ get() = mSuggestions!!
+ set(suggestions) {
+ if (mSuggestions === suggestions) {
+ return
+ }
+ if (isClosed) {
+ suggestions?.release()
+ return
+ }
+ if (mDataSetObserver == null) {
+ mDataSetObserver = MySuggestionsObserver()
+ }
+ // TODO: delay the change if there are no suggestions for the currently visible tab.
+ if (mSuggestions != null) {
+ mSuggestions!!.unregisterDataSetObserver(mDataSetObserver)
+ mSuggestions!!.release()
+ }
+ mSuggestions = suggestions
+ if (mSuggestions != null) {
+ mSuggestions!!.registerDataSetObserver(mDataSetObserver)
+ }
+ onSuggestionsChanged()
+ }
+
+ @Override abstract override fun getSuggestion(suggestionId: Long): SuggestionPosition
+ protected val count: Int
+ get() = if (currentSuggestions == null) 0 else currentSuggestions!!.count
+
+ protected fun getSuggestion(position: Int): SuggestionPosition? {
+ return if (currentSuggestions == null) null
+ else SuggestionPosition(currentSuggestions!!, position)
+ }
+
+ protected val viewTypeCount: Int
+ get() = mViewTypeMap.size
+
+ private fun suggestionViewType(suggestion: Suggestion): String? {
+ val viewType = mViewFactory.getViewType(suggestion)
+ if (!mViewTypeMap.containsKey(viewType)) {
+ throw IllegalStateException("Unknown viewType $viewType")
+ }
+ return viewType
+ }
+
+ protected fun getSuggestionViewType(cursor: SuggestionCursor?, position: Int): Int {
+ if (cursor == null) {
+ return 0
+ }
+ cursor.moveTo(position)
+ return mViewTypeMap.get(suggestionViewType(cursor)!!) as Int
+ }
+
+ protected val suggestionViewTypeCount: Int
+ get() = mViewTypeMap.size
+
+ protected fun getView(
+ suggestions: SuggestionCursor?,
+ position: Int,
+ suggestionId: Long,
+ convertView: View?,
+ parent: ViewGroup?
+ ): View? {
+ suggestions?.moveTo(position)
+ val v: View? = mViewFactory.getView(suggestions, suggestions?.userQuery, convertView, parent)
+ if (v is SuggestionView) {
+ (v as SuggestionView?)!!.bindAdapter(this, suggestionId)
+ } else {
+ val l = SuggestionViewClickListener(suggestionId)
+ v?.setOnClickListener(l)
+ }
+ if (mOnFocusChangeListener != null) {
+ v?.setOnFocusChangeListener(mOnFocusChangeListener)
+ }
+ return v
+ }
+
+ protected fun onSuggestionsChanged() {
+ if (DBG) Log.d(TAG, "onSuggestionsChanged($mSuggestions)")
+ var cursor: SuggestionCursor? = null
+ if (mSuggestions != null) {
+ cursor = mSuggestions!!.getResult()
+ }
+ changeSuggestions(cursor)
+ }
+
+ /**
+ * Replace the cursor.
+ *
+ * This does not close the old cursor. Instead, all the cursors are closed in [.setSuggestions].
+ */
+ private fun changeSuggestions(newCursor: SuggestionCursor?) {
+ if (DBG) {
+ Log.d(TAG, "changeCursor(" + newCursor + ") count=" + (newCursor?.count ?: 0))
+ }
+ if (newCursor === currentSuggestions) {
+ if (newCursor != null) {
+ // Shortcuts may have changed without the cursor changing.
+ notifyDataSetChanged()
+ }
+ return
+ }
+ currentSuggestions = newCursor
+ if (currentSuggestions != null) {
+ notifyDataSetChanged()
+ } else {
+ notifyDataSetInvalidated()
+ }
+ }
+
+ @Override
+ override fun onSuggestionClicked(suggestionId: Long) {
+ if (isClosed) {
+ Log.w(TAG, "onSuggestionClicked after close")
+ } else if (mSuggestionClickListener != null) {
+ mSuggestionClickListener!!.onSuggestionClicked(this, suggestionId)
+ }
+ }
+
+ @Override
+ override fun onSuggestionQueryRefineClicked(suggestionId: Long) {
+ if (isClosed) {
+ Log.w(TAG, "onSuggestionQueryRefineClicked after close")
+ } else if (mSuggestionClickListener != null) {
+ mSuggestionClickListener!!.onSuggestionQueryRefineClicked(this, suggestionId)
+ }
+ }
+
+ @get:Override abstract override val listAdapter: A
+ protected abstract fun notifyDataSetInvalidated()
+ protected abstract fun notifyDataSetChanged()
+ private inner class MySuggestionsObserver : DataSetObserver() {
+ @Override
+ override fun onChanged() {
+ onSuggestionsChanged()
+ }
+ }
+
+ private inner class SuggestionViewClickListener(private val mSuggestionId: Long) :
+ View.OnClickListener {
+ @Override
+ override fun onClick(v: View?) {
+ onSuggestionClicked(mSuggestionId)
+ }
+ }
+
+ companion object {
+ private const val DBG = false
+ private const val TAG = "QSB.SuggestionsAdapter"
+ }
+
+ init {
+ mViewTypeMap = hashMapOf<String, Int>()
+ for (viewType in mViewFactory.suggestionViewTypes) {
+ if (!mViewTypeMap.containsKey(viewType)) {
+ mViewTypeMap.put(viewType, mViewTypeMap.size)
+ }
+ }
+ }
+}
diff --git a/src/com/android/quicksearchbox/ui/SuggestionsListAdapter.kt b/src/com/android/quicksearchbox/ui/SuggestionsListAdapter.kt
new file mode 100644
index 0000000..e6ace36
--- /dev/null
+++ b/src/com/android/quicksearchbox/ui/SuggestionsListAdapter.kt
@@ -0,0 +1,97 @@
+/*
+ * 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.ui
+
+import android.view.View
+import android.view.ViewGroup
+import android.widget.BaseAdapter
+import android.widget.ListAdapter
+
+import com.android.quicksearchbox.SuggestionCursor
+import com.android.quicksearchbox.SuggestionPosition
+
+/** Uses a [Suggestions] object to back a [SuggestionsView]. */
+class SuggestionsListAdapter(viewFactory: SuggestionViewFactory?) :
+ SuggestionsAdapterBase<ListAdapter?>(viewFactory!!) {
+ private val mAdapter: SuggestionsListAdapter.Adapter
+
+ @get:Override
+ override val isEmpty: Boolean
+ get() = mAdapter.getCount() == 0
+
+ @Override
+ override fun getSuggestion(suggestionId: Long): SuggestionPosition {
+ return SuggestionPosition(currentSuggestions, suggestionId.toInt())
+ }
+
+ @get:Override
+ override val listAdapter: BaseAdapter
+ get() = mAdapter
+
+ @Override
+ public override fun notifyDataSetChanged() {
+ mAdapter.notifyDataSetChanged()
+ }
+
+ @Override
+ public override fun notifyDataSetInvalidated() {
+ mAdapter.notifyDataSetInvalidated()
+ }
+
+ internal inner class Adapter : BaseAdapter() {
+ @Override
+ override fun getCount(): Int {
+ val s: SuggestionCursor? = currentSuggestions
+ return s?.count ?: 0
+ }
+
+ @Override
+ override fun getItem(position: Int): Any? {
+ return getSuggestion(position)
+ }
+
+ @Override
+ override fun getItemId(position: Int): Long {
+ return position.toLong()
+ }
+
+ @Override
+ override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View? {
+ return this@SuggestionsListAdapter.getView(
+ currentSuggestions,
+ position,
+ position.toLong(),
+ convertView,
+ parent
+ )
+ }
+
+ @Override
+ override fun getItemViewType(position: Int): Int {
+ return getSuggestionViewType(currentSuggestions, position)
+ }
+
+ @Override
+ override fun getViewTypeCount(): Int {
+ return suggestionViewTypeCount
+ }
+ }
+
+ init {
+ mAdapter = Adapter()
+ }
+}
diff --git a/src/com/android/quicksearchbox/ui/SuggestionsListView.kt b/src/com/android/quicksearchbox/ui/SuggestionsListView.kt
index ab8c829..75a0d77 100644
--- a/src/com/android/quicksearchbox/ui/SuggestionsListView.kt
+++ b/src/com/android/quicksearchbox/ui/SuggestionsListView.kt
@@ -46,12 +46,12 @@ interface SuggestionsListView<A> {
/**
* Sets the adapter for the list. See [AbsListView.setAdapter]
*/
- fun setSuggestionsAdapter(adapter: SuggestionsAdapter<A>?)
+ fun setSuggestionsAdapter(adapter: SuggestionsAdapter<A?>?)
/**
* Gets the adapter for the list.
*/
- fun getSuggestionsAdapter(): SuggestionsAdapter<A>?
+ fun getSuggestionsAdapter(): SuggestionsAdapter<A?>?
/**
* Gets the ID of the currently selected item.
diff --git a/src/com/android/quicksearchbox/ui/SuggestionsView.kt b/src/com/android/quicksearchbox/ui/SuggestionsView.kt
new file mode 100644
index 0000000..29f1efa
--- /dev/null
+++ b/src/com/android/quicksearchbox/ui/SuggestionsView.kt
@@ -0,0 +1,68 @@
+/*
+ * 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.ui
+
+import android.content.Context
+import android.util.AttributeSet
+import android.widget.ListAdapter
+import android.widget.ListView
+
+import com.android.quicksearchbox.SuggestionPosition
+
+/** Holds a list of suggestions. */
+class SuggestionsView(context: Context?, attrs: AttributeSet?) :
+ ListView(context, attrs), SuggestionsListView<ListAdapter?> {
+ private var mSuggestionsAdapter: SuggestionsAdapter<ListAdapter?>? = null
+
+ @Override
+ override fun setSuggestionsAdapter(adapter: SuggestionsAdapter<ListAdapter?>?) {
+ super.setAdapter(adapter?.listAdapter)
+ mSuggestionsAdapter = adapter
+ }
+
+ @Override
+ override fun getSuggestionsAdapter(): SuggestionsAdapter<ListAdapter?>? {
+ return mSuggestionsAdapter
+ }
+
+ @Override
+ override fun onFinishInflate() {
+ super.onFinishInflate()
+ setItemsCanFocus(true)
+ }
+
+ /**
+ * Gets the position of the selected suggestion.
+ *
+ * @return A 0-based index, or `-1` if no suggestion is selected.
+ */
+ val selectedPosition: Int
+ get() = getSelectedItemPosition()
+
+ /**
+ * Gets the selected suggestion.
+ *
+ * @return `null` if no suggestion is selected.
+ */
+ val selectedSuggestion: SuggestionPosition
+ get() = getSelectedItem() as SuggestionPosition
+
+ companion object {
+ private const val DBG = false
+ private const val TAG = "QSB.SuggestionsView"
+ }
+}
diff --git a/src/com/android/quicksearchbox/ui/WebSearchSuggestionView.kt b/src/com/android/quicksearchbox/ui/WebSearchSuggestionView.kt
new file mode 100644
index 0000000..421c427
--- /dev/null
+++ b/src/com/android/quicksearchbox/ui/WebSearchSuggestionView.kt
@@ -0,0 +1,101 @@
+/*
+ * 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.ui
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.KeyEvent
+import android.view.View
+
+import com.android.quicksearchbox.QsbApplication
+import com.android.quicksearchbox.R
+import com.android.quicksearchbox.Suggestion
+import com.android.quicksearchbox.SuggestionFormatter
+
+/** View for web search suggestions. */
+class WebSearchSuggestionView(context: Context?, attrs: AttributeSet?) :
+ BaseSuggestionView(context, attrs) {
+ private val mSuggestionFormatter: SuggestionFormatter?
+
+ @Override
+ override fun onFinishInflate() {
+ super.onFinishInflate()
+ val keyListener: WebSearchSuggestionView.KeyListener = KeyListener()
+ setOnKeyListener(keyListener)
+ mIcon2?.setOnKeyListener(keyListener)
+ mIcon2?.setOnClickListener(
+ object : OnClickListener {
+ override fun onClick(v: View?) {
+ onSuggestionQueryRefineClicked()
+ }
+ }
+ )
+ mIcon2?.setFocusable(true)
+ }
+
+ @Override
+ override fun bindAsSuggestion(suggestion: Suggestion?, userQuery: String?) {
+ super.bindAsSuggestion(suggestion, userQuery)
+ val text1 = mSuggestionFormatter?.formatSuggestion(userQuery, suggestion?.suggestionText1)
+ setText1(text1)
+ setIsHistorySuggestion(suggestion?.isHistorySuggestion)
+ }
+
+ private fun setIsHistorySuggestion(isHistory: Boolean?) {
+ if (isHistory == true) {
+ mIcon1?.setImageResource(R.drawable.ic_history_suggestion)
+ mIcon1?.setVisibility(VISIBLE)
+ } else {
+ mIcon1?.setVisibility(INVISIBLE)
+ }
+ }
+
+ private inner class KeyListener : View.OnKeyListener {
+ override fun onKey(v: View, keyCode: Int, event: KeyEvent): Boolean {
+ var consumed = false
+ if (event.getAction() == KeyEvent.ACTION_DOWN) {
+ if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT && v !== mIcon2) {
+ consumed = mIcon2!!.requestFocus()
+ } else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT && v === mIcon2) {
+ consumed = requestFocus()
+ }
+ }
+ return consumed
+ }
+ }
+
+ class Factory(context: Context?) :
+ SuggestionViewInflater(
+ VIEW_ID,
+ WebSearchSuggestionView::class.java,
+ R.layout.web_search_suggestion,
+ context
+ ) {
+ @Override
+ override fun canCreateView(suggestion: Suggestion?): Boolean {
+ return suggestion!!.isWebSearchSuggestion
+ }
+ }
+
+ companion object {
+ private const val VIEW_ID = "web_search"
+ }
+
+ init {
+ mSuggestionFormatter = QsbApplication[context].suggestionFormatter
+ }
+}