| package com.google.chip.chiptool.clusterclient |
| |
| import android.app.Activity |
| import android.app.AlertDialog |
| import android.content.Intent |
| import android.net.Uri |
| import android.os.Bundle |
| import android.provider.OpenableColumns |
| 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.Button |
| import android.widget.EditText |
| import android.widget.Spinner |
| import android.widget.Toast |
| import androidx.fragment.app.Fragment |
| import androidx.lifecycle.lifecycleScope |
| import chip.devicecontroller.ChipClusters |
| import chip.devicecontroller.ChipClusters.DefaultClusterCallback |
| import chip.devicecontroller.ChipDeviceController |
| import chip.devicecontroller.ClusterIDMapping |
| import chip.devicecontroller.OTAProviderDelegate |
| import chip.devicecontroller.OTAProviderDelegate.QueryImageResponseStatusEnum |
| import chip.devicecontroller.ReportCallback |
| import chip.devicecontroller.WriteAttributesCallback |
| import chip.devicecontroller.cluster.structs.AccessControlClusterAccessControlEntryStruct |
| import chip.devicecontroller.cluster.structs.OtaSoftwareUpdateRequestorClusterProviderLocation |
| import chip.devicecontroller.model.AttributeWriteRequest |
| import chip.devicecontroller.model.ChipAttributePath |
| import chip.devicecontroller.model.ChipEventPath |
| import chip.devicecontroller.model.NodeState |
| import chip.devicecontroller.model.Status |
| import com.google.chip.chiptool.ChipClient |
| import com.google.chip.chiptool.GenericChipDeviceListener |
| import com.google.chip.chiptool.R |
| import com.google.chip.chiptool.databinding.OtaProviderClientFragmentBinding |
| import com.google.chip.chiptool.util.toAny |
| import java.io.BufferedInputStream |
| import java.io.IOException |
| import java.io.InputStream |
| 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 OtaProviderClientFragment : Fragment() { |
| private val deviceController: ChipDeviceController |
| get() = ChipClient.getDeviceController(requireContext()) |
| |
| private lateinit var scope: CoroutineScope |
| |
| private lateinit var addressUpdateFragment: AddressUpdateFragment |
| |
| private var _binding: OtaProviderClientFragmentBinding? = null |
| |
| private var fileUri: Uri? = null |
| |
| private val vendorId: Int |
| get() = binding.vendorIdEd.text.toString().toInt() |
| |
| private val otaProviderCallback = OtaProviderCallback() |
| private val binding |
| get() = _binding!! |
| |
| private val attributeList = ClusterIDMapping.OtaSoftwareUpdateRequestor.Attribute.values() |
| |
| override fun onCreateView( |
| inflater: LayoutInflater, |
| container: ViewGroup?, |
| savedInstanceState: Bundle? |
| ): View { |
| _binding = OtaProviderClientFragmentBinding.inflate(inflater, container, false) |
| scope = viewLifecycleOwner.lifecycleScope |
| |
| deviceController.setCompletionListener(ChipControllerCallback()) |
| |
| addressUpdateFragment = |
| childFragmentManager.findFragmentById(R.id.addressUpdateFragment) as AddressUpdateFragment |
| |
| binding.selectFirmwareFileBtn.setOnClickListener { selectFirmwareFileBtnClick() } |
| binding.updateOTAStatusBtn.setOnClickListener { updateOTAStatusBtnClick() } |
| binding.announceOTAProviderBtn.setOnClickListener { |
| scope.launch { sendAnnounceOTAProviderBtnClick() } |
| } |
| |
| binding.writeAclBtn.setOnClickListener { scope.launch { sendAclBtnClick() } } |
| |
| binding.readAttributeBtn.setOnClickListener { scope.launch { readAttributeBtnClick() } } |
| |
| binding.writeAttributeBtn.setOnClickListener { scope.launch { writeAttributeBtnClick() } } |
| |
| setQueryImageSpinnerListener() |
| |
| val attributeNames = attributeList.map { it.name } |
| |
| binding.attributeSp.adapter = |
| ArrayAdapter(requireContext(), android.R.layout.simple_spinner_dropdown_item, attributeNames) |
| |
| binding.vendorIdEd.setText(ChipClient.VENDOR_ID.toString()) |
| binding.delayActionTimeEd.setText("0") |
| |
| deviceController.startOTAProvider(otaProviderCallback) |
| return binding.root |
| } |
| |
| private suspend fun sendAclBtnClick() { |
| val endpointId = 0 |
| val clusterId = ClusterIDMapping.AccessControl.ID |
| val attributeId = ClusterIDMapping.AccessControl.Attribute.Acl.id |
| |
| val attributePath = 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 |
| ) { |
| Log.d(TAG, "onError : ", e) |
| showMessage("Error : $e") |
| } |
| |
| override fun onReport(nodeState: NodeState?) { |
| Log.d(TAG, "onResponse") |
| val tlv = |
| nodeState |
| ?.getEndpointState(endpointId) |
| ?.getClusterState(clusterId) |
| ?.getAttributeState(attributeId) |
| ?.tlv |
| requireActivity().runOnUiThread { showAddAccessControlDialog(tlv) } |
| } |
| }, |
| devicePtr, |
| listOf(attributePath), |
| 0 |
| ) |
| } |
| |
| private fun showAddAccessControlDialog(tlv: ByteArray?) { |
| if (tlv == null) { |
| Log.d(TAG, "Access Control read fail") |
| showMessage("Access Control read fail") |
| return |
| } |
| |
| val dialogView = |
| requireActivity().layoutInflater.inflate(R.layout.add_access_control_dialog, null) |
| val groupIdEd = dialogView.findViewById<EditText>(R.id.groupIdEd) |
| groupIdEd.visibility = View.GONE |
| val nodeIdEd = dialogView.findViewById<EditText>(R.id.nodeIdEd) |
| nodeIdEd.visibility = View.VISIBLE |
| val accessControlEntrySp = dialogView.findViewById<Spinner>(R.id.accessControlEntrySp) |
| accessControlEntrySp.adapter = |
| ArrayAdapter( |
| requireContext(), |
| android.R.layout.simple_spinner_dropdown_item, |
| GroupSettingFragment.Companion.AccessControlEntry.values() |
| ) |
| |
| val dialog = AlertDialog.Builder(requireContext()).apply { setView(dialogView) }.create() |
| dialogView.findViewById<Button>(R.id.addAccessControlBtn).setOnClickListener { |
| scope.launch { |
| sendAccessControl( |
| tlv, |
| nodeIdEd.text.toString().toULong(), |
| GroupSettingFragment.Companion.AccessControlEntry.valueOf( |
| accessControlEntrySp.selectedItem.toString() |
| ) |
| .id |
| ) |
| requireActivity().runOnUiThread { dialog.dismiss() } |
| } |
| } |
| dialog.show() |
| } |
| |
| private suspend fun sendAccessControl(tlv: ByteArray, nodeId: ULong, privilege: UInt) { |
| val tlvWriter = TlvWriter().startArray(AnonymousTag) |
| var entryStructList: List<AccessControlClusterAccessControlEntryStruct> |
| TlvReader(tlv).also { |
| entryStructList = buildList { |
| it.enterArray(AnonymousTag) |
| while (!it.isEndOfContainer()) { |
| add(AccessControlClusterAccessControlEntryStruct.fromTlv(AnonymousTag, it)) |
| } |
| it.exitContainer() |
| } |
| } |
| |
| // If GroupID is already added to AccessControl, do not add it. |
| for (entry in entryStructList) { |
| if ( |
| entry.authMode == 2U /* CASE */ && |
| entry.subjects != null && |
| entry.subjects!!.contains(nodeId) |
| ) { |
| continue |
| } |
| |
| entry.toTlv(AnonymousTag, tlvWriter) |
| } |
| |
| val newEntry = |
| AccessControlClusterAccessControlEntryStruct( |
| privilege, |
| 2U /* CASE */, |
| listOf(nodeId), |
| null, |
| deviceController.fabricIndex.toUInt() |
| ) |
| newEntry.toTlv(AnonymousTag, tlvWriter) |
| tlvWriter.endArray() |
| |
| 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?, e: Exception?) { |
| Log.d(TAG, "onError : ", e) |
| showMessage("Error : ${e.toString()}") |
| } |
| |
| override fun onResponse(attributePath: ChipAttributePath, status: Status) { |
| Log.d(TAG, "onResponse") |
| showMessage("$attributePath : Write response: $status") |
| } |
| }, |
| devicePtr, |
| listOf( |
| AttributeWriteRequest.newInstance( |
| 0, |
| ClusterIDMapping.AccessControl.ID, |
| ClusterIDMapping.AccessControl.Attribute.Acl.id, |
| tlvWriter.getEncoded() |
| ) |
| ), |
| 0, |
| 0 |
| ) |
| } |
| |
| private suspend fun readAttributeBtnClick() { |
| val attribute = attributeList[binding.attributeSp.selectedItemPosition] |
| val endpointId = OTA_REQUESTER_ENDPOINT_ID |
| val clusterId = ClusterIDMapping.OtaSoftwareUpdateRequestor.ID |
| val attributeId = attribute.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 tlv = |
| nodeState |
| ?.getEndpointState(endpointId) |
| ?.getClusterState(clusterId) |
| ?.getAttributeState(attributeId) |
| ?.tlv |
| |
| val value = tlv?.let { TlvReader(it).toAny() } |
| Log.i(TAG, "OtaSoftwareUpdateRequestor ${attribute.name} value: $value") |
| showMessage("OtaSoftwareUpdateRequestor ${attribute.name} value: $value") |
| } |
| }, |
| devicePtr, |
| listOf<ChipAttributePath>(path), |
| 0 |
| ) |
| } |
| |
| private fun writeAttributeBtnClick() { |
| val attribute = attributeList[binding.attributeSp.selectedItemPosition] |
| if (attribute != ClusterIDMapping.OtaSoftwareUpdateRequestor.Attribute.DefaultOTAProviders) { |
| requireActivity().runOnUiThread { |
| Toast.makeText( |
| requireActivity(), |
| R.string.ota_provider_invalid_attribute, |
| Toast.LENGTH_SHORT |
| ) |
| .show() |
| } |
| return |
| } |
| val dialogView = |
| requireActivity().layoutInflater.inflate(R.layout.write_default_otaproviders_dialog, null) |
| val fabricIndexEd = dialogView.findViewById<EditText>(R.id.fabricIndexEd) |
| val providerNodeIdEd = dialogView.findViewById<EditText>(R.id.providerNodeIdEd) |
| val endpointIdEd = dialogView.findViewById<EditText>(R.id.endpointIdEd) |
| |
| fabricIndexEd.setText(deviceController.fabricIndex.toUInt().toString()) |
| providerNodeIdEd.setText(deviceController.controllerNodeId.toULong().toString()) |
| endpointIdEd.setText(OTA_PROVIDER_ENDPOINT_ID.toUInt().toString()) |
| |
| val dialog = AlertDialog.Builder(requireContext()).apply { setView(dialogView) }.create() |
| |
| dialogView.findViewById<Button>(R.id.writeDefaultOtaProvidersBtn).setOnClickListener { |
| scope.launch { |
| sendWriteDefaultOTAProviders( |
| providerNodeIdEd.text.toString().toULong(), |
| endpointIdEd.text.toString().toUInt(), |
| fabricIndexEd.text.toString().toUInt() |
| ) |
| requireActivity().runOnUiThread { dialog.dismiss() } |
| } |
| } |
| dialog.show() |
| } |
| |
| private suspend fun sendWriteDefaultOTAProviders( |
| providerNodeId: ULong, |
| endpointId: UInt, |
| fabricIndex: UInt |
| ) { |
| val endpoint = OTA_REQUESTER_ENDPOINT_ID |
| val clusterId = ClusterIDMapping.OtaSoftwareUpdateRequestor.ID |
| val attributeId = ClusterIDMapping.OtaSoftwareUpdateRequestor.Attribute.DefaultOTAProviders.id |
| |
| val tlv = |
| TlvWriter() |
| .apply { |
| startArray(AnonymousTag) |
| OtaSoftwareUpdateRequestorClusterProviderLocation(providerNodeId, endpointId, fabricIndex) |
| .toTlv(AnonymousTag, this) |
| endArray() |
| } |
| .getEncoded() |
| |
| val writeRequest = AttributeWriteRequest.newInstance(endpoint, clusterId, attributeId, tlv) |
| |
| 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?, e: Exception?) { |
| Log.d(TAG, "onError") |
| showMessage("error : ${e.toString()}") |
| } |
| |
| override fun onResponse(attributePath: ChipAttributePath, status: Status) { |
| Log.d(TAG, "onResponse") |
| showMessage("$attributePath : Write response: $status") |
| } |
| }, |
| devicePtr, |
| listOf<AttributeWriteRequest>(writeRequest), |
| 0, |
| 0 |
| ) |
| } |
| |
| private fun setQueryImageSpinnerListener() { |
| val statusList = QueryImageResponseStatusEnum.values() |
| binding.titleStatusSp.adapter = |
| ArrayAdapter(requireContext(), android.R.layout.simple_spinner_dropdown_item, statusList) |
| binding.titleStatusSp.onItemSelectedListener = |
| object : AdapterView.OnItemSelectedListener { |
| override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { |
| val isBusy = statusList[position] == QueryImageResponseStatusEnum.Busy |
| |
| requireActivity().runOnUiThread { |
| binding.delayActionTimeTv.visibility = |
| if (isBusy) { |
| View.VISIBLE |
| } else { |
| View.GONE |
| } |
| binding.delayActionTimeEd.visibility = binding.delayActionTimeTv.visibility |
| } |
| } |
| |
| override fun onNothingSelected(parent: AdapterView<*>?) { |
| Log.d(TAG, "onNothingSelected") |
| } |
| } |
| } |
| |
| private fun selectFirmwareFileBtnClick() { |
| startActivityForResult( |
| Intent(Intent.ACTION_OPEN_DOCUMENT).apply { |
| addCategory(Intent.CATEGORY_OPENABLE) |
| type = "*/*" |
| }, |
| REQUEST_CODE_OPEN_SAF |
| ) |
| } |
| |
| private fun getInputStream(uri: Uri?): InputStream? { |
| if (uri == null) { |
| return null |
| } |
| return requireContext().contentResolver.openInputStream(uri) |
| } |
| |
| private fun queryName(uri: Uri?): String? { |
| if (uri == null) { |
| return null |
| } |
| val cursor = requireContext().contentResolver.query(uri, null, null, null, null) |
| return cursor?.let { c -> |
| val nameIndex = c.getColumnIndex(OpenableColumns.DISPLAY_NAME) |
| c.moveToFirst() |
| |
| val name = cursor.getString(nameIndex) |
| c.close() |
| name |
| } |
| } |
| |
| override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) { |
| super.onActivityResult(requestCode, resultCode, intent) |
| if (resultCode != Activity.RESULT_OK || intent == null) { |
| return |
| } |
| val uri = intent.data |
| if (uri == null) { |
| Log.d(TAG, "onActivityResult : null") |
| return |
| } |
| val filename = queryName(uri) |
| fileUri = uri |
| requireActivity().runOnUiThread { binding.firmwareFileTv.text = filename } |
| } |
| |
| private fun updateOTAStatusBtnClick() { |
| val version = 2L |
| val versionString = "2.0" |
| |
| val filename = binding.firmwareFileTv.text.toString() |
| Log.d(TAG, "updateOTAStatusBtnClick : $filename") |
| |
| when (binding.titleStatusSp.selectedItem.toString()) { |
| QueryImageResponseStatusEnum.UpdateAvailable.name -> |
| otaProviderCallback.setOTAFile(version, versionString, filename, fileUri) |
| QueryImageResponseStatusEnum.Busy.name -> |
| otaProviderCallback.setOTABusyError( |
| binding.delayActionTimeEd.text.toString().toUInt(), |
| binding.titleUserConsentNeededSp.selectedItem.toString().toBoolean() |
| ) |
| QueryImageResponseStatusEnum.NotAvailable.name -> |
| otaProviderCallback.setOTANotAvailableError( |
| binding.titleUserConsentNeededSp.selectedItem.toString().toBoolean() |
| ) |
| } |
| } |
| |
| private suspend fun sendAnnounceOTAProviderBtnClick() { |
| requireActivity().runOnUiThread { updateOTAStatusBtnClick() } |
| |
| val devicePtr = |
| try { |
| ChipClient.getConnectedDevicePointer(requireContext(), addressUpdateFragment.deviceId) |
| } catch (e: IllegalStateException) { |
| Log.d(TAG, "getConnectedDevicePointer exception", e) |
| showMessage("Get DevicePointer fail!") |
| return |
| } |
| |
| val otaRequestCluster = |
| ChipClusters.OtaSoftwareUpdateRequestorCluster(devicePtr, OTA_REQUESTER_ENDPOINT_ID) |
| otaRequestCluster.announceOTAProvider( |
| object : DefaultClusterCallback { |
| override fun onSuccess() { |
| showMessage("announceOTAProvider command success") |
| Log.e(TAG, "announceOTAProvider command success") |
| } |
| |
| override fun onError(ex: java.lang.Exception?) { |
| showMessage("announceOTAProvider command failure $ex") |
| Log.e(TAG, "announceOTAProvider command failure", ex) |
| } |
| }, |
| deviceController.controllerNodeId.toULong().toLong(), |
| vendorId, |
| 0 /* AnnounceReason */, |
| Optional.empty(), |
| OTA_PROVIDER_ENDPOINT_ID |
| ) |
| } |
| |
| override fun onDestroyView() { |
| super.onDestroyView() |
| deviceController.finishOTAProvider() |
| _binding = null |
| } |
| |
| inner class OtaProviderCallback : OTAProviderDelegate { |
| private var fileName: String? = null |
| private var version: Long = 0 |
| private var versionString: String? = null |
| private var uri: Uri? = null |
| |
| private var inputStream: InputStream? = null |
| private var bufferedInputStream: BufferedInputStream? = null |
| |
| private var status: QueryImageResponseStatusEnum? = null |
| private var delayedTime: UInt? = null |
| private var userConsentNeeded: Boolean? = null |
| |
| fun setOTAFile( |
| version: Long, |
| versionString: String, |
| fileName: String, |
| uri: Uri?, |
| userConsentNeeded: Boolean? = null |
| ) { |
| this.status = QueryImageResponseStatusEnum.UpdateAvailable |
| this.version = version |
| this.versionString = versionString |
| this.fileName = fileName |
| this.uri = uri |
| this.delayedTime = null |
| this.userConsentNeeded = userConsentNeeded |
| } |
| |
| fun setOTABusyError(delayedTime: UInt, userConsentNeeded: Boolean? = null) { |
| this.status = QueryImageResponseStatusEnum.Busy |
| this.delayedTime = delayedTime |
| this.userConsentNeeded = userConsentNeeded |
| } |
| |
| fun setOTANotAvailableError(userConsentNeeded: Boolean? = null) { |
| this.status = QueryImageResponseStatusEnum.NotAvailable |
| this.delayedTime = null |
| this.userConsentNeeded = userConsentNeeded |
| } |
| |
| override fun handleQueryImage( |
| vendorId: Int, |
| productId: Int, |
| softwareVersion: Long, |
| hardwareVersion: Int?, |
| location: String?, |
| requestorCanConsent: Boolean?, |
| metadataForProvider: ByteArray? |
| ): OTAProviderDelegate.QueryImageResponse? { |
| Log.d( |
| TAG, |
| "handleQueryImage, $vendorId, $productId, $softwareVersion, $hardwareVersion, $location" |
| ) |
| |
| return when (status) { |
| QueryImageResponseStatusEnum.UpdateAvailable -> |
| OTAProviderDelegate.QueryImageResponse(version, versionString, fileName, null) |
| QueryImageResponseStatusEnum.Busy -> |
| OTAProviderDelegate.QueryImageResponse( |
| status, |
| delayedTime?.toLong() ?: 0, |
| userConsentNeeded ?: false |
| ) |
| QueryImageResponseStatusEnum.NotAvailable -> |
| OTAProviderDelegate.QueryImageResponse(status, userConsentNeeded ?: false) |
| else -> null |
| } |
| } |
| |
| override fun handleOTAQueryFailure(error: Int) { |
| Log.d(TAG, "handleOTAQueryFailure, $error") |
| showMessage("handleOTAQueryFailure : $error") |
| } |
| |
| override fun handleApplyUpdateRequest( |
| nodeId: Long, |
| newVersion: Long |
| ): OTAProviderDelegate.ApplyUpdateResponse { |
| Log.d(TAG, "handleApplyUpdateRequest, $nodeId, $newVersion") |
| return OTAProviderDelegate.ApplyUpdateResponse( |
| OTAProviderDelegate.ApplyUpdateActionEnum.Proceed, |
| APPLY_WAITING_TIME |
| ) |
| } |
| |
| override fun handleNotifyUpdateApplied(nodeId: Long) { |
| Log.d(TAG, "handleNotifyUpdateApplied, $nodeId") |
| showMessage("Finish Firmware Update : $nodeId") |
| } |
| |
| override fun handleBDXTransferSessionBegin( |
| nodeId: Long, |
| fileDesignator: String?, |
| offset: Long |
| ) { |
| Log.d(TAG, "handleBDXTransferSessionBegin, $nodeId, $fileDesignator, $offset") |
| try { |
| inputStream = getInputStream(uri) |
| bufferedInputStream = BufferedInputStream(inputStream) |
| } catch (e: IOException) { |
| Log.d(TAG, "exception", e) |
| inputStream?.close() |
| bufferedInputStream?.close() |
| inputStream = null |
| bufferedInputStream = null |
| return |
| } |
| } |
| |
| override fun handleBDXTransferSessionEnd(errorCode: Long, nodeId: Long) { |
| Log.d(TAG, "handleBDXTransferSessionEnd, $errorCode, $nodeId") |
| inputStream?.close() |
| bufferedInputStream?.close() |
| inputStream = null |
| bufferedInputStream = null |
| } |
| |
| override fun handleBDXQuery( |
| nodeId: Long, |
| blockSize: Int, |
| blockIndex: Long, |
| bytesToSkip: Long |
| ): OTAProviderDelegate.BDXData? { |
| // This code is just example code. This code doesn't check blockIndex and bytesToSkip |
| // variable. |
| Log.d(TAG, "handleBDXQuery, $nodeId, $blockSize, $blockIndex, $bytesToSkip") |
| showMessage("sending.. $blockIndex") |
| if (bufferedInputStream == null) { |
| return OTAProviderDelegate.BDXData(ByteArray(0), true) |
| } |
| val packet = ByteArray(blockSize) |
| val len = bufferedInputStream!!.read(packet) |
| |
| val sendPacket = |
| if (len < blockSize) { |
| packet.copyOf(len) |
| } else if (len < 0) { |
| ByteArray(0) |
| } else { |
| packet.clone() |
| } |
| |
| val isEOF = len < blockSize |
| |
| return OTAProviderDelegate.BDXData(sendPacket, isEOF) |
| } |
| } |
| |
| inner class ChipControllerCallback : GenericChipDeviceListener() { |
| override fun onCommissioningComplete(nodeId: Long, errorCode: Long) { |
| Log.d(TAG, "onCommissioningComplete for nodeId $nodeId: $errorCode") |
| showMessage("Address update complete for nodeId $nodeId with code $errorCode") |
| } |
| |
| override fun onNotifyChipConnectionClosed() { |
| Log.d(TAG, "onNotifyChipConnectionClosed") |
| } |
| |
| override fun onCloseBleComplete() { |
| Log.d(TAG, "onCloseBleComplete") |
| } |
| |
| override fun onError(error: Throwable?) { |
| Log.d(TAG, "onError: $error") |
| } |
| } |
| |
| private fun showMessage(msg: String) { |
| requireActivity().runOnUiThread { binding.commandStatusTv.text = msg } |
| } |
| |
| companion object { |
| private const val TAG = "OtaProviderClientFragment" |
| |
| fun newInstance(): OtaProviderClientFragment = OtaProviderClientFragment() |
| |
| private const val REQUEST_CODE_OPEN_SAF = 100 |
| |
| private const val APPLY_WAITING_TIME = 10L |
| private const val OTA_PROVIDER_ENDPOINT_ID = 0 |
| private const val OTA_REQUESTER_ENDPOINT_ID = 0 |
| } |
| } |