Skip to content

Commit 25d4643

Browse files
authored
[Android] Add Stay Active Button in Chiptool (#32983)
* Add send Stay Active in Android Chiptool * Add kotlin static exception
1 parent 8d47f40 commit 25d4643

File tree

5 files changed

+238
-19
lines changed

5 files changed

+238
-19
lines changed

examples/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/ChipClient.kt

+8
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import chip.platform.NsdManagerServiceResolver
3333
import chip.platform.PreferencesConfigurationManager
3434
import chip.platform.PreferencesKeyValueStoreManager
3535
import com.google.chip.chiptool.attestation.ExampleAttestationTrustStoreDelegate
36+
import com.google.chip.chiptool.clusterclient.ICDCheckInCallback
3637
import kotlin.coroutines.resume
3738
import kotlin.coroutines.resumeWithException
3839
import kotlinx.coroutines.suspendCancellableCoroutine
@@ -45,6 +46,8 @@ object ChipClient {
4546
/* 0xFFF4 is a test vendor ID, replace with your assigned company ID */
4647
const val VENDOR_ID = 0xFFF4
4748

49+
private var icdCheckInCallback: ICDCheckInCallback? = null
50+
4851
fun getDeviceController(context: Context): ChipDeviceController {
4952
getAndroidChipPlatform(context)
5053

@@ -67,6 +70,7 @@ object ChipClient {
6770
object : ICDCheckInDelegate {
6871
override fun onCheckInComplete(info: ICDClientInfo) {
6972
Log.d(TAG, "onCheckInComplete : $info")
73+
icdCheckInCallback?.notifyCheckInMessage(info)
7074
}
7175

7276
override fun onKeyRefreshNeeded(info: ICDClientInfo): ByteArray? {
@@ -106,6 +110,10 @@ object ChipClient {
106110
return androidPlatform
107111
}
108112

113+
fun setICDCheckInCallback(callback: ICDCheckInCallback) {
114+
icdCheckInCallback = callback
115+
}
116+
109117
/**
110118
* Wrapper around [ChipDeviceController.getConnectedDevicePointer] to return the value directly.
111119
*/

examples/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/clusterclient/AddressUpdateFragment.kt

+158-4
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,37 @@ package com.google.chip.chiptool.clusterclient
22

33
import android.content.Context
44
import android.os.Bundle
5+
import android.os.Handler
6+
import android.os.Looper
57
import android.util.Log
68
import android.view.LayoutInflater
79
import android.view.View
810
import android.view.ViewGroup
911
import android.widget.AdapterView
1012
import android.widget.ArrayAdapter
13+
import android.widget.Toast
1114
import androidx.fragment.app.Fragment
15+
import androidx.lifecycle.lifecycleScope
16+
import chip.devicecontroller.ChipClusterException
17+
import chip.devicecontroller.ChipClusters
1218
import chip.devicecontroller.ChipDeviceController
19+
import chip.devicecontroller.ICDClientInfo
1320
import com.google.chip.chiptool.ChipClient
1421
import com.google.chip.chiptool.databinding.AddressUpdateFragmentBinding
1522
import com.google.chip.chiptool.util.DeviceIdUtil
23+
import kotlin.coroutines.resume
24+
import kotlin.coroutines.resumeWithException
25+
import kotlin.coroutines.suspendCoroutine
26+
import kotlinx.coroutines.CoroutineScope
27+
import kotlinx.coroutines.launch
1628

1729
/** Fragment for updating the address of a device given its fabric and node ID. */
18-
class AddressUpdateFragment : Fragment() {
30+
class AddressUpdateFragment : ICDCheckInCallback, Fragment() {
1931
private val deviceController: ChipDeviceController
2032
get() = ChipClient.getDeviceController(requireContext())
2133

2234
val deviceId: Long
23-
get() = binding.deviceIdEd.text.toString().toULong().toLong()
35+
get() = binding.deviceIdEd.text.toString().toULong(16).toLong()
2436

2537
var endpointId: Int
2638
get() = binding.epIdEd.text.toString().toInt()
@@ -32,22 +44,44 @@ class AddressUpdateFragment : Fragment() {
3244
private val binding
3345
get() = _binding!!
3446

47+
private lateinit var scope: CoroutineScope
48+
49+
private var icdDeviceId: Long = 0L
50+
private var icdTotalRemainStayActiveTimeMs = 0L
51+
private var icdDeviceRemainStayActiveTimeMs = 0L
52+
private var isSendingStayActiveCommand = false
53+
private val icdRequestActiveDurationMs: Long
54+
get() = binding.icdActiveDurationEd.text.toString().toLong() * 1000
55+
56+
private val handler = Handler(Looper.getMainLooper())
57+
3558
override fun onCreateView(
3659
inflater: LayoutInflater,
3760
container: ViewGroup?,
3861
savedInstanceState: Bundle?
3962
): View {
4063
_binding = AddressUpdateFragmentBinding.inflate(inflater, container, false)
64+
scope = viewLifecycleOwner.lifecycleScope
4165
return binding.root
4266
}
4367

4468
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
4569
super.onViewCreated(view, savedInstanceState)
4670

71+
ChipClient.setICDCheckInCallback(this)
72+
4773
val compressedFabricId = deviceController.compressedFabricId
48-
binding.fabricIdEd.setText(compressedFabricId.toULong().toString().padStart(16, '0'))
74+
binding.fabricIdEd.setText(compressedFabricId.toULong().toString(16).padStart(16, '0'))
4975
binding.deviceIdEd.setText(DeviceIdUtil.getLastDeviceId(requireContext()).toString(16))
5076
binding.epIdEd.setText(endpointId.toString())
77+
binding.icdActiveDurationEd.setText((ICD_STAY_ACTIVE_DURATION / 1000).toString())
78+
79+
binding.icdInteractionSwitch.setOnClickListener {
80+
val isChecked = binding.icdInteractionSwitch.isChecked
81+
if (updateUIForICDInteractionSwitch(isChecked)) {
82+
icdInteractionSwitchClick(isChecked)
83+
}
84+
}
5185

5286
updateDeviceIdSpinner()
5387
}
@@ -68,6 +102,61 @@ class AddressUpdateFragment : Fragment() {
68102
}
69103
}
70104

105+
private fun icdInteractionSwitchClick(isEnabled: Boolean) {
106+
if (isEnabled) {
107+
icdDeviceId = deviceId
108+
} else {
109+
icdDeviceId = 0
110+
if (icdDeviceRemainStayActiveTimeMs != 0L || icdTotalRemainStayActiveTimeMs != 0L) {
111+
scope.launch {
112+
icdDeviceRemainStayActiveTimeMs = sendStayActive(0L)
113+
icdTotalRemainStayActiveTimeMs = icdDeviceRemainStayActiveTimeMs
114+
}
115+
}
116+
}
117+
}
118+
119+
private suspend fun sendStayActive(duration: Long): Long {
120+
isSendingStayActiveCommand = true
121+
val devicePtr =
122+
try {
123+
ChipClient.getConnectedDevicePointer(requireContext(), deviceId)
124+
} catch (e: IllegalStateException) {
125+
Log.d(TAG, "getConnectedDevicePointer exception", e)
126+
showToastMessage("Get DevicePointer fail!")
127+
throw e
128+
}
129+
130+
val cluster = ChipClusters.IcdManagementCluster(devicePtr, 0)
131+
val duration = suspendCoroutine { cont ->
132+
cluster.stayActiveRequest(
133+
object : ChipClusters.IcdManagementCluster.StayActiveResponseCallback {
134+
override fun onError(error: Exception) {
135+
cont.resumeWithException(error)
136+
}
137+
138+
override fun onSuccess(promisedActiveDuration: Long) {
139+
cont.resume(promisedActiveDuration)
140+
}
141+
},
142+
duration
143+
)
144+
}
145+
isSendingStayActiveCommand = false
146+
return duration
147+
}
148+
149+
private fun updateUIForICDInteractionSwitch(isEnabled: Boolean): Boolean {
150+
val isICD =
151+
deviceController.icdClientInfo.firstOrNull { info -> info.peerNodeId == deviceId } != null
152+
if (isEnabled && !isICD) {
153+
binding.icdInteractionSwitch.isChecked = false
154+
return false
155+
}
156+
157+
return true
158+
}
159+
71160
override fun onDestroyView() {
72161
super.onDestroyView()
73162
_binding = null
@@ -81,6 +170,12 @@ class AddressUpdateFragment : Fragment() {
81170
}
82171
}
83172

173+
private fun showToastMessage(msg: String) {
174+
requireActivity().runOnUiThread {
175+
Toast.makeText(requireActivity(), msg, Toast.LENGTH_SHORT).show()
176+
}
177+
}
178+
84179
fun isGroupId(): Boolean {
85180
return isGroupNodeId(getNodeId())
86181
}
@@ -90,7 +185,58 @@ class AddressUpdateFragment : Fragment() {
90185
}
91186

92187
fun getNodeId(): ULong {
93-
return binding.deviceIdEd.text.toString().toULong()
188+
return binding.deviceIdEd.text.toString().toULong(16)
189+
}
190+
191+
override fun notifyCheckInMessage(info: ICDClientInfo) {
192+
if (info.peerNodeId != icdDeviceId) {
193+
return
194+
}
195+
196+
scope.launch {
197+
try {
198+
icdDeviceRemainStayActiveTimeMs = sendStayActive(icdRequestActiveDurationMs)
199+
icdTotalRemainStayActiveTimeMs = icdRequestActiveDurationMs
200+
turnOnActiveMode()
201+
} catch (e: IllegalStateException) {
202+
Log.d(TAG, "IlligalStateException", e)
203+
} catch (e: ChipClusterException) {
204+
Log.d(TAG, "ChipClusterException", e)
205+
}
206+
}
207+
}
208+
209+
private fun turnOnActiveMode() {
210+
requireActivity().runOnUiThread {
211+
binding.icdProgressBar.max = (icdTotalRemainStayActiveTimeMs / 1000).toInt()
212+
binding.icdProgressBar.progress = (icdTotalRemainStayActiveTimeMs / 1000).toInt()
213+
}
214+
215+
val runnable =
216+
object : Runnable {
217+
override fun run() {
218+
if (icdTotalRemainStayActiveTimeMs >= ICD_PROGRESS_STEP) {
219+
icdDeviceRemainStayActiveTimeMs -= ICD_PROGRESS_STEP
220+
icdTotalRemainStayActiveTimeMs -= ICD_PROGRESS_STEP
221+
handler.postDelayed(this, ICD_PROGRESS_STEP)
222+
requireActivity().runOnUiThread {
223+
binding.icdProgressBar.progress = (icdTotalRemainStayActiveTimeMs / 1000).toInt()
224+
}
225+
226+
if (
227+
!isSendingStayActiveCommand &&
228+
(ICD_RESEND_STAY_ACTIVE_TIME > icdDeviceRemainStayActiveTimeMs) &&
229+
(ICD_RESEND_STAY_ACTIVE_TIME < icdTotalRemainStayActiveTimeMs)
230+
)
231+
scope.launch {
232+
icdDeviceRemainStayActiveTimeMs = sendStayActive(icdTotalRemainStayActiveTimeMs)
233+
}
234+
} else {
235+
requireActivity().runOnUiThread { binding.icdProgressBar.progress = 0 }
236+
}
237+
}
238+
}
239+
handler.post(runnable)
94240
}
95241

96242
companion object {
@@ -99,6 +245,10 @@ class AddressUpdateFragment : Fragment() {
99245
private const val MIN_GROUP_NODE_ID = 0xFFFF_FFFF_FFFF_0000UL
100246
private const val MASK_GROUP_ID = 0x0000_0000_0000_FFFFUL
101247

248+
private const val ICD_STAY_ACTIVE_DURATION = 30000L // 30 secs.
249+
private const val ICD_PROGRESS_STEP = 1000L // 1 sec.
250+
private const val ICD_RESEND_STAY_ACTIVE_TIME = 2000L // 2 secs.
251+
102252
fun isGroupNodeId(nodeId: ULong): Boolean {
103253
return nodeId >= MIN_GROUP_NODE_ID
104254
}
@@ -112,3 +262,7 @@ class AddressUpdateFragment : Fragment() {
112262
}
113263
}
114264
}
265+
266+
interface ICDCheckInCallback {
267+
fun notifyCheckInMessage(info: ICDClientInfo)
268+
}

examples/android/CHIPTool/app/src/main/res/layout/address_update_fragment.xml

+69-15
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,24 @@
99
android:id="@+id/fabricIdEd"
1010
android:layout_width="wrap_content"
1111
android:layout_height="wrap_content"
12-
android:textSize="20sp"
12+
android:textSize="16sp"
1313
android:hint="@string/enter_fabric_id_hint_text"
14-
android:inputType="number"
14+
android:digits="0123456789ABCDEF"
15+
android:inputType="textCapCharacters"
1516
app:layout_constraintTop_toTopOf="parent"
16-
app:layout_constraintRight_toLeftOf="@+id/deviceIdEd"/>
17+
app:layout_constraintStart_toStartOf="parent"
18+
app:layout_constraintRight_toLeftOf="@id/deviceIdEd"/>
1719

1820
<EditText
1921
android:id="@+id/deviceIdEd"
2022
android:layout_width="wrap_content"
2123
android:layout_height="wrap_content"
22-
android:inputType="number"
23-
android:textSize="20sp"
24+
android:digits="0123456789ABCDEF"
25+
android:inputType="textCapCharacters"
26+
android:textSize="16sp"
2427
android:hint="@string/enter_device_id_hint_text"
25-
app:layout_constraintStart_toEndOf="@+id/fabricIdEd"
28+
app:layout_constraintStart_toEndOf="@id/fabricIdEd"
29+
app:layout_constraintEnd_toStartOf="@id/epIdEd"
2630
app:layout_constraintTop_toTopOf="parent" />
2731

2832
<EditText
@@ -31,25 +35,73 @@
3135
android:layout_height="wrap_content"
3236
android:hint="@string/enter_endpoint_id_hint_text"
3337
android:inputType="number"
34-
android:textSize="20sp"
38+
android:textSize="16sp"
3539
android:text ="1"
3640
app:layout_constraintStart_toEndOf="@+id/deviceIdEd"
41+
app:layout_constraintEnd_toEndOf="parent"
3742
app:layout_constraintTop_toTopOf="parent" />
3843

39-
<androidx.constraintlayout.widget.Barrier
40-
android:id="@+id/barrier"
41-
android:layout_width="wrap_content"
42-
android:layout_height="wrap_content"
43-
app:barrierDirection="bottom"
44-
app:constraint_referenced_ids="fabricIdEd,deviceIdEd,epIdEd" />
44+
<androidx.constraintlayout.widget.Barrier
45+
android:id="@+id/barrier"
46+
android:layout_width="wrap_content"
47+
android:layout_height="wrap_content"
48+
app:barrierDirection="bottom"
49+
app:constraint_referenced_ids="fabricIdEd,deviceIdEd,epIdEd" />
50+
51+
<Switch
52+
android:id="@+id/icdInteractionSwitch"
53+
android:layout_width="wrap_content"
54+
android:layout_height="wrap_content"
55+
android:layout_marginStart="6dp"
56+
android:layout_marginTop="6dp"
57+
android:layout_marginBottom="6dp"
58+
android:textSize="16sp"
59+
android:text="@string/icd_interaction_text"
60+
app:layout_constraintTop_toBottomOf="@id/fabricIdEd"
61+
app:layout_constraintStart_toStartOf="parent"
62+
tools:ignore="UseSwitchCompatOrMaterialXml" />
63+
64+
<EditText
65+
android:id="@+id/icdActiveDurationEd"
66+
android:layout_width="wrap_content"
67+
android:layout_height="wrap_content"
68+
android:hint="@string/icd_active_duration_hint_text"
69+
android:inputType="number"
70+
android:textSize="16sp"
71+
android:text ="30"
72+
app:layout_constraintTop_toBottomOf="@id/fabricIdEd"
73+
app:layout_constraintStart_toEndOf="@id/icdInteractionSwitch"
74+
app:layout_constraintEnd_toStartOf="@id/icdProgressBar" />
75+
76+
<ProgressBar
77+
android:id="@+id/icdProgressBar"
78+
android:layout_width="100dp"
79+
android:layout_height="30dp"
80+
style="?android:attr/progressBarStyleHorizontal"
81+
android:layout_marginStart="6dp"
82+
android:layout_marginTop="6dp"
83+
android:layout_marginBottom="6dp"
84+
android:progress="0"
85+
android:max="100"
86+
app:layout_constraintTop_toBottomOf="@id/fabricIdEd"
87+
app:layout_constraintStart_toEndOf="@id/icdActiveDurationEd"
88+
app:layout_constraintEnd_toEndOf="parent"/>
89+
90+
<androidx.constraintlayout.widget.Barrier
91+
android:id="@+id/barrier2"
92+
android:layout_width="wrap_content"
93+
android:layout_height="wrap_content"
94+
app:barrierDirection="bottom"
95+
app:constraint_referenced_ids="icdInteractionSwitch,icdActiveDurationEd,icdProgressBar" />
4596

4697
<TextView
4798
android:id="@+id/deviceIdTv"
4899
android:layout_width="wrap_content"
49100
android:layout_height="wrap_content"
50-
android:textSize="20sp"
101+
android:layout_marginTop="6dp"
102+
android:textSize="16sp"
51103
android:text = "@string/commissioned_device_id_text"
52-
app:layout_constraintTop_toBottomOf="@id/fabricIdEd"
104+
app:layout_constraintTop_toBottomOf="@id/icdInteractionSwitch"
53105
/>
54106

55107
<Spinner
@@ -60,4 +112,6 @@
60112
app:layout_constraintEnd_toEndOf="parent"
61113
app:layout_constraintTop_toBottomOf="@id/deviceIdTv" />
62114

115+
116+
63117
</androidx.constraintlayout.widget.ConstraintLayout>

0 commit comments

Comments
 (0)