diff --git a/examples/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/SelectActionFragment.kt b/examples/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/SelectActionFragment.kt
index fb8141e72d33a8..09c3f85a454963 100644
--- a/examples/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/SelectActionFragment.kt
+++ b/examples/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/SelectActionFragment.kt
@@ -76,6 +76,7 @@ class SelectActionFragment : Fragment() {
     binding.groupSettingBtn.setOnClickListener { handleGroupSettingClicked() }
     binding.otaProviderBtn.setOnClickListener { handleOTAProviderClicked() }
     binding.icdBtn.setOnClickListener { handleICDClicked() }
+    binding.modeSelectBtn.setOnClickListener { handleModeSelectClicked() }
 
     return binding.root
   }
@@ -255,6 +256,10 @@ class SelectActionFragment : Fragment() {
     showFragment(ICDFragment.newInstance())
   }
 
+  private fun handleModeSelectClicked() {
+    showFragment(ModeSelectClientFragment.newInstance())
+  }
+
   companion object {
 
     @JvmStatic fun newInstance() = SelectActionFragment()
diff --git a/examples/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/clusterclient/ModeSelectClientFragment.kt b/examples/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/clusterclient/ModeSelectClientFragment.kt
new file mode 100644
index 00000000000000..e19e0fe0a58682
--- /dev/null
+++ b/examples/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/clusterclient/ModeSelectClientFragment.kt
@@ -0,0 +1,297 @@
+package com.google.chip.chiptool.clusterclient
+
+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.EditText
+import android.widget.TextView
+import android.widget.Toast
+import androidx.fragment.app.Fragment
+import androidx.lifecycle.lifecycleScope
+import chip.devicecontroller.ChipClusters
+import chip.devicecontroller.ChipDeviceController
+import chip.devicecontroller.ClusterIDMapping
+import chip.devicecontroller.ClusterIDMapping.ModeSelect
+import chip.devicecontroller.ReportCallback
+import chip.devicecontroller.WriteAttributesCallback
+import chip.devicecontroller.cluster.structs.ModeSelectClusterModeOptionStruct
+import chip.devicecontroller.model.AttributeState
+import chip.devicecontroller.model.AttributeWriteRequest
+import chip.devicecontroller.model.ChipAttributePath
+import chip.devicecontroller.model.ChipEventPath
+import chip.devicecontroller.model.ChipPathId
+import chip.devicecontroller.model.NodeState
+import chip.devicecontroller.model.Status
+import com.google.chip.chiptool.ChipClient
+import com.google.chip.chiptool.R
+import com.google.chip.chiptool.databinding.ModeSelectFragmentBinding
+import java.util.Optional
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+import matter.tlv.AnonymousTag
+import matter.tlv.TlvReader
+import matter.tlv.TlvWriter
+
+class ModeSelectClientFragment : Fragment() {
+  private val deviceController: ChipDeviceController
+    get() = ChipClient.getDeviceController(requireContext())
+
+  private lateinit var scope: CoroutineScope
+
+  private lateinit var addressUpdateFragment: AddressUpdateFragment
+
+  private var _binding: ModeSelectFragmentBinding? = null
+
+  private val startUpMode: UInt
+    get() = binding.startUpModeEd.text.toString().toUIntOrNull() ?: 0U
+
+  private val onMode: UInt
+    get() = binding.onModeEd.text.toString().toUIntOrNull() ?: 0U
+
+  private val currentMode: Int
+    get() = binding.supportedModesSp.selectedItem.toString().split("-")[0].toInt()
+
+  private val binding
+    get() = _binding!!
+
+  override fun onCreateView(
+    inflater: LayoutInflater,
+    container: ViewGroup?,
+    savedInstanceState: Bundle?
+  ): View {
+    _binding = ModeSelectFragmentBinding.inflate(inflater, container, false)
+    scope = viewLifecycleOwner.lifecycleScope
+
+    addressUpdateFragment =
+      childFragmentManager.findFragmentById(R.id.addressUpdateFragment) as AddressUpdateFragment
+
+    binding.readAttributeBtn.setOnClickListener { scope.launch { readAttributeBtnClick() } }
+    binding.changeToModeBtn.setOnClickListener { scope.launch { changeToModeBtnClick() } }
+    binding.onModeWriteBtn.setOnClickListener {
+      scope.launch { writeAttributeBtnClick(ClusterIDMapping.ModeSelect.Attribute.OnMode, onMode) }
+    }
+    binding.startUpModeWriteBtn.setOnClickListener {
+      scope.launch {
+        writeAttributeBtnClick(ClusterIDMapping.ModeSelect.Attribute.StartUpMode, startUpMode)
+      }
+    }
+
+    return binding.root
+  }
+
+  private suspend fun readAttributeBtnClick() {
+    val endpointId = addressUpdateFragment.endpointId
+    val clusterId = ModeSelect.ID
+    val attributeId = ChipPathId.forWildcard().id
+    val path = ChipAttributePath.newInstance(endpointId, clusterId, attributeId)
+    val devicePtr =
+      try {
+        ChipClient.getConnectedDevicePointer(requireContext(), addressUpdateFragment.deviceId)
+      } catch (e: IllegalStateException) {
+        Log.d(TAG, "getConnectedDevicePointer exception", e)
+        showMessage("Get DevicePointer fail!")
+        return
+      }
+    deviceController.readAttributePath(
+      object : ReportCallback {
+        override fun onError(
+          attributePath: ChipAttributePath?,
+          eventPath: ChipEventPath?,
+          e: Exception
+        ) {
+          requireActivity().runOnUiThread {
+            Toast.makeText(
+                requireActivity(),
+                R.string.ota_provider_invalid_attribute,
+                Toast.LENGTH_SHORT
+              )
+              .show()
+          }
+        }
+
+        override fun onReport(nodeState: NodeState?) {
+          val attributeStates =
+            nodeState?.getEndpointState(endpointId)?.getClusterState(clusterId)?.attributeStates
+              ?: return
+
+          requireActivity().runOnUiThread {
+            val description = attributeStates[ClusterIDMapping.ModeSelect.Attribute.Description.id]
+            binding.descriptionEd.setText(description?.value?.toString())
+
+            val standardNamespace =
+              attributeStates[ClusterIDMapping.ModeSelect.Attribute.StandardNamespace.id]
+            binding.standardNamespaceEd.setText(standardNamespace?.value?.toString())
+
+            val currentMode = attributeStates[ClusterIDMapping.ModeSelect.Attribute.CurrentMode.id]
+            binding.currentModeEd.setText(currentMode?.value?.toString())
+
+            setVisibility(
+              attributeStates[ClusterIDMapping.ModeSelect.Attribute.StartUpMode.id],
+              binding.startUpModeEd,
+              binding.startUpModeTv,
+              binding.startUpModeWriteBtn
+            )
+            setVisibility(
+              attributeStates[ClusterIDMapping.ModeSelect.Attribute.OnMode.id],
+              binding.onModeEd,
+              binding.onModeTv,
+              binding.onModeWriteBtn
+            )
+
+            val supportedModesTlv =
+              attributeStates[ClusterIDMapping.ModeSelect.Attribute.SupportedModes.id]?.tlv
+
+            supportedModesTlv?.let {
+              setSupportedModeSpinner(it, currentMode?.value?.toString()?.toUInt())
+            }
+          }
+        }
+      },
+      devicePtr,
+      listOf<ChipAttributePath>(path),
+      0
+    )
+  }
+
+  private fun setVisibility(
+    attribute: AttributeState?,
+    modeEd: EditText,
+    modeTv: TextView,
+    writeBtn: TextView
+  ) {
+    val modeVisibility =
+      if (attribute != null) {
+        modeEd.setText(attribute.value?.toString() ?: "NULL")
+        View.VISIBLE
+      } else {
+        View.GONE
+      }
+    modeEd.visibility = modeVisibility
+    modeTv.visibility = modeVisibility
+    writeBtn.visibility = modeVisibility
+  }
+
+  private fun setSupportedModeSpinner(supportedModesTlv: ByteArray, currentModeValue: UInt?) {
+    var pos = 0
+    var currentItemId = 0
+    val modeOptionStructList: List<ModeSelectClusterModeOptionStruct>
+    TlvReader(supportedModesTlv).also {
+      modeOptionStructList = buildList {
+        it.enterArray(AnonymousTag)
+        while (!it.isEndOfContainer()) {
+          val struct = ModeSelectClusterModeOptionStruct.fromTlv(AnonymousTag, it)
+          add(struct)
+          if (currentModeValue != null && struct.mode == currentModeValue) {
+            currentItemId = pos
+          }
+          pos++
+        }
+        it.exitContainer()
+      }
+      binding.supportedModesSp.adapter =
+        ArrayAdapter(
+          requireContext(),
+          android.R.layout.simple_spinner_dropdown_item,
+          modeOptionStructList.map { it.show() }
+        )
+      binding.supportedModesSp.setSelection(currentItemId)
+      binding.currentModeEd.setText(binding.supportedModesSp.selectedItem.toString())
+    }
+  }
+
+  private suspend fun changeToModeBtnClick() {
+    val devicePtr =
+      try {
+        ChipClient.getConnectedDevicePointer(requireContext(), addressUpdateFragment.deviceId)
+      } catch (e: IllegalStateException) {
+        Log.d(TAG, "getConnectedDevicePointer exception", e)
+        showMessage("Get DevicePointer fail!")
+        return
+      }
+    ChipClusters.ModeSelectCluster(devicePtr, addressUpdateFragment.endpointId)
+      .changeToMode(
+        object : ChipClusters.DefaultClusterCallback {
+          override fun onError(error: java.lang.Exception?) {
+            Log.d(TAG, "onError", error)
+            showMessage("Error : ${error.toString()}")
+          }
+
+          override fun onSuccess() {
+            showMessage("Change Success")
+            scope.launch { readAttributeBtnClick() }
+          }
+        },
+        currentMode
+      )
+  }
+
+  private suspend fun writeAttributeBtnClick(attribute: ModeSelect.Attribute, value: UInt) {
+    val clusterId = ModeSelect.ID
+    val devicePtr =
+      try {
+        ChipClient.getConnectedDevicePointer(requireContext(), addressUpdateFragment.deviceId)
+      } catch (e: IllegalStateException) {
+        Log.d(TAG, "getConnectedDevicePointer exception", e)
+        showMessage("Get DevicePointer fail!")
+        return
+      }
+    deviceController.write(
+      object : WriteAttributesCallback {
+        override fun onError(attributePath: ChipAttributePath?, ex: java.lang.Exception?) {
+          showMessage("Write ${attribute.name} failure $ex")
+          Log.e(TAG, "Write ${attribute.name} failure", ex)
+        }
+
+        override fun onResponse(attributePath: ChipAttributePath, status: Status) {
+          showMessage("Write ${attribute.name} response: $status")
+        }
+      },
+      devicePtr,
+      listOf(
+        AttributeWriteRequest.newInstance(
+          addressUpdateFragment.endpointId,
+          clusterId,
+          attribute.id,
+          TlvWriter().put(AnonymousTag, value).getEncoded(),
+          Optional.empty()
+        )
+      ),
+      0,
+      0
+    )
+  }
+
+  private fun ModeSelectClusterModeOptionStruct.show(): String {
+    val value = this
+    return StringBuilder()
+      .apply {
+        append("${value.mode}-${value.label}")
+        append("[")
+        for (semanticTag in value.semanticTags) {
+          append("${semanticTag.value}:${semanticTag.mfgCode}")
+          append(",")
+        }
+        append("]")
+      }
+      .toString()
+  }
+
+  override fun onDestroyView() {
+    super.onDestroyView()
+    deviceController.finishOTAProvider()
+    _binding = null
+  }
+
+  private fun showMessage(msg: String) {
+    requireActivity().runOnUiThread { binding.commandStatusTv.text = msg }
+  }
+
+  companion object {
+    private const val TAG = "ModeSelectClientFragment"
+
+    fun newInstance(): ModeSelectClientFragment = ModeSelectClientFragment()
+  }
+}
diff --git a/examples/android/CHIPTool/app/src/main/res/layout/mode_select_fragment.xml b/examples/android/CHIPTool/app/src/main/res/layout/mode_select_fragment.xml
new file mode 100644
index 00000000000000..3f13ca9b654d36
--- /dev/null
+++ b/examples/android/CHIPTool/app/src/main/res/layout/mode_select_fragment.xml
@@ -0,0 +1,219 @@
+<?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"
+    android:paddingEnd="16dp"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <androidx.fragment.app.FragmentContainerView
+        android:id="@+id/addressUpdateFragment"
+        android:name="com.google.chip.chiptool.clusterclient.AddressUpdateFragment"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"/>
+
+    <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="descriptionTv, descriptionEd, descriptionEmptyTv,
+                                       standardNamespaceTv, standardNamespaceEd, standardNamespaceEmptyTv,
+                                       supportedModesTv, supportedModesSp, changeToModeBtn,
+                                       currentModeTv, currentModeEd, currentModeEmptyTv,
+                                       startUpModeTv, startUpModeEd, startUpModeWriteBtn,
+                                       onModeTv, onModeEd, onModeWriteBtn"
+        app:flow_horizontalBias="0.0"
+        app:flow_horizontalGap="8dp"
+        app:flow_horizontalStyle="packed"
+        app:flow_maxElementsWrap="3"
+        app:flow_wrapMode="aligned"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/addressUpdateFragment" />
+
+    <TextView
+        android:id="@+id/descriptionTv"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:padding="8dp"
+        android:textSize="16sp"
+        android:text="@string/mode_select_description_text" />
+
+    <EditText
+        android:id="@+id/descriptionEd"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:inputType="text"
+        android:hint=""
+        android:enabled="false"
+        tools:ignore="LabelFor" />
+
+    <TextView
+        android:id="@+id/descriptionEmptyTv"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:padding="8dp"
+        android:textSize="16sp"
+        android:text="" />
+
+    <TextView
+        android:id="@+id/standardNamespaceTv"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:padding="8dp"
+        android:textSize="16sp"
+        android:text="@string/mode_select_standard_namespace_text" />
+
+    <EditText
+        android:id="@+id/standardNamespaceEd"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:autofillHints=""
+        android:inputType="text"
+        android:digits="01234567890abcdefABCDEF"
+        android:hint=""
+        android:enabled="false"
+        tools:ignore="LabelFor" />
+
+    <TextView
+        android:id="@+id/standardNamespaceEmptyTv"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:padding="8dp"
+        android:textSize="16sp"
+        android:text="" />
+
+    <TextView
+        android:id="@+id/supportedModesTv"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:padding="8dp"
+        android:textSize="16sp"
+        android:text="@string/mode_select_supported_modes_text" />
+
+    <Spinner
+        android:id="@+id/supportedModesSp"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:autofillHints=""
+        android:inputType="text"
+        android:hint=""
+        tools:ignore="LabelFor" />
+
+    <TextView
+        android:id="@+id/changeToModeBtn"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:padding="8dp"
+        android:textSize="16sp"
+        android:background="@android:color/darker_gray"
+        android:text="@string/mode_select_change_to_mode_text" />
+
+    <TextView
+        android:id="@+id/currentModeTv"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:padding="8dp"
+        android:textSize="16sp"
+        android:text="@string/mode_select_current_mode_text" />
+
+    <EditText
+        android:id="@+id/currentModeEd"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:autofillHints=""
+        android:inputType="number"
+        android:hint=""
+        android:enabled="false"
+        tools:ignore="LabelFor" />
+
+    <TextView
+        android:id="@+id/currentModeEmptyTv"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:padding="8dp"
+        android:textSize="16sp"
+        android:text="" />
+
+    <TextView
+        android:id="@+id/startUpModeTv"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:padding="8dp"
+        android:textSize="16sp"
+        android:text="@string/mode_select_start_up_mode_text" />
+
+    <EditText
+        android:id="@+id/startUpModeEd"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:autofillHints=""
+        android:inputType="number"
+        android:hint=""
+        tools:ignore="LabelFor" />
+
+    <TextView
+        android:id="@+id/startUpModeWriteBtn"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:padding="8dp"
+        android:textSize="16sp"
+        android:background="@android:color/darker_gray"
+        android:text="@string/mode_select_write_text" />
+
+    <TextView
+        android:id="@+id/onModeTv"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:padding="8dp"
+        android:textSize="16sp"
+        android:text="@string/mode_select_on_mode_text" />
+
+    <EditText
+        android:id="@+id/onModeEd"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:autofillHints=""
+        android:inputType="number"
+        android:hint=""
+        tools:ignore="LabelFor" />
+
+    <TextView
+        android:id="@+id/onModeWriteBtn"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:padding="8dp"
+        android:textSize="16sp"
+        android:background="@android:color/darker_gray"
+        android:text="@string/mode_select_write_text" />
+
+    <TextView
+        android:id="@+id/readAttributeBtn"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_margin="8dp"
+        android:background="@android:color/darker_gray"
+        android:padding="8dp"
+        android:text="@string/mode_select_read_attribute_text"
+        android:textSize="16sp"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/flow" />
+
+    <TextView
+        android:id="@+id/commandStatusTv"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:padding="16dp"
+        android:minLines="4"
+        android:singleLine="false"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/readAttributeBtn"
+        android:textSize="16sp" />
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/examples/android/CHIPTool/app/src/main/res/layout/select_action_fragment.xml b/examples/android/CHIPTool/app/src/main/res/layout/select_action_fragment.xml
index db999600999741..e2f5ce8f1d7db0 100644
--- a/examples/android/CHIPTool/app/src/main/res/layout/select_action_fragment.xml
+++ b/examples/android/CHIPTool/app/src/main/res/layout/select_action_fragment.xml
@@ -143,6 +143,14 @@
             android:layout_marginStart="8dp"
             android:layout_marginTop="8dp"
             android:text="@string/icd_btn_text" />
+
+        <Button
+            android:id="@+id/modeSelectBtn"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="8dp"
+            android:layout_marginTop="8dp"
+            android:text="@string/mode_select_btn_text" />
     </LinearLayout>
 
 </ScrollView>
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 5c9e3d0e425f1d..0d01e003160854 100644
--- a/examples/android/CHIPTool/app/src/main/res/values/strings.xml
+++ b/examples/android/CHIPTool/app/src/main/res/values/strings.xml
@@ -292,6 +292,17 @@
     <string name="ota_provider_write_text">Write</string>
     <string name="ota_provider_node_id_text">Node ID</string>
 
+    <string name="mode_select_btn_text">Mode Select</string>
+    <string name="mode_select_description_text">Description</string>
+    <string name="mode_select_standard_namespace_text">Standard Namespace</string>
+    <string name="mode_select_supported_modes_text">Supported Modes</string>
+    <string name="mode_select_current_mode_text">CurrentMode</string>
+    <string name="mode_select_start_up_mode_text">StartUpMode</string>
+    <string name="mode_select_on_mode_text">OnMode</string>
+    <string name="mode_select_read_attribute_text">Read</string>
+    <string name="mode_select_change_to_mode_text">Change</string>
+    <string name="mode_select_write_text">Write</string>
+
     <string name="icd_btn_text">Intermittently Connected Device</string>
     <string name="icd_registration_completed">ICD device registration completed, How to trigger to switch to Active Mode : %1$s</string>
 </resources>