diff --git a/examples/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/ChipClient.kt b/examples/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/ChipClient.kt
index 0534f7f3ee3589..bee06caf2b50d6 100644
--- a/examples/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/ChipClient.kt
+++ b/examples/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/ChipClient.kt
@@ -33,6 +33,7 @@ import chip.platform.NsdManagerServiceResolver
import chip.platform.PreferencesConfigurationManager
import chip.platform.PreferencesKeyValueStoreManager
import com.google.chip.chiptool.attestation.ExampleAttestationTrustStoreDelegate
+import com.google.chip.chiptool.clusterclient.ICDCheckInCallback
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlinx.coroutines.suspendCancellableCoroutine
@@ -45,6 +46,8 @@ object ChipClient {
/* 0xFFF4 is a test vendor ID, replace with your assigned company ID */
const val VENDOR_ID = 0xFFF4
+ private var icdCheckInCallback: ICDCheckInCallback? = null
+
fun getDeviceController(context: Context): ChipDeviceController {
getAndroidChipPlatform(context)
@@ -67,6 +70,7 @@ object ChipClient {
object : ICDCheckInDelegate {
override fun onCheckInComplete(info: ICDClientInfo) {
Log.d(TAG, "onCheckInComplete : $info")
+ icdCheckInCallback?.notifyCheckInMessage(info)
}
override fun onKeyRefreshNeeded(info: ICDClientInfo): ByteArray? {
@@ -106,6 +110,10 @@ object ChipClient {
return androidPlatform
}
+ fun setICDCheckInCallback(callback: ICDCheckInCallback) {
+ icdCheckInCallback = callback
+ }
+
/**
* Wrapper around [ChipDeviceController.getConnectedDevicePointer] to return the value directly.
*/
diff --git a/examples/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/clusterclient/AddressUpdateFragment.kt b/examples/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/clusterclient/AddressUpdateFragment.kt
index b4c87dc53c6538..1fa6fadcff9ed3 100644
--- a/examples/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/clusterclient/AddressUpdateFragment.kt
+++ b/examples/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/clusterclient/AddressUpdateFragment.kt
@@ -2,25 +2,37 @@ package com.google.chip.chiptool.clusterclient
import android.content.Context
import android.os.Bundle
+import android.os.Handler
+import android.os.Looper
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.Toast
import androidx.fragment.app.Fragment
+import androidx.lifecycle.lifecycleScope
+import chip.devicecontroller.ChipClusterException
+import chip.devicecontroller.ChipClusters
import chip.devicecontroller.ChipDeviceController
+import chip.devicecontroller.ICDClientInfo
import com.google.chip.chiptool.ChipClient
import com.google.chip.chiptool.databinding.AddressUpdateFragmentBinding
import com.google.chip.chiptool.util.DeviceIdUtil
+import kotlin.coroutines.resume
+import kotlin.coroutines.resumeWithException
+import kotlin.coroutines.suspendCoroutine
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
/** Fragment for updating the address of a device given its fabric and node ID. */
-class AddressUpdateFragment : Fragment() {
+class AddressUpdateFragment : ICDCheckInCallback, Fragment() {
private val deviceController: ChipDeviceController
get() = ChipClient.getDeviceController(requireContext())
val deviceId: Long
- get() = binding.deviceIdEd.text.toString().toULong().toLong()
+ get() = binding.deviceIdEd.text.toString().toULong(16).toLong()
var endpointId: Int
get() = binding.epIdEd.text.toString().toInt()
@@ -32,22 +44,44 @@ class AddressUpdateFragment : Fragment() {
private val binding
get() = _binding!!
+ private lateinit var scope: CoroutineScope
+
+ private var icdDeviceId: Long = 0L
+ private var icdTotalRemainStayActiveTimeMs = 0L
+ private var icdDeviceRemainStayActiveTimeMs = 0L
+ private var isSendingStayActiveCommand = false
+ private val icdRequestActiveDurationMs: Long
+ get() = binding.icdActiveDurationEd.text.toString().toLong() * 1000
+
+ private val handler = Handler(Looper.getMainLooper())
+
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = AddressUpdateFragmentBinding.inflate(inflater, container, false)
+ scope = viewLifecycleOwner.lifecycleScope
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
+ ChipClient.setICDCheckInCallback(this)
+
val compressedFabricId = deviceController.compressedFabricId
- binding.fabricIdEd.setText(compressedFabricId.toULong().toString().padStart(16, '0'))
+ binding.fabricIdEd.setText(compressedFabricId.toULong().toString(16).padStart(16, '0'))
binding.deviceIdEd.setText(DeviceIdUtil.getLastDeviceId(requireContext()).toString(16))
binding.epIdEd.setText(endpointId.toString())
+ binding.icdActiveDurationEd.setText((ICD_STAY_ACTIVE_DURATION / 1000).toString())
+
+ binding.icdInteractionSwitch.setOnClickListener {
+ val isChecked = binding.icdInteractionSwitch.isChecked
+ if (updateUIForICDInteractionSwitch(isChecked)) {
+ icdInteractionSwitchClick(isChecked)
+ }
+ }
updateDeviceIdSpinner()
}
@@ -68,6 +102,61 @@ class AddressUpdateFragment : Fragment() {
}
}
+ private fun icdInteractionSwitchClick(isEnabled: Boolean) {
+ if (isEnabled) {
+ icdDeviceId = deviceId
+ } else {
+ icdDeviceId = 0
+ if (icdDeviceRemainStayActiveTimeMs != 0L || icdTotalRemainStayActiveTimeMs != 0L) {
+ scope.launch {
+ icdDeviceRemainStayActiveTimeMs = sendStayActive(0L)
+ icdTotalRemainStayActiveTimeMs = icdDeviceRemainStayActiveTimeMs
+ }
+ }
+ }
+ }
+
+ private suspend fun sendStayActive(duration: Long): Long {
+ isSendingStayActiveCommand = true
+ val devicePtr =
+ try {
+ ChipClient.getConnectedDevicePointer(requireContext(), deviceId)
+ } catch (e: IllegalStateException) {
+ Log.d(TAG, "getConnectedDevicePointer exception", e)
+ showToastMessage("Get DevicePointer fail!")
+ throw e
+ }
+
+ val cluster = ChipClusters.IcdManagementCluster(devicePtr, 0)
+ val duration = suspendCoroutine { cont ->
+ cluster.stayActiveRequest(
+ object : ChipClusters.IcdManagementCluster.StayActiveResponseCallback {
+ override fun onError(error: Exception) {
+ cont.resumeWithException(error)
+ }
+
+ override fun onSuccess(promisedActiveDuration: Long) {
+ cont.resume(promisedActiveDuration)
+ }
+ },
+ duration
+ )
+ }
+ isSendingStayActiveCommand = false
+ return duration
+ }
+
+ private fun updateUIForICDInteractionSwitch(isEnabled: Boolean): Boolean {
+ val isICD =
+ deviceController.icdClientInfo.firstOrNull { info -> info.peerNodeId == deviceId } != null
+ if (isEnabled && !isICD) {
+ binding.icdInteractionSwitch.isChecked = false
+ return false
+ }
+
+ return true
+ }
+
override fun onDestroyView() {
super.onDestroyView()
_binding = null
@@ -81,6 +170,12 @@ class AddressUpdateFragment : Fragment() {
}
}
+ private fun showToastMessage(msg: String) {
+ requireActivity().runOnUiThread {
+ Toast.makeText(requireActivity(), msg, Toast.LENGTH_SHORT).show()
+ }
+ }
+
fun isGroupId(): Boolean {
return isGroupNodeId(getNodeId())
}
@@ -90,7 +185,58 @@ class AddressUpdateFragment : Fragment() {
}
fun getNodeId(): ULong {
- return binding.deviceIdEd.text.toString().toULong()
+ return binding.deviceIdEd.text.toString().toULong(16)
+ }
+
+ override fun notifyCheckInMessage(info: ICDClientInfo) {
+ if (info.peerNodeId != icdDeviceId) {
+ return
+ }
+
+ scope.launch {
+ try {
+ icdDeviceRemainStayActiveTimeMs = sendStayActive(icdRequestActiveDurationMs)
+ icdTotalRemainStayActiveTimeMs = icdRequestActiveDurationMs
+ turnOnActiveMode()
+ } catch (e: IllegalStateException) {
+ Log.d(TAG, "IlligalStateException", e)
+ } catch (e: ChipClusterException) {
+ Log.d(TAG, "ChipClusterException", e)
+ }
+ }
+ }
+
+ private fun turnOnActiveMode() {
+ requireActivity().runOnUiThread {
+ binding.icdProgressBar.max = (icdTotalRemainStayActiveTimeMs / 1000).toInt()
+ binding.icdProgressBar.progress = (icdTotalRemainStayActiveTimeMs / 1000).toInt()
+ }
+
+ val runnable =
+ object : Runnable {
+ override fun run() {
+ if (icdTotalRemainStayActiveTimeMs >= ICD_PROGRESS_STEP) {
+ icdDeviceRemainStayActiveTimeMs -= ICD_PROGRESS_STEP
+ icdTotalRemainStayActiveTimeMs -= ICD_PROGRESS_STEP
+ handler.postDelayed(this, ICD_PROGRESS_STEP)
+ requireActivity().runOnUiThread {
+ binding.icdProgressBar.progress = (icdTotalRemainStayActiveTimeMs / 1000).toInt()
+ }
+
+ if (
+ !isSendingStayActiveCommand &&
+ (ICD_RESEND_STAY_ACTIVE_TIME > icdDeviceRemainStayActiveTimeMs) &&
+ (ICD_RESEND_STAY_ACTIVE_TIME < icdTotalRemainStayActiveTimeMs)
+ )
+ scope.launch {
+ icdDeviceRemainStayActiveTimeMs = sendStayActive(icdTotalRemainStayActiveTimeMs)
+ }
+ } else {
+ requireActivity().runOnUiThread { binding.icdProgressBar.progress = 0 }
+ }
+ }
+ }
+ handler.post(runnable)
}
companion object {
@@ -99,6 +245,10 @@ class AddressUpdateFragment : Fragment() {
private const val MIN_GROUP_NODE_ID = 0xFFFF_FFFF_FFFF_0000UL
private const val MASK_GROUP_ID = 0x0000_0000_0000_FFFFUL
+ private const val ICD_STAY_ACTIVE_DURATION = 30000L // 30 secs.
+ private const val ICD_PROGRESS_STEP = 1000L // 1 sec.
+ private const val ICD_RESEND_STAY_ACTIVE_TIME = 2000L // 2 secs.
+
fun isGroupNodeId(nodeId: ULong): Boolean {
return nodeId >= MIN_GROUP_NODE_ID
}
@@ -112,3 +262,7 @@ class AddressUpdateFragment : Fragment() {
}
}
}
+
+interface ICDCheckInCallback {
+ fun notifyCheckInMessage(info: ICDClientInfo)
+}
diff --git a/examples/android/CHIPTool/app/src/main/res/layout/address_update_fragment.xml b/examples/android/CHIPTool/app/src/main/res/layout/address_update_fragment.xml
index c7883536078439..f206db45b62e29 100644
--- a/examples/android/CHIPTool/app/src/main/res/layout/address_update_fragment.xml
+++ b/examples/android/CHIPTool/app/src/main/res/layout/address_update_fragment.xml
@@ -9,20 +9,24 @@
android:id="@+id/fabricIdEd"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:textSize="20sp"
+ android:textSize="16sp"
android:hint="@string/enter_fabric_id_hint_text"
- android:inputType="number"
+ android:digits="0123456789ABCDEF"
+ android:inputType="textCapCharacters"
app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintRight_toLeftOf="@+id/deviceIdEd"/>
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintRight_toLeftOf="@id/deviceIdEd"/>
-
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
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 b9f659ac33793c..7b15bbdb53fc3e 100644
--- a/examples/android/CHIPTool/app/src/main/res/values/strings.xml
+++ b/examples/android/CHIPTool/app/src/main/res/values/strings.xml
@@ -31,6 +31,8 @@
Update address
Device address update failed: %1$s
Commissioned Device ID :
+ ICD Stay Active :
+ Duration
Commission with IP address
3840