Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Android] Add Stay Active Button in Chiptool #32983

Merged
merged 4 commits into from
Apr 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)

Expand All @@ -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? {
Expand Down Expand Up @@ -106,6 +110,10 @@ object ChipClient {
return androidPlatform
}

fun setICDCheckInCallback(callback: ICDCheckInCallback) {
icdCheckInCallback = callback
}

/**
* Wrapper around [ChipDeviceController.getConnectedDevicePointer] to return the value directly.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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()
}
Expand All @@ -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
Expand All @@ -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())
}
Expand All @@ -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 {
Expand All @@ -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
}
Expand All @@ -112,3 +262,7 @@ class AddressUpdateFragment : Fragment() {
}
}
}

interface ICDCheckInCallback {
fun notifyCheckInMessage(info: ICDClientInfo)
}
Original file line number Diff line number Diff line change
Expand Up @@ -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"/>

<EditText
android:id="@+id/deviceIdEd"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:inputType="number"
android:textSize="20sp"
android:digits="0123456789ABCDEF"
android:inputType="textCapCharacters"
android:textSize="16sp"
android:hint="@string/enter_device_id_hint_text"
app:layout_constraintStart_toEndOf="@+id/fabricIdEd"
app:layout_constraintStart_toEndOf="@id/fabricIdEd"
app:layout_constraintEnd_toStartOf="@id/epIdEd"
app:layout_constraintTop_toTopOf="parent" />

<EditText
Expand All @@ -31,25 +35,73 @@
android:layout_height="wrap_content"
android:hint="@string/enter_endpoint_id_hint_text"
android:inputType="number"
android:textSize="20sp"
android:textSize="16sp"
android:text ="1"
app:layout_constraintStart_toEndOf="@+id/deviceIdEd"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<androidx.constraintlayout.widget.Barrier
android:id="@+id/barrier"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="bottom"
app:constraint_referenced_ids="fabricIdEd,deviceIdEd,epIdEd" />
<androidx.constraintlayout.widget.Barrier
android:id="@+id/barrier"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="bottom"
app:constraint_referenced_ids="fabricIdEd,deviceIdEd,epIdEd" />

<Switch
android:id="@+id/icdInteractionSwitch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="6dp"
android:layout_marginTop="6dp"
android:layout_marginBottom="6dp"
android:textSize="16sp"
android:text="@string/icd_interaction_text"
app:layout_constraintTop_toBottomOf="@id/fabricIdEd"
app:layout_constraintStart_toStartOf="parent"
tools:ignore="UseSwitchCompatOrMaterialXml" />

<EditText
android:id="@+id/icdActiveDurationEd"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:hint="@string/icd_active_duration_hint_text"
android:inputType="number"
android:textSize="16sp"
android:text ="30"
app:layout_constraintTop_toBottomOf="@id/fabricIdEd"
app:layout_constraintStart_toEndOf="@id/icdInteractionSwitch"
app:layout_constraintEnd_toStartOf="@id/icdProgressBar" />

<ProgressBar
android:id="@+id/icdProgressBar"
android:layout_width="100dp"
android:layout_height="30dp"
style="?android:attr/progressBarStyleHorizontal"
android:layout_marginStart="6dp"
android:layout_marginTop="6dp"
android:layout_marginBottom="6dp"
android:progress="0"
android:max="100"
app:layout_constraintTop_toBottomOf="@id/fabricIdEd"
app:layout_constraintStart_toEndOf="@id/icdActiveDurationEd"
app:layout_constraintEnd_toEndOf="parent"/>

<androidx.constraintlayout.widget.Barrier
android:id="@+id/barrier2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="bottom"
app:constraint_referenced_ids="icdInteractionSwitch,icdActiveDurationEd,icdProgressBar" />

<TextView
android:id="@+id/deviceIdTv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="20sp"
android:layout_marginTop="6dp"
android:textSize="16sp"
android:text = "@string/commissioned_device_id_text"
app:layout_constraintTop_toBottomOf="@id/fabricIdEd"
app:layout_constraintTop_toBottomOf="@id/icdInteractionSwitch"
/>

<Spinner
Expand All @@ -60,4 +112,6 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/deviceIdTv" />



</androidx.constraintlayout.widget.ConstraintLayout>
Loading
Loading