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