blob: 48370305e74713776b4a1e6d7a37774e429c4217 [file] [log] [blame]
package com.google.chip.chiptool.clusterclient
import android.app.AlertDialog
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import android.widget.Button
import android.widget.EditText
import android.widget.Spinner
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import chip.devicecontroller.ChipClusters
import chip.devicecontroller.ChipDeviceController
import chip.devicecontroller.ChipStructs
import chip.devicecontroller.ChipStructs.AccessControlClusterAccessControlEntryStruct
import chip.devicecontroller.ChipStructs.GroupKeyManagementClusterGroupKeySetStruct
import chip.devicecontroller.GroupKeySecurityPolicy
import com.google.chip.chiptool.ChipClient
import com.google.chip.chiptool.GenericChipDeviceListener
import com.google.chip.chiptool.R
import com.google.chip.chiptool.databinding.GroupSettingFragmentBinding
import com.google.chip.chiptool.util.DeviceIdUtil
import java.lang.Exception
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
class GroupSettingFragment : Fragment() {
private val deviceController: ChipDeviceController
get() = ChipClient.getDeviceController(requireContext())
private lateinit var scope: CoroutineScope
private lateinit var addressUpdateFragment: AddressUpdateFragment
private var _binding: GroupSettingFragmentBinding? = null
private val binding
get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = GroupSettingFragmentBinding.inflate(inflater, container, false)
scope = viewLifecycleOwner.lifecycleScope
deviceController.setCompletionListener(ChipControllerCallback())
addressUpdateFragment =
childFragmentManager.findFragmentById(R.id.addressUpdateFragment) as AddressUpdateFragment
binding.sendKeySetWriteBtn.setOnClickListener { sendKeySetWriteBtnClick() }
binding.writeGroupKeyMapBtn.setOnClickListener { writeGroupKeyMapBtnClick() }
binding.sendAddGroupBtn.setOnClickListener { sendAddGroupBtnClick() }
binding.addAccessControlBtn.setOnClickListener { scope.launch { readAccessControl() } }
binding.addGroupBtn.setOnClickListener { addGroupBtnClick() }
binding.removeGroupBtn.setOnClickListener { removeGroupBtnClick() }
binding.addkeysetBtn.setOnClickListener { addKeySetBtnClick() }
binding.removekeysetBtn.setOnClickListener { removeKeySetBtnClick() }
binding.bindkeysetBtn.setOnClickListener { bindKeySetBtnClick(true) }
binding.unbindkeysetBtn.setOnClickListener { bindKeySetBtnClick(false) }
return binding.root
}
private fun addGroupBtnClick() {
val dialogView = requireActivity().layoutInflater.inflate(R.layout.add_group_dialog, null)
val groupIdEd = dialogView.findViewById<EditText>(R.id.groupIdEd)
val groupNameEd = dialogView.findViewById<EditText>(R.id.groupNameEd)
dialogView.findViewById<Button>(R.id.sendGroupDialogBtn).visibility = View.GONE
val dialog = AlertDialog.Builder(requireContext()).apply { setView(dialogView) }.create()
dialogView.findViewById<Button>(R.id.addGroupDialogBtn).setOnClickListener {
val groupId = groupIdEd.text.toString().toUInt()
val ret = deviceController.addGroup(groupId.toInt(), groupNameEd.text.toString())
showMessage("addGroup : $ret")
updateGroupSettingView()
DeviceIdUtil.setCommissionedNodeId(
requireContext(),
AddressUpdateFragment.getNodeIdFromGroupId(groupId).toLong()
)
addressUpdateFragment.updateDeviceIdSpinner()
requireActivity().runOnUiThread { dialog.dismiss() }
}
dialog.show()
}
private fun removeGroupBtnClick() {
val dialogView = requireActivity().layoutInflater.inflate(R.layout.remove_group_dialog, null)
val groupList = deviceController.availableGroupIds
val groupListSp = dialogView.findViewById<Spinner>(R.id.groupListSp)
groupListSp.adapter =
ArrayAdapter(
requireContext(),
android.R.layout.simple_spinner_dropdown_item,
groupList.map { it.toString() }
)
val dialog = AlertDialog.Builder(requireContext()).apply { setView(dialogView) }.create()
dialogView.findViewById<Button>(R.id.removeGroupDialogBtn).setOnClickListener {
val selectedGroupId = groupListSp.selectedItem.toString().toUInt()
val ret = deviceController.removeGroup(selectedGroupId.toInt())
showMessage("removeGroup : $selectedGroupId - $ret")
updateGroupSettingView()
DeviceIdUtil.removeCommissionedNodeId(
requireContext(),
AddressUpdateFragment.getNodeIdFromGroupId(selectedGroupId).toLong()
)
addressUpdateFragment.updateDeviceIdSpinner()
requireActivity().runOnUiThread { dialog.dismiss() }
}
dialog.show()
}
private fun addKeySetBtnClick() {
val dialogView = requireActivity().layoutInflater.inflate(R.layout.add_key_set_dialog, null)
val keySetIdEd = dialogView.findViewById<EditText>(R.id.keySetIdEd)
val keySecurityPolicySp = dialogView.findViewById<Spinner>(R.id.keySecurityPolicySp)
keySecurityPolicySp.adapter =
ArrayAdapter(
requireContext(),
android.R.layout.simple_spinner_dropdown_item,
GroupKeySecurityPolicy.values()
)
val validityTimeEd = dialogView.findViewById<EditText>(R.id.validityTimeEd)
val epochKeyEd = dialogView.findViewById<EditText>(R.id.epochKeyEd)
val dialog = AlertDialog.Builder(requireContext()).apply { setView(dialogView) }.create()
dialogView.findViewById<Button>(R.id.addKeySetDialogBtn).setOnClickListener {
val ret =
deviceController.addKeySet(
keySetIdEd.text.toString().toUInt().toInt(),
GroupKeySecurityPolicy.valueOf(keySecurityPolicySp.selectedItem.toString()),
validityTimeEd.text.toString().toULong().toLong(),
hexStringToByteArray(epochKeyEd.text.toString())
)
showMessage("addKeySet : $ret")
updateGroupSettingView()
requireActivity().runOnUiThread { dialog.dismiss() }
}
dialog.show()
}
private fun removeKeySetBtnClick() {
val dialogView = requireActivity().layoutInflater.inflate(R.layout.remove_key_set_dialog, null)
val keySetList = deviceController.keySetIds
val keySetListSp = dialogView.findViewById<Spinner>(R.id.keySetListSp)
keySetListSp.adapter =
ArrayAdapter(
requireContext(),
android.R.layout.simple_spinner_dropdown_item,
keySetList.map { it.toString() }
)
val dialog = AlertDialog.Builder(requireContext()).apply { setView(dialogView) }.create()
dialogView.findViewById<Button>(R.id.removeGroupDialogBtn).setOnClickListener {
val selectedKeySetId = keySetListSp.selectedItem.toString().toInt()
val ret = deviceController.removeKeySet(selectedKeySetId)
showMessage("removeKeySet : $selectedKeySetId - $ret")
updateGroupSettingView()
requireActivity().runOnUiThread { dialog.dismiss() }
}
dialog.show()
}
private fun bindKeySetBtnClick(isBind: Boolean) {
val dialogView = requireActivity().layoutInflater.inflate(R.layout.bind_key_set_dialog, null)
val groupList = deviceController.availableGroupIds
val keySetList = deviceController.keySetIds
val groupSp = dialogView.findViewById<Spinner>(R.id.groupSp)
groupSp.adapter =
ArrayAdapter(
requireContext(),
android.R.layout.simple_spinner_dropdown_item,
groupList.map { it.toString() }
)
val keySetSp = dialogView.findViewById<Spinner>(R.id.keySetSp)
keySetSp.adapter =
ArrayAdapter(
requireContext(),
android.R.layout.simple_spinner_dropdown_item,
keySetList.map { it.toString() }
)
dialogView.findViewById<Button>(R.id.bindKeySetDialogBtn).visibility =
if (isBind) {
View.VISIBLE
} else {
View.GONE
}
dialogView.findViewById<Button>(R.id.unbindKeySetDialogBtn).visibility =
if (!isBind) {
View.VISIBLE
} else {
View.GONE
}
val dialog = AlertDialog.Builder(requireContext()).apply { setView(dialogView) }.create()
dialogView.findViewById<Button>(R.id.bindKeySetDialogBtn).setOnClickListener {
val groupId = groupSp.selectedItem.toString().toUInt().toInt()
val keySetId = keySetSp.selectedItem.toString().toUInt().toInt()
val ret = deviceController.bindKeySet(groupId, keySetId)
showMessage("bindKeySet : $groupId, $keySetId - $ret")
updateGroupSettingView()
requireActivity().runOnUiThread { dialog.dismiss() }
}
dialogView.findViewById<Button>(R.id.unbindKeySetDialogBtn).setOnClickListener {
val groupId = groupSp.selectedItem.toString().toUInt().toInt()
val keySetId = keySetSp.selectedItem.toString().toUInt().toInt()
val ret = deviceController.unbindKeySet(groupId, keySetId)
showMessage("unbindKeySet : $groupId, $keySetId - $ret")
updateGroupSettingView()
requireActivity().runOnUiThread { dialog.dismiss() }
}
dialog.show()
}
private fun sendKeySetWriteBtnClick() {
val dialogView = requireActivity().layoutInflater.inflate(R.layout.send_key_set_dialog, null)
val keySetIdEd = dialogView.findViewById<EditText>(R.id.keySetIdEd)
val keySecurityPolicySp = dialogView.findViewById<Spinner>(R.id.keySecurityPolicySp)
keySecurityPolicySp.adapter =
ArrayAdapter(
requireContext(),
android.R.layout.simple_spinner_dropdown_item,
GroupKeySecurityPolicy.values()
)
val epochKey0Ed = dialogView.findViewById<EditText>(R.id.epochKey0Ed)
val epochStartTime0Ed = dialogView.findViewById<EditText>(R.id.epochStartTime0Ed)
val epochKey1Ed = dialogView.findViewById<EditText>(R.id.epochKey1Ed)
val epochStartTime1Ed = dialogView.findViewById<EditText>(R.id.epochStartTime1Ed)
val epochKey2Ed = dialogView.findViewById<EditText>(R.id.epochKey2Ed)
val epochStartTime2Ed = dialogView.findViewById<EditText>(R.id.epochStartTime2Ed)
val dialog = AlertDialog.Builder(requireContext()).apply { setView(dialogView) }.create()
dialogView.findViewById<Button>(R.id.addKeySetDialogBtn).setOnClickListener {
scope.launch {
val keySetWritestruct =
GroupKeyManagementClusterGroupKeySetStruct(
keySetIdEd.text.toString().toUInt().toInt(),
GroupKeySecurityPolicy.valueOf(keySecurityPolicySp.selectedItem.toString())
.id
.toUInt()
.toInt(),
hexStringToByteArray(epochKey0Ed.text.toString()),
epochStartTime0Ed.text.toString().toULong().toLong(),
hexStringToByteArray(epochKey1Ed.text.toString()),
epochStartTime1Ed.text.toString().toULong().toLong(),
hexStringToByteArray(epochKey2Ed.text.toString()),
epochStartTime2Ed.text.toString().toULong().toLong()
)
sendKeySetWrite(keySetWritestruct)
requireActivity().runOnUiThread { dialog.dismiss() }
}
}
dialog.show()
}
private suspend fun sendKeySetWrite(
groupKeySetStruct: GroupKeyManagementClusterGroupKeySetStruct
) {
val devicePtr =
try {
ChipClient.getConnectedDevicePointer(requireContext(), addressUpdateFragment.deviceId)
} catch (e: IllegalStateException) {
Log.d(TAG, "getConnectedDevicePointer exception", e)
showMessage("Get DevicePointer fail!")
return
}
val cluster = ChipClusters.GroupKeyManagementCluster(devicePtr, 0)
cluster.keySetWrite(
object : ChipClusters.DefaultClusterCallback {
override fun onError(e: Exception?) {
Log.d(TAG, "onError : ", e)
showMessage("onError : ${e.toString()}")
}
override fun onSuccess() {
Log.d(TAG, "onResponse")
showMessage("onResponse")
}
},
groupKeySetStruct
)
}
private fun writeGroupKeyMapBtnClick() {
val dialogView = requireActivity().layoutInflater.inflate(R.layout.write_group_key_dialog, null)
val groupIdEd = dialogView.findViewById<EditText>(R.id.groupIdEd)
val groupKeySetIdEd = dialogView.findViewById<EditText>(R.id.groupKeySetIdEd)
val dialog = AlertDialog.Builder(requireContext()).apply { setView(dialogView) }.create()
dialogView.findViewById<Button>(R.id.writeGroupKeyMapBtn).setOnClickListener {
scope.launch {
writeGroupKeyMap(
groupIdEd.text.toString().toUInt(),
groupKeySetIdEd.text.toString().toUInt()
)
requireActivity().runOnUiThread { dialog.dismiss() }
}
}
dialog.show()
}
private suspend fun writeGroupKeyMap(groupId: UInt, groupKeySetId: UInt) {
val devicePtr =
try {
ChipClient.getConnectedDevicePointer(requireContext(), addressUpdateFragment.deviceId)
} catch (e: IllegalStateException) {
Log.d(TAG, "getConnectedDevicePointer exception", e)
showMessage("Get DevicePointer fail!")
return
}
val cluster = ChipClusters.GroupKeyManagementCluster(devicePtr, 0)
cluster.writeGroupKeyMapAttribute(
object : ChipClusters.DefaultClusterCallback {
override fun onError(e: Exception?) {
Log.d(TAG, "onError : ", e)
showMessage("Error : ${e.toString()}")
}
override fun onSuccess() {
Log.d(TAG, "onResponse")
showMessage("write Success")
}
},
arrayListOf(
ChipStructs.GroupKeyManagementClusterGroupKeyMapStruct(
groupId.toInt(),
groupKeySetId.toInt(),
deviceController.fabricIndex
)
)
)
}
private fun sendAddGroupBtnClick() {
val dialogView = requireActivity().layoutInflater.inflate(R.layout.add_group_dialog, null)
val groupIdEd = dialogView.findViewById<EditText>(R.id.groupIdEd)
val groupNameEd = dialogView.findViewById<EditText>(R.id.groupNameEd)
dialogView.findViewById<Button>(R.id.addGroupDialogBtn).visibility = View.GONE
val dialog = AlertDialog.Builder(requireContext()).apply { setView(dialogView) }.create()
dialogView.findViewById<Button>(R.id.sendGroupDialogBtn).setOnClickListener {
scope.launch {
sendAddGroup(groupIdEd.text.toString().toUInt(), groupNameEd.text.toString())
requireActivity().runOnUiThread { dialog.dismiss() }
}
}
dialog.show()
}
private suspend fun sendAddGroup(groupId: UInt, groupName: String) {
val devicePtr =
try {
ChipClient.getConnectedDevicePointer(requireContext(), addressUpdateFragment.deviceId)
} catch (e: IllegalStateException) {
Log.d(TAG, "getConnectedDevicePointer exception", e)
showMessage("Get DevicePointer fail!")
return
}
val cluster = ChipClusters.GroupsCluster(devicePtr, 0)
cluster.addGroup(
object : ChipClusters.GroupsCluster.AddGroupResponseCallback {
override fun onError(e: Exception?) {
Log.d(TAG, "onError : ", e)
showMessage("Error : ${e.toString()}")
}
override fun onSuccess(status: Int?, groupID: Int?) {
Log.d(TAG, "onResponse")
showMessage("onResponse : $status, $groupID")
}
},
groupId.toInt(),
groupName
)
}
private suspend fun readAccessControl() {
val devicePtr =
try {
ChipClient.getConnectedDevicePointer(requireContext(), addressUpdateFragment.deviceId)
} catch (e: IllegalStateException) {
Log.d(TAG, "getConnectedDevicePointer exception", e)
showMessage("Get DevicePointer fail!")
return
}
val cluster = ChipClusters.AccessControlCluster(devicePtr, 0)
cluster.readAclAttribute(
object : ChipClusters.AccessControlCluster.AclAttributeCallback {
override fun onError(e: Exception?) {
Log.d(TAG, "onError : ", e)
showMessage("Error : $e")
}
override fun onSuccess(value: MutableList<AccessControlClusterAccessControlEntryStruct>?) {
requireActivity().runOnUiThread { showAddAccessControlDialog(value) }
}
}
)
}
private fun showAddAccessControlDialog(
value: List<AccessControlClusterAccessControlEntryStruct>?
) {
if (value == null) {
Log.d(TAG, "Access Control read fail")
showMessage("Access Control read fail")
return
}
val dialogView =
requireActivity().layoutInflater.inflate(R.layout.add_access_control_dialog, null)
val groupIdEd = dialogView.findViewById<EditText>(R.id.groupIdEd)
val accessControlEntrySp = dialogView.findViewById<Spinner>(R.id.accessControlEntrySp)
accessControlEntrySp.adapter =
ArrayAdapter(
requireContext(),
android.R.layout.simple_spinner_dropdown_item,
AccessControlEntry.values()
)
val dialog = AlertDialog.Builder(requireContext()).apply { setView(dialogView) }.create()
dialogView.findViewById<Button>(R.id.addAccessControlBtn).setOnClickListener {
scope.launch {
sendAccessControl(
value,
groupIdEd.text.toString().toUInt(),
AccessControlEntry.valueOf(accessControlEntrySp.selectedItem.toString()).id
)
requireActivity().runOnUiThread { dialog.dismiss() }
}
}
dialog.show()
}
private suspend fun sendAccessControl(
value: List<AccessControlClusterAccessControlEntryStruct>,
groupId: UInt,
privilege: UInt
) {
val devicePtr =
try {
ChipClient.getConnectedDevicePointer(requireContext(), addressUpdateFragment.deviceId)
} catch (e: IllegalStateException) {
Log.d(TAG, "getConnectedDevicePointer exception", e)
showMessage("Get DevicePointer fail!")
return
}
// If GroupID is already added to AccessControl, do not add it.
val cluster = ChipClusters.AccessControlCluster(devicePtr, 0)
val sendEntry = ArrayList<AccessControlClusterAccessControlEntryStruct>()
for (entry in value) {
if (
entry.authMode == AccessControlEntry.Operate.id.toInt() /* Group */ &&
entry.subjects != null &&
entry.subjects!!.contains(groupId.toULong().toLong())
) {
continue
}
sendEntry.add(entry)
}
val newEntry =
AccessControlClusterAccessControlEntryStruct(
privilege.toInt(),
AccessControlEntry.Operate.id.toInt() /* Group */,
arrayListOf(groupId.toULong().toLong()),
null,
deviceController.fabricIndex.toUInt().toInt()
)
sendEntry.add(newEntry)
cluster.writeAclAttribute(
object : ChipClusters.DefaultClusterCallback {
override fun onError(e: Exception?) {
Log.d(TAG, "onError : ", e)
showMessage("Error : ${e.toString()}")
}
override fun onSuccess() {
Log.d(TAG, "onResponse")
showMessage("write Success")
}
},
sendEntry
)
}
override fun onStart() {
super.onStart()
updateGroupSettingView()
}
private fun updateGroupSettingView() {
requireActivity().runOnUiThread {
var viewString = "Group :\n"
for (groupId in deviceController.availableGroupIds) {
val keySetIdString =
if (deviceController.findKeySetId(groupId).isPresent) {
deviceController.findKeySetId(groupId).get().toString()
} else {
"[]"
}
viewString += "\t$groupId, ${deviceController.getGroupName(groupId)}, $keySetIdString\n"
}
viewString += "\nKeySet :\n"
for (keySetId in deviceController.keySetIds) {
viewString +=
"\t$keySetId, ${deviceController.getKeySecurityPolicy(keySetId).map { it.name }}\n"
}
binding.multiAdminClusterCommandStatus.text = viewString
}
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
inner class ChipControllerCallback : GenericChipDeviceListener() {
override fun onCommissioningComplete(nodeId: Long, errorCode: Long) {
Log.d(TAG, "onCommissioningComplete for nodeId $nodeId: $errorCode")
}
override fun onNotifyChipConnectionClosed() {
Log.d(TAG, "onNotifyChipConnectionClosed")
}
override fun onCloseBleComplete() {
Log.d(TAG, "onCloseBleComplete")
}
override fun onError(error: Throwable?) {
Log.d(TAG, "onError: $error")
}
}
private fun showMessage(msg: String) {
requireActivity().runOnUiThread { binding.groupSettingStatusTv.text = msg }
}
companion object {
private const val TAG = "GroupSettingFragment"
fun newInstance(): GroupSettingFragment = GroupSettingFragment()
private const val HEX_RADIX = 16
private const val HEX_BIT_SHIFT = 4
private fun hexStringToByteArray(hexString: String): ByteArray {
val len = hexString.length
val data = ByteArray(len / 2)
var i = 0
while (i < len) {
data[i / 2] =
((Character.digit(hexString[i], HEX_RADIX) shl HEX_BIT_SHIFT) +
Character.digit(hexString[i + 1], HEX_RADIX))
.toByte()
i += 2
}
return data
}
enum class AccessControlEntry(val id: UInt) {
Manage(4U),
Operate(3U),
ProxyView(2U),
View(1U),
Administer(5U)
}
}
}