Merge "AOSP/DeskClock - Add Kotlin code for deskclock/settings files"
diff --git a/Android.bp b/Android.bp
index bcca51c..b15f1f7 100644
--- a/Android.bp
+++ b/Android.bp
@@ -43,7 +43,8 @@
exclude_srcs: [
"src/**/alarmclock/*.java",
"src/**/deskclock/controller/*.java",
- "src/**/deskclock/events/*.java"
+ "src/**/deskclock/events/*.java",
+ "src/**/deskclock/settings/*.java"
],
product_specific: true,
static_libs: [
diff --git a/src/com/android/deskclock/settings/AlarmVolumePreference.kt b/src/com/android/deskclock/settings/AlarmVolumePreference.kt
new file mode 100644
index 0000000..51f4ec6
--- /dev/null
+++ b/src/com/android/deskclock/settings/AlarmVolumePreference.kt
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2020 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.deskclock.settings
+
+import android.annotation.TargetApi
+import android.app.NotificationManager
+import android.content.Context
+import android.content.Context.AUDIO_SERVICE
+import android.content.Context.NOTIFICATION_SERVICE
+import android.database.ContentObserver
+import android.media.AudioManager
+import android.media.AudioManager.STREAM_ALARM
+import android.os.Build
+import android.provider.Settings
+import android.util.AttributeSet
+import android.view.View
+import android.widget.ImageView
+import android.widget.SeekBar
+import androidx.preference.Preference
+import androidx.preference.PreferenceViewHolder
+
+import com.android.deskclock.R
+import com.android.deskclock.RingtonePreviewKlaxon
+import com.android.deskclock.Utils
+import com.android.deskclock.data.DataModel
+
+class AlarmVolumePreference(context: Context?, attrs: AttributeSet?) : Preference(context, attrs) {
+ private lateinit var mSeekbar: SeekBar
+ private lateinit var mAlarmIcon: ImageView
+
+ private var mPreviewPlaying = false
+
+ override fun onBindViewHolder(holder: PreferenceViewHolder) {
+ super.onBindViewHolder(holder)
+ val context: Context = getContext()
+ val audioManager: AudioManager = context.getSystemService(AUDIO_SERVICE) as AudioManager
+
+ // Disable click feedback for this preference.
+ holder.itemView.setClickable(false)
+ mSeekbar = holder.findViewById(R.id.alarm_volume_slider) as SeekBar
+ mSeekbar.setMax(audioManager.getStreamMaxVolume(STREAM_ALARM))
+ mSeekbar.setProgress(audioManager.getStreamVolume(STREAM_ALARM))
+ mAlarmIcon = holder.findViewById(R.id.alarm_icon) as ImageView
+ onSeekbarChanged()
+
+ val volumeObserver: ContentObserver = object : ContentObserver(mSeekbar.getHandler()) {
+ override fun onChange(selfChange: Boolean) {
+ // Volume was changed elsewhere, update our slider.
+ mSeekbar.setProgress(audioManager.getStreamVolume(STREAM_ALARM))
+ }
+ }
+
+ mSeekbar.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
+ override fun onViewAttachedToWindow(v: View?) {
+ context.getContentResolver().registerContentObserver(Settings.System.CONTENT_URI,
+ true, volumeObserver)
+ }
+
+ override fun onViewDetachedFromWindow(v: View?) {
+ context.getContentResolver().unregisterContentObserver(volumeObserver)
+ }
+ })
+
+ mSeekbar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
+ override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
+ if (fromUser) {
+ audioManager.setStreamVolume(STREAM_ALARM, progress, 0)
+ }
+ onSeekbarChanged()
+ }
+
+ override fun onStartTrackingTouch(seekBar: SeekBar?) {
+ }
+
+ override fun onStopTrackingTouch(seekBar: SeekBar) {
+ if (!mPreviewPlaying && seekBar.getProgress() != 0) {
+ // If we are not currently playing and progress is set to non-zero, start.
+ RingtonePreviewKlaxon
+ .start(context, DataModel.getDataModel().defaultAlarmRingtoneUri)
+ mPreviewPlaying = true
+ seekBar.postDelayed(Runnable {
+ RingtonePreviewKlaxon.stop(context)
+ mPreviewPlaying = false
+ }, ALARM_PREVIEW_DURATION_MS)
+ }
+ }
+ })
+ }
+
+ private fun onSeekbarChanged() {
+ mSeekbar.setEnabled(doesDoNotDisturbAllowAlarmPlayback())
+ val imageRes = if (mSeekbar.getProgress() == 0) {
+ R.drawable.ic_alarm_off_24dp
+ } else {
+ R.drawable.ic_alarm_small
+ }
+ mAlarmIcon.setImageResource(imageRes)
+ }
+
+ private fun doesDoNotDisturbAllowAlarmPlayback(): Boolean {
+ return !Utils.isNOrLater() || doesDoNotDisturbAllowAlarmPlaybackNPlus()
+ }
+
+ @TargetApi(Build.VERSION_CODES.N)
+ private fun doesDoNotDisturbAllowAlarmPlaybackNPlus(): Boolean {
+ val notificationManager =
+ getContext().getSystemService(NOTIFICATION_SERVICE) as NotificationManager
+ return notificationManager.getCurrentInterruptionFilter() !=
+ NotificationManager.INTERRUPTION_FILTER_NONE
+ }
+
+ companion object {
+ private const val ALARM_PREVIEW_DURATION_MS: Long = 2000
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/deskclock/settings/ScreensaverSettingsActivity.kt b/src/com/android/deskclock/settings/ScreensaverSettingsActivity.kt
new file mode 100644
index 0000000..1af8fa9
--- /dev/null
+++ b/src/com/android/deskclock/settings/ScreensaverSettingsActivity.kt
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2020 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.deskclock.settings
+
+import android.annotation.TargetApi
+import android.os.Build
+import android.os.Bundle
+import android.preference.ListPreference
+import android.preference.Preference
+import android.preference.PreferenceFragment
+import android.view.MenuItem
+import androidx.appcompat.app.AppCompatActivity
+
+import com.android.deskclock.R
+import com.android.deskclock.Utils
+
+/**
+ * Settings for Clock screen saver
+ */
+class ScreensaverSettingsActivity : AppCompatActivity() {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.screensaver_settings)
+ }
+
+ override fun onOptionsItemSelected(item: MenuItem): Boolean {
+ when (item.getItemId()) {
+ android.R.id.home -> {
+ finish()
+ return true
+ }
+ else -> {
+ }
+ }
+ return super.onOptionsItemSelected(item)
+ }
+
+ // TODO(colinmarsch) Replace all deprecated Preference usage with AndroidX equivalents
+ class PrefsFragment : PreferenceFragment(), Preference.OnPreferenceChangeListener {
+
+ @TargetApi(Build.VERSION_CODES.N)
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ if (Utils.isNOrLater()) {
+ getPreferenceManager().setStorageDeviceProtected()
+ }
+ addPreferencesFromResource(R.xml.screensaver_settings)
+ }
+
+ override fun onResume() {
+ super.onResume()
+ refresh()
+ }
+
+ override fun onPreferenceChange(pref: Preference, newValue: Any?): Boolean {
+ if (KEY_CLOCK_STYLE == pref.getKey()) {
+ val clockStylePref: ListPreference = pref as ListPreference
+ val index: Int = clockStylePref.findIndexOfValue(newValue as String?)
+ clockStylePref.setSummary(clockStylePref.getEntries().get(index))
+ }
+ return true
+ }
+
+ private fun refresh() {
+ val clockStylePref = findPreference(KEY_CLOCK_STYLE) as ListPreference
+ clockStylePref.setSummary(clockStylePref.getEntry())
+ clockStylePref.setOnPreferenceChangeListener(this)
+ }
+ }
+
+ companion object {
+ const val KEY_CLOCK_STYLE = "screensaver_clock_style"
+ const val KEY_NIGHT_MODE = "screensaver_night_mode"
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/deskclock/settings/SettingsActivity.kt b/src/com/android/deskclock/settings/SettingsActivity.kt
new file mode 100644
index 0000000..dc854a1
--- /dev/null
+++ b/src/com/android/deskclock/settings/SettingsActivity.kt
@@ -0,0 +1,316 @@
+/*
+ * Copyright (C) 2020 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.deskclock.settings
+
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import android.os.Vibrator
+import android.provider.Settings
+import android.view.Menu
+import android.view.MenuItem
+import android.view.View
+import androidx.preference.ListPreference
+import androidx.preference.ListPreferenceDialogFragmentCompat
+import androidx.preference.Preference
+import androidx.preference.PreferenceDialogFragmentCompat
+import androidx.preference.PreferenceFragmentCompat
+import androidx.preference.TwoStatePreference
+
+import com.android.deskclock.BaseActivity
+import com.android.deskclock.DropShadowController
+import com.android.deskclock.R
+import com.android.deskclock.Utils
+import com.android.deskclock.actionbarmenu.MenuItemControllerFactory
+import com.android.deskclock.actionbarmenu.NavUpMenuItemController
+import com.android.deskclock.actionbarmenu.OptionsMenuManager
+import com.android.deskclock.data.DataModel
+import com.android.deskclock.ringtone.RingtonePickerActivity
+
+/**
+ * Settings for the Alarm Clock.
+ */
+class SettingsActivity : BaseActivity() {
+ private val mOptionsMenuManager = OptionsMenuManager()
+
+ /**
+ * The controller that shows the drop shadow when content is not scrolled to the top.
+ */
+ private lateinit var mDropShadowController: DropShadowController
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.settings)
+
+ mOptionsMenuManager.addMenuItemController(NavUpMenuItemController(this))
+ .addMenuItemController(*MenuItemControllerFactory.getInstance()
+ .buildMenuItemControllers(this))
+
+ // Create the prefs fragment in code to ensure it's created before PreferenceDialogFragment
+ if (savedInstanceState == null) {
+ getSupportFragmentManager().beginTransaction()
+ .replace(R.id.main, PrefsFragment(), PREFS_FRAGMENT_TAG)
+ .disallowAddToBackStack()
+ .commit()
+ }
+ }
+
+ override fun onResume() {
+ super.onResume()
+
+ val dropShadow: View = findViewById(R.id.drop_shadow)
+ val fragment = getSupportFragmentManager().findFragmentById(R.id.main) as PrefsFragment
+ mDropShadowController = DropShadowController(dropShadow, fragment.getListView())
+ }
+
+ override fun onPause() {
+ mDropShadowController.stop()
+ super.onPause()
+ }
+
+ override fun onCreateOptionsMenu(menu: Menu): Boolean {
+ mOptionsMenuManager.onCreateOptionsMenu(menu)
+ return true
+ }
+
+ override fun onPrepareOptionsMenu(menu: Menu): Boolean {
+ mOptionsMenuManager.onPrepareOptionsMenu(menu)
+ return true
+ }
+
+ override fun onOptionsItemSelected(item: MenuItem): Boolean {
+ return (mOptionsMenuManager.onOptionsItemSelected(item) ||
+ super.onOptionsItemSelected(item))
+ }
+
+ class PrefsFragment :
+ PreferenceFragmentCompat(),
+ Preference.OnPreferenceChangeListener,
+ Preference.OnPreferenceClickListener {
+
+ override fun onCreatePreferences(bundle: Bundle?, rootKey: String?) {
+ getPreferenceManager().setStorageDeviceProtected()
+ addPreferencesFromResource(R.xml.settings)
+ val timerVibrate: Preference? = findPreference(KEY_TIMER_VIBRATE)
+ timerVibrate?.let {
+ val hasVibrator: Boolean = (it.getContext()
+ .getSystemService(VIBRATOR_SERVICE) as Vibrator).hasVibrator()
+ it.setVisible(hasVibrator)
+ }
+ loadTimeZoneList()
+ }
+
+ override fun onActivityCreated(savedInstanceState: Bundle?) {
+ super.onActivityCreated(savedInstanceState)
+
+ // By default, do not recreate the DeskClock activity
+ getActivity()?.setResult(RESULT_CANCELED)
+ }
+
+ override fun onResume() {
+ super.onResume()
+ refresh()
+ }
+
+ override fun onPreferenceChange(pref: Preference, newValue: Any): Boolean {
+ when (pref.getKey()) {
+ KEY_ALARM_CRESCENDO, KEY_HOME_TZ, KEY_ALARM_SNOOZE, KEY_TIMER_CRESCENDO -> {
+ val preference: ListPreference = pref as ListPreference
+ val index: Int = preference.findIndexOfValue(newValue as String)
+ preference.setSummary(preference.getEntries().get(index))
+ }
+ KEY_CLOCK_STYLE, KEY_WEEK_START, KEY_VOLUME_BUTTONS -> {
+ val simpleMenuPreference = pref as SimpleMenuPreference
+ val i: Int = simpleMenuPreference.findIndexOfValue(newValue as String)
+ pref.setSummary(simpleMenuPreference.getEntries().get(i))
+ }
+ KEY_CLOCK_DISPLAY_SECONDS -> {
+ DataModel.getDataModel().displayClockSeconds = newValue as Boolean
+ }
+ KEY_AUTO_SILENCE -> {
+ val delay = newValue as String
+ updateAutoSnoozeSummary(pref as ListPreference, delay)
+ }
+ KEY_AUTO_HOME_CLOCK -> {
+ val autoHomeClockEnabled: Boolean = (pref as TwoStatePreference).isChecked()
+ val homeTimeZonePref: Preference? = findPreference(KEY_HOME_TZ)
+ homeTimeZonePref?.setEnabled(!autoHomeClockEnabled)
+ }
+ KEY_TIMER_VIBRATE -> {
+ val timerVibratePref: TwoStatePreference = pref as TwoStatePreference
+ DataModel.getDataModel().timerVibrate = timerVibratePref.isChecked()
+ }
+ KEY_TIMER_RINGTONE -> pref.setSummary(DataModel.getDataModel().timerRingtoneTitle)
+ }
+
+ // Set result so DeskClock knows to refresh itself
+ getActivity()?.setResult(RESULT_OK)
+ return true
+ }
+
+ override fun onPreferenceClick(pref: Preference): Boolean {
+ val context: Context = getActivity() ?: return false
+
+ when (pref.getKey()) {
+ KEY_DATE_TIME -> {
+ val dialogIntent = Intent(Settings.ACTION_DATE_SETTINGS)
+ dialogIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ startActivity(dialogIntent)
+ return true
+ }
+ KEY_TIMER_RINGTONE -> {
+ startActivity(RingtonePickerActivity.createTimerRingtonePickerIntent(context))
+ return true
+ }
+ else -> return false
+ }
+ }
+
+ override fun onDisplayPreferenceDialog(preference: Preference) {
+ // Only single-selection lists are currently supported.
+ val f: PreferenceDialogFragmentCompat
+ f = if (preference is ListPreference) {
+ ListPreferenceDialogFragmentCompat.newInstance(preference.getKey())
+ } else {
+ throw IllegalArgumentException("Unsupported DialogPreference type")
+ }
+ showDialog(f)
+ }
+
+ private fun showDialog(fragment: PreferenceDialogFragmentCompat) {
+ // TODO(colinmarsch) Replace deprecated getFragmentManager with AndroidX equivalent
+ // Don't show dialog if one is already shown.
+ if (getFragmentManager()?.findFragmentByTag(PREFERENCE_DIALOG_FRAGMENT_TAG) != null) {
+ return
+ }
+ // Always set the target fragment, this is required by PreferenceDialogFragment
+ // internally.
+ fragment.setTargetFragment(this, 0)
+ // Don't use getChildFragmentManager(), it causes issues on older platforms when the
+ // target fragment is being restored after an orientation change.
+ fragment.show(getFragmentManager()!!, PREFERENCE_DIALOG_FRAGMENT_TAG)
+ }
+
+ /**
+ * Reconstruct the timezone list.
+ */
+ private fun loadTimeZoneList() {
+ val timezones = DataModel.getDataModel().timeZones
+ val homeTimezonePref: ListPreference? = findPreference(KEY_HOME_TZ)
+ homeTimezonePref?.let {
+ it.setEntryValues(timezones.timeZoneIds)
+ it.setEntries(timezones.timeZoneNames)
+ it.setSummary(homeTimezonePref.getEntry())
+ it.setOnPreferenceChangeListener(this)
+ }
+ }
+
+ private fun refresh() {
+ val autoSilencePref: ListPreference? = findPreference(KEY_AUTO_SILENCE)
+ autoSilencePref?.let {
+ val delay: String = it.getValue()
+ updateAutoSnoozeSummary(it, delay)
+ it.setOnPreferenceChangeListener(this)
+ }
+
+ val clockStylePref: SimpleMenuPreference? = findPreference(KEY_CLOCK_STYLE)
+ clockStylePref?.let {
+ it.setSummary(it.getEntry())
+ it.setOnPreferenceChangeListener(this)
+ }
+
+ val volumeButtonsPref: SimpleMenuPreference? = findPreference(KEY_VOLUME_BUTTONS)
+ volumeButtonsPref?.let {
+ it.setSummary(volumeButtonsPref.getEntry())
+ it.setOnPreferenceChangeListener(this)
+ }
+
+ val clockSecondsPref: Preference? = findPreference(KEY_CLOCK_DISPLAY_SECONDS)
+ clockSecondsPref?.setOnPreferenceChangeListener(this)
+
+ val autoHomeClockPref: Preference? = findPreference(KEY_AUTO_HOME_CLOCK)
+ val autoHomeClockEnabled: Boolean =
+ (autoHomeClockPref as TwoStatePreference).isChecked()
+ autoHomeClockPref.setOnPreferenceChangeListener(this)
+
+ val homeTimezonePref: ListPreference? = findPreference(KEY_HOME_TZ)
+ homeTimezonePref?.setEnabled(autoHomeClockEnabled)
+ refreshListPreference(homeTimezonePref!!)
+
+ refreshListPreference(findPreference(KEY_ALARM_CRESCENDO)!!)
+ refreshListPreference(findPreference(KEY_TIMER_CRESCENDO)!!)
+ refreshListPreference(findPreference(KEY_ALARM_SNOOZE)!!)
+
+ val dateAndTimeSetting: Preference? = findPreference(KEY_DATE_TIME)
+ dateAndTimeSetting?.setOnPreferenceClickListener(this)
+
+ val weekStartPref: SimpleMenuPreference? = findPreference(KEY_WEEK_START)
+ // Set the default value programmatically
+ val weekdayOrder = DataModel.getDataModel().weekdayOrder
+ val firstDay = weekdayOrder.calendarDays[0]
+ val value = firstDay.toString()
+ weekStartPref?.let {
+ val idx: Int = it.findIndexOfValue(value)
+ it.setValueIndex(idx)
+ it.setSummary(weekStartPref.getEntries().get(idx))
+ it.setOnPreferenceChangeListener(this)
+ }
+
+ val timerRingtonePref: Preference? = findPreference(KEY_TIMER_RINGTONE)
+ timerRingtonePref?.let {
+ it.setOnPreferenceClickListener(this)
+ it.setSummary(DataModel.getDataModel().timerRingtoneTitle)
+ }
+ }
+
+ private fun refreshListPreference(preference: ListPreference) {
+ preference.setSummary(preference.getEntry())
+ preference.setOnPreferenceChangeListener(this)
+ }
+
+ private fun updateAutoSnoozeSummary(listPref: ListPreference, delay: String) {
+ val i = delay.toInt()
+ if (i == -1) {
+ listPref.setSummary(R.string.auto_silence_never)
+ } else {
+ listPref.setSummary(Utils.getNumberFormattedQuantityString(getActivity(),
+ R.plurals.auto_silence_summary, i))
+ }
+ }
+ }
+
+ companion object {
+ const val KEY_ALARM_SNOOZE = "snooze_duration"
+ const val KEY_ALARM_CRESCENDO = "alarm_crescendo_duration"
+ const val KEY_TIMER_CRESCENDO = "timer_crescendo_duration"
+ const val KEY_TIMER_RINGTONE = "timer_ringtone"
+ const val KEY_TIMER_VIBRATE = "timer_vibrate"
+ const val KEY_AUTO_SILENCE = "auto_silence"
+ const val KEY_CLOCK_STYLE = "clock_style"
+ const val KEY_CLOCK_DISPLAY_SECONDS = "display_clock_seconds"
+ const val KEY_HOME_TZ = "home_time_zone"
+ const val KEY_AUTO_HOME_CLOCK = "automatic_home_clock"
+ const val KEY_DATE_TIME = "date_time"
+ const val KEY_VOLUME_BUTTONS = "volume_button_setting"
+ const val KEY_WEEK_START = "week_start"
+ const val DEFAULT_VOLUME_BEHAVIOR = "0"
+ const val VOLUME_BEHAVIOR_SNOOZE = "1"
+ const val VOLUME_BEHAVIOR_DISMISS = "2"
+ const val PREFS_FRAGMENT_TAG = "prefs_fragment"
+ const val PREFERENCE_DIALOG_FRAGMENT_TAG = "preference_dialog"
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/deskclock/settings/SimpleMenuPreference.kt b/src/com/android/deskclock/settings/SimpleMenuPreference.kt
new file mode 100644
index 0000000..9eee1b4
--- /dev/null
+++ b/src/com/android/deskclock/settings/SimpleMenuPreference.kt
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2020 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.deskclock.settings
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ArrayAdapter
+import androidx.core.content.ContextCompat
+import androidx.preference.DropDownPreference
+
+import com.android.deskclock.R
+import com.android.deskclock.Utils
+
+/**
+ * Bend [DropDownPreference] to support
+ * [Simple Menus](https://material.google.com/components/menus.html#menus-behavior).
+ */
+class SimpleMenuPreference(
+ context: Context?,
+ attrs: AttributeSet?,
+ defStyleAttr: Int,
+ defStyleRes: Int
+) : DropDownPreference(context, attrs, defStyleAttr, defStyleRes) {
+ private lateinit var mAdapter: SimpleMenuAdapter
+
+ constructor(context: Context?) : this(context, null) {
+ }
+
+ constructor(context: Context?, attrs: AttributeSet?) :
+ this(context, attrs, R.attr.dropdownPreferenceStyle) {
+ }
+
+ constructor(context: Context?, attrs: AttributeSet?, defStyle: Int) :
+ this(context, attrs, defStyle, 0) {
+ }
+
+ override fun createAdapter(): ArrayAdapter<CharSequence?> {
+ mAdapter = SimpleMenuAdapter(getContext(), R.layout.simple_menu_dropdown_item)
+ return mAdapter
+ }
+
+ override fun setSummary(summary: CharSequence) {
+ val entries: Array<CharSequence> = getEntries()
+ val index = Utils.indexOf(entries, summary)
+ require(index != -1) { "Illegal Summary" }
+ val lastSelectedOriginalPosition = mAdapter.lastSelectedOriginalPosition
+ mAdapter.setSelectedPosition(index)
+ setSelectedPosition(entries, lastSelectedOriginalPosition, index)
+ setSelectedPosition(getEntryValues(), lastSelectedOriginalPosition, index)
+ super.setSummary(summary)
+ }
+
+ private class SimpleMenuAdapter internal constructor(context: Context, resource: Int) :
+ ArrayAdapter<CharSequence?>(context, resource) {
+
+ /** The original position of the last selected element */
+ var lastSelectedOriginalPosition = 0
+ private set
+
+ private fun restoreOriginalOrder() {
+ val item: CharSequence? = getItem(0)
+ remove(item)
+ insert(item, lastSelectedOriginalPosition)
+ }
+
+ private fun swapSelectedToFront(position: Int) {
+ val item: CharSequence? = getItem(position)
+ remove(item)
+ insert(item, 0)
+ lastSelectedOriginalPosition = position
+ }
+
+ fun setSelectedPosition(position: Int) {
+ setNotifyOnChange(false)
+ val item: CharSequence? = getItem(position)
+ restoreOriginalOrder()
+ val originalPosition: Int = getPosition(item)
+ swapSelectedToFront(originalPosition)
+ notifyDataSetChanged()
+ }
+
+ override fun getDropDownView(position: Int, convertView: View?, parent: ViewGroup): View {
+ val view: View = super.getDropDownView(position, convertView, parent)
+ if (position == 0) {
+ view.setBackgroundColor(ContextCompat.getColor(getContext(), R.color.white_08p))
+ } else {
+ view.setBackgroundColor(ContextCompat.getColor(getContext(), R.color.transparent))
+ }
+ return view
+ }
+ }
+
+ companion object {
+ private fun restoreOriginalOrder(
+ array: Array<CharSequence>,
+ lastSelectedOriginalPosition: Int
+ ) {
+ val item = array[0]
+ System.arraycopy(array, 1, array, 0, lastSelectedOriginalPosition)
+ array[lastSelectedOriginalPosition] = item
+ }
+
+ private fun swapSelectedToFront(array: Array<CharSequence>, position: Int) {
+ val item = array[position]
+ System.arraycopy(array, 0, array, 1, position)
+ array[0] = item
+ }
+
+ private fun setSelectedPosition(
+ array: Array<CharSequence>,
+ lastSelectedOriginalPosition: Int,
+ position: Int
+ ) {
+ val item = array[position]
+ restoreOriginalOrder(array, lastSelectedOriginalPosition)
+ val originalPosition = Utils.indexOf(array, item)
+ swapSelectedToFront(array, originalPosition)
+ }
+ }
+}
\ No newline at end of file