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.app.PendingIntent
19 import android.app.SearchManager
20 import android.appwidget.AppWidgetManager
21 import android.content.BroadcastReceiver
22 import android.content.ComponentName
23 import android.content.Context
24 import android.content.Intent
25 import android.os.Bundle
26 import android.util.Log
27 import android.view.View
28 import android.widget.RemoteViews
29 import com.android.common.Search
30 
31 /** Search widget provider. */
32 class SearchWidgetProvider : BroadcastReceiver() {
33   @Override
onReceivenull34   override fun onReceive(context: Context?, intent: Intent) {
35     if (DBG) Log.d(TAG, "onReceive(" + intent.toUri(0).toString() + ")")
36     val action: String? = intent.getAction()
37     if (AppWidgetManager.ACTION_APPWIDGET_ENABLED.equals(action)) {
38       // nothing needs doing
39     } else if (AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action)) {
40       updateSearchWidgets(context)
41     } else {
42       if (DBG) Log.d(TAG, "Unhandled intent action=$action")
43     }
44   }
45 
46   private class SearchWidgetState(private val mAppWidgetId: Int) {
47     private var mQueryTextViewIntent: Intent? = null
48     private var mVoiceSearchIntent: Intent? = null
setQueryTextViewIntentnull49     fun setQueryTextViewIntent(queryTextViewIntent: Intent?) {
50       mQueryTextViewIntent = queryTextViewIntent
51     }
52 
setVoiceSearchIntentnull53     fun setVoiceSearchIntent(voiceSearchIntent: Intent?) {
54       mVoiceSearchIntent = voiceSearchIntent
55     }
56 
updateWidgetnull57     fun updateWidget(context: Context?, appWidgetMgr: AppWidgetManager) {
58       if (DBG) Log.d(TAG, "Updating appwidget $mAppWidgetId")
59       val views = RemoteViews(context!!.getPackageName(), R.layout.search_widget)
60       setOnClickActivityIntent(context, views, R.id.search_widget_text, mQueryTextViewIntent)
61       // Voice Search button
62       if (mVoiceSearchIntent != null) {
63         setOnClickActivityIntent(context, views, R.id.search_widget_voice_btn, mVoiceSearchIntent)
64         views.setViewVisibility(R.id.search_widget_voice_btn, View.VISIBLE)
65       } else {
66         views.setViewVisibility(R.id.search_widget_voice_btn, View.GONE)
67       }
68       appWidgetMgr.updateAppWidget(mAppWidgetId, views)
69     }
70 
setOnClickActivityIntentnull71     private fun setOnClickActivityIntent(
72       context: Context?,
73       views: RemoteViews,
74       viewId: Int,
75       intent: Intent?
76     ) {
77       intent?.setPackage(context?.getPackageName())
78       val pendingIntent: PendingIntent = PendingIntent.getActivity(context, 0, intent, 0)
79       views.setOnClickPendingIntent(viewId, pendingIntent)
80     }
81   }
82 
83   companion object {
84     private const val DBG = false
85     private const val TAG = "QSB.SearchWidgetProvider"
86 
87     /** The [Search.SOURCE] value used when starting searches from the search widget. */
88     private const val WIDGET_SEARCH_SOURCE = "launcher-widget"
getSearchWidgetStatesnull89     private fun getSearchWidgetStates(context: Context?): Array<SearchWidgetState?> {
90       val appWidgetManager: AppWidgetManager = AppWidgetManager.getInstance(context)
91       val appWidgetIds: IntArray = appWidgetManager.getAppWidgetIds(myComponentName(context))
92       val states: Array<SearchWidgetState?> = arrayOfNulls(appWidgetIds.size)
93       for (i in appWidgetIds.indices) {
94         states[i] = getSearchWidgetState(context, appWidgetIds[i])
95       }
96       return states
97     }
98 
99     /** Updates all search widgets. */
100     @JvmStatic
updateSearchWidgetsnull101     fun updateSearchWidgets(context: Context?) {
102       if (DBG) Log.d(TAG, "updateSearchWidgets")
103       val states: Array<SearchWidgetState?> = getSearchWidgetStates(context)
104       for (state in states) {
105         state?.updateWidget(context, AppWidgetManager.getInstance(context))
106       }
107     }
108 
109     /** Gets the component name of this search widget provider. */
myComponentNamenull110     private fun myComponentName(context: Context?): ComponentName {
111       val pkg: String = context!!.getPackageName()
112       val cls = "$pkg.SearchWidgetProvider"
113       return ComponentName(pkg, cls)
114     }
115 
createQsbActivityIntentnull116     private fun createQsbActivityIntent(
117       context: Context?,
118       action: String,
119       widgetAppData: Bundle
120     ): Intent {
121       val qsbIntent = Intent(action)
122       qsbIntent.setPackage(context?.getPackageName())
123       qsbIntent.setFlags(
124         Intent.FLAG_ACTIVITY_NEW_TASK or
125           Intent.FLAG_ACTIVITY_CLEAR_TOP or
126           Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
127       )
128       qsbIntent.putExtra(SearchManager.APP_DATA, widgetAppData)
129       return qsbIntent
130     }
131 
getSearchWidgetStatenull132     private fun getSearchWidgetState(context: Context?, appWidgetId: Int): SearchWidgetState {
133       if (DBG) Log.d(TAG, "Creating appwidget state $appWidgetId")
134       val state: SearchWidgetState = SearchWidgetState(appWidgetId)
135       val widgetAppData = Bundle()
136       widgetAppData.putString(Search.SOURCE, WIDGET_SEARCH_SOURCE)
137 
138       // Text field click
139       val qsbIntent: Intent =
140         createQsbActivityIntent(context, SearchManager.INTENT_ACTION_GLOBAL_SEARCH, widgetAppData)
141       state.setQueryTextViewIntent(qsbIntent)
142 
143       // Voice search button
144       val voiceSearchIntent: Intent? = getVoiceSearchIntent(context, widgetAppData)
145       state.setVoiceSearchIntent(voiceSearchIntent)
146       return state
147     }
148 
getVoiceSearchIntentnull149     private fun getVoiceSearchIntent(context: Context?, widgetAppData: Bundle): Intent? {
150       val voiceSearch: VoiceSearch? = QsbApplication[context].voiceSearch
151       return voiceSearch?.createVoiceWebSearchIntent(widgetAppData)
152     }
153   }
154 }
155