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 
17 package com.android.quicksearchbox.ui
18 
19 import android.database.DataSetObserver
20 import android.util.Log
21 import android.view.View.OnFocusChangeListener
22 import com.android.quicksearchbox.SuggestionCursor
23 import com.android.quicksearchbox.SuggestionPosition
24 import com.android.quicksearchbox.Suggestions
25 
26 /**
27  * A [SuggestionsListAdapter] that doesn't expose the new suggestions until there are some results
28  * to show.
29  */
30 class DelayingSuggestionsAdapter<A>(private val mDelayedAdapter: SuggestionsAdapterBase<A>) :
31   SuggestionsAdapter<A> {
32   private var mPendingDataSetObserver: DataSetObserver? = null
33   private var mPendingSuggestions: Suggestions? = null
closenull34   fun close() {
35     setPendingSuggestions(null)
36     mDelayedAdapter.close()
37   }
38 
39   /** Gets whether the given suggestions are non-empty for the selected source. */
shouldPublishnull40   private fun shouldPublish(suggestions: Suggestions?): Boolean {
41     if (suggestions!!.isDone) return true
42     val cursor: SuggestionCursor? = suggestions.getResult()
43     return cursor != null && cursor.count > 0
44   }
45 
setPendingSuggestionsnull46   private fun setPendingSuggestions(suggestions: Suggestions?) {
47     if (mPendingSuggestions === suggestions) {
48       return
49     }
50     if (mDelayedAdapter.isClosed) {
51       suggestions?.release()
52       return
53     }
54     if (mPendingDataSetObserver == null) {
55       mPendingDataSetObserver = PendingSuggestionsObserver()
56     }
57     if (mPendingSuggestions != null) {
58       mPendingSuggestions!!.unregisterDataSetObserver(mPendingDataSetObserver)
59       // Close old suggestions, but only if they are not also the current
60       // suggestions.
61       if (mPendingSuggestions !== this.suggestions) {
62         mPendingSuggestions!!.release()
63       }
64     }
65     mPendingSuggestions = suggestions
66     if (mPendingSuggestions != null) {
67       mPendingSuggestions!!.registerDataSetObserver(mPendingDataSetObserver)
68     }
69   }
70 
onPendingSuggestionsChangednull71   protected fun onPendingSuggestionsChanged() {
72     if (DBG) Log.d(TAG, "onPendingSuggestionsChanged(), mPendingSuggestions=" + mPendingSuggestions)
73     if (shouldPublish(mPendingSuggestions)) {
74       if (DBG) Log.d(TAG, "Suggestions now available, publishing: $mPendingSuggestions")
75       mDelayedAdapter.suggestions = mPendingSuggestions
76       // The suggestions are no longer pending.
77       setPendingSuggestions(null)
78     }
79   }
80 
81   private inner class PendingSuggestionsObserver : DataSetObserver() {
82     @Override
onChangednull83     override fun onChanged() {
84       onPendingSuggestionsChanged()
85     }
86   }
87 
88   @get:Override
89   override val listAdapter: A
90     get() = mDelayedAdapter.listAdapter
91   val currentPromotedSuggestions: SuggestionCursor?
92     get() = mDelayedAdapter.currentSuggestions
93 
94   // Clear any old pending suggestions.
95   @get:Override
96   @set:Override
97   override var suggestions: Suggestions?
98     get() = mDelayedAdapter.suggestions
99     set(suggestions) {
100       if (suggestions == null) {
101         mDelayedAdapter.suggestions = null
102         setPendingSuggestions(null)
103         return
104       }
105       if (shouldPublish(suggestions)) {
106         if (DBG) Log.d(TAG, "Publishing suggestions immediately: $suggestions")
107         mDelayedAdapter.suggestions = suggestions
108         // Clear any old pending suggestions.
109         setPendingSuggestions(null)
110       } else {
111         if (DBG) Log.d(TAG, "Delaying suggestions publishing: $suggestions")
112         setPendingSuggestions(suggestions)
113       }
114     }
115 
116   @Override
getSuggestionnull117   override fun getSuggestion(suggestionId: Long): SuggestionPosition? {
118     return mDelayedAdapter.getSuggestion(suggestionId)
119   }
120 
121   @Override
onSuggestionClickednull122   override fun onSuggestionClicked(suggestionId: Long) {
123     mDelayedAdapter.onSuggestionClicked(suggestionId)
124   }
125 
126   @Override
onSuggestionQueryRefineClickednull127   override fun onSuggestionQueryRefineClicked(suggestionId: Long) {
128     mDelayedAdapter.onSuggestionQueryRefineClicked(suggestionId)
129   }
130 
131   @Override
setOnFocusChangeListenernull132   override fun setOnFocusChangeListener(l: OnFocusChangeListener?) {
133     mDelayedAdapter.setOnFocusChangeListener(l)
134   }
135 
136   @Override
setSuggestionClickListenernull137   override fun setSuggestionClickListener(listener: SuggestionClickListener?) {
138     mDelayedAdapter.setSuggestionClickListener(listener)
139   }
140 
141   @get:Override
142   override val isEmpty: Boolean
143     get() = mDelayedAdapter.isEmpty
144 
145   companion object {
146     private const val DBG = false
147     private const val TAG = "QSB.DelayingSuggestionsAdapter"
148   }
149 }
150