blob: b78d49e56e380c0b3887f283cc42b9e448d3e7ff [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.content.Context
import android.content.SharedPreferences
import android.util.Log
import com.android.quicksearchbox.R
import com.android.quicksearchbox.SearchSettings
import com.android.quicksearchbox.SearchSettingsImpl
import com.android.quicksearchbox.util.HttpHelper
import java.util.Locale
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
/** Helper to build the base URL for all search requests. */
class SearchBaseUrlHelper(
context: Context?,
helper: HttpHelper,
searchSettings: SearchSettings,
prefs: SharedPreferences
) : SharedPreferences.OnSharedPreferenceChangeListener {
private val mHttpHelper: HttpHelper
private val mContext: Context?
private val mSearchSettings: SearchSettings
private val scope = CoroutineScope(Dispatchers.IO)
/**
* Update the base search url, either: (a) it has never been set (first run) (b) it has expired
* (c) if the caller forces an update by setting the "force" parameter.
*
* @param force if true, then the URL is reset whether or not it has expired.
*/
fun maybeUpdateBaseUrlSetting(force: Boolean) {
val lastUpdateTime: Long = mSearchSettings.searchBaseDomainApplyTime
val currentTime: Long = System.currentTimeMillis()
if (
force || lastUpdateTime == -1L || currentTime - lastUpdateTime >= SEARCH_BASE_URL_EXPIRY_MS
) {
if (mSearchSettings.shouldUseGoogleCom()) {
setSearchBaseDomain(defaultBaseDomain)
} else {
checkSearchDomain()
}
}
}
/** @return the base url for searches. */
val searchBaseUrl: String?
get() =
mContext
?.getResources()
?.getString(
R.string.google_search_base_pattern,
searchDomain,
GoogleSearch.getLanguage(Locale.getDefault())
) // This is required to deal with the case wherein getSearchDomain
// is called before checkSearchDomain returns a valid URL. This will
// happen *only* on the first run of the app when the "use google.com"
// option is unchecked. In other cases, the previously set domain (or
// the default) will be returned.
//
// We have no choice in this case but to use the default search domain.
/**
* @return the search domain. This is of the form "google.co.xx" or "google.com", used by UI code.
*/
val searchDomain: String?
get() {
var domain: String? = mSearchSettings.searchBaseDomain
if (domain == null) {
if (DBG) {
Log.w(
TAG,
"Search base domain was null, last apply time=" +
mSearchSettings.searchBaseDomainApplyTime
)
}
// This is required to deal with the case wherein getSearchDomain
// is called before checkSearchDomain returns a valid URL. This will
// happen *only* on the first run of the app when the "use google.com"
// option is unchecked. In other cases, the previously set domain (or
// the default) will be returned.
//
// We have no choice in this case but to use the default search domain.
domain = defaultBaseDomain
}
if (domain?.startsWith(".") == true) {
if (DBG) Log.d(TAG, "Prepending www to $domain")
domain = "www$domain"
}
return domain
}
/**
* Issue a request to google.com/searchdomaincheck to retrieve the base URL for search requests.
*/
private fun checkSearchDomain() {
val request = HttpHelper.GetRequest(DOMAIN_CHECK_URL)
scope.async {
if (DBG) Log.d(TAG, "Starting request to /searchdomaincheck")
var domain: String?
try {
domain = mHttpHelper[request]
} catch (e: Exception) {
if (DBG) Log.d(TAG, "Request to /searchdomaincheck failed : $e")
// Swallow any exceptions thrown by the HTTP helper, in
// this rare case, we just use the default URL.
domain = defaultBaseDomain
}
if (DBG) Log.d(TAG, "Request to /searchdomaincheck succeeded")
setSearchBaseDomain(domain)
}
}
private val defaultBaseDomain: String?
get() = mContext?.getResources()?.getString(R.string.default_search_domain)
private fun setSearchBaseDomain(domain: String?) {
if (DBG) Log.d(TAG, "Setting search domain to : $domain")
mSearchSettings.searchBaseDomain = domain
}
@Override
override fun onSharedPreferenceChanged(pref: SharedPreferences?, key: String?) {
// Listen for changes only to the SEARCH_BASE_URL preference.
if (DBG) Log.d(TAG, "Handling changed preference : $key")
if (SearchSettingsImpl.USE_GOOGLE_COM_PREF.equals(key)) {
maybeUpdateBaseUrlSetting(true)
}
}
companion object {
private const val DBG = false
private const val TAG = "QSB.SearchBaseUrlHelper"
private const val DOMAIN_CHECK_URL = "https://www.google.com/searchdomaincheck?format=domain"
private const val SEARCH_BASE_URL_EXPIRY_MS = 24 * 3600 * 1000L
}
/**
* Note that this constructor will spawn a thread to issue a HTTP request if shouldUseGoogleCom is
* false.
*/
init {
mHttpHelper = helper
mContext = context
mSearchSettings = searchSettings
// Note: This earlier used an inner class, but that causes issues
// because SharedPreferencesImpl uses a WeakHashMap< > and the listener
// will be GC'ed unless we keep a reference to it here.
prefs.registerOnSharedPreferenceChangeListener(this)
maybeUpdateBaseUrlSetting(false)
}
}