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] Implement DiagnosticLog #36591

Merged
9 changes: 9 additions & 0 deletions examples/android/CHIPTool/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,15 @@
<data android:scheme="mt" android:host="modelinfo" /> <!-- Process Redirect URIs -->
</intent-filter>
</activity>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
</application>

<queries>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ class SelectActionFragment : Fragment() {
binding.provisionCustomFlowBtn.setOnClickListener { handleProvisionCustomFlowClicked() }
binding.wildcardBtn.setOnClickListener { handleWildcardClicked() }
binding.unpairDeviceBtn.setOnClickListener { handleUnpairDeviceClicked() }
binding.diagnosticLogBtn.setOnClickListener { handleDiagnosticLogClicked() }
binding.groupSettingBtn.setOnClickListener { handleGroupSettingClicked() }
binding.otaProviderBtn.setOnClickListener { handleOTAProviderClicked() }
binding.icdBtn.setOnClickListener { handleICDClicked() }
Expand Down Expand Up @@ -225,6 +226,10 @@ class SelectActionFragment : Fragment() {
showFragment(OtaProviderClientFragment.newInstance())
}

private fun handleDiagnosticLogClicked() {
showFragment(DiagnosticLogFragment.newInstance())
}

/** Notifies listener of provision-WiFi-credentials button click. */
private fun handleProvisionWiFiCredentialsClicked() {
getCallback()?.setNetworkType(ProvisionNetworkType.WIFI)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
package com.google.chip.chiptool.clusterclient

import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.os.Environment
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import androidx.core.content.FileProvider
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import chip.devicecontroller.ChipDeviceController
import chip.devicecontroller.DiagnosticLogType
import chip.devicecontroller.DownloadLogCallback
import com.google.chip.chiptool.ChipClient
import com.google.chip.chiptool.R
import com.google.chip.chiptool.databinding.DiagnosticLogFragmentBinding
import kotlinx.coroutines.*
import java.io.File
import java.io.FileOutputStream
import java.io.IOException

class DiagnosticLogFragment : Fragment() {
private val deviceController: ChipDeviceController
get() = ChipClient.getDeviceController(requireContext())

private lateinit var scope: CoroutineScope

private lateinit var addressUpdateFragment: AddressUpdateFragment

private var _binding: DiagnosticLogFragmentBinding? = null
private val binding
get() = _binding!!

private val timeout: Int
get() = binding.timeoutEd.text.toString().toUIntOrNull()?.toInt() ?: 0

private val diagnosticLogTypeList = DiagnosticLogType.values()
private val diagnosticLogType: DiagnosticLogType
get() = diagnosticLogTypeList[binding.diagnosticTypeSp.selectedItemPosition]

private var mDownloadFile: File? = null
private var mDownloadFileOutputStream: FileOutputStream? = null

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = DiagnosticLogFragmentBinding.inflate(inflater, container, false)
scope = viewLifecycleOwner.lifecycleScope

addressUpdateFragment =
childFragmentManager.findFragmentById(R.id.addressUpdateFragment) as AddressUpdateFragment

binding.getDiagnosticLogBtn.setOnClickListener { scope.launch { getDiagnosticLogClick() } }

binding.diagnosticTypeSp.adapter =
ArrayAdapter(requireContext(), android.R.layout.simple_spinner_dropdown_item, diagnosticLogTypeList)

return binding.root
}

override fun onDestroyView() {
super.onDestroyView()
_binding = null
}

inner class ChipDownloadLogCallback : DownloadLogCallback {
override fun onError(fabricIndex: Int, nodeId: Long, errorCode: Long) {
Log.d(TAG, "onError: $fabricIndex, ${nodeId.toULong()}, $errorCode")
}

override fun onTransferData(
fabricIndex: Int,
nodeId: Long,
data: ByteArray,
isEof: Boolean
): Boolean {
Log.d(TAG, "onTransferData : ${data.size}, $isEof")
if (mDownloadFileOutputStream == null || mDownloadFile == null) {
Log.d(TAG, "mDownloadFileOutputStream or mDownloadFile is null")
return false
}
try {
mDownloadFileOutputStream!!.write(data)
if (isEof) {
mDownloadFileOutputStream!!.flush()
showNotification(mDownloadFile!!)
}
} catch (e: IOException) {
Log.d(TAG, "IOException", e)
return false
}
return true
}
}

private fun getDiagnosticLogClick() {
mDownloadFile = createLogFile(deviceController.fabricIndex.toUInt(), addressUpdateFragment.deviceId.toULong(), diagnosticLogType)
mDownloadFileOutputStream = FileOutputStream(mDownloadFile)
deviceController.downloadLogFromNode(addressUpdateFragment.deviceId, diagnosticLogType, timeout, ChipDownloadLogCallback())
}

private fun isExternalStorageWritable(): Boolean {
return Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED
}

private fun createLogFile(fabricIndex: UInt, nodeId: ULong, type:DiagnosticLogType) : File? {
if (!isExternalStorageWritable()) {
return null
}
val now = System.currentTimeMillis()
val fileName = "${type}_${fabricIndex}_${nodeId}_$now.txt"
return File(requireContext().getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS), fileName)
}

private fun showNotification(file: File) {
val intent = Intent(Intent.ACTION_VIEW).apply {
setDataAndType(getFileUri(file), "text/plain")
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}

requireActivity().startActivity(intent)
}

private fun getFileUri(file: File): Uri {
return FileProvider.getUriForFile(requireContext(), "${requireContext().packageName}.provider", file)
}

companion object {
private const val TAG = "DiagnosticLogFragment"

fun newInstance(): DiagnosticLogFragment = DiagnosticLogFragment()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">

<androidx.fragment.app.FragmentContainerView
android:id="@+id/addressUpdateFragment"
android:name="com.google.chip.chiptool.clusterclient.AddressUpdateFragment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>

<TextView
android:id="@+id/titleDiagnosticType"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/diagnostic_log_type_title_text"
android:textSize="16sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/addressUpdateFragment" />

<Spinner
android:id="@+id/diagnosticTypeSp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:inputType="text"
android:spinnerMode="dropdown"
android:textSize="16sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/titleDiagnosticType" />

<EditText
android:id="@+id/timeoutTv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:enabled="false"
android:padding="8dp"
android:text="@string/diagnostic_log_timeout_title_text"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/timeoutEd"
app:layout_constraintTop_toBottomOf="@id/diagnosticTypeSp"
android:textSize="16sp" />

<EditText
android:id="@+id/timeoutEd"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:autofillHints="@string/diagnostic_log_timeout_title_text"
android:inputType="numberDecimal"
android:padding="8dp"
app:layout_constraintStart_toEndOf="@id/timeoutTv"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/diagnosticTypeSp"
android:textSize="16sp" />

<Button
android:id="@+id/getDiagnosticLogBtn"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:text="@string/diagnostic_log_btn_text"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/timeoutTv"/>

</androidx.constraintlayout.widget.ConstraintLayout>
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,14 @@
android:layout_marginTop="8dp"
android:text="@string/unpair_device_btn_text" />

<Button
android:id="@+id/diagnosticLogBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:text="@string/diagnostic_log_btn_text" />

<Button
android:id="@+id/groupSettingBtn"
android:layout_width="wrap_content"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,10 @@

<string name="unpair_device_btn_text">Unpair</string>

<string name="diagnostic_log_btn_text">Get Diagnostic Log</string>
<string name="diagnostic_log_type_title_text">Log Type</string>
<string name="diagnostic_log_timeout_title_text">Timeout(Sec)</string>

<string name="group_setting_btn_text">Group Setting</string>
<string name="group_setting_group_text">Group :</string>
<string name="group_setting_key_text">Key :</string>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-files-path name="external_files" path="." />
</paths>
1 change: 1 addition & 0 deletions examples/java-matter-controller/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ kotlin_binary("java-matter-controller") {

sources = [
"java/src/com/matter/controller/Main.kt",
"java/src/com/matter/controller/commands/bdx/DownloadLogCommand.kt",
"java/src/com/matter/controller/commands/common/Argument.kt",
"java/src/com/matter/controller/commands/common/ArgumentType.kt",
"java/src/com/matter/controller/commands/common/Command.kt",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package com.matter.controller

import chip.devicecontroller.ChipDeviceController
import chip.devicecontroller.ControllerParams
import com.matter.controller.commands.bdx.*
import com.matter.controller.commands.common.*
import com.matter.controller.commands.discover.*
import com.matter.controller.commands.icd.*
Expand Down Expand Up @@ -80,6 +81,15 @@ private fun getICDCommands(
)
}

private fun getBdxCommands(
controller: ChipDeviceController,
credentialsIssuer: CredentialsIssuer
): List<Command> {
return listOf(
DownloadLogCommand(controller, credentialsIssuer),
)
}

fun main(args: Array<String>) {
val controller =
ChipDeviceController(
Expand All @@ -96,6 +106,7 @@ fun main(args: Array<String>) {
commandManager.register("pairing", getPairingCommands(controller, credentialsIssuer))
commandManager.register("im", getImCommands(controller, credentialsIssuer))
commandManager.register("icd", getICDCommands(controller, credentialsIssuer))
commandManager.register("bdx", getBdxCommands(controller, credentialsIssuer))

try {
commandManager.run(args)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* Copyright (c) 2023 Project CHIP Authors
joonhaengHeo marked this conversation as resolved.
Show resolved Hide resolved
* All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.matter.controller.commands.bdx

import chip.devicecontroller.ChipDeviceController
import chip.devicecontroller.DiagnosticLogType
import chip.devicecontroller.DownloadLogCallback
import com.matter.controller.commands.common.CredentialsIssuer
import com.matter.controller.commands.common.MatterCommand
import java.nio.ByteBuffer
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicInteger
import java.util.concurrent.atomic.AtomicLong

class DownloadLogCommand(
controller: ChipDeviceController,
credsIssuer: CredentialsIssuer?
) : MatterCommand(controller, credsIssuer, "downloadLog") {
private val nodeId: AtomicLong = AtomicLong()
private val logType: StringBuffer = StringBuffer()
private val timeout: AtomicInteger = AtomicInteger()
private val buffer: StringBuffer = StringBuffer()

init {
addArgument("nodeid", 0, Long.MAX_VALUE, nodeId, null, false)
addArgument("logType", logType, null, false)
addArgument("timeout", 0, Int.MAX_VALUE, timeout, null, false)
}
override fun runCommand() {
currentCommissioner().downloadLogFromNode(nodeId.toLong(), DiagnosticLogType.value(logType.toString()), timeout.toInt(), object: DownloadLogCallback {
override fun onError(fabricIndex: Int, nodeId: Long, errorCode: Long) {
println("error :")
println("FabricIndex : $fabricIndex, NodeID : $nodeId, errorCode : $errorCode")
}

override fun onTransferData(fabricIndex: Int, nodeId: Long, data: ByteArray, isEof: Boolean): Boolean {
buffer.append(String(data))
if (isEof) {
println()
println("FabricIndex : $fabricIndex, NodeID : $nodeId")
println(buffer.toString())
println("Log Download Finish!")
}
return true
}
})
try {
TimeUnit.SECONDS.sleep(timeout.toLong())
} catch (e: InterruptedException) {
throw RuntimeException(e)
}
}
}
Loading
Loading