/* * Copyright (C) 2021 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.calendar import android.app.Activity import android.app.FragmentManager import android.app.backup.BackupManager import android.content.Context import android.content.Intent import android.content.SharedPreferences import android.content.SharedPreferences.OnSharedPreferenceChangeListener import android.media.Ringtone import android.media.RingtoneManager import android.net.Uri import android.os.Bundle import android.os.Vibrator import android.preference.CheckBoxPreference import android.preference.ListPreference import android.preference.Preference import android.preference.Preference.OnPreferenceChangeListener import android.preference.Preference.OnPreferenceClickListener import android.preference.PreferenceCategory import android.preference.PreferenceFragment import android.preference.PreferenceManager import android.preference.PreferenceScreen import android.provider.CalendarContract import android.provider.CalendarContract.CalendarCache import android.text.TextUtils import android.text.format.Time import com.android.calendar.alerts.AlertReceiver import com.android.timezonepicker.TimeZoneInfo import com.android.timezonepicker.TimeZonePickerDialog import com.android.timezonepicker.TimeZonePickerDialog.OnTimeZoneSetListener import com.android.timezonepicker.TimeZonePickerUtils class GeneralPreferences : PreferenceFragment(), OnSharedPreferenceChangeListener, OnPreferenceChangeListener, OnTimeZoneSetListener { var mAlert: CheckBoxPreference? = null var mVibrate: CheckBoxPreference? = null var mPopup: CheckBoxPreference? = null var mUseHomeTZ: CheckBoxPreference? = null var mHideDeclined: CheckBoxPreference? = null var mHomeTZ: Preference? = null var mTzPickerUtils: TimeZonePickerUtils? = null var mWeekStart: ListPreference? = null var mDefaultReminder: ListPreference? = null private var mTimeZoneId: String? = null @Override override fun onCreate(icicle: Bundle?) { super.onCreate(icicle) val activity: Activity = getActivity() // Make sure to always use the same preferences file regardless of the package name // we're running under val preferenceManager: PreferenceManager = getPreferenceManager() val sharedPreferences: SharedPreferences? = getSharedPreferences(activity) preferenceManager.setSharedPreferencesName(SHARED_PREFS_NAME) // Load the preferences from an XML resource addPreferencesFromResource(R.xml.general_preferences) val preferenceScreen: PreferenceScreen = getPreferenceScreen() mAlert = preferenceScreen.findPreference(KEY_ALERTS) as CheckBoxPreference mVibrate = preferenceScreen.findPreference(KEY_ALERTS_VIBRATE) as CheckBoxPreference val vibrator: Vibrator = activity.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator if (vibrator == null || !vibrator.hasVibrator()) { val mAlertGroup: PreferenceCategory = preferenceScreen .findPreference(KEY_ALERTS_CATEGORY) as PreferenceCategory mAlertGroup.removePreference(mVibrate) } mPopup = preferenceScreen.findPreference(KEY_ALERTS_POPUP) as CheckBoxPreference mUseHomeTZ = preferenceScreen.findPreference(KEY_HOME_TZ_ENABLED) as CheckBoxPreference mHideDeclined = preferenceScreen.findPreference(KEY_HIDE_DECLINED) as CheckBoxPreference mWeekStart = preferenceScreen.findPreference(KEY_WEEK_START_DAY) as ListPreference mDefaultReminder = preferenceScreen.findPreference(KEY_DEFAULT_REMINDER) as ListPreference mHomeTZ = preferenceScreen.findPreference(KEY_HOME_TZ) mWeekStart?.setSummary(mWeekStart?.getEntry()) mDefaultReminder?.setSummary(mDefaultReminder?.getEntry()) // This triggers an asynchronous call to the provider to refresh the data in shared pref mTimeZoneId = Utils.getTimeZone(activity, null) val prefs: SharedPreferences = CalendarUtils.getSharedPreferences(activity, Utils.SHARED_PREFS_NAME) // Utils.getTimeZone will return the currentTimeZone instead of the one // in the shared_pref if home time zone is disabled. So if home tz is // off, we will explicitly read it. if (!prefs.getBoolean(KEY_HOME_TZ_ENABLED, false)) { mTimeZoneId = prefs.getString(KEY_HOME_TZ, Time.getCurrentTimezone()) } mHomeTZ?.setOnPreferenceClickListener(object : Preference.OnPreferenceClickListener { @Override override fun onPreferenceClick(preference: Preference?): Boolean { showTimezoneDialog() return true } }) if (mTzPickerUtils == null) { mTzPickerUtils = TimeZonePickerUtils(getActivity()) } val timezoneName: CharSequence? = mTzPickerUtils?.getGmtDisplayName(getActivity(), mTimeZoneId, System.currentTimeMillis(), false) mHomeTZ?.setSummary(timezoneName ?: mTimeZoneId) val tzpd: TimeZonePickerDialog = activity.getFragmentManager() .findFragmentByTag(FRAG_TAG_TIME_ZONE_PICKER) as TimeZonePickerDialog if (tzpd != null) { tzpd.setOnTimeZoneSetListener(this) } migrateOldPreferences(sharedPreferences) updateChildPreferences() } private fun showTimezoneDialog() { val activity: Activity = getActivity() ?: return val b = Bundle() b.putLong(TimeZonePickerDialog.BUNDLE_START_TIME_MILLIS, System.currentTimeMillis()) b.putString(TimeZonePickerDialog.BUNDLE_TIME_ZONE, Utils.getTimeZone(activity, null)) val fm: FragmentManager = getActivity().getFragmentManager() var tzpd: TimeZonePickerDialog? = fm .findFragmentByTag(FRAG_TAG_TIME_ZONE_PICKER) as TimeZonePickerDialog if (tzpd != null) { tzpd.dismiss() } tzpd = TimeZonePickerDialog() tzpd.setArguments(b) tzpd.setOnTimeZoneSetListener(this) tzpd.show(fm, FRAG_TAG_TIME_ZONE_PICKER) } @Override override fun onStart() { super.onStart() getPreferenceScreen().getSharedPreferences() .registerOnSharedPreferenceChangeListener(this) setPreferenceListeners(this) } /** * Sets up all the preference change listeners to use the specified * listener. */ private fun setPreferenceListeners(listener: OnPreferenceChangeListener?) { mUseHomeTZ?.setOnPreferenceChangeListener(listener) mHomeTZ?.setOnPreferenceChangeListener(listener) mWeekStart?.setOnPreferenceChangeListener(listener) mDefaultReminder?.setOnPreferenceChangeListener(listener) mHideDeclined?.setOnPreferenceChangeListener(listener) mVibrate?.setOnPreferenceChangeListener(listener) } @Override override fun onStop() { getPreferenceScreen().getSharedPreferences() .unregisterOnSharedPreferenceChangeListener(this) setPreferenceListeners(null) super.onStop() } @Override override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String?) { val a: Activity = getActivity() if (key.equals(KEY_ALERTS)) { updateChildPreferences() if (a != null) { val intent = Intent() intent.setClass(a, AlertReceiver::class.java) if (mAlert?.isChecked() ?: false) { intent.setAction(AlertReceiver.ACTION_DISMISS_OLD_REMINDERS) } else { intent.setAction(AlertReceiver.EVENT_REMINDER_APP_ACTION) } a.sendBroadcast(intent) } } if (a != null) { BackupManager.dataChanged(a.getPackageName()) } } /** * Handles time zone preference changes */ @Override override fun onPreferenceChange(preference: Preference, newValue: Any): Boolean { val tz: String? val activity: Activity = getActivity() if (preference === mUseHomeTZ) { tz = if (newValue != null) { mTimeZoneId } else { CalendarCache.TIMEZONE_TYPE_AUTO } Utils.setTimeZone(activity, tz) return true } else if (preference === mHideDeclined) { mHideDeclined?.setChecked(newValue as Boolean) val intent = Intent(Utils.getWidgetScheduledUpdateAction(activity)) intent.setDataAndType(CalendarContract.CONTENT_URI, Utils.APPWIDGET_DATA_TYPE) activity.sendBroadcast(intent) return true } else if (preference === mWeekStart) { mWeekStart?.setValue(newValue as String) mWeekStart?.setSummary(mWeekStart?.getEntry()) } else if (preference === mDefaultReminder) { mDefaultReminder?.setValue(newValue as String) mDefaultReminder?.setSummary(mDefaultReminder?.getEntry()) } else if (preference === mVibrate) { mVibrate?.setChecked(newValue as Boolean) return true } else { return true } return false } fun getRingtoneTitleFromUri(context: Context?, uri: String?): String? { if (TextUtils.isEmpty(uri)) { return null } val ring: Ringtone = RingtoneManager.getRingtone(getActivity(), Uri.parse(uri)) return if (ring != null) { ring.getTitle(context) } else null } /** * If necessary, upgrades previous versions of preferences to the current * set of keys and values. * @param prefs the preferences to upgrade */ private fun migrateOldPreferences(prefs: SharedPreferences?) { // If needed, migrate vibration setting from a previous version mVibrate?.setChecked(Utils.getDefaultVibrate(getActivity(), prefs)) // If needed, migrate the old alerts type settin if (prefs?.contains(KEY_ALERTS) == false && prefs.contains(KEY_ALERTS_TYPE) == true) { val type: String? = prefs.getString(KEY_ALERTS_TYPE, ALERT_TYPE_STATUS_BAR) if (type.equals(ALERT_TYPE_OFF)) { mAlert?.setChecked(false) mPopup?.setChecked(false) mPopup?.setEnabled(false) } else if (type.equals(ALERT_TYPE_STATUS_BAR)) { mAlert?.setChecked(true) mPopup?.setChecked(false) mPopup?.setEnabled(true) } else if (type.equals(ALERT_TYPE_ALERTS)) { mAlert?.setChecked(true) mPopup?.setChecked(true) mPopup?.setEnabled(true) } // clear out the old setting prefs.edit().remove(KEY_ALERTS_TYPE).commit() } } /** * Keeps the dependent settings in sync with the parent preference, so for * example, when notifications are turned off, we disable the preferences * for configuring the exact notification behavior. */ private fun updateChildPreferences() { if (mAlert?.isChecked() ?: false) { mVibrate?.setEnabled(true) mPopup?.setEnabled(true) } else { mVibrate?.setEnabled(false) mPopup?.setEnabled(false) } } @Override override fun onPreferenceTreeClick( preferenceScreen: PreferenceScreen?, preference: Preference ): Boolean { val key: String = preference.getKey() return super.onPreferenceTreeClick(preferenceScreen, preference) } @Override override fun onTimeZoneSet(tzi: TimeZoneInfo) { if (mTzPickerUtils == null) { mTzPickerUtils = TimeZonePickerUtils(getActivity()) } val timezoneName: CharSequence? = mTzPickerUtils?.getGmtDisplayName( getActivity(), tzi.mTzId, System.currentTimeMillis(), false) mHomeTZ?.setSummary(timezoneName) Utils.setTimeZone(getActivity(), tzi.mTzId) } companion object { // The name of the shared preferences file. This name must be maintained for historical // reasons, as it's what PreferenceManager assigned the first time the file was created. const val SHARED_PREFS_NAME = "com.android.calendar_preferences" const val SHARED_PREFS_NAME_NO_BACKUP = "com.android.calendar_preferences_no_backup" private const val FRAG_TAG_TIME_ZONE_PICKER = "TimeZonePicker" // Preference keys const val KEY_HIDE_DECLINED = "preferences_hide_declined" const val KEY_WEEK_START_DAY = "preferences_week_start_day" const val KEY_SHOW_WEEK_NUM = "preferences_show_week_num" const val KEY_DAYS_PER_WEEK = "preferences_days_per_week" const val KEY_SKIP_SETUP = "preferences_skip_setup" const val KEY_CLEAR_SEARCH_HISTORY = "preferences_clear_search_history" const val KEY_ALERTS_CATEGORY = "preferences_alerts_category" const val KEY_ALERTS = "preferences_alerts" const val KEY_ALERTS_VIBRATE = "preferences_alerts_vibrate" const val KEY_ALERTS_RINGTONE = "preferences_alerts_ringtone" const val KEY_ALERTS_POPUP = "preferences_alerts_popup" const val KEY_SHOW_CONTROLS = "preferences_show_controls" const val KEY_DEFAULT_REMINDER = "preferences_default_reminder" const val NO_REMINDER = -1 const val NO_REMINDER_STRING = "-1" const val REMINDER_DEFAULT_TIME = 10 // in minutes const val KEY_DEFAULT_CELL_HEIGHT = "preferences_default_cell_height" const val KEY_VERSION = "preferences_version" /** Key to SharePreference for default view (CalendarController.ViewType) */ const val KEY_START_VIEW = "preferred_startView" /** * Key to SharePreference for default detail view (CalendarController.ViewType) * Typically used by widget */ const val KEY_DETAILED_VIEW = "preferred_detailedView" const val KEY_DEFAULT_CALENDAR = "preference_defaultCalendar" // These must be in sync with the array preferences_week_start_day_values const val WEEK_START_DEFAULT = "-1" const val WEEK_START_SATURDAY = "7" const val WEEK_START_SUNDAY = "1" const val WEEK_START_MONDAY = "2" // These keys are kept to enable migrating users from previous versions private const val KEY_ALERTS_TYPE = "preferences_alerts_type" private const val ALERT_TYPE_ALERTS = "0" private const val ALERT_TYPE_STATUS_BAR = "1" private const val ALERT_TYPE_OFF = "2" const val KEY_HOME_TZ_ENABLED = "preferences_home_tz_enabled" const val KEY_HOME_TZ = "preferences_home_tz" // Default preference values const val DEFAULT_START_VIEW: Int = CalendarController.ViewType.WEEK const val DEFAULT_DETAILED_VIEW: Int = CalendarController.ViewType.DAY const val DEFAULT_SHOW_WEEK_NUM = false // This should match the XML file. const val DEFAULT_RINGTONE = "content://settings/system/notification_sound" /** Return a properly configured SharedPreferences instance */ @JvmStatic fun getSharedPreferences(context: Context?): SharedPreferences? { return context?.getSharedPreferences(SHARED_PREFS_NAME, Context.MODE_PRIVATE) } /** Set the default shared preferences in the proper context */ @JvmStatic fun setDefaultValues(context: Context?) { PreferenceManager.setDefaultValues(context, SHARED_PREFS_NAME, Context.MODE_PRIVATE, R.xml.general_preferences, false) } } }