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.content.Context 19 import android.content.SharedPreferences 20 import android.util.Log 21 import com.android.quicksearchbox.R 22 import com.android.quicksearchbox.SearchSettings 23 import com.android.quicksearchbox.SearchSettingsImpl 24 import com.android.quicksearchbox.util.HttpHelper 25 import java.util.Locale 26 import kotlinx.coroutines.CoroutineScope 27 import kotlinx.coroutines.Dispatchers 28 import kotlinx.coroutines.async 29 30 /** Helper to build the base URL for all search requests. */ 31 class SearchBaseUrlHelper( 32 context: Context?, 33 helper: HttpHelper, 34 searchSettings: SearchSettings, 35 prefs: SharedPreferences 36 ) : SharedPreferences.OnSharedPreferenceChangeListener { 37 private val mHttpHelper: HttpHelper 38 private val mContext: Context? 39 private val mSearchSettings: SearchSettings 40 private val scope = CoroutineScope(Dispatchers.IO) 41 42 /** 43 * Update the base search url, either: (a) it has never been set (first run) (b) it has expired 44 * (c) if the caller forces an update by setting the "force" parameter. 45 * 46 * @param force if true, then the URL is reset whether or not it has expired. 47 */ maybeUpdateBaseUrlSettingnull48 fun maybeUpdateBaseUrlSetting(force: Boolean) { 49 val lastUpdateTime: Long = mSearchSettings.searchBaseDomainApplyTime 50 val currentTime: Long = System.currentTimeMillis() 51 if ( 52 force || lastUpdateTime == -1L || currentTime - lastUpdateTime >= SEARCH_BASE_URL_EXPIRY_MS 53 ) { 54 if (mSearchSettings.shouldUseGoogleCom()) { 55 setSearchBaseDomain(defaultBaseDomain) 56 } else { 57 checkSearchDomain() 58 } 59 } 60 } 61 62 /** @return the base url for searches. */ 63 val searchBaseUrl: String? 64 get() = 65 mContext 66 ?.getResources() 67 ?.getString( 68 R.string.google_search_base_pattern, 69 searchDomain, 70 GoogleSearch.getLanguage(Locale.getDefault()) 71 ) // This is required to deal with the case wherein getSearchDomain 72 // is called before checkSearchDomain returns a valid URL. This will 73 // happen *only* on the first run of the app when the "use google.com" 74 // option is unchecked. In other cases, the previously set domain (or 75 // the default) will be returned. 76 // 77 // We have no choice in this case but to use the default search domain. 78 /** 79 * @return the search domain. This is of the form "google.co.xx" or "google.com", used by UI code. 80 */ 81 val searchDomain: String? 82 get() { 83 var domain: String? = mSearchSettings.searchBaseDomain 84 if (domain == null) { 85 if (DBG) { 86 Log.w( 87 TAG, 88 "Search base domain was null, last apply time=" + 89 mSearchSettings.searchBaseDomainApplyTime 90 ) 91 } 92 93 // This is required to deal with the case wherein getSearchDomain 94 // is called before checkSearchDomain returns a valid URL. This will 95 // happen *only* on the first run of the app when the "use google.com" 96 // option is unchecked. In other cases, the previously set domain (or 97 // the default) will be returned. 98 // 99 // We have no choice in this case but to use the default search domain. 100 domain = defaultBaseDomain 101 } 102 if (domain?.startsWith(".") == true) { 103 if (DBG) Log.d(TAG, "Prepending www to $domain") 104 domain = "www$domain" 105 } 106 return domain 107 } 108 109 /** 110 * Issue a request to google.com/searchdomaincheck to retrieve the base URL for search requests. 111 */ checkSearchDomainnull112 private fun checkSearchDomain() { 113 val request = HttpHelper.GetRequest(DOMAIN_CHECK_URL) 114 scope.async { 115 if (DBG) Log.d(TAG, "Starting request to /searchdomaincheck") 116 var domain: String? 117 try { 118 domain = mHttpHelper[request] 119 } catch (e: Exception) { 120 if (DBG) Log.d(TAG, "Request to /searchdomaincheck failed : $e") 121 // Swallow any exceptions thrown by the HTTP helper, in 122 // this rare case, we just use the default URL. 123 domain = defaultBaseDomain 124 } 125 if (DBG) Log.d(TAG, "Request to /searchdomaincheck succeeded") 126 setSearchBaseDomain(domain) 127 } 128 } 129 130 private val defaultBaseDomain: String? 131 get() = mContext?.getResources()?.getString(R.string.default_search_domain) 132 setSearchBaseDomainnull133 private fun setSearchBaseDomain(domain: String?) { 134 if (DBG) Log.d(TAG, "Setting search domain to : $domain") 135 mSearchSettings.searchBaseDomain = domain 136 } 137 138 @Override onSharedPreferenceChangednull139 override fun onSharedPreferenceChanged(pref: SharedPreferences?, key: String?) { 140 // Listen for changes only to the SEARCH_BASE_URL preference. 141 if (DBG) Log.d(TAG, "Handling changed preference : $key") 142 if (SearchSettingsImpl.USE_GOOGLE_COM_PREF.equals(key)) { 143 maybeUpdateBaseUrlSetting(true) 144 } 145 } 146 147 companion object { 148 private const val DBG = false 149 private const val TAG = "QSB.SearchBaseUrlHelper" 150 private const val DOMAIN_CHECK_URL = "https://www.google.com/searchdomaincheck?format=domain" 151 private const val SEARCH_BASE_URL_EXPIRY_MS = 24 * 3600 * 1000L 152 } 153 154 /** 155 * Note that this constructor will spawn a thread to issue a HTTP request if shouldUseGoogleCom is 156 * false. 157 */ 158 init { 159 mHttpHelper = helper 160 mContext = context 161 mSearchSettings = searchSettings 162 163 // Note: This earlier used an inner class, but that causes issues 164 // because SharedPreferencesImpl uses a WeakHashMap< > and the listener 165 // will be GC'ed unless we keep a reference to it here. 166 prefs.registerOnSharedPreferenceChangeListener(this) 167 maybeUpdateBaseUrlSetting(false) 168 } 169 } 170