Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Created history tab to review most-recent-first commands #12622

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import android.widget.AutoCompleteTextView
import android.widget.Button
import android.widget.LinearLayout
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.forEach
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import chip.clusterinfo.ClusterCommandCallback
import chip.clusterinfo.ClusterInfo
import chip.clusterinfo.InteractionInfo
Expand All @@ -22,8 +24,11 @@ import chip.devicecontroller.ChipClusters
import chip.devicecontroller.ChipDeviceController
import chip.devicecontroller.ClusterInfoMapping
import com.google.chip.chiptool.ChipClient
import com.google.chip.chiptool.ChipClient.getConnectedDevicePointer
import com.google.chip.chiptool.GenericChipDeviceListener
import com.google.chip.chiptool.R
import com.google.chip.chiptool.clusterclient.clusterinteraction.ClusterInteractionHistoryFragment.Companion.clusterInteractionHistoryList
import kotlin.properties.Delegates
import kotlinx.android.synthetic.main.cluster_callback_item.view.clusterCallbackDataTv
import kotlinx.android.synthetic.main.cluster_callback_item.view.clusterCallbackNameTv
import kotlinx.android.synthetic.main.cluster_callback_item.view.clusterCallbackTypeTv
Expand All @@ -39,6 +44,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch

/**
* ClusterDetailFragment allows user to pick cluster, command, specify parameters and see
Expand All @@ -48,46 +54,134 @@ class ClusterDetailFragment : Fragment() {
private val deviceController: ChipDeviceController
get() = ChipClient.getDeviceController(requireContext())

private val scope = CoroutineScope(Dispatchers.Main + Job())
private lateinit var clusterMap: Map<String, ClusterInfo>
private lateinit var scope: CoroutineScope
private var clusterMap: Map<String, ClusterInfo> = ClusterInfoMapping().clusterMap
private lateinit var selectedClusterInfo: ClusterInfo
private lateinit var selectedCluster: ChipClusters.BaseChipCluster
private lateinit var selectedCommandCallback: DelegatedClusterCallback
private lateinit var selectedInteractionInfo: InteractionInfo
private var devicePtr = 0L
private var endpointId = 0
private var devicePtr by Delegates.notNull<Long>()
private var deviceId by Delegates.notNull<Long>()
private var endpointId by Delegates.notNull<Int>()

JasonLiuZhuoCheng marked this conversation as resolved.
Show resolved Hide resolved
// when user opens detail page from home page of cluster interaction, historyCommand will be
// null, and nothing will be autocompleted. If the detail page is opened from history page,
// cluster name, command name and potential parameter list will be filled out based on historyCommand
private var historyCommand: HistoryCommand? = null

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
clusterMap = ClusterInfoMapping().clusterMap
devicePtr = checkNotNull(requireArguments().getLong(DEVICE_PTR_KEY))
scope = viewLifecycleOwner.lifecycleScope
deviceId = checkNotNull(requireArguments().getLong(DEVICE_ID))
scope.launch {
devicePtr = getConnectedDevicePointer(requireContext(), deviceId)
}
endpointId = checkNotNull(requireArguments().getInt(ENDPOINT_ID_KEY))
historyCommand = requireArguments().getSerializable(HISTORY_COMMAND) as HistoryCommand?
return inflater.inflate(R.layout.cluster_detail_fragment, container, false).apply {
deviceController.setCompletionListener(GenericChipDeviceListener())
commandAutoCompleteTv.visibility = View.GONE
clusterAutoCompleteSetup(
if (historyCommand != null) {
autoCompleteBasedOnHistoryCommand(
historyCommand!!,
clusterAutoCompleteTv,
commandAutoCompleteTv,
parameterList,
inflater,
callbackList
)
} else {
commandAutoCompleteTv.visibility = View.GONE
clusterAutoCompleteSetup(
clusterAutoCompleteTv,
commandAutoCompleteTv,
parameterList,
callbackList
)
commandAutoCompleteSetup(commandAutoCompleteTv, inflater, parameterList, callbackList)
}
setInvokeCommandOnClickListener(
invokeCommand,
callbackList,
clusterAutoCompleteTv,
commandAutoCompleteTv,
parameterList,
callbackList
parameterList
)
commandAutoCompleteSetup(commandAutoCompleteTv, inflater, parameterList, callbackList)
invokeCommand.setOnClickListener {
callbackList.removeAllViews()
val commandArguments = HashMap<String, Any>()
parameterList.forEach {
val type =
selectedInteractionInfo.commandParameters[it.clusterParameterNameTv.text.toString()]!!.type
val data = castStringToType(it.clusterParameterData.text.toString(), type)!!
}
}

commandArguments[it.clusterParameterNameTv.text.toString()] = data
}
selectedInteractionInfo.getCommandFunction()
.invokeCommand(selectedCluster, selectedCommandCallback, commandArguments)
private fun setInvokeCommandOnClickListener(
invokeCommand: Button,
callbackList: LinearLayout,
clusterAutoCompleteTv: AutoCompleteTextView,
commandAutoCompleteTv: AutoCompleteTextView,
parameterList: LinearLayout
) {
invokeCommand.setOnClickListener {
callbackList.removeAllViews()
val commandArguments = HashMap<String, Any>()
clusterInteractionHistoryList.addFirst(
HistoryCommand(
clusterAutoCompleteTv.text.toString(),
commandAutoCompleteTv.text.toString(),
mutableListOf(),
null,
null,
endpointId,
deviceId
)
)
parameterList.forEach {
val parameterName = it.clusterParameterNameTv.text.toString()
val castType =
selectedInteractionInfo.commandParameters[parameterName]!!.type
val data = castStringToType(it.clusterParameterData.text.toString(), castType)!!
commandArguments[it.clusterParameterNameTv.text.toString()] = data
clusterInteractionHistoryList[0].parameterList.add(
HistoryParameterInfo(
parameterName,
data.toString(),
castType
)
)
}
selectedInteractionInfo.getCommandFunction()
.invokeCommand(selectedCluster, selectedCommandCallback, commandArguments)
}
}

// Cluster name, command name and parameter list will be autofill based on the given historyCommand
private fun autoCompleteBasedOnHistoryCommand(
historyCommand: HistoryCommand,
clusterAutoComplete: AutoCompleteTextView,
commandAutoComplete: AutoCompleteTextView,
parameterList: LinearLayout, inflater: LayoutInflater,
callbackList: LinearLayout
) {
clusterAutoComplete.setText(historyCommand.clusterName)
commandAutoComplete.visibility = View.VISIBLE
commandAutoComplete.setText(historyCommand.commandName)
selectedClusterInfo = clusterMap[historyCommand.clusterName]!!
selectedCluster = selectedClusterInfo.createClusterFunction.create(devicePtr, endpointId)
selectedInteractionInfo = selectedClusterInfo.commands[historyCommand.commandName]!!
selectedCommandCallback = selectedInteractionInfo.commandCallbackSupplier.get()
setCallbackDelegate(inflater, callbackList)
historyCommand.parameterList.forEach {
val param = inflater.inflate(R.layout.cluster_parameter_item, null, false) as ConstraintLayout
param.clusterParameterNameTv.text = "${it.parameterName}"
param.clusterParameterTypeTv.text = formatParameterType(it.parameterType)
param.clusterParameterData.setText(it.parameterData)
parameterList.addView(param)
}
}

private fun formatParameterType(castType: Class<*>): String {
return if (castType == ByteArray::class.java) {
"Byte[]"
} else {
castType.toString()
}
}

Expand Down Expand Up @@ -145,26 +239,35 @@ class ClusterDetailFragment : Fragment() {
selectedInteractionInfo = selectedClusterInfo.commands[selectedCommand]!!
selectedCommandCallback = selectedInteractionInfo.commandCallbackSupplier.get()
populateCommandParameter(inflater, parameterList)
selectedCommandCallback.setCallbackDelegate(object : ClusterCommandCallback {
override fun onSuccess(responseValues: Map<CommandResponseInfo, Any>) {
showMessage("Command success")
// Populate UI based on response values. We know the types from CommandInfo.getCommandResponses().
requireActivity().runOnUiThread {
populateCallbackResult(
responseValues,
inflater,
callbackList
)
}
responseValues.forEach { Log.d(TAG, it.toString()) }
}
setCallbackDelegate(inflater, callbackList)
}
}

override fun onFailure(exception: Exception) {
showMessage("Command failed")
Log.e(TAG, exception.toString())
private fun setCallbackDelegate(inflater: LayoutInflater, callbackList: LinearLayout) {
selectedCommandCallback.setCallbackDelegate(object : ClusterCommandCallback {
override fun onSuccess(responseValues: Map<CommandResponseInfo, Any>) {
showMessage("Command success")
// Populate UI based on response values. We know the types from CommandInfo.getCommandResponses().
requireActivity().runOnUiThread {
populateCallbackResult(
responseValues,
inflater,
callbackList,
)
}
})
}
clusterInteractionHistoryList[0].responseValue = responseValues
clusterInteractionHistoryList[0].status = "Success"
responseValues.forEach { Log.d(TAG, it.toString()) }
}

override fun onFailure(exception: Exception) {
showMessage("Command failed")
var errorStatus = exception.toString().split(':')
clusterInteractionHistoryList[0].status =
errorStatus[errorStatus.size - 2] + " " + errorStatus[errorStatus.size - 1]
Log.e(TAG, exception.toString())
}
})
}

private fun populateCommandParameter(inflater: LayoutInflater, parameterList: LinearLayout) {
Expand All @@ -174,11 +277,7 @@ class ClusterDetailFragment : Fragment() {
// byte[].class will be output as class [B, which is not readable, so dynamically change it
// to Byte[]. If more custom logic is required, should add a className field in
// commandParameterInfo
JasonLiuZhuoCheng marked this conversation as resolved.
Show resolved Hide resolved
if (paramInfo.type == ByteArray::class.java) {
param.clusterParameterTypeTv.text = "Byte[]"
} else {
param.clusterParameterTypeTv.text = "${paramInfo.type}"
}
param.clusterParameterTypeTv.text = formatParameterType(paramInfo.type)
parameterList.addView(param)
}
}
Expand Down Expand Up @@ -241,7 +340,7 @@ class ClusterDetailFragment : Fragment() {
} else {
it!!.javaClass.toString().split('$').last()
}
attributeCallbackItem.clusterCallbackDataTv.text = objectString
attributeCallbackItem.clusterCallbackDataTv.text = callbackClassName
attributeCallbackItem.clusterCallbackDataTv.setOnClickListener {
AlertDialog.Builder(requireContext())
.setTitle(callbackClassName)
Expand Down Expand Up @@ -274,19 +373,22 @@ class ClusterDetailFragment : Fragment() {

companion object {
private const val TAG = "ClusterDetailFragment"
private const val ENDPOINT_ID_KEY = "endpoint_id"
private const val DEVICE_PTR_KEY = "device_ptr"
private const val ENDPOINT_ID_KEY = "endpointId"
private const val HISTORY_COMMAND = "historyCommand"
private const val DEVICE_ID = "deviceId"

fun newInstance(
JasonLiuZhuoCheng marked this conversation as resolved.
Show resolved Hide resolved
deviceId: Long,
endpointId: Int
endpointId: Int,
historyCommand: HistoryCommand?
): ClusterDetailFragment {
return ClusterDetailFragment().apply {
arguments = Bundle(2).apply {
putLong(DEVICE_PTR_KEY, deviceId)
arguments = Bundle(4).apply {
putLong(DEVICE_ID, deviceId)
putSerializable(HISTORY_COMMAND, historyCommand)
putInt(ENDPOINT_ID_KEY, endpointId)
}
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,15 @@ import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import chip.devicecontroller.ChipDeviceController
import com.google.android.material.bottomnavigation.BottomNavigationView
import com.google.chip.chiptool.ChipClient
import com.google.chip.chiptool.GenericChipDeviceListener
import com.google.chip.chiptool.R
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.cancel
import com.google.chip.chiptool.clusterclient.AddressUpdateFragment
import kotlin.properties.Delegates
import kotlinx.android.synthetic.main.cluster_interaction_fragment.view.bottomNavigationBar
import kotlinx.android.synthetic.main.cluster_interaction_fragment.view.endpointList
import kotlinx.android.synthetic.main.cluster_interaction_fragment.view.getEndpointListBtn
import kotlinx.coroutines.launch
Expand All @@ -25,7 +28,7 @@ class ClusterInteractionFragment : Fragment() {

private lateinit var scope: CoroutineScope
private lateinit var addressUpdateFragment: AddressUpdateFragment
private var devicePtr = 0L
private var devicePtr by Delegates.notNull<Long>()

override fun onCreateView(
inflater: LayoutInflater,
Expand Down Expand Up @@ -55,6 +58,7 @@ class ClusterInteractionFragment : Fragment() {
}
endpointList.adapter = EndpointAdapter(dataList, EndpointListener())
endpointList.layoutManager = LinearLayoutManager(requireContext())
bottomNavigationBar.setOnNavigationItemSelectedListener(bottomNavigationListener)
}
}

Expand All @@ -74,22 +78,33 @@ class ClusterInteractionFragment : Fragment() {
fun newInstance(): ClusterInteractionFragment = ClusterInteractionFragment()
}

private fun showFragment(fragment: Fragment, showOnBack: Boolean = true) {
private fun showFragment(fragment: Fragment) {
val fragmentTransaction = requireActivity().supportFragmentManager
.beginTransaction()
.replace(R.id.fragment_container, fragment, fragment.javaClass.simpleName)
fragmentTransaction.addToBackStack(null)
fragmentTransaction.commit()
}

if (showOnBack) {
fragmentTransaction.addToBackStack(null)
private val bottomNavigationListener = BottomNavigationView.OnNavigationItemSelectedListener { menuItem ->
when (menuItem.itemId) {
R.id.clusterInteractionHistory -> {
val fragment = ClusterInteractionHistoryFragment.newInstance()
showFragment(fragment)
return@OnNavigationItemSelectedListener true
}
R.id.clusterInteractionSettings -> {
val fragment = ClusterInteractionSettingsFragment()
showFragment(fragment)
return@OnNavigationItemSelectedListener true
}
}

fragmentTransaction.commit()
false
}

inner class EndpointListener : EndpointAdapter.OnItemClickListener {
override fun onItemClick(position: Int) {
Toast.makeText(requireContext(), "Item $position clicked", Toast.LENGTH_SHORT).show()
showFragment(ClusterDetailFragment.newInstance(devicePtr, position))
showFragment(ClusterDetailFragment.newInstance(addressUpdateFragment.deviceId, position, null))
}
}
}
}
Loading