From 8e71fc853b4ddf1042f9f7a70f6a423efe1a8831 Mon Sep 17 00:00:00 2001 From: Joonhaeng Heo Date: Mon, 15 Apr 2024 17:37:42 +0900 Subject: [PATCH 1/2] Add send Stay Active in Android Chiptool --- .../com/google/chip/chiptool/ChipClient.kt | 8 + .../clusterclient/AddressUpdateFragment.kt | 162 +++++++++++++++++- .../res/layout/address_update_fragment.xml | 84 +++++++-- .../app/src/main/res/values/strings.xml | 2 + 4 files changed, 237 insertions(+), 19 deletions(-) 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 From 5d638abfaecb4bc6ab8338d78608a5c82ae7f8a7 Mon Sep 17 00:00:00 2001 From: Joonhaeng Heo Date: Mon, 15 Apr 2024 17:43:52 +0900 Subject: [PATCH 2/2] Add kotlin static exception --- kotlin-detect-config.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/kotlin-detect-config.yaml b/kotlin-detect-config.yaml index c8398293d93e8b..f039b94ee0deae 100644 --- a/kotlin-detect-config.yaml +++ b/kotlin-detect-config.yaml @@ -287,6 +287,7 @@ complexity: excludes: - "**/examples/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/CHIPToolActivity.kt" - "**/examples/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/clusterclient/clusterinteraction/ClusterDetailFragment.kt" + - "**/examples/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/clusterclient/AddressUpdateFragment.kt" - "**/examples/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/clusterclient/GroupSettingFragment.kt" - "**/examples/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/clusterclient/OnOffClientFragment.kt" - "**/examples/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/clusterclient/OtaProviderClientFragment.kt"