/*
* Copyright (C) 2025 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.settingslib.bluetooth.hearingdevices.ui
import android.bluetooth.BluetoothHapClient
import android.bluetooth.BluetoothHapPresetInfo
import android.content.Context
import android.util.AttributeSet
import android.view.View
import android.widget.AdapterView
import android.widget.LinearLayout
import android.widget.Spinner
import android.widget.TextView
import com.android.settingslib.R
/**
* A custom view that combines a title and a [Spinner] to display and manage a list of hearing aid
* presets.
*
*
This view handles the display logic, populating the spinner with preset names and notifying a
* listener when a new preset is selected.
*/
class PresetSpinner
@JvmOverloads
constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
defStyleRes: Int = 0,
private val spinnerAdapter: HearingDevicesSpinnerAdapter = HearingDevicesSpinnerAdapter(context),
) : LinearLayout(context, attrs, defStyleAttr, defStyleRes) {
/** Listener for handling value changes in the spinner. */
fun interface OnChangeListener {
/**
* Called when the spinner's selected value changes.
*
* @param spinner The [PresetSpinner] that triggered the event.
* @param presetIndex The index of the newly selected preset.
*/
fun onValueChange(spinner: PresetSpinner, presetIndex: Int)
}
init {
inflate(context, R.layout.hearing_device_preset_spinner, this)
}
private val titleView: TextView = findViewById(R.id.preset_title)
private val spinnerView: Spinner = findViewById(R.id.preset_spinner)
private var onChangeListener: OnChangeListener? = null
private var presetInfos: List? = null
init {
spinnerView.adapter = spinnerAdapter
spinnerView.onItemSelectedListener =
object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(
parent: AdapterView<*>?,
view: View?,
position: Int,
id: Long,
) {
spinnerAdapter.setSelected(position)
presetInfos?.let { infos ->
onChangeListener?.onValueChange(this@PresetSpinner, infos[position].index)
}
}
override fun onNothingSelected(p0: AdapterView<*>?) {
// Do nothing
}
}
}
override fun setEnabled(enabled: Boolean) {
spinnerView.isEnabled = enabled
alpha = if (enabled) ENABLED_ALPHA else DISABLED_ALPHA
}
override fun isEnabled(): Boolean {
return spinnerView.isEnabled
}
/**
* Sets the title text of the control.
*
* @param title The CharSequence to be set as the title.
*/
fun setTitle(title: CharSequence) {
titleView.text = title
titleView.visibility = if (title.isEmpty()) GONE else VISIBLE
}
/**
* Gets the current title text of the control.
*
* @return The current title as a CharSequence.
*/
fun getTitle(): CharSequence = titleView.text
/**
* Sets a listener to be notified when the selected preset changes.
*
* @param listener The listener to be set.
*/
fun setOnChangeListener(listener: OnChangeListener) {
onChangeListener = listener
}
/**
* Sets the list of presets to be displayed in the spinner.
*
* @param infos The list of [BluetoothHapPresetInfo] to populate the spinner with.
*/
fun setList(infos: List) {
presetInfos = infos
spinnerAdapter.clear()
spinnerAdapter.addAll(infos.map { info -> info.name }.toList())
}
/**
* Retrieves the list of presets currently being displayed in the spinner.
*
* @return The list of [BluetoothHapPresetInfo], or {@code null} if the list has not been set.
*/
fun getList(): List? {
return presetInfos
}
/**
* Sets the spinner's selection based on a given preset index.
*
* @param index The preset index to select.
*/
fun setValue(index: Int) {
presetInfos?.apply {
val position = indexOfFirst { it.index == index }
if (position != -1) {
spinnerView.setSelection(position, false)
spinnerAdapter.setSelected(position)
}
}
}
/**
* Gets the index of the currently selected preset.
*
* @return The index of the selected preset, or [BluetoothHapClient.PRESET_INDEX_UNAVAILABLE] if
* no preset is selected or the list is null.
*/
fun getValue(): Int {
return presetInfos?.getOrNull(spinnerView.selectedItemPosition)?.index
?: BluetoothHapClient.PRESET_INDEX_UNAVAILABLE
}
/**
* Gets the name of the currently selected preset.
*
* @return The name of the selected preset, or an empty string ("") if no preset is selected
* or the list is null.
*/
fun getValueName(): CharSequence {
return presetInfos?.getOrNull(spinnerView.selectedItemPosition)?.name ?: ""
}
companion object {
private const val ENABLED_ALPHA = 1f
private const val DISABLED_ALPHA = 0.38f
}
}