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.google 17 18 import android.app.Activity 19 import android.app.PendingIntent 20 import android.app.SearchManager 21 import android.content.ActivityNotFoundException 22 import android.content.Intent 23 import android.net.Uri 24 import android.os.Bundle 25 import android.provider.Browser 26 import android.text.TextUtils 27 import android.util.Log 28 import com.android.common.Search 29 import com.android.quicksearchbox.QsbApplication 30 import java.io.UnsupportedEncodingException 31 import java.net.URLEncoder 32 import java.util.Locale 33 34 /** 35 * This class is purely here to get search queries and route them to the global 36 * [Intent.ACTION_WEB_SEARCH]. 37 */ 38 class GoogleSearch : Activity() { 39 // Used to figure out which domain to base search requests 40 // on. 41 private var mSearchDomainHelper: SearchBaseUrlHelper? = null 42 43 @Override onCreatenull44 protected override fun onCreate(savedInstanceState: Bundle?) { 45 super.onCreate(savedInstanceState) 46 val intent: Intent? = getIntent() 47 val action: String? = if (intent != null) intent.getAction() else null 48 49 // This should probably be moved so as to 50 // send out the request to /checksearchdomain as early as possible. 51 mSearchDomainHelper = QsbApplication.get(this).searchBaseUrlHelper 52 if (Intent.ACTION_WEB_SEARCH.equals(action) || Intent.ACTION_SEARCH.equals(action)) { 53 handleWebSearchIntent(intent) 54 } 55 finish() 56 } 57 handleWebSearchIntentnull58 private fun handleWebSearchIntent(intent: Intent?) { 59 val launchUriIntent: Intent? = createLaunchUriIntentFromSearchIntent(intent) 60 61 @Suppress("DEPRECATION") 62 val pending: PendingIntent? = 63 intent?.getParcelableExtra(SearchManager.EXTRA_WEB_SEARCH_PENDINGINTENT) 64 if (pending == null || !launchPendingIntent(pending, launchUriIntent)) { 65 launchIntent(launchUriIntent) 66 } 67 } 68 createLaunchUriIntentFromSearchIntentnull69 private fun createLaunchUriIntentFromSearchIntent(intent: Intent?): Intent? { 70 val query: String? = intent?.getStringExtra(SearchManager.QUERY) 71 if (TextUtils.isEmpty(query)) { 72 Log.w(TAG, "Got search intent with no query.") 73 return null 74 } 75 76 // If the caller specified a 'source' url parameter, use that and if not use default. 77 val appSearchData: Bundle? = intent?.getBundleExtra(SearchManager.APP_DATA) 78 var source: String? = GoogleSearch.Companion.GOOGLE_SEARCH_SOURCE_UNKNOWN 79 if (appSearchData != null) { 80 source = appSearchData.getString(Search.SOURCE) 81 } 82 83 // The browser can pass along an application id which it uses to figure out which 84 // window to place a new search into. So if this exists, we'll pass it back to 85 // the browser. Otherwise, add our own package name as the application id, so that 86 // the browser can organize all searches launched from this provider together. 87 var applicationId: String? = intent?.getStringExtra(Browser.EXTRA_APPLICATION_ID) 88 if (applicationId == null) { 89 applicationId = getPackageName() 90 } 91 return try { 92 val searchUri = 93 (mSearchDomainHelper!!.searchBaseUrl.toString() + 94 "&source=android-" + 95 source + 96 "&q=" + 97 URLEncoder.encode(query, "UTF-8")) 98 val launchUriIntent = Intent(Intent.ACTION_VIEW, Uri.parse(searchUri)) 99 launchUriIntent.putExtra(Browser.EXTRA_APPLICATION_ID, applicationId) 100 launchUriIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 101 launchUriIntent 102 } catch (e: UnsupportedEncodingException) { 103 Log.w(TAG, "Error", e) 104 null 105 } 106 } 107 launchIntentnull108 private fun launchIntent(intent: Intent?) { 109 try { 110 Log.i(TAG, "Launching intent: " + intent?.toUri(0)) 111 startActivity(intent) 112 } catch (ex: ActivityNotFoundException) { 113 Log.w(TAG, "No activity found to handle: $intent") 114 } 115 } 116 launchPendingIntentnull117 private fun launchPendingIntent(pending: PendingIntent, fillIn: Intent?): Boolean { 118 return try { 119 pending.send(this, Activity.RESULT_OK, fillIn) 120 true 121 } catch (ex: PendingIntent.CanceledException) { 122 Log.i(TAG, "Pending intent cancelled: $pending") 123 false 124 } 125 } 126 127 companion object { 128 private const val TAG = "GoogleSearch" 129 private const val DBG = false 130 131 // "source" parameter for Google search requests from unknown sources (e.g. apps). This will get 132 // prefixed with the string 'android-' before being sent on the wire. 133 const val GOOGLE_SEARCH_SOURCE_UNKNOWN = "unknown" 134 135 /** Construct the language code (hl= parameter) for the given locale. */ getLanguagenull136 fun getLanguage(locale: Locale): String { 137 val language: String = locale.getLanguage() 138 val hl: StringBuilder = StringBuilder(language) 139 val country: String = locale.getCountry() 140 if (!TextUtils.isEmpty(country) && useLangCountryHl(language, country)) { 141 hl.append('-') 142 hl.append(country) 143 } 144 if (DBG) Log.d(TAG, "language $language, country $country -> hl=$hl") 145 return hl.toString() 146 } 147 148 // TODO: This is a workaround for bug 3232296. When that is fixed, this method can be removed. useLangCountryHlnull149 private fun useLangCountryHl(language: String, country: String): Boolean { 150 // lang-country is currently only supported for a small number of locales 151 return if ("en".equals(language)) { 152 "GB".equals(country) 153 } else if ("zh".equals(language)) { 154 "CN".equals(country) || "TW".equals(country) 155 } else if ("pt".equals(language)) { 156 "BR".equals(country) || "PT".equals(country) 157 } else { 158 false 159 } 160 } 161 } 162 } 163