blob: 9c01cfe27736e50240b4c14a2f636d657381b550 [file] [log] [blame]
/*
* 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.google
import android.app.Activity
import android.app.PendingIntent
import android.app.SearchManager
import android.content.ActivityNotFoundException
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.provider.Browser
import android.text.TextUtils
import android.util.Log
import com.android.common.Search
import com.android.quicksearchbox.QsbApplication
import java.io.UnsupportedEncodingException
import java.net.URLEncoder
import java.util.Locale
/**
* This class is purely here to get search queries and route them to the global
* [Intent.ACTION_WEB_SEARCH].
*/
class GoogleSearch : Activity() {
// Used to figure out which domain to base search requests
// on.
private var mSearchDomainHelper: SearchBaseUrlHelper? = null
@Override
protected override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val intent: Intent? = getIntent()
val action: String? = if (intent != null) intent.getAction() else null
// This should probably be moved so as to
// send out the request to /checksearchdomain as early as possible.
mSearchDomainHelper = QsbApplication.get(this).searchBaseUrlHelper
if (Intent.ACTION_WEB_SEARCH.equals(action) || Intent.ACTION_SEARCH.equals(action)) {
handleWebSearchIntent(intent)
}
finish()
}
private fun handleWebSearchIntent(intent: Intent?) {
val launchUriIntent: Intent? = createLaunchUriIntentFromSearchIntent(intent)
@Suppress("DEPRECATION")
val pending: PendingIntent? =
intent?.getParcelableExtra(SearchManager.EXTRA_WEB_SEARCH_PENDINGINTENT)
if (pending == null || !launchPendingIntent(pending, launchUriIntent)) {
launchIntent(launchUriIntent)
}
}
private fun createLaunchUriIntentFromSearchIntent(intent: Intent?): Intent? {
val query: String? = intent?.getStringExtra(SearchManager.QUERY)
if (TextUtils.isEmpty(query)) {
Log.w(TAG, "Got search intent with no query.")
return null
}
// If the caller specified a 'source' url parameter, use that and if not use default.
val appSearchData: Bundle? = intent?.getBundleExtra(SearchManager.APP_DATA)
var source: String? = GoogleSearch.Companion.GOOGLE_SEARCH_SOURCE_UNKNOWN
if (appSearchData != null) {
source = appSearchData.getString(Search.SOURCE)
}
// The browser can pass along an application id which it uses to figure out which
// window to place a new search into. So if this exists, we'll pass it back to
// the browser. Otherwise, add our own package name as the application id, so that
// the browser can organize all searches launched from this provider together.
var applicationId: String? = intent?.getStringExtra(Browser.EXTRA_APPLICATION_ID)
if (applicationId == null) {
applicationId = getPackageName()
}
return try {
val searchUri =
(mSearchDomainHelper!!.searchBaseUrl.toString() +
"&source=android-" +
source +
"&q=" +
URLEncoder.encode(query, "UTF-8"))
val launchUriIntent = Intent(Intent.ACTION_VIEW, Uri.parse(searchUri))
launchUriIntent.putExtra(Browser.EXTRA_APPLICATION_ID, applicationId)
launchUriIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
launchUriIntent
} catch (e: UnsupportedEncodingException) {
Log.w(TAG, "Error", e)
null
}
}
private fun launchIntent(intent: Intent?) {
try {
Log.i(TAG, "Launching intent: " + intent?.toUri(0))
startActivity(intent)
} catch (ex: ActivityNotFoundException) {
Log.w(TAG, "No activity found to handle: $intent")
}
}
private fun launchPendingIntent(pending: PendingIntent, fillIn: Intent?): Boolean {
return try {
pending.send(this, Activity.RESULT_OK, fillIn)
true
} catch (ex: PendingIntent.CanceledException) {
Log.i(TAG, "Pending intent cancelled: $pending")
false
}
}
companion object {
private const val TAG = "GoogleSearch"
private const val DBG = false
// "source" parameter for Google search requests from unknown sources (e.g. apps). This will get
// prefixed with the string 'android-' before being sent on the wire.
const val GOOGLE_SEARCH_SOURCE_UNKNOWN = "unknown"
/** Construct the language code (hl= parameter) for the given locale. */
fun getLanguage(locale: Locale): String {
val language: String = locale.getLanguage()
val hl: StringBuilder = StringBuilder(language)
val country: String = locale.getCountry()
if (!TextUtils.isEmpty(country) && useLangCountryHl(language, country)) {
hl.append('-')
hl.append(country)
}
if (DBG) Log.d(TAG, "language $language, country $country -> hl=$hl")
return hl.toString()
}
// TODO: This is a workaround for bug 3232296. When that is fixed, this method can be removed.
private fun useLangCountryHl(language: String, country: String): Boolean {
// lang-country is currently only supported for a small number of locales
return if ("en".equals(language)) {
"GB".equals(country)
} else if ("zh".equals(language)) {
"CN".equals(country) || "TW".equals(country)
} else if ("pt".equals(language)) {
"BR".equals(country) || "PT".equals(country)
} else {
false
}
}
}
}