1 /* 2 * Copyright (C) 2021 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.calendar 17 18 import android.app.Activity 19 import android.app.FragmentManager 20 import android.app.backup.BackupManager 21 import android.content.Context 22 import android.content.Intent 23 import android.content.SharedPreferences 24 import android.content.SharedPreferences.OnSharedPreferenceChangeListener 25 import android.media.Ringtone 26 import android.media.RingtoneManager 27 import android.net.Uri 28 import android.os.Bundle 29 import android.os.Vibrator 30 import android.preference.CheckBoxPreference 31 import android.preference.ListPreference 32 import android.preference.Preference 33 import android.preference.Preference.OnPreferenceChangeListener 34 import android.preference.Preference.OnPreferenceClickListener 35 import android.preference.PreferenceCategory 36 import android.preference.PreferenceFragment 37 import android.preference.PreferenceManager 38 import android.preference.PreferenceScreen 39 import android.provider.CalendarContract 40 import android.provider.CalendarContract.CalendarCache 41 import android.text.TextUtils 42 import android.text.format.Time 43 import com.android.calendar.alerts.AlertReceiver 44 import com.android.timezonepicker.TimeZoneInfo 45 import com.android.timezonepicker.TimeZonePickerDialog 46 import com.android.timezonepicker.TimeZonePickerDialog.OnTimeZoneSetListener 47 import com.android.timezonepicker.TimeZonePickerUtils 48 49 class GeneralPreferences : PreferenceFragment(), OnSharedPreferenceChangeListener, 50 OnPreferenceChangeListener, OnTimeZoneSetListener { 51 var mAlert: CheckBoxPreference? = null 52 var mVibrate: CheckBoxPreference? = null 53 var mPopup: CheckBoxPreference? = null 54 var mUseHomeTZ: CheckBoxPreference? = null 55 var mHideDeclined: CheckBoxPreference? = null 56 var mHomeTZ: Preference? = null 57 var mTzPickerUtils: TimeZonePickerUtils? = null 58 var mWeekStart: ListPreference? = null 59 var mDefaultReminder: ListPreference? = null 60 private var mTimeZoneId: String? = null 61 62 @Override onCreatenull63 override fun onCreate(icicle: Bundle?) { 64 super.onCreate(icicle) 65 val activity: Activity = getActivity() 66 67 // Make sure to always use the same preferences file regardless of the package name 68 // we're running under 69 val preferenceManager: PreferenceManager = getPreferenceManager() 70 val sharedPreferences: SharedPreferences? = getSharedPreferences(activity) 71 preferenceManager.setSharedPreferencesName(SHARED_PREFS_NAME) 72 73 // Load the preferences from an XML resource 74 addPreferencesFromResource(R.xml.general_preferences) 75 val preferenceScreen: PreferenceScreen = getPreferenceScreen() 76 mAlert = preferenceScreen.findPreference(KEY_ALERTS) as CheckBoxPreference 77 mVibrate = preferenceScreen.findPreference(KEY_ALERTS_VIBRATE) as CheckBoxPreference 78 val vibrator: Vibrator = activity.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator 79 if (vibrator == null || !vibrator.hasVibrator()) { 80 val mAlertGroup: PreferenceCategory = preferenceScreen 81 .findPreference(KEY_ALERTS_CATEGORY) as PreferenceCategory 82 mAlertGroup.removePreference(mVibrate) 83 } 84 mPopup = preferenceScreen.findPreference(KEY_ALERTS_POPUP) as CheckBoxPreference 85 mUseHomeTZ = preferenceScreen.findPreference(KEY_HOME_TZ_ENABLED) as CheckBoxPreference 86 mHideDeclined = preferenceScreen.findPreference(KEY_HIDE_DECLINED) as CheckBoxPreference 87 mWeekStart = preferenceScreen.findPreference(KEY_WEEK_START_DAY) as ListPreference 88 mDefaultReminder = preferenceScreen.findPreference(KEY_DEFAULT_REMINDER) as ListPreference 89 mHomeTZ = preferenceScreen.findPreference(KEY_HOME_TZ) 90 mWeekStart?.setSummary(mWeekStart?.getEntry()) 91 mDefaultReminder?.setSummary(mDefaultReminder?.getEntry()) 92 93 // This triggers an asynchronous call to the provider to refresh the data in shared pref 94 mTimeZoneId = Utils.getTimeZone(activity, null) 95 val prefs: SharedPreferences = CalendarUtils.getSharedPreferences(activity, 96 Utils.SHARED_PREFS_NAME) 97 98 // Utils.getTimeZone will return the currentTimeZone instead of the one 99 // in the shared_pref if home time zone is disabled. So if home tz is 100 // off, we will explicitly read it. 101 if (!prefs.getBoolean(KEY_HOME_TZ_ENABLED, false)) { 102 mTimeZoneId = prefs.getString(KEY_HOME_TZ, Time.getCurrentTimezone()) 103 } 104 105 mHomeTZ?.setOnPreferenceClickListener(object : Preference.OnPreferenceClickListener { 106 @Override 107 override fun onPreferenceClick(preference: Preference?): Boolean { 108 showTimezoneDialog() 109 return true 110 } 111 }) 112 113 if (mTzPickerUtils == null) { 114 mTzPickerUtils = TimeZonePickerUtils(getActivity()) 115 } 116 val timezoneName: CharSequence? = mTzPickerUtils?.getGmtDisplayName(getActivity(), 117 mTimeZoneId, System.currentTimeMillis(), false) 118 mHomeTZ?.setSummary(timezoneName ?: mTimeZoneId) 119 val tzpd: TimeZonePickerDialog = activity.getFragmentManager() 120 .findFragmentByTag(FRAG_TAG_TIME_ZONE_PICKER) as TimeZonePickerDialog 121 if (tzpd != null) { 122 tzpd.setOnTimeZoneSetListener(this) 123 } 124 migrateOldPreferences(sharedPreferences) 125 updateChildPreferences() 126 } 127 showTimezoneDialognull128 private fun showTimezoneDialog() { 129 val activity: Activity = getActivity() ?: return 130 val b = Bundle() 131 b.putLong(TimeZonePickerDialog.BUNDLE_START_TIME_MILLIS, System.currentTimeMillis()) 132 b.putString(TimeZonePickerDialog.BUNDLE_TIME_ZONE, Utils.getTimeZone(activity, null)) 133 val fm: FragmentManager = getActivity().getFragmentManager() 134 var tzpd: TimeZonePickerDialog? = fm 135 .findFragmentByTag(FRAG_TAG_TIME_ZONE_PICKER) as TimeZonePickerDialog 136 if (tzpd != null) { 137 tzpd.dismiss() 138 } 139 tzpd = TimeZonePickerDialog() 140 tzpd.setArguments(b) 141 tzpd.setOnTimeZoneSetListener(this) 142 tzpd.show(fm, FRAG_TAG_TIME_ZONE_PICKER) 143 } 144 145 @Override onStartnull146 override fun onStart() { 147 super.onStart() 148 getPreferenceScreen().getSharedPreferences() 149 .registerOnSharedPreferenceChangeListener(this) 150 setPreferenceListeners(this) 151 } 152 153 /** 154 * Sets up all the preference change listeners to use the specified 155 * listener. 156 */ setPreferenceListenersnull157 private fun setPreferenceListeners(listener: OnPreferenceChangeListener?) { 158 mUseHomeTZ?.setOnPreferenceChangeListener(listener) 159 mHomeTZ?.setOnPreferenceChangeListener(listener) 160 mWeekStart?.setOnPreferenceChangeListener(listener) 161 mDefaultReminder?.setOnPreferenceChangeListener(listener) 162 mHideDeclined?.setOnPreferenceChangeListener(listener) 163 mVibrate?.setOnPreferenceChangeListener(listener) 164 } 165 166 @Override onStopnull167 override fun onStop() { 168 getPreferenceScreen().getSharedPreferences() 169 .unregisterOnSharedPreferenceChangeListener(this) 170 setPreferenceListeners(null) 171 super.onStop() 172 } 173 174 @Override onSharedPreferenceChangednull175 override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String?) { 176 val a: Activity = getActivity() 177 if (key.equals(KEY_ALERTS)) { 178 updateChildPreferences() 179 if (a != null) { 180 val intent = Intent() 181 intent.setClass(a, AlertReceiver::class.java) 182 if (mAlert?.isChecked() ?: false) { 183 intent.setAction(AlertReceiver.ACTION_DISMISS_OLD_REMINDERS) 184 } else { 185 intent.setAction(AlertReceiver.EVENT_REMINDER_APP_ACTION) 186 } 187 a.sendBroadcast(intent) 188 } 189 } 190 if (a != null) { 191 BackupManager.dataChanged(a.getPackageName()) 192 } 193 } 194 195 /** 196 * Handles time zone preference changes 197 */ 198 @Override onPreferenceChangenull199 override fun onPreferenceChange(preference: Preference, newValue: Any): Boolean { 200 val tz: String? 201 val activity: Activity = getActivity() 202 if (preference === mUseHomeTZ) { 203 tz = if (newValue != null) { 204 mTimeZoneId 205 } else { 206 CalendarCache.TIMEZONE_TYPE_AUTO 207 } 208 Utils.setTimeZone(activity, tz) 209 return true 210 } else if (preference === mHideDeclined) { 211 mHideDeclined?.setChecked(newValue as Boolean) 212 val intent = Intent(Utils.getWidgetScheduledUpdateAction(activity)) 213 intent.setDataAndType(CalendarContract.CONTENT_URI, Utils.APPWIDGET_DATA_TYPE) 214 activity.sendBroadcast(intent) 215 return true 216 } else if (preference === mWeekStart) { 217 mWeekStart?.setValue(newValue as String) 218 mWeekStart?.setSummary(mWeekStart?.getEntry()) 219 } else if (preference === mDefaultReminder) { 220 mDefaultReminder?.setValue(newValue as String) 221 mDefaultReminder?.setSummary(mDefaultReminder?.getEntry()) 222 } else if (preference === mVibrate) { 223 mVibrate?.setChecked(newValue as Boolean) 224 return true 225 } else { 226 return true 227 } 228 return false 229 } 230 getRingtoneTitleFromUrinull231 fun getRingtoneTitleFromUri(context: Context?, uri: String?): String? { 232 if (TextUtils.isEmpty(uri)) { 233 return null 234 } 235 val ring: Ringtone = RingtoneManager.getRingtone(getActivity(), Uri.parse(uri)) 236 return if (ring != null) { 237 ring.getTitle(context) 238 } else null 239 } 240 241 /** 242 * If necessary, upgrades previous versions of preferences to the current 243 * set of keys and values. 244 * @param prefs the preferences to upgrade 245 */ migrateOldPreferencesnull246 private fun migrateOldPreferences(prefs: SharedPreferences?) { 247 // If needed, migrate vibration setting from a previous version 248 mVibrate?.setChecked(Utils.getDefaultVibrate(getActivity(), prefs)) 249 250 // If needed, migrate the old alerts type settin 251 if (prefs?.contains(KEY_ALERTS) == false && prefs.contains(KEY_ALERTS_TYPE) == true) { 252 val type: String? = prefs.getString(KEY_ALERTS_TYPE, ALERT_TYPE_STATUS_BAR) 253 if (type.equals(ALERT_TYPE_OFF)) { 254 mAlert?.setChecked(false) 255 mPopup?.setChecked(false) 256 mPopup?.setEnabled(false) 257 } else if (type.equals(ALERT_TYPE_STATUS_BAR)) { 258 mAlert?.setChecked(true) 259 mPopup?.setChecked(false) 260 mPopup?.setEnabled(true) 261 } else if (type.equals(ALERT_TYPE_ALERTS)) { 262 mAlert?.setChecked(true) 263 mPopup?.setChecked(true) 264 mPopup?.setEnabled(true) 265 } 266 // clear out the old setting 267 prefs.edit().remove(KEY_ALERTS_TYPE).commit() 268 } 269 } 270 271 /** 272 * Keeps the dependent settings in sync with the parent preference, so for 273 * example, when notifications are turned off, we disable the preferences 274 * for configuring the exact notification behavior. 275 */ updateChildPreferencesnull276 private fun updateChildPreferences() { 277 if (mAlert?.isChecked() ?: false) { 278 mVibrate?.setEnabled(true) 279 mPopup?.setEnabled(true) 280 } else { 281 mVibrate?.setEnabled(false) 282 mPopup?.setEnabled(false) 283 } 284 } 285 286 @Override onPreferenceTreeClicknull287 override fun onPreferenceTreeClick( 288 preferenceScreen: PreferenceScreen?, 289 preference: Preference 290 ): Boolean { 291 val key: String = preference.getKey() 292 return super.onPreferenceTreeClick(preferenceScreen, preference) 293 } 294 295 @Override onTimeZoneSetnull296 override fun onTimeZoneSet(tzi: TimeZoneInfo) { 297 if (mTzPickerUtils == null) { 298 mTzPickerUtils = TimeZonePickerUtils(getActivity()) 299 } 300 val timezoneName: CharSequence? = mTzPickerUtils?.getGmtDisplayName( 301 getActivity(), tzi.mTzId, System.currentTimeMillis(), false) 302 mHomeTZ?.setSummary(timezoneName) 303 Utils.setTimeZone(getActivity(), tzi.mTzId) 304 } 305 306 companion object { 307 // The name of the shared preferences file. This name must be maintained for historical 308 // reasons, as it's what PreferenceManager assigned the first time the file was created. 309 const val SHARED_PREFS_NAME = "com.android.calendar_preferences" 310 const val SHARED_PREFS_NAME_NO_BACKUP = "com.android.calendar_preferences_no_backup" 311 private const val FRAG_TAG_TIME_ZONE_PICKER = "TimeZonePicker" 312 313 // Preference keys 314 const val KEY_HIDE_DECLINED = "preferences_hide_declined" 315 const val KEY_WEEK_START_DAY = "preferences_week_start_day" 316 const val KEY_SHOW_WEEK_NUM = "preferences_show_week_num" 317 const val KEY_DAYS_PER_WEEK = "preferences_days_per_week" 318 const val KEY_SKIP_SETUP = "preferences_skip_setup" 319 const val KEY_CLEAR_SEARCH_HISTORY = "preferences_clear_search_history" 320 const val KEY_ALERTS_CATEGORY = "preferences_alerts_category" 321 const val KEY_ALERTS = "preferences_alerts" 322 const val KEY_ALERTS_VIBRATE = "preferences_alerts_vibrate" 323 const val KEY_ALERTS_RINGTONE = "preferences_alerts_ringtone" 324 const val KEY_ALERTS_POPUP = "preferences_alerts_popup" 325 const val KEY_SHOW_CONTROLS = "preferences_show_controls" 326 const val KEY_DEFAULT_REMINDER = "preferences_default_reminder" 327 const val NO_REMINDER = -1 328 const val NO_REMINDER_STRING = "-1" 329 const val REMINDER_DEFAULT_TIME = 10 // in minutes 330 const val KEY_DEFAULT_CELL_HEIGHT = "preferences_default_cell_height" 331 const val KEY_VERSION = "preferences_version" 332 333 /** Key to SharePreference for default view (CalendarController.ViewType) */ 334 const val KEY_START_VIEW = "preferred_startView" 335 336 /** 337 * Key to SharePreference for default detail view (CalendarController.ViewType) 338 * Typically used by widget 339 */ 340 const val KEY_DETAILED_VIEW = "preferred_detailedView" 341 const val KEY_DEFAULT_CALENDAR = "preference_defaultCalendar" 342 343 // These must be in sync with the array preferences_week_start_day_values 344 const val WEEK_START_DEFAULT = "-1" 345 const val WEEK_START_SATURDAY = "7" 346 const val WEEK_START_SUNDAY = "1" 347 const val WEEK_START_MONDAY = "2" 348 349 // These keys are kept to enable migrating users from previous versions 350 private const val KEY_ALERTS_TYPE = "preferences_alerts_type" 351 private const val ALERT_TYPE_ALERTS = "0" 352 private const val ALERT_TYPE_STATUS_BAR = "1" 353 private const val ALERT_TYPE_OFF = "2" 354 const val KEY_HOME_TZ_ENABLED = "preferences_home_tz_enabled" 355 const val KEY_HOME_TZ = "preferences_home_tz" 356 357 // Default preference values 358 const val DEFAULT_START_VIEW: Int = CalendarController.ViewType.WEEK 359 const val DEFAULT_DETAILED_VIEW: Int = CalendarController.ViewType.DAY 360 const val DEFAULT_SHOW_WEEK_NUM = false 361 362 // This should match the XML file. 363 const val DEFAULT_RINGTONE = "content://settings/system/notification_sound" 364 365 /** Return a properly configured SharedPreferences instance */ 366 @JvmStatic getSharedPreferencesnull367 fun getSharedPreferences(context: Context?): SharedPreferences? { 368 return context?.getSharedPreferences(SHARED_PREFS_NAME, Context.MODE_PRIVATE) 369 } 370 371 /** Set the default shared preferences in the proper context */ 372 @JvmStatic setDefaultValuesnull373 fun setDefaultValues(context: Context?) { 374 PreferenceManager.setDefaultValues(context, SHARED_PREFS_NAME, Context.MODE_PRIVATE, 375 R.xml.general_preferences, false) 376 } 377 } 378 }