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

Restyle Cluster command detail page #11355

Closed
wants to merge 8 commits into from
Closed
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
@@ -1,15 +1,39 @@
package com.google.chip.chiptool.clusterclient.clusterinteraction

import android.os.Bundle
import android.text.Layout
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.AdapterView
import android.widget.ArrayAdapter
import android.widget.AutoCompleteTextView
import android.widget.LinearLayout
import android.widget.Toast
import androidx.core.view.forEach
import androidx.fragment.app.Fragment
import chip.clusterinfo.ClusterCommandCallback
import chip.clusterinfo.ClusterInfo
import chip.clusterinfo.CommandInfo
import chip.clusterinfo.DelegatedClusterCallback
import chip.clusterinfo.ResponseValueInfo
import chip.devicecontroller.ChipClusters
import chip.devicecontroller.ChipDeviceController
import com.google.chip.chiptool.ChipClient
import com.google.chip.chiptool.GenericChipDeviceListener
import com.google.chip.chiptool.R
import kotlinx.android.synthetic.main.callback_item.view.callbackData
import kotlinx.android.synthetic.main.callback_item.view.callbackName
import kotlinx.android.synthetic.main.callback_item.view.callbackType
import kotlinx.android.synthetic.main.cluster_detail_fragment.view.callbackList
import kotlinx.android.synthetic.main.cluster_detail_fragment.view.clusterAutoComplete
import kotlinx.android.synthetic.main.cluster_detail_fragment.view.commandAutoComplete
import kotlinx.android.synthetic.main.cluster_detail_fragment.view.invokeCommand
import kotlinx.android.synthetic.main.cluster_detail_fragment.view.parameterList
import kotlinx.android.synthetic.main.parameter_item.view.parameterData
import kotlinx.android.synthetic.main.parameter_item.view.parameterName
import kotlinx.android.synthetic.main.parameter_item.view.parameterType
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
Expand All @@ -19,35 +43,181 @@ import kotlinx.coroutines.cancel
* ClusterDetailFragment allows user to pick cluster, command, specify parameters and see
* the callback result.
*/
class ClusterDetailFragment : Fragment(){
class ClusterDetailFragment : Fragment() {
private val deviceController: ChipDeviceController
get() = ChipClient.getDeviceController(requireContext())

private val scope = CoroutineScope(Dispatchers.Main + Job())
private lateinit var clusterMap: HashMap<String, ClusterInfo>
private lateinit var selectedClusterInfo: ClusterInfo
private lateinit var selectedCluster: ChipClusters.BaseChipCluster
private lateinit var selectedCommandCallback: DelegatedClusterCallback
private var commandArguments = HashMap<String, Any>()
private lateinit var selectedCommandInfo: CommandInfo
private var devicePtr = 0L
private var endpointId = 1

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
clusterMap =
checkNotNull(requireArguments().getSerializable(CLUSTER_MAP_INFO)) as HashMap<String, ClusterInfo>
devicePtr = checkNotNull(requireArguments().getLong(DEVICE_PTR))
return inflater.inflate(R.layout.cluster_detail_fragment, container, false).apply {
deviceController.setCompletionListener(GenericChipDeviceListener())
commandAutoComplete.visibility = View.GONE
clusterAutoCompleteSetup(clusterAutoComplete, commandAutoComplete, parameterList)
commandAutoCompleteSetup(commandAutoComplete, inflater, parameterList, callbackList)
invokeCommand.setOnClickListener {
parameterList.forEach {
val type =
selectedCommandInfo.commandParameters[it.parameterName.text.toString()]!!.type!!
val data = castCorrectType(type, it.parameterData.text.toString())!!
commandArguments[it.parameterName.text.toString()] = data
}

selectedCommandInfo.getCommandFunction()
.invokeCommand(selectedCluster, selectedCommandCallback, commandArguments)
}
}
}

private fun castCorrectType(type: Class<*>, data: String): Any? {

return when (type) {
Int::class.java -> data.toInt()
String::class.java -> data
Boolean::class.java -> data.toBoolean()
else -> null
}
}


private fun showMessage(msg: String) {
requireActivity().runOnUiThread {
Toast.makeText(requireContext(), msg, Toast.LENGTH_SHORT).show()
}
}

private fun clusterAutoCompleteSetup(
clusterAutoComplete: AutoCompleteTextView,
commandAutoComplete: AutoCompleteTextView,
parameterList: LinearLayout
) {
val clusterNameList = constructHint(clusterMap)
val clusterAdapter =
ArrayAdapter(requireContext(), android.R.layout.simple_list_item_1, clusterNameList)
clusterAutoComplete.setAdapter(clusterAdapter)
clusterAutoComplete.setOnItemClickListener { parent, view, position, id ->
commandAutoComplete.visibility = View.VISIBLE
// when new cluster is selected, clear the command text and possible parameterList
commandAutoComplete.setText("", false)
parameterList.removeAllViews()
// populate all the commands that belong to the selected cluster
val selectedCluster: String = clusterAutoComplete.adapter.getItem(position).toString()
val commandAdapter = getCommandOptions(selectedCluster)
commandAutoComplete.setAdapter(commandAdapter)
}
}

private fun commandAutoCompleteSetup(
commandAutoComplete: AutoCompleteTextView,
inflater: LayoutInflater,
parameterList: LinearLayout,
callbackList: LinearLayout
) {
commandAutoComplete.setOnItemClickListener { parent, view, position, id ->
// when new command is selected, clear all the parameterList
parameterList.removeAllViews()
selectedCluster = selectedClusterInfo.createClusterFunction.create(devicePtr, endpointId)
val selectedCommand: String = commandAutoComplete.adapter.getItem(position).toString()
selectedCommandInfo = selectedClusterInfo.commands[selectedCommand]!!
selectedCommandCallback = selectedCommandInfo.commandCallbackSupplier.get()
populateCommandParameter(inflater, parameterList)
selectedCommandCallback!!.setCallbackDelegate(object : ClusterCommandCallback {
override fun onSuccess(responseValues: Map<ResponseValueInfo, 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()) }
}

override fun onFailure(exception: Exception) {
showMessage("Command failed")
Log.e(TAG, exception.toString())
}
})
}
}

private fun populateCommandParameter(inflater: LayoutInflater, parameterList: LinearLayout) {

selectedCommandInfo.commandParameters.forEach { (paramName, paramInfo) ->
val param = inflater.inflate(R.layout.parameter_item, null, false) as LinearLayout
param.parameterName.text = "${paramName}"
param.parameterType.text = "${paramInfo.type}"
parameterList.addView(param)
}
}

private fun populateCallbackResult(
responseValues: Map<ResponseValueInfo, Any>,
inflater: LayoutInflater,
callbackList: LinearLayout
) {
responseValues.forEach { (variableNameType, response) ->
val callback = inflater.inflate(R.layout.callback_item, null, false) as LinearLayout
callback.callbackName.text = variableNameType.name
callback.callbackData.text = response.toString()
callback.callbackType.text = variableNameType.type
callbackList.addView(callback)
}
}

private fun getCommandOptions(
clusterName: String
): ArrayAdapter<String> {
selectedClusterInfo = clusterMap[clusterName]!!
val commandNameList = constructHint(selectedClusterInfo.commands as HashMap<String, Any>)
return ArrayAdapter(requireContext(), android.R.layout.simple_list_item_1, commandNameList)
}

private fun constructHint(clusterMap: HashMap<String, *>): Array<String> {
val clusterName = mutableListOf<String>()
for ((name, info) in clusterMap) {
clusterName.add(name)
}
return clusterName.toTypedArray()
}

override fun onStop() {
super.onStop()
scope.cancel()
}

companion object {
private const val TAG = "ClusterDetailFragment"
fun newInstance(): ClusterDetailFragment = ClusterDetailFragment()
private const val CLUSTER_MAP_INFO = "cluster_map_info"
private const val DEVICE_PTR = "device_ptr"
fun newInstance(
clusterMap: HashMap<String, ClusterInfo>,
deviceId: Long
): ClusterDetailFragment {
return ClusterDetailFragment().apply {
arguments = Bundle(1).apply {
putSerializable(CLUSTER_MAP_INFO, clusterMap)
putLong(DEVICE_PTR, deviceId)
}
}
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,20 @@ class ClusterInteractionFragment : Fragment() {
private val scope = CoroutineScope(Dispatchers.Main + Job())
private lateinit var addressUpdateFragment: AddressUpdateFragment
private lateinit var clusterMap: Map<String, ClusterInfo>
private var devicePtr = 0L

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
return inflater.inflate(R.layout.cluster_interaction_fragment, container, false).apply {
deviceController.setCompletionListener(ChipControllerCallback())
deviceController.setCompletionListener(GenericChipDeviceListener())
endpointList.visibility = View.GONE
getEndpointListBtn.setOnClickListener {
scope.launch {
devicePtr =
ChipClient.getConnectedDevicePointer(requireContext(), addressUpdateFragment.deviceId)
showMessage("Retrieving endpoints")
endpointList.visibility = View.VISIBLE
}
Expand All @@ -65,25 +69,6 @@ class ClusterInteractionFragment : Fragment() {
}
}

inner class ChipControllerCallback : GenericChipDeviceListener() {
override fun onConnectDeviceComplete() {}

override fun onCommissioningComplete(nodeId: Long, errorCode: Int) {
}

override fun onNotifyChipConnectionClosed() {
Log.d(TAG, "onNotifyChipConnectionClosed")
}

override fun onCloseBleComplete() {
Log.d(TAG, "onCloseBleComplete")
}

override fun onError(error: Throwable?) {
Log.d(TAG, "onError: $error")
}
}

override fun onStop() {
super.onStop()
scope.cancel()
Expand All @@ -109,7 +94,7 @@ class ClusterInteractionFragment : Fragment() {
inner class EndpointListener : EndpointAdapter.OnItemClickListener {
override fun onItemClick(position: Int) {
Toast.makeText(requireContext(), "Item $position clicked", Toast.LENGTH_SHORT).show()
showFragment(ClusterDetailFragment.newInstance())
showFragment(ClusterDetailFragment.newInstance(clusterMap as HashMap<String, ClusterInfo>, devicePtr))
}
}
}
29 changes: 29 additions & 0 deletions src/android/CHIPTool/app/src/main/res/layout/callback_item.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/callbackRow"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">

<TextView
android:id="@+id/callbackName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="16dp" />

<TextView
android:id="@+id/callbackData"
android:textStyle="bold"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="16dp" />


<TextView
android:id="@+id/callbackType"
android:textStyle="bold"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="16dp" />
</LinearLayout>
Original file line number Diff line number Diff line change
@@ -1,8 +1,51 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="match_parent"
android:padding="16dp"
android:orientation="vertical">
<AutoCompleteTextView
android:id="@+id/clusterAutoComplete"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:completionHint="Select a cluster"
android:completionThreshold="0"
android:hint="Select a cluster"
android:minHeight="48dp"></AutoCompleteTextView>

</androidx.constraintlayout.widget.ConstraintLayout>
<AutoCompleteTextView
android:id="@+id/commandAutoComplete"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:completionHint="Select a cluster"
android:completionThreshold="0"
android:hint="Select a command"
android:minHeight="48dp"></AutoCompleteTextView>

<LinearLayout
android:id="@+id/parameterList"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:layout_below="@id/commandAutoComplete"
android:orientation="vertical" />

<Button
android:id="@+id/invokeCommand"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|right"
android:text="Invoke" />

<LinearLayout
android:id="@+id/callbackList"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:layout_below="@+id/invokeCommand"
android:orientation="vertical" />


</LinearLayout>
Loading