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 17 package com.android.quicksearchbox 18 19 import android.app.SearchManager 20 import android.content.Context 21 import android.content.Intent 22 import android.content.SharedPreferences 23 import android.content.SharedPreferences.Editor 24 import android.util.Log 25 import com.android.common.SharedPreferencesCompat 26 27 /** Manages user settings. */ 28 class SearchSettingsImpl(context: Context?, config: Config?) : SearchSettings { 29 private val mContext: Context? 30 protected val config: Config? 31 protected val context: Context? 32 get() = mContext 33 upgradeSettingsIfNeedednull34 @Override override fun upgradeSettingsIfNeeded() {} 35 36 val searchPreferences: SharedPreferences 37 get() = context!!.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE) 38 storeBooleannull39 protected fun storeBoolean(name: String?, value: Boolean) { 40 SharedPreferencesCompat.apply(searchPreferences.edit().putBoolean(name, value)) 41 } 42 storeIntnull43 protected fun storeInt(name: String?, value: Int) { 44 SharedPreferencesCompat.apply(searchPreferences.edit().putInt(name, value)) 45 } 46 storeLongnull47 protected fun storeLong(name: String?, value: Long) { 48 SharedPreferencesCompat.apply(searchPreferences.edit().putLong(name, value)) 49 } 50 storeStringnull51 protected fun storeString(name: String?, value: String?) { 52 SharedPreferencesCompat.apply(searchPreferences.edit().putString(name, value)) 53 } 54 removePrefnull55 protected fun removePref(name: String?) { 56 SharedPreferencesCompat.apply(searchPreferences.edit().remove(name)) 57 } 58 59 /** Informs our listeners about the updated settings data. */ 60 @Override broadcastSettingsChangednull61 override fun broadcastSettingsChanged() { 62 // We use a message broadcast since the listeners could be in multiple processes. 63 val intent = Intent(SearchManager.INTENT_ACTION_SEARCH_SETTINGS_CHANGED) 64 Log.i(TAG, "Broadcasting: $intent") 65 context?.sendBroadcast(intent) 66 } 67 68 @Override getNextVoiceSearchHintIndexnull69 override fun getNextVoiceSearchHintIndex(size: Int): Int { 70 val i = getAndIncrementIntPreference(searchPreferences, NEXT_VOICE_SEARCH_HINT_INDEX_PREF) 71 return i % size 72 } 73 74 // TODO: Could this be made atomic to avoid races? getAndIncrementIntPreferencenull75 private fun getAndIncrementIntPreference(prefs: SharedPreferences, name: String): Int { 76 val i: Int = prefs.getInt(name, 0) 77 storeInt(name, i + 1) 78 return i 79 } 80 81 @Override resetVoiceSearchHintFirstSeenTimenull82 override fun resetVoiceSearchHintFirstSeenTime() { 83 storeLong(FIRST_VOICE_HINT_DISPLAY_TIME, System.currentTimeMillis()) 84 } 85 86 @Override haveVoiceSearchHintsExpirednull87 override fun haveVoiceSearchHintsExpired(currentVoiceSearchVersion: Int): Boolean { 88 val prefs: SharedPreferences = searchPreferences 89 return if (currentVoiceSearchVersion != 0) { 90 val currentTime: Long = System.currentTimeMillis() 91 val lastVoiceSearchVersion: Int = prefs.getInt(LAST_SEEN_VOICE_SEARCH_VERSION, 0) 92 var firstHintTime: Long = prefs.getLong(FIRST_VOICE_HINT_DISPLAY_TIME, 0) 93 if (firstHintTime == 0L || currentVoiceSearchVersion != lastVoiceSearchVersion) { 94 SharedPreferencesCompat.apply( 95 prefs 96 .edit() 97 .putInt(LAST_SEEN_VOICE_SEARCH_VERSION, currentVoiceSearchVersion) 98 .putLong(FIRST_VOICE_HINT_DISPLAY_TIME, currentTime) 99 ) 100 firstHintTime = currentTime 101 } 102 if (currentTime - firstHintTime > config!!.voiceSearchHintActivePeriod) { 103 if (DBG) Log.d(TAG, "Voice search hint period expired; not showing hints.") 104 return true 105 } else { 106 false 107 } 108 } else { 109 if (DBG) Log.d(TAG, "Could not determine voice search version; not showing hints.") 110 true 111 } 112 } 113 114 /** @return true if user searches should always be based at google.com, false otherwise. */ 115 @Override shouldUseGoogleComnull116 override fun shouldUseGoogleCom(): Boolean { 117 // Note that this preserves the old behaviour of using google.com 118 // for searches, with the gl= parameter set. 119 return searchPreferences.getBoolean(USE_GOOGLE_COM_PREF, true) 120 } 121 122 @Override setUseGoogleComnull123 override fun setUseGoogleCom(useGoogleCom: Boolean) { 124 storeBoolean(USE_GOOGLE_COM_PREF, useGoogleCom) 125 } 126 127 @get:Override 128 override val searchBaseDomainApplyTime: Long 129 get() = searchPreferences.getLong(SEARCH_BASE_DOMAIN_APPLY_TIME, -1) 130 131 // Note that the only time this will return null is on the first run 132 // of the app, or when settings have been cleared. Callers should 133 // ideally check that getSearchBaseDomainApplyTime() is not -1 before 134 // calling this function. 135 @get:Override 136 @set:Override 137 override var searchBaseDomain: String? 138 get() = searchPreferences.getString(SEARCH_BASE_DOMAIN_PREF, null) 139 set(searchBaseUrl) { 140 val sharedPrefEditor: Editor = searchPreferences.edit() 141 sharedPrefEditor.putString(SEARCH_BASE_DOMAIN_PREF, searchBaseUrl) 142 sharedPrefEditor.putLong(SEARCH_BASE_DOMAIN_APPLY_TIME, System.currentTimeMillis()) 143 SharedPreferencesCompat.apply(sharedPrefEditor) 144 } 145 146 companion object { 147 private const val DBG = false 148 private const val TAG = "QSB.SearchSettingsImpl" 149 150 // Name of the preferences file used to store search preference 151 const val PREFERENCES_NAME = "SearchSettings" 152 153 /** Preference key used for storing the index of the next voice search hint to show. */ 154 private const val NEXT_VOICE_SEARCH_HINT_INDEX_PREF = "next_voice_search_hint" 155 156 /** Preference key used to store the time at which the first voice search hint was displayed. */ 157 private const val FIRST_VOICE_HINT_DISPLAY_TIME = "first_voice_search_hint_time" 158 159 /** Preference key for the version of voice search we last got hints from. */ 160 private const val LAST_SEEN_VOICE_SEARCH_VERSION = "voice_search_version" 161 162 /** 163 * Preference key for storing whether searches always go to google.com. Public so that it can be 164 * used by PreferenceControllers. 165 */ 166 const val USE_GOOGLE_COM_PREF = "use_google_com" 167 168 /** 169 * Preference key for the base search URL. This value is normally set by a SearchBaseUrlHelper 170 * instance. Public so classes can listen to changes on this key. 171 */ 172 const val SEARCH_BASE_DOMAIN_PREF = "search_base_domain" 173 174 /** 175 * This is the time at which the base URL was stored, and is set using 176 * @link{System.currentTimeMillis()}. 177 */ 178 private const val SEARCH_BASE_DOMAIN_APPLY_TIME = "search_base_domain_apply_time" 179 } 180 181 init { 182 mContext = context 183 this.config = config 184 } 185 } 186