Modify wildcardFragment for multiple attribute,event (#25677)

diff --git a/examples/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/clusterclient/WildcardFragment.kt b/examples/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/clusterclient/WildcardFragment.kt
index a9861aa..70d1849 100644
--- a/examples/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/clusterclient/WildcardFragment.kt
+++ b/examples/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/clusterclient/WildcardFragment.kt
@@ -11,6 +11,7 @@
 import android.widget.EditText
 import android.widget.Spinner
 import android.widget.TextView
+import android.widget.Toast
 import androidx.fragment.app.Fragment
 import androidx.lifecycle.lifecycleScope
 import chip.devicecontroller.ChipDeviceController
@@ -28,6 +29,7 @@
 import chip.devicecontroller.model.NodeState
 import chip.tlv.AnonymousTag
 import chip.tlv.ContextSpecificTag
+import chip.tlv.TlvReader
 import chip.tlv.TlvWriter
 import com.google.chip.chiptool.ChipClient
 import com.google.chip.chiptool.R
@@ -37,6 +39,8 @@
 import java.util.Optional
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.launch
+import kotlin.coroutines.resume
+import kotlin.coroutines.suspendCoroutine
 
 class WildcardFragment : Fragment() {
   private var _binding: WildcardFragmentBinding? = null
@@ -49,6 +53,10 @@
 
   private lateinit var addressUpdateFragment: AddressUpdateFragment
 
+  private val attributePath = ArrayList<ChipAttributePath>()
+  private val eventPath = ArrayList<ChipEventPath>()
+  private val subscribeIdList = ArrayList<ULong>()
+
   private val reportCallback = object : ReportCallback {
     override fun onError(attributePath: ChipAttributePath?, eventPath: ChipEventPath?, ex: Exception) {
       if (attributePath != null)
@@ -108,12 +116,42 @@
   ): View {
     _binding = WildcardFragmentBinding.inflate(inflater, container, false)
     scope = viewLifecycleOwner.lifecycleScope
-    binding.subscribeBtn.setOnClickListener { scope.launch { showSubscribeDialog(ATTRIBUTE) } }
-    binding.readBtn.setOnClickListener { scope.launch { showReadDialog(ATTRIBUTE) } }
-    binding.writeBtn.setOnClickListener { scope.launch { showWriteDialog() } }
-    binding.subscribeEventBtn.setOnClickListener { scope.launch { showSubscribeDialog(EVENT) } }
-    binding.readEventBtn.setOnClickListener { scope.launch { showReadDialog(EVENT) } }
-    binding.invokeBtn.setOnClickListener { scope.launch { showInvokeDialog() } }
+
+    binding.selectTypeRadioGroup.setOnCheckedChangeListener { _, i ->
+      val readBtnOn = (i == R.id.readRadioBtn)
+      val subscribeBtnOn = (i == R.id.subscribeRadioBtn)
+      val writeBtnOn = (i == R.id.writeRadioBtn)
+      val invokeBtnOn = (i == R.id.invokeRadioBtn)
+
+      binding.addLayout.visibility = getVisibility(readBtnOn || subscribeBtnOn)
+      binding.attributeIdLabel.visibility = getVisibility(readBtnOn || subscribeBtnOn || writeBtnOn)
+      binding.attributeIdEd.visibility = getVisibility(readBtnOn || subscribeBtnOn || writeBtnOn)
+      binding.eventIdLabel.visibility = getVisibility(readBtnOn || subscribeBtnOn)
+      binding.eventIdEd.visibility = getVisibility(readBtnOn || subscribeBtnOn)
+      binding.commandIdLabel.visibility = getVisibility(invokeBtnOn)
+      binding.commandIdEd.visibility = getVisibility(invokeBtnOn)
+      binding.isUrgentLabel.visibility = getVisibility(subscribeBtnOn)
+      binding.isUrgentSp.visibility = getVisibility(subscribeBtnOn)
+      binding.shutdownSubscriptionBtn.visibility = getVisibility(subscribeBtnOn)
+    }
+
+    binding.sendBtn.setOnClickListener {
+      if (binding.readRadioBtn.isChecked) {
+        showReadDialog()
+      } else if (binding.subscribeRadioBtn.isChecked) {
+        showSubscribeDialog()
+      } else if (binding.writeRadioBtn.isChecked) {
+        showWriteDialog()
+      } else if (binding.invokeRadioBtn.isChecked) {
+        showInvokeDialog()
+      }
+    }
+
+    binding.shutdownSubscriptionBtn.setOnClickListener { showShutdownSubscriptionDialog() }
+
+    binding.addAttributeBtn.setOnClickListener { addPathList(ATTRIBUTE) }
+    binding.addEventBtn.setOnClickListener { addPathList(EVENT) }
+    binding.resetBtn.setOnClickListener { resetPath() }
 
     addressUpdateFragment =
       childFragmentManager.findFragmentById(R.id.addressUpdateFragment) as AddressUpdateFragment
@@ -121,6 +159,43 @@
     return binding.root
   }
 
+  private fun getVisibility(isShowing: Boolean) : Int {
+    return if (isShowing) { View.VISIBLE } else { View.GONE }
+  }
+
+  private fun addPathList(type: Int) {
+    val endpointId = getChipPathIdForText(binding.endpointIdEd.text.toString())
+    val clusterId = getChipPathIdForText(binding.clusterIdEd.text.toString())
+    val attributeId = getChipPathIdForText(binding.attributeIdEd.text.toString())
+    val eventId = getChipPathIdForText(binding.eventIdEd.text.toString())
+    // Only Subscribe used
+    val isUrgent = (binding.subscribeRadioBtn.isChecked) && (binding.isUrgentSp.selectedItem.toString().toBoolean())
+
+    if (type == ATTRIBUTE) {
+      attributePath.add(ChipAttributePath.newInstance(endpointId, clusterId, attributeId))
+    } else if (type == EVENT) {
+      eventPath.add(ChipEventPath.newInstance(endpointId, clusterId, eventId, isUrgent))
+    }
+    updateAddListView()
+  }
+
+  private fun resetPath() {
+    attributePath.clear()
+    eventPath.clear()
+    updateAddListView()
+  }
+
+  private fun updateAddListView() {
+    val builder = StringBuilder()
+    for (attribute in attributePath) {
+      builder.append("attribute($attribute)\n")
+    }
+    for (event in eventPath) {
+      builder.append("event($event)\n")
+    }
+    binding.sendListView.text = builder.toString()
+  }
+
   override fun onDestroyView() {
     super.onDestroyView()
     _binding = null
@@ -154,75 +229,43 @@
     return stringBuilder.toString()
   }
 
-  private suspend fun subscribe(type: Int, minInterval: Int, maxInterval: Int, keepSubscriptions: Boolean, isFabricFiltered: Boolean, isUrgent: Boolean) {
+  private suspend fun subscribe(minInterval: Int, maxInterval: Int, keepSubscriptions: Boolean, isFabricFiltered: Boolean) {
     val subscriptionEstablishedCallback =
-      SubscriptionEstablishedCallback { Log.i(TAG, "Subscription to device established") }
+      SubscriptionEstablishedCallback {
+        subscriptionId ->
+        Log.i(TAG, "Subscription to device established : ${subscriptionId.toULong()}")
+        subscribeIdList.add(subscriptionId.toULong())
+        requireActivity().runOnUiThread {
+          Toast.makeText(requireActivity(), "${getString(R.string.wildcard_subscribe_established_toast_message)} : $subscriptionId", Toast.LENGTH_SHORT).show()
+        }
+      }
 
     val resubscriptionAttemptCallback =
       ResubscriptionAttemptCallback { terminationCause, nextResubscribeIntervalMsec
                                      -> Log.i(TAG, "ResubscriptionAttempt terminationCause:$terminationCause, nextResubscribeIntervalMsec:$nextResubscribeIntervalMsec") }
 
-    val endpointId = getChipPathIdForText(binding.endpointIdEd.text.toString())
-    val clusterId = getChipPathIdForText(binding.clusterIdEd.text.toString())
-    val attributeId = getChipPathIdForText(binding.attributeIdEd.text.toString())
-    val eventId = getChipPathIdForText(binding.eventIdEd.text.toString())
-
-    if (type == ATTRIBUTE) {
-      val attributePath = ChipAttributePath.newInstance(endpointId, clusterId, attributeId)
-      deviceController.subscribeToPath(subscriptionEstablishedCallback,
-                                       resubscriptionAttemptCallback,
-                                       reportCallback,
-                                       ChipClient.getConnectedDevicePointer(requireContext(),
-                                       addressUpdateFragment.deviceId),
-                                       listOf(attributePath),
-                                       null,
-                                       minInterval,
-                                       maxInterval,
-                                       keepSubscriptions,
-                                       isFabricFiltered,
-                                       /* imTimeoutMs= */ 0)
-    } else if (type == EVENT) {
-      val eventPath = ChipEventPath.newInstance(endpointId, clusterId, eventId, isUrgent)
-      deviceController.subscribeToPath(subscriptionEstablishedCallback,
-                                      resubscriptionAttemptCallback,
-                                      reportCallback,
-                                      ChipClient.getConnectedDevicePointer(requireContext(),
-                                      addressUpdateFragment.deviceId),
-                                      null,
-                                      listOf(eventPath),
-                                      minInterval,
-                                      maxInterval,
-                                      keepSubscriptions,
-                                      isFabricFiltered,
-                                      /* imTimeoutMs= */ 0)
-    }
+    deviceController.subscribeToPath(subscriptionEstablishedCallback,
+            resubscriptionAttemptCallback,
+            reportCallback,
+            ChipClient.getConnectedDevicePointer(requireContext(),
+                    addressUpdateFragment.deviceId),
+            attributePath.ifEmpty { null },
+            eventPath.ifEmpty { null },
+            minInterval,
+            maxInterval,
+            keepSubscriptions,
+            isFabricFiltered,
+            /* imTimeoutMs= */ 0)
   }
 
-  private suspend fun read(type: Int, isFabricFiltered: Boolean) {
-    val endpointId = getChipPathIdForText(binding.endpointIdEd.text.toString())
-    val clusterId = getChipPathIdForText(binding.clusterIdEd.text.toString())
-    val attributeId = getChipPathIdForText(binding.attributeIdEd.text.toString())
-    val eventId = getChipPathIdForText(binding.eventIdEd.text.toString())
-
-    if (type == ATTRIBUTE) {
-      val attributePath = ChipAttributePath.newInstance(endpointId, clusterId, attributeId)
-      deviceController.readPath(reportCallback,
-                          ChipClient.getConnectedDevicePointer(requireContext(),
-                          addressUpdateFragment.deviceId),
-                          listOf(attributePath),
-                          null,
-                          isFabricFiltered,
-                          /* imTimeoutMs= */ 0)
-    } else if (type == EVENT) {
-      val eventPath = ChipEventPath.newInstance(endpointId, clusterId, eventId)
-      deviceController.readPath(reportCallback,
-                          ChipClient.getConnectedDevicePointer(requireContext(),
-                          addressUpdateFragment.deviceId),
-                          null,
-                          listOf(eventPath),
-                          isFabricFiltered,
-                          /* imTimeoutMs= */ 0)
-    }
+  private suspend fun read(isFabricFiltered: Boolean) {
+    deviceController.readPath(reportCallback,
+            ChipClient.getConnectedDevicePointer(requireContext(),
+                    addressUpdateFragment.deviceId),
+            attributePath.ifEmpty { null },
+            eventPath.ifEmpty { null },
+            isFabricFiltered,
+            /* imTimeoutMs= */ 0)
   }
 
   private suspend fun write(writeValueType: String, writeValue: String, dataVersion: Int?, timedRequestTimeoutMs: Int, imTimeoutMs: Int) {
@@ -285,7 +328,13 @@
             imTimeoutMs)
   }
 
-  private fun showReadDialog(type: Int) {
+  private fun showReadDialog() {
+    if (attributePath.isEmpty() && eventPath.isEmpty()) {
+      requireActivity().runOnUiThread {
+        Toast.makeText(requireActivity(), R.string.wildcard_empty_error_toast_message, Toast.LENGTH_SHORT).show()
+      }
+      return
+    }
     val dialogView = requireActivity().layoutInflater.inflate(R.layout.read_dialog, null)
     val dialog = AlertDialog.Builder(requireContext()).apply {
       setView(dialogView)
@@ -294,7 +343,7 @@
     val isFabricFilteredEd = dialogView.findViewById<EditText>(R.id.isFabricFilteredSp)
     dialogView.findViewById<Button>(R.id.readBtn).setOnClickListener {
       scope.launch {
-        read(type, isFabricFilteredEd.text.toString().toBoolean())
+        read(isFabricFilteredEd.text.toString().toBoolean())
         requireActivity().runOnUiThread { dialog.dismiss() }
       }
     }
@@ -305,7 +354,7 @@
     binding.outputTv.text = ""
     val dialogView = requireActivity().layoutInflater.inflate(R.layout.write_dialog, null)
     val writeValueTypeSp = dialogView.findViewById<Spinner>(R.id.writeValueTypeSp)
-    val spinnerAdapter = ArrayAdapter(requireActivity(), android.R.layout.simple_spinner_item, TLV_MAP.keys.toList())
+    val spinnerAdapter = ArrayAdapter(requireActivity(), android.R.layout.simple_spinner_dropdown_item, TLV_MAP.keys.toList())
     writeValueTypeSp.adapter = spinnerAdapter
     val dialog = AlertDialog.Builder(requireContext()).apply {
       setView(dialogView)
@@ -328,17 +377,14 @@
     dialog.show()
   }
 
-  private fun showSubscribeDialog(type: Int) {
-    val dialogView = requireActivity().layoutInflater.inflate(R.layout.subscribe_dialog, null)
-    val isUrgentTv = dialogView.findViewById<TextView>(R.id.titleisUrgent)
-    val isUrgentSp = dialogView.findViewById<Spinner>(R.id.isUrgentSp)
-    if (type == EVENT) {
-      isUrgentTv.visibility = View.VISIBLE
-      isUrgentSp.visibility = View.VISIBLE
-    } else {
-      isUrgentTv.visibility = View.GONE
-      isUrgentSp.visibility = View.GONE
+  private fun showSubscribeDialog() {
+    if (attributePath.isEmpty() && eventPath.isEmpty()) {
+      requireActivity().runOnUiThread {
+        Toast.makeText(requireActivity(), R.string.wildcard_empty_error_toast_message, Toast.LENGTH_SHORT).show()
+      }
+      return
     }
+    val dialogView = requireActivity().layoutInflater.inflate(R.layout.subscribe_dialog, null)
     val dialog = AlertDialog.Builder(requireContext()).apply {
       setView(dialogView)
     }.create()
@@ -351,12 +397,10 @@
       scope.launch {
         if(minIntervalEd.text.isNotBlank() && maxIntervalEd.text.isNotBlank()) {
           subscribe(
-            type,
             minIntervalEd.text.toString().toInt(),
             maxIntervalEd.text.toString().toInt(),
             keepSubscriptionsSp.selectedItem.toString().toBoolean(),
             isFabricFilteredSp.selectedItem.toString().toBoolean(),
-            isUrgentSp.selectedItem.toString().toBoolean(),
           )
         } else {
           Log.e(TAG, "minInterval or maxInterval is empty!" )
@@ -389,6 +433,87 @@
     dialog.show()
   }
 
+  private suspend fun readCurrentFabricIndex() : UInt {
+    val context = requireContext()
+    val endpointId = 0L
+    val clusterId = 62L // OperationalCredentials
+    val attributeId = 5L // CurrentFabricIndex
+    val deviceId = addressUpdateFragment.deviceId
+    val devicePointer = ChipClient.getConnectedDevicePointer(context, deviceId)
+    return suspendCoroutine { cont ->
+      deviceController.readAttributePath(object : ReportCallback {
+        override fun onError(attributePath: ChipAttributePath?, eventPath: ChipEventPath?, e: java.lang.Exception?) {
+          cont.resume(0u)
+        }
+
+        override fun onReport(nodeState: NodeState?) {
+          val state = nodeState?.getEndpointState(endpointId.toInt())?.
+                                  getClusterState(clusterId)?.
+                                  getAttributeState(attributeId)
+          if (state == null) {
+            cont.resume(0u)
+            return
+          }
+          val ret = TlvReader(state.tlv).getUInt(AnonymousTag)
+          cont.resume(ret)
+        }
+      }, devicePointer,
+         listOf(ChipAttributePath.newInstance(endpointId, clusterId, attributeId)),
+         0 /* imTimeoutMs */
+      )
+    }
+  }
+
+  private fun shutdownSubscription(fabricIndex: UInt, subscribeId: ULong? = null) {
+    val deviceId = addressUpdateFragment.deviceId
+    if (subscribeId != null) {
+      deviceController.shutdownSubscriptions(fabricIndex.toInt(), deviceId, subscribeId.toLong())
+      subscribeIdList.remove(subscribeId)
+    } else {
+      deviceController.shutdownSubscriptions(fabricIndex.toInt(), deviceId)
+    }
+  }
+
+  private fun showShutdownSubscriptionDialog() {
+    val dialogView = requireActivity().layoutInflater.inflate(R.layout.shutdown_subscribe_dialog, null)
+    val subscriptionIdSp = dialogView.findViewById<Spinner>(R.id.subscribeIdSp)
+    val fabricIndexTv = dialogView.findViewById<TextView>(R.id.fabricIndexValue)
+    val shutdownBtn = dialogView.findViewById<Button>(R.id.shutdownBtn)
+    val shutdownAllBtn = dialogView.findViewById<Button>(R.id.shutdownAllBtn)
+    val spinnerAdapter = ArrayAdapter(requireActivity(), android.R.layout.simple_spinner_dropdown_item, subscribeIdList)
+    subscriptionIdSp.adapter = spinnerAdapter
+    val dialog = AlertDialog.Builder(requireContext()).apply {
+      setView(dialogView)
+    }.create()
+
+    shutdownBtn.setOnClickListener {
+      val fabricIndex = fabricIndexTv.text.toString().toUInt()
+      val subscribeId = subscriptionIdSp.selectedItem.toString().toULong()
+      scope.launch {
+        shutdownSubscription(fabricIndex, subscribeId)
+        requireActivity().runOnUiThread { dialog.dismiss() }
+      }
+    }
+
+    shutdownAllBtn.setOnClickListener {
+      scope.launch {
+        val fabricIndex = fabricIndexTv.text.toString().toUInt()
+        shutdownSubscription(fabricIndex)
+        requireActivity().runOnUiThread { dialog.dismiss() }
+      }
+    }
+    dialog.show()
+
+    scope.launch {
+      val fabricIndex = readCurrentFabricIndex()
+      requireActivity().runOnUiThread {
+        fabricIndexTv.text = fabricIndex.toString()
+        shutdownBtn.isEnabled = true
+        shutdownAllBtn.isEnabled = true
+      }
+    }
+  }
+
   private fun getChipPathIdForText(text: String): ChipPathId {
     return if (text.isEmpty()) ChipPathId.forWildcard() else ChipPathId.forId(text.toLong())
   }
diff --git a/examples/android/CHIPTool/app/src/main/res/layout/read_dialog.xml b/examples/android/CHIPTool/app/src/main/res/layout/read_dialog.xml
index 0713df0..4ac2a1c 100644
--- a/examples/android/CHIPTool/app/src/main/res/layout/read_dialog.xml
+++ b/examples/android/CHIPTool/app/src/main/res/layout/read_dialog.xml
@@ -22,8 +22,8 @@
       android:textSize="16sp"
       android:layout_marginBottom="8dp"
       app:layout_constraintStart_toStartOf="parent"
-      app:layout_constraintTop_toBottomOf="@id/isFabricFilteredSp"
-      app:layout_constraintBottom_toTopOf="@+id/subscribeBtn"/>
+      app:layout_constraintTop_toBottomOf="@id/titleText"
+      app:layout_constraintBottom_toTopOf="@+id/readBtn"/>
 
   <Button
       android:id="@+id/readBtn"
diff --git a/examples/android/CHIPTool/app/src/main/res/layout/shutdown_subscribe_dialog.xml b/examples/android/CHIPTool/app/src/main/res/layout/shutdown_subscribe_dialog.xml
new file mode 100644
index 0000000..28d0b32
--- /dev/null
+++ b/examples/android/CHIPTool/app/src/main/res/layout/shutdown_subscribe_dialog.xml
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:padding="16dp">
+  <TextView
+      android:id="@+id/titleText"
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      android:text="@string/shutdown_dialog_title_text"
+      android:textSize="22sp"
+      app:layout_constraintStart_toStartOf="parent"
+      app:layout_constraintTop_toTopOf="parent" />
+
+  <TextView
+      android:id="@+id/titleFabricIndex"
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:text="@string/shutdown_dialog_fabric_index_hint"
+      android:textSize="16sp"
+      android:layout_marginTop="8dp"
+      app:layout_constraintStart_toStartOf="parent"
+      app:layout_constraintTop_toBottomOf="@id/titleText"
+      />
+
+  <TextView
+      android:id="@+id/fabricIndexValue"
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:textSize="16sp"
+      android:layout_marginTop="8dp"
+      app:layout_constraintStart_toStartOf="parent"
+      app:layout_constraintTop_toBottomOf="@id/titleFabricIndex"
+      />
+
+  <TextView
+      android:id="@+id/titleSubscriptionId"
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:text="@string/shutdown_dialog_subscription_id_hint"
+      android:textSize="16sp"
+      android:layout_marginTop="8dp"
+      app:layout_constraintStart_toStartOf="parent"
+      app:layout_constraintTop_toBottomOf="@id/fabricIndexValue"
+      />
+  <Spinner
+      android:id="@+id/subscribeIdSp"
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      android:hint="@string/shutdown_dialog_subscription_id_hint"
+      android:inputType="text"
+      android:spinnerMode="dropdown"
+      android:textSize="16sp"
+      android:layout_marginTop="8dp"
+      app:layout_constraintStart_toStartOf="parent"
+      app:layout_constraintBottom_toTopOf="@+id/shutdownBtn"
+      app:layout_constraintTop_toBottomOf="@id/titleSubscriptionId" />
+
+  <Button
+      android:id="@+id/shutdownBtn"
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      android:enabled="false"
+      android:text="@string/shutdown_dialog_shutdown_btn_text"
+      app:layout_constraintBottom_toBottomOf="parent"
+      app:layout_constraintEnd_toStartOf="@id/shutdownAllBtn"
+      app:layout_constraintStart_toStartOf="parent" />
+
+  <Button
+      android:id="@+id/shutdownAllBtn"
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      android:enabled="false"
+      android:text="@string/shutdown_dialog_shutdown_all_btn_text"
+      app:layout_constraintBottom_toBottomOf="parent"
+      app:layout_constraintStart_toEndOf="@id/shutdownBtn"
+      app:layout_constraintEnd_toEndOf="parent" />
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/examples/android/CHIPTool/app/src/main/res/layout/subscribe_dialog.xml b/examples/android/CHIPTool/app/src/main/res/layout/subscribe_dialog.xml
index a80d414..3830ddf 100644
--- a/examples/android/CHIPTool/app/src/main/res/layout/subscribe_dialog.xml
+++ b/examples/android/CHIPTool/app/src/main/res/layout/subscribe_dialog.xml
@@ -35,28 +35,6 @@
       app:layout_constraintStart_toStartOf="parent"
       app:layout_constraintTop_toBottomOf="@id/minIntervalEd" />
   <TextView
-      android:id="@+id/titleisUrgent"
-      android:layout_width="match_parent"
-      android:layout_height="wrap_content"
-      android:text="@string/subscribe_dialog_is_urgent_hint"
-      android:textSize="16sp"
-      android:layout_marginTop="8dp"
-      app:layout_constraintStart_toStartOf="parent"
-      app:layout_constraintTop_toBottomOf="@id/maxIntervalEd"
-      />
-  <Spinner
-      android:id="@+id/isUrgentSp"
-      android:layout_width="wrap_content"
-      android:layout_height="wrap_content"
-      android:hint="@string/subscribe_dialog_is_urgent_hint"
-      android:entries="@array/chip_select_menu"
-      android:inputType="text"
-      android:spinnerMode="dropdown"
-      android:textSize="16sp"
-      android:layout_marginTop="8dp"
-      app:layout_constraintStart_toStartOf="parent"
-      app:layout_constraintTop_toBottomOf="@id/titleisUrgent" />
-  <TextView
       android:id="@+id/titleKeepSubscriptions"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
@@ -64,7 +42,7 @@
       android:textSize="16sp"
       android:layout_marginTop="8dp"
       app:layout_constraintStart_toStartOf="parent"
-      app:layout_constraintTop_toBottomOf="@id/isUrgentSp"
+      app:layout_constraintTop_toBottomOf="@id/maxIntervalEd"
       />
   <Spinner
       android:id="@+id/keepSubscriptionsSp"
diff --git a/examples/android/CHIPTool/app/src/main/res/layout/wildcard_fragment.xml b/examples/android/CHIPTool/app/src/main/res/layout/wildcard_fragment.xml
index cf68b2c..a6c7762 100644
--- a/examples/android/CHIPTool/app/src/main/res/layout/wildcard_fragment.xml
+++ b/examples/android/CHIPTool/app/src/main/res/layout/wildcard_fragment.xml
@@ -1,5 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:paddingStart="16dp"
@@ -15,12 +16,47 @@
       app:layout_constraintStart_toStartOf="parent"
       app:layout_constraintTop_toTopOf="parent"/>
 
+  <RadioGroup
+      android:id="@+id/selectTypeRadioGroup"
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      android:orientation="horizontal"
+      app:layout_constraintEnd_toEndOf="parent"
+      app:layout_constraintStart_toStartOf="parent"
+      app:layout_constraintTop_toBottomOf="@id/addressUpdateFragment">
+    <RadioButton
+        android:id="@+id/readRadioBtn"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:checked="true"
+        android:textSize="16sp"
+        android:text="@string/wildcard_read_btn_text"/>
+    <RadioButton
+        android:id="@+id/subscribeRadioBtn"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textSize="16sp"
+        android:text="@string/wildcard_subscribe_btn_text"/>
+    <RadioButton
+        android:id="@+id/writeRadioBtn"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textSize="16sp"
+        android:text="@string/wildcard_write_btn_text"/>
+    <RadioButton
+        android:id="@+id/invokeRadioBtn"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textSize="16sp"
+        android:text="@string/wildcard_invoke_btn_text"/>
+  </RadioGroup>
+
   <androidx.constraintlayout.helper.widget.Flow
       android:id="@+id/flow"
       android:layout_width="0dp"
       android:layout_height="wrap_content"
       android:layout_marginTop="8dp"
-      app:constraint_referenced_ids="endpointIdLabel,endpointIdEd,clusterIdLabel,clusterIdEd,attributeIdLabel,attributeIdEd,eventIdLabel,eventIdEd,commandIdLabel,commandIdEd"
+      app:constraint_referenced_ids="endpointIdLabel,endpointIdEd,clusterIdLabel,clusterIdEd,attributeIdLabel,attributeIdEd,eventIdLabel,eventIdEd,commandIdLabel,commandIdEd,isUrgentLabel,isUrgentSp"
       app:flow_horizontalBias="0.0"
       app:flow_horizontalGap="8dp"
       app:flow_horizontalStyle="packed"
@@ -28,7 +64,7 @@
       app:flow_wrapMode="aligned"
       app:layout_constraintEnd_toEndOf="parent"
       app:layout_constraintStart_toStartOf="parent"
-      app:layout_constraintTop_toBottomOf="@id/addressUpdateFragment" />
+      app:layout_constraintTop_toBottomOf="@id/selectTypeRadioGroup" />
 
   <TextView
       android:id="@+id/endpointIdLabel"
@@ -98,6 +134,7 @@
       android:layout_height="wrap_content"
       android:padding="8dp"
       android:textSize="16sp"
+      android:visibility="gone"
       android:text="@string/command_id_label" />
 
   <EditText
@@ -106,91 +143,104 @@
       android:layout_height="wrap_content"
       android:layout_margin="16dp"
       android:inputType="number"
+      android:visibility="gone"
       android:hint="@string/wildcard_help_label"/>
 
-  <Button
-      android:id="@+id/readBtn"
+  <TextView
+      android:id="@+id/isUrgentLabel"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
-      android:padding="16dp"
-      android:layout_marginTop="16dp"
-      android:layout_marginBottom="8dp"
-      android:layout_gravity="center"
-      android:text="@string/wildcard_read_btn_text"
+      android:padding="8dp"
       android:textSize="16sp"
-      app:layout_constraintStart_toStartOf="parent"
-      app:layout_constraintEnd_toStartOf="@id/writeBtn"
-      app:layout_constraintTop_toBottomOf="@id/flow"/>
+      android:visibility="gone"
+      android:text="@string/isUrgent_label"/>
 
-  <Button
-      android:id="@+id/writeBtn"
-      android:layout_width="wrap_content"
+  <Spinner
+      android:id="@+id/isUrgentSp"
+      android:layout_width="0dp"
       android:layout_height="wrap_content"
-      android:padding="16dp"
-      android:layout_marginTop="16dp"
-      android:layout_marginBottom="8dp"
-      android:layout_gravity="center"
-      android:text="@string/wildcard_write_btn_text"
+      android:hint="@string/isUrgent_label"
+      android:entries="@array/chip_select_menu"
+      android:inputType="text"
+      android:spinnerMode="dropdown"
       android:textSize="16sp"
-      app:layout_constraintStart_toEndOf="@id/readBtn"
-      app:layout_constraintEnd_toStartOf="@id/invokeBtn"
-      app:layout_constraintTop_toBottomOf="@id/flow"/>
+      android:visibility="gone"/>
 
-  <Button
-      android:id="@+id/invokeBtn"
+  <androidx.constraintlayout.widget.ConstraintLayout
+      android:id="@+id/addLayout"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
-      android:padding="16dp"
-      android:layout_marginTop="16dp"
-      android:layout_marginBottom="8dp"
-      android:layout_gravity="center"
-      android:text="@string/wildcard_invoke_btn_text"
-      android:textSize="16sp"
-      app:layout_constraintStart_toEndOf="@id/writeBtn"
-      app:layout_constraintEnd_toStartOf="@id/subscribeBtn"
-      app:layout_constraintTop_toBottomOf="@id/flow"/>
-
-  <Button
-      android:id="@+id/subscribeBtn"
-      android:layout_width="wrap_content"
-      android:layout_height="wrap_content"
-      android:padding="16dp"
-      android:layout_marginTop="16dp"
-      android:layout_marginBottom="8dp"
-      android:layout_gravity="center"
-      android:text="@string/wildcard_subscribe_btn_text"
-      android:textSize="16sp"
-      app:layout_constraintStart_toEndOf="@id/invokeBtn"
+      android:orientation="horizontal"
       app:layout_constraintEnd_toEndOf="parent"
-      app:layout_constraintTop_toBottomOf="@id/flow"/>
-
-  <Button
-      android:id="@+id/readEventBtn"
-      android:layout_width="wrap_content"
-      android:layout_height="wrap_content"
-      android:padding="16dp"
-      android:layout_marginTop="16dp"
-      android:layout_marginBottom="8dp"
-      android:layout_gravity="center"
-      android:text="@string/wildcard_read_event_btn_text"
-      android:textSize="16sp"
       app:layout_constraintStart_toStartOf="parent"
-      app:layout_constraintEnd_toStartOf="@id/subscribeEventBtn"
-      app:layout_constraintTop_toBottomOf="@id/readBtn"/>
+      app:layout_constraintTop_toBottomOf="@id/flow">
+    <TextView
+        android:id="@+id/sendListView"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"/>
+    <Button
+        android:id="@+id/addAttributeBtn"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:padding="16dp"
+        android:text="@string/wildcard_add_attribute_btn_text"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintEnd_toStartOf="@id/addEventBtn"
+        app:layout_constraintTop_toBottomOf="@id/sendListView"
+        android:textSize="16sp" />
+    <Button
+        android:id="@+id/addEventBtn"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:padding="16dp"
+        android:text="@string/wildcard_add_event_btn_text"
+        app:layout_constraintStart_toEndOf="@id/addAttributeBtn"
+        app:layout_constraintEnd_toStartOf="@id/resetBtn"
+        app:layout_constraintTop_toBottomOf="@id/sendListView"
+        android:textSize="16sp" />
+    <Button
+        android:id="@+id/resetBtn"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:padding="16dp"
+        android:text="@string/wildcard_reset_btn_text"
+        app:layout_constraintStart_toEndOf="@id/addEventBtn"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/sendListView"
+        android:textSize="16sp" />
+  </androidx.constraintlayout.widget.ConstraintLayout>
+
+    <Button
+        android:id="@+id/sendBtn"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:padding="16dp"
+        android:layout_marginTop="16dp"
+        android:layout_marginBottom="8dp"
+        android:layout_gravity="center"
+        android:text="@string/wildcard_send_btn_text"
+        android:textSize="16sp"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintEnd_toStartOf="@id/shutdownSubscriptionBtn"
+        app:layout_constraintTop_toBottomOf="@id/addLayout"/>
 
   <Button
-      android:id="@+id/subscribeEventBtn"
+      android:id="@+id/shutdownSubscriptionBtn"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:padding="16dp"
       android:layout_marginTop="16dp"
       android:layout_marginBottom="8dp"
       android:layout_gravity="center"
-      android:text="@string/wildcard_subscribe_event_btn_text"
+      android:text="@string/wildcard_shutdown_subscription_btn_text"
       android:textSize="16sp"
-      app:layout_constraintStart_toEndOf="@id/readEventBtn"
+      android:visibility="gone"
+      app:layout_constraintStart_toEndOf="@id/sendBtn"
       app:layout_constraintEnd_toEndOf="parent"
-      app:layout_constraintTop_toBottomOf="@id/subscribeBtn"/>
+      app:layout_constraintTop_toBottomOf="@id/addLayout"/>
 
   <ScrollView
       android:layout_width="match_parent"
@@ -198,7 +248,7 @@
       android:fadeScrollbars="false"
       app:layout_constraintBottom_toBottomOf="parent"
       app:layout_constraintStart_toStartOf="parent"
-      app:layout_constraintTop_toBottomOf="@id/subscribeEventBtn">
+      app:layout_constraintTop_toBottomOf="@id/sendBtn">
     <TextView
         android:id="@+id/outputTv"
         android:layout_width="match_parent"
@@ -208,6 +258,4 @@
         android:singleLine="false"
         android:textSize="20sp" />
   </ScrollView>
-
-
 </androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/examples/android/CHIPTool/app/src/main/res/values/strings.xml b/examples/android/CHIPTool/app/src/main/res/values/strings.xml
index 7ca5726..5a37d3e 100644
--- a/examples/android/CHIPTool/app/src/main/res/values/strings.xml
+++ b/examples/android/CHIPTool/app/src/main/res/values/strings.xml
@@ -142,7 +142,6 @@
     <string name="subscribe_dialog_subscribe_btn_text">Subscribe</string>
     <string name="subscribe_dialog_min_interval_hint">Minimum interval (seconds)</string>
     <string name="subscribe_dialog_max_interval_hint">Maximum interval (seconds)</string>
-    <string name="subscribe_dialog_is_urgent_hint">is Urgent Event (bool)</string>
     <string name="subscribe_dialog_keep_subscriptions_hint">keep subscriptions (bool)</string>
     <string name="subscribe_dialog_is_fabric_filtered_hint">is Fabric Filtered (bool)</string>
 
@@ -163,18 +162,32 @@
     <string name="invoke_dialog_im_timeout_hint">Timeout</string>
     <string name="invoke_dialog_user_guide_hint">Usage : [Type]:[Value],[Type]:[Value]\nType:UnsignedInt, Int, Boolean, Float, Double, String, ByteArray(Hex)\nExample:\nLevelControlCluster, moveToLevel : UnsignedInt:100,UnsignedInt:0</string>
 
+    <string name="shutdown_dialog_title_text">Shutdown Subscription</string>
+    <string name="shutdown_dialog_fabric_index_hint">Fabric Index</string>
+    <string name="shutdown_dialog_subscription_id_hint">Subscription Id</string>
+    <string name="shutdown_dialog_shutdown_btn_text">Shutdown</string>
+    <string name="shutdown_dialog_shutdown_all_btn_text">Shutdown All</string>
+
     <string name="endpoint_id_label">Endpoint ID</string>
     <string name="cluster_id_label">Cluster ID</string>
     <string name="attribute_id_label">Attribute ID</string>
     <string name="event_id_label">Event ID</string>
     <string name="command_id_label">Command ID</string>
+    <string name="isUrgent_label">isUrgent</string>
     <string name="wildcard_help_label">Leave blank for wildcard</string>
     <string name="wildcard_subscribe_btn_text">Subscribe</string>
     <string name="wildcard_read_btn_text">Read</string>
     <string name="wildcard_invoke_btn_text">Invoke</string>
     <string name="wildcard_write_btn_text">Write</string>
+    <string name="wildcard_add_attribute_btn_text">Add Attribute</string>
+    <string name="wildcard_add_event_btn_text">Add Event</string>
+    <string name="wildcard_reset_btn_text">Reset</string>
+    <string name="wildcard_send_btn_text">Send</string>
+    <string name="wildcard_shutdown_subscription_btn_text">Shutdown Subscription</string>
     <string name="wildcard_subscribe_event_btn_text">Subscribe Event</string>
     <string name="wildcard_read_event_btn_text">Read Event</string>
+    <string name="wildcard_empty_error_toast_message">Please add it to the list using Add Button.</string>
+    <string name="wildcard_subscribe_established_toast_message">Subscription established</string>
 
     <string name="provision_custom_flow_btn_text">Provision CHIP device with Custom Flow</string>
     <string name="chip_device_info_commissioning_flow_label">Commissioning Flow:</string>