diff --git a/src/android/CHIPTool/.idea/misc.xml b/src/android/CHIPTool/.idea/misc.xml
deleted file mode 100644
index 37a750962da6f2..00000000000000
--- a/src/android/CHIPTool/.idea/misc.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/src/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/clusterclient/clusterinteraction/ClusterDetailFragment.kt b/src/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/clusterclient/clusterinteraction/ClusterDetailFragment.kt
index d6c84234ea5e38..72f0c0e33ab319 100644
--- a/src/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/clusterclient/clusterinteraction/ClusterDetailFragment.kt
+++ b/src/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/clusterclient/clusterinteraction/ClusterDetailFragment.kt
@@ -1,37 +1,95 @@
package com.google.chip.chiptool.clusterclient.clusterinteraction
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.AutoCompleteTextView
+import android.widget.LinearLayout
import android.widget.Toast
+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.CommandInfo
+import chip.clusterinfo.CommandResponseInfo
+import chip.clusterinfo.DelegatedClusterCallback
+import chip.devicecontroller.ChipClusters
import chip.devicecontroller.ChipDeviceController
+import chip.devicecontroller.ClusterInfoMapping
import com.google.chip.chiptool.ChipClient
import com.google.chip.chiptool.GenericChipDeviceListener
import com.google.chip.chiptool.R
+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
+import kotlinx.android.synthetic.main.cluster_detail_fragment.view.callbackList
+import kotlinx.android.synthetic.main.cluster_detail_fragment.view.clusterAutoCompleteTv
+import kotlinx.android.synthetic.main.cluster_detail_fragment.view.commandAutoCompleteTv
+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.cluster_parameter_item.view.clusterParameterData
+import kotlinx.android.synthetic.main.cluster_parameter_item.view.clusterParameterNameTv
+import kotlinx.android.synthetic.main.cluster_parameter_item.view.clusterParameterTypeTv
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+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 lateinit var scope: CoroutineScope
+ private val scope = CoroutineScope(Dispatchers.Main + Job())
+ private lateinit var clusterMap: Map
+ private lateinit var selectedClusterInfo: ClusterInfo
+ private lateinit var selectedCluster: ChipClusters.BaseChipCluster
+ private lateinit var selectedCommandCallback: DelegatedClusterCallback
+ private lateinit var selectedCommandInfo: CommandInfo
+ private var devicePtr = 0L
+ private var endpointId = 0
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
- scope = viewLifecycleOwner.lifecycleScope
-
+ clusterMap = ClusterInfoMapping().clusterMap
+ devicePtr = checkNotNull(requireArguments().getLong(DEVICE_PTR_KEY))
+ endpointId = checkNotNull(requireArguments().getInt(ENDPOINT_ID_KEY))
return inflater.inflate(R.layout.cluster_detail_fragment, container, false).apply {
deviceController.setCompletionListener(GenericChipDeviceListener())
+ commandAutoCompleteTv.visibility = View.GONE
+ clusterAutoCompleteSetup(clusterAutoCompleteTv, commandAutoCompleteTv, parameterList)
+ commandAutoCompleteSetup(commandAutoCompleteTv, inflater, parameterList, callbackList)
+ invokeCommand.setOnClickListener {
+ val commandArguments = HashMap()
+ parameterList.forEach {
+ val type =
+ selectedCommandInfo.commandParameters[it.clusterParameterNameTv.text.toString()]!!.type
+ val data = castStringToType(it.clusterParameterData.text.toString(), type)!!
+
+ commandArguments[it.clusterParameterNameTv.text.toString()] = data
+ }
+ selectedCommandInfo.getCommandFunction()
+ .invokeCommand(selectedCluster, selectedCommandCallback, commandArguments)
+ }
+ }
+ }
+
+ private fun castStringToType(data: String, type: Class<*>): Any? {
+ return when (type) {
+ Int::class.java -> data.toInt()
+ String::class.java -> data
+ Boolean::class.java -> data.toBoolean()
+ else -> null
}
}
@@ -41,8 +99,119 @@ class ClusterDetailFragment : Fragment(){
}
}
+ 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) {
+ 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.cluster_parameter_item, null, false) as ConstraintLayout
+ param.clusterParameterNameTv.text = "${paramName}"
+ param.clusterParameterTypeTv.text = "${paramInfo.type}"
+ parameterList.addView(param)
+ }
+ }
+
+ private fun populateCallbackResult(
+ responseValues: Map,
+ inflater: LayoutInflater,
+ callbackList: LinearLayout
+ ) {
+ responseValues.forEach { (variableNameType, response) ->
+ val callback =
+ inflater.inflate(R.layout.cluster_callback_item, null, false) as ConstraintLayout
+ callback.clusterCallbackNameTv.text = variableNameType.name
+ callback.clusterCallbackDataTv.text = response.toString()
+ callback.clusterCallbackTypeTv.text = variableNameType.type
+ callbackList.addView(callback)
+ }
+ }
+
+ private fun getCommandOptions(
+ clusterName: String
+ ): ArrayAdapter {
+ selectedClusterInfo = clusterMap[clusterName]!!
+ val commandNameList = constructHint(selectedClusterInfo.commands)
+ return ArrayAdapter(requireContext(), android.R.layout.simple_list_item_1, commandNameList)
+ }
+
+ private fun constructHint(clusterMap: Map): Array {
+ return clusterMap.keys.toTypedArray()
+ }
+
+ override fun onStop() {
+ super.onStop()
+ scope.cancel()
+ }
+
companion object {
private const val TAG = "ClusterDetailFragment"
- fun newInstance(): ClusterDetailFragment = ClusterDetailFragment()
+ private const val ENDPOINT_ID_KEY = "endpoint_id"
+ private const val DEVICE_PTR_KEY = "device_ptr"
+
+ fun newInstance(
+ deviceId: Long,
+ endpointId: Int
+ ): ClusterDetailFragment {
+ return ClusterDetailFragment().apply {
+ arguments = Bundle(2).apply {
+ putLong(DEVICE_PTR_KEY, deviceId)
+ putInt(ENDPOINT_ID_KEY, endpointId)
+ }
+ }
+ }
}
}
\ No newline at end of file
diff --git a/src/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/clusterclient/clusterinteraction/ClusterInteractionFragment.kt b/src/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/clusterclient/clusterinteraction/ClusterInteractionFragment.kt
index 077e2cbf0aed93..27c1b14f5d43cd 100644
--- a/src/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/clusterclient/clusterinteraction/ClusterInteractionFragment.kt
+++ b/src/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/clusterclient/clusterinteraction/ClusterInteractionFragment.kt
@@ -1,7 +1,6 @@
package com.google.chip.chiptool.clusterclient.clusterinteraction
import android.os.Bundle
-import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@@ -9,16 +8,17 @@ import android.widget.Toast
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
-import chip.clusterinfo.ClusterInfo
import chip.devicecontroller.ChipDeviceController
-import chip.devicecontroller.ClusterInfoMapping
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.Dispatchers
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.cancel
import com.google.chip.chiptool.clusterclient.AddressUpdateFragment
import kotlinx.android.synthetic.main.cluster_interaction_fragment.view.endpointList
import kotlinx.android.synthetic.main.cluster_interaction_fragment.view.getEndpointListBtn
-import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
class ClusterInteractionFragment : Fragment() {
@@ -27,7 +27,7 @@ class ClusterInteractionFragment : Fragment() {
private lateinit var scope: CoroutineScope
private lateinit var addressUpdateFragment: AddressUpdateFragment
- private lateinit var clusterMap: Map
+ private var devicePtr = 0L
override fun onCreateView(
inflater: LayoutInflater,
@@ -37,17 +37,18 @@ class ClusterInteractionFragment : Fragment() {
scope = viewLifecycleOwner.lifecycleScope
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
}
}
-
addressUpdateFragment =
childFragmentManager.findFragmentById(R.id.addressUpdateFragment) as AddressUpdateFragment
- clusterMap = ClusterInfoMapping().clusterMap
var dataList: List = ArrayList()
// TODO: Dynamically retrieve endpoint information using descriptor cluster
// hardcode the endpoint for now
@@ -65,23 +66,9 @@ 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()
}
companion object {
@@ -104,7 +91,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(devicePtr, position))
}
}
}
\ No newline at end of file
diff --git a/src/android/CHIPTool/app/src/main/res/layout/cluster_callback_item.xml b/src/android/CHIPTool/app/src/main/res/layout/cluster_callback_item.xml
new file mode 100644
index 00000000000000..13844ffaa80d12
--- /dev/null
+++ b/src/android/CHIPTool/app/src/main/res/layout/cluster_callback_item.xml
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/android/CHIPTool/app/src/main/res/layout/cluster_detail_fragment.xml b/src/android/CHIPTool/app/src/main/res/layout/cluster_detail_fragment.xml
index df7ab6283f1ef4..2b9bd001c3b5a5 100644
--- a/src/android/CHIPTool/app/src/main/res/layout/cluster_detail_fragment.xml
+++ b/src/android/CHIPTool/app/src/main/res/layout/cluster_detail_fragment.xml
@@ -1,8 +1,48 @@
-
+ android:layout_height="match_parent"
+ android:padding="16dp"
+ android:orientation="vertical">
+
-
\ No newline at end of file
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/android/CHIPTool/app/src/main/res/layout/cluster_parameter_item.xml b/src/android/CHIPTool/app/src/main/res/layout/cluster_parameter_item.xml
new file mode 100644
index 00000000000000..32ad0df199df77
--- /dev/null
+++ b/src/android/CHIPTool/app/src/main/res/layout/cluster_parameter_item.xml
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/android/CHIPTool/app/src/main/res/values/strings.xml b/src/android/CHIPTool/app/src/main/res/values/strings.xml
index f37d4d4ef75189..02850a2baf4a3b 100644
--- a/src/android/CHIPTool/app/src/main/res/values/strings.xml
+++ b/src/android/CHIPTool/app/src/main/res/values/strings.xml
@@ -147,4 +147,7 @@
custom
Retrieve Endpoint List
+ Invoke
+ Select a command
+ Select a cluster
diff --git a/src/controller/java/BUILD.gn b/src/controller/java/BUILD.gn
index 0a0f6b7f2db200..31fafee2743e64 100644
--- a/src/controller/java/BUILD.gn
+++ b/src/controller/java/BUILD.gn
@@ -71,6 +71,7 @@ android_library("java") {
"src/chip/clusterinfo/ClusterInfo.java",
"src/chip/clusterinfo/CommandInfo.java",
"src/chip/clusterinfo/CommandParameterInfo.java",
+ "src/chip/clusterinfo/CommandResponseInfo.java",
"src/chip/clusterinfo/DelegatedClusterCallback.java",
"src/chip/devicecontroller/ChipClusterException.java",
"src/chip/devicecontroller/ChipCommandType.java",
diff --git a/src/controller/java/src/chip/clusterinfo/ClusterCommandCallback.java b/src/controller/java/src/chip/clusterinfo/ClusterCommandCallback.java
index f7cfef34e7b103..34b63f05e7f225 100644
--- a/src/controller/java/src/chip/clusterinfo/ClusterCommandCallback.java
+++ b/src/controller/java/src/chip/clusterinfo/ClusterCommandCallback.java
@@ -1,12 +1,12 @@
package chip.clusterinfo;
-import java.util.List;
+import java.util.Map;
/**
* Interface for making customized callback that implements both onSuccess and onFailure functions.
*/
public interface ClusterCommandCallback {
- void onSuccess(List