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