Skip to content

Commit ca772d7

Browse files
authored
[Android] Implement DiagnosticLog (#36591)
* Implement Android DiagnosticLog * Update from comment * Restyled * Update DiagnosticLog UX (Download complete / failed) * Fix timeout issue * Fix java-controller build error * modify from comment * Add javadoc, test cases * kotlin code style * Fix from comment * Fix build error
1 parent 71dc879 commit ca772d7

25 files changed

+1341
-0
lines changed

.github/workflows/java-tests.yaml

+11
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,17 @@ jobs:
236236
--tool-args "onnetwork-long --nodeid 1 --setup-pin-code 20202021 --discriminator 3840 -t 1000" \
237237
--factoryreset \
238238
'
239+
- name: Run Pairing Onnetwork and get diagnostic log Test
240+
run: |
241+
scripts/run_in_python_env.sh out/venv \
242+
'./scripts/tests/run_java_test.py \
243+
--app out/linux-x64-all-clusters-ipv6only-no-ble-no-wifi-tsan-clang-test/chip-all-clusters-app \
244+
--app-args "--discriminator 3840 --interface-id -1 --crash_log ./crashLog.log --end_user_support_log ./enduser.log --network_diagnostics_log ./network.log" \
245+
--tool-path out/linux-x64-java-matter-controller \
246+
--tool-cluster "bdx" \
247+
--tool-args "onnetwork-long-downloadLog --nodeid 1 --setup-pin-code 20202021 --discriminator 3840 -t 3000 --logType CrashLogs --fileName ./crashLog.log" \
248+
--factoryreset \
249+
'
239250
- name: Run Pairing Onnetwork Test
240251
run: |
241252
scripts/run_in_python_env.sh out/venv \

examples/android/CHIPTool/app/src/main/AndroidManifest.xml

+9
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,15 @@
4646
<data android:scheme="mt" android:host="modelinfo" /> <!-- Process Redirect URIs -->
4747
</intent-filter>
4848
</activity>
49+
<provider
50+
android:name="androidx.core.content.FileProvider"
51+
android:authorities="${applicationId}.provider"
52+
android:exported="false"
53+
android:grantUriPermissions="true">
54+
<meta-data
55+
android:name="android.support.FILE_PROVIDER_PATHS"
56+
android:resource="@xml/file_paths" />
57+
</provider>
4958
</application>
5059

5160
<queries>

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

+5
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ class SelectActionFragment : Fragment() {
7272
binding.provisionCustomFlowBtn.setOnClickListener { handleProvisionCustomFlowClicked() }
7373
binding.wildcardBtn.setOnClickListener { handleWildcardClicked() }
7474
binding.unpairDeviceBtn.setOnClickListener { handleUnpairDeviceClicked() }
75+
binding.diagnosticLogBtn.setOnClickListener { handleDiagnosticLogClicked() }
7576
binding.groupSettingBtn.setOnClickListener { handleGroupSettingClicked() }
7677
binding.otaProviderBtn.setOnClickListener { handleOTAProviderClicked() }
7778
binding.icdBtn.setOnClickListener { handleICDClicked() }
@@ -225,6 +226,10 @@ class SelectActionFragment : Fragment() {
225226
showFragment(OtaProviderClientFragment.newInstance())
226227
}
227228

229+
private fun handleDiagnosticLogClicked() {
230+
showFragment(DiagnosticLogFragment.newInstance())
231+
}
232+
228233
/** Notifies listener of provision-WiFi-credentials button click. */
229234
private fun handleProvisionWiFiCredentialsClicked() {
230235
getCallback()?.setNetworkType(ProvisionNetworkType.WIFI)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
package com.google.chip.chiptool.clusterclient
2+
3+
import android.content.Intent
4+
import android.net.Uri
5+
import android.os.Bundle
6+
import android.os.Environment
7+
import android.util.Log
8+
import android.view.LayoutInflater
9+
import android.view.View
10+
import android.view.ViewGroup
11+
import android.widget.ArrayAdapter
12+
import androidx.core.content.FileProvider
13+
import androidx.fragment.app.Fragment
14+
import androidx.lifecycle.lifecycleScope
15+
import chip.devicecontroller.ChipDeviceController
16+
import chip.devicecontroller.DiagnosticLogType
17+
import chip.devicecontroller.DownloadLogCallback
18+
import com.google.chip.chiptool.ChipClient
19+
import com.google.chip.chiptool.R
20+
import com.google.chip.chiptool.databinding.DiagnosticLogFragmentBinding
21+
import java.io.File
22+
import java.io.FileOutputStream
23+
import java.io.IOException
24+
import kotlinx.coroutines.CoroutineScope
25+
import kotlinx.coroutines.launch
26+
27+
class DiagnosticLogFragment : Fragment() {
28+
private val deviceController: ChipDeviceController
29+
get() = ChipClient.getDeviceController(requireContext())
30+
31+
private lateinit var scope: CoroutineScope
32+
33+
private lateinit var addressUpdateFragment: AddressUpdateFragment
34+
35+
private var _binding: DiagnosticLogFragmentBinding? = null
36+
private val binding
37+
get() = _binding!!
38+
39+
private val timeout: Long
40+
get() = binding.timeoutEd.text.toString().toULongOrNull()?.toLong() ?: 0L
41+
42+
private val diagnosticLogTypeList = DiagnosticLogType.values()
43+
private val diagnosticLogType: DiagnosticLogType
44+
get() = diagnosticLogTypeList[binding.diagnosticTypeSp.selectedItemPosition]
45+
46+
private var mDownloadFile: File? = null
47+
private var mDownloadFileOutputStream: FileOutputStream? = null
48+
49+
private var mReceiveFileLen = 0U
50+
51+
override fun onCreateView(
52+
inflater: LayoutInflater,
53+
container: ViewGroup?,
54+
savedInstanceState: Bundle?
55+
): View {
56+
_binding = DiagnosticLogFragmentBinding.inflate(inflater, container, false)
57+
scope = viewLifecycleOwner.lifecycleScope
58+
59+
addressUpdateFragment =
60+
childFragmentManager.findFragmentById(R.id.addressUpdateFragment) as AddressUpdateFragment
61+
62+
binding.getDiagnosticLogBtn.setOnClickListener { scope.launch { getDiagnosticLogClick() } }
63+
64+
binding.diagnosticTypeSp.adapter =
65+
ArrayAdapter(
66+
requireContext(),
67+
android.R.layout.simple_spinner_dropdown_item,
68+
diagnosticLogTypeList
69+
)
70+
71+
return binding.root
72+
}
73+
74+
override fun onDestroyView() {
75+
super.onDestroyView()
76+
_binding = null
77+
}
78+
79+
inner class ChipDownloadLogCallback : DownloadLogCallback {
80+
override fun onError(fabricIndex: Int, nodeId: Long, errorCode: Long) {
81+
Log.d(TAG, "onError: $fabricIndex, ${nodeId.toULong()}, $errorCode")
82+
showMessage("Downloading Failed")
83+
mDownloadFileOutputStream?.flush() ?: return
84+
}
85+
86+
override fun onSuccess(fabricIndex: Int, nodeId: Long) {
87+
Log.d(TAG, "onSuccess: $fabricIndex, ${nodeId.toULong()}")
88+
mDownloadFileOutputStream?.flush() ?: return
89+
showMessage("Downloading Completed")
90+
mDownloadFile?.let { showNotification(it) } ?: return
91+
}
92+
93+
override fun onTransferData(fabricIndex: Int, nodeId: Long, data: ByteArray): Boolean {
94+
Log.d(TAG, "onTransferData : ${data.size}")
95+
if (mDownloadFileOutputStream == null) {
96+
Log.d(TAG, "mDownloadFileOutputStream or mDownloadFile is null")
97+
return false
98+
}
99+
return addData(mDownloadFileOutputStream!!, data)
100+
}
101+
102+
private fun addData(outputStream: FileOutputStream, data: ByteArray): Boolean {
103+
try {
104+
outputStream.write(data)
105+
} catch (e: IOException) {
106+
Log.d(TAG, "IOException", e)
107+
return false
108+
}
109+
mReceiveFileLen += data.size.toUInt()
110+
showMessage("Receive Data Size : $mReceiveFileLen")
111+
return true
112+
}
113+
}
114+
115+
private fun getDiagnosticLogClick() {
116+
mDownloadFile =
117+
createLogFile(
118+
deviceController.fabricIndex.toUInt(),
119+
addressUpdateFragment.deviceId.toULong(),
120+
diagnosticLogType
121+
)
122+
mDownloadFileOutputStream = FileOutputStream(mDownloadFile)
123+
deviceController.downloadLogFromNode(
124+
addressUpdateFragment.deviceId,
125+
diagnosticLogType,
126+
timeout,
127+
ChipDownloadLogCallback()
128+
)
129+
}
130+
131+
private fun isExternalStorageWritable(): Boolean {
132+
return Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED
133+
}
134+
135+
private fun createLogFile(fabricIndex: UInt, nodeId: ULong, type: DiagnosticLogType): File? {
136+
if (!isExternalStorageWritable()) {
137+
return null
138+
}
139+
val now = System.currentTimeMillis()
140+
val fileName = "${type}_${fabricIndex}_${nodeId}_$now.txt"
141+
mReceiveFileLen = 0U
142+
return File(requireContext().getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS), fileName)
143+
}
144+
145+
private fun showNotification(file: File) {
146+
val intent =
147+
Intent(Intent.ACTION_VIEW).apply {
148+
setDataAndType(getFileUri(file), "text/plain")
149+
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
150+
}
151+
152+
requireActivity().startActivity(intent)
153+
}
154+
155+
private fun getFileUri(file: File): Uri {
156+
return FileProvider.getUriForFile(
157+
requireContext(),
158+
"${requireContext().packageName}.provider",
159+
file
160+
)
161+
}
162+
163+
private fun showMessage(msg: String) {
164+
requireActivity().runOnUiThread { binding.diagnosticLogTv.text = msg }
165+
}
166+
167+
companion object {
168+
private const val TAG = "DiagnosticLogFragment"
169+
170+
fun newInstance(): DiagnosticLogFragment = DiagnosticLogFragment()
171+
}
172+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<androidx.constraintlayout.widget.ConstraintLayout
3+
xmlns:android="http://schemas.android.com/apk/res/android"
4+
android:layout_width="match_parent"
5+
android:layout_height="match_parent"
6+
xmlns:app="http://schemas.android.com/apk/res-auto">
7+
8+
<androidx.fragment.app.FragmentContainerView
9+
android:id="@+id/addressUpdateFragment"
10+
android:name="com.google.chip.chiptool.clusterclient.AddressUpdateFragment"
11+
android:layout_width="match_parent"
12+
android:layout_height="wrap_content"
13+
app:layout_constraintTop_toTopOf="parent"
14+
app:layout_constraintStart_toStartOf="parent"
15+
app:layout_constraintEnd_toEndOf="parent"/>
16+
17+
<TextView
18+
android:id="@+id/titleDiagnosticType"
19+
android:layout_width="match_parent"
20+
android:layout_height="wrap_content"
21+
android:text="@string/diagnostic_log_type_title_text"
22+
android:textSize="16sp"
23+
app:layout_constraintStart_toStartOf="parent"
24+
app:layout_constraintTop_toBottomOf="@id/addressUpdateFragment" />
25+
26+
<Spinner
27+
android:id="@+id/diagnosticTypeSp"
28+
android:layout_width="wrap_content"
29+
android:layout_height="wrap_content"
30+
android:inputType="text"
31+
android:spinnerMode="dropdown"
32+
android:textSize="16sp"
33+
app:layout_constraintStart_toStartOf="parent"
34+
app:layout_constraintTop_toBottomOf="@id/titleDiagnosticType" />
35+
36+
<EditText
37+
android:id="@+id/timeoutTv"
38+
android:layout_width="wrap_content"
39+
android:layout_height="wrap_content"
40+
android:layout_alignParentStart="true"
41+
android:layout_marginStart="8dp"
42+
android:layout_marginEnd="8dp"
43+
android:enabled="false"
44+
android:padding="8dp"
45+
android:text="@string/diagnostic_log_timeout_title_text"
46+
app:layout_constraintStart_toStartOf="parent"
47+
app:layout_constraintEnd_toStartOf="@id/timeoutEd"
48+
app:layout_constraintTop_toBottomOf="@id/diagnosticTypeSp"
49+
android:textSize="16sp" />
50+
51+
<EditText
52+
android:id="@+id/timeoutEd"
53+
android:layout_width="0dp"
54+
android:layout_height="wrap_content"
55+
android:layout_marginStart="8dp"
56+
android:layout_marginEnd="8dp"
57+
android:autofillHints="@string/diagnostic_log_timeout_title_text"
58+
android:inputType="numberDecimal"
59+
android:padding="8dp"
60+
app:layout_constraintStart_toEndOf="@id/timeoutTv"
61+
app:layout_constraintEnd_toEndOf="parent"
62+
app:layout_constraintTop_toBottomOf="@id/diagnosticTypeSp"
63+
android:textSize="16sp" />
64+
65+
<Button
66+
android:id="@+id/getDiagnosticLogBtn"
67+
android:layout_width="0dp"
68+
android:layout_height="wrap_content"
69+
android:layout_margin="10dp"
70+
android:text="@string/diagnostic_log_btn_text"
71+
app:layout_constraintStart_toStartOf="parent"
72+
app:layout_constraintEnd_toEndOf="parent"
73+
app:layout_constraintTop_toBottomOf="@id/timeoutTv"/>
74+
75+
<TextView
76+
android:id="@+id/diagnosticLogTv"
77+
android:layout_width="match_parent"
78+
android:layout_height="wrap_content"
79+
android:minLines="4"
80+
android:padding="16dp"
81+
android:singleLine="false"
82+
android:textSize="20sp"
83+
app:layout_constraintStart_toStartOf="parent"
84+
app:layout_constraintEnd_toEndOf="parent"
85+
app:layout_constraintTop_toBottomOf="@id/getDiagnosticLogBtn" />
86+
87+
</androidx.constraintlayout.widget.ConstraintLayout>

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

+8
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,14 @@
112112
android:layout_marginTop="8dp"
113113
android:text="@string/unpair_device_btn_text" />
114114

115+
<Button
116+
android:id="@+id/diagnosticLogBtn"
117+
android:layout_width="wrap_content"
118+
android:layout_height="wrap_content"
119+
android:layout_marginStart="8dp"
120+
android:layout_marginTop="8dp"
121+
android:text="@string/diagnostic_log_btn_text" />
122+
115123
<Button
116124
android:id="@+id/groupSettingBtn"
117125
android:layout_width="wrap_content"

examples/android/CHIPTool/app/src/main/res/values/strings.xml

+4
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,10 @@
232232

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

235+
<string name="diagnostic_log_btn_text">Get Diagnostic Log</string>
236+
<string name="diagnostic_log_type_title_text">Log Type</string>
237+
<string name="diagnostic_log_timeout_title_text">Timeout(Sec)</string>
238+
235239
<string name="group_setting_btn_text">Group Setting</string>
236240
<string name="group_setting_group_text">Group :</string>
237241
<string name="group_setting_key_text">Key :</string>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<paths xmlns:android="http://schemas.android.com/apk/res/android">
3+
<external-files-path name="external_files" path="." />
4+
</paths>

examples/java-matter-controller/BUILD.gn

+2
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ kotlin_binary("java-matter-controller") {
2929

3030
sources = [
3131
"java/src/com/matter/controller/Main.kt",
32+
"java/src/com/matter/controller/commands/bdx/DownloadLogCommand.kt",
33+
"java/src/com/matter/controller/commands/bdx/PairOnNetworkLongDownloadLogCommand.kt",
3234
"java/src/com/matter/controller/commands/common/Argument.kt",
3335
"java/src/com/matter/controller/commands/common/ArgumentType.kt",
3436
"java/src/com/matter/controller/commands/common/Command.kt",

examples/java-matter-controller/java/src/com/matter/controller/Main.kt

+12
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package com.matter.controller
1919

2020
import chip.devicecontroller.ChipDeviceController
2121
import chip.devicecontroller.ControllerParams
22+
import com.matter.controller.commands.bdx.*
2223
import com.matter.controller.commands.common.*
2324
import com.matter.controller.commands.discover.*
2425
import com.matter.controller.commands.icd.*
@@ -80,6 +81,16 @@ private fun getICDCommands(
8081
)
8182
}
8283

84+
private fun getBdxCommands(
85+
controller: ChipDeviceController,
86+
credentialsIssuer: CredentialsIssuer
87+
): List<Command> {
88+
return listOf(
89+
DownloadLogCommand(controller, credentialsIssuer),
90+
PairOnNetworkLongDownloadLogCommand(controller, credentialsIssuer),
91+
)
92+
}
93+
8394
fun main(args: Array<String>) {
8495
val controller =
8596
ChipDeviceController(
@@ -96,6 +107,7 @@ fun main(args: Array<String>) {
96107
commandManager.register("pairing", getPairingCommands(controller, credentialsIssuer))
97108
commandManager.register("im", getImCommands(controller, credentialsIssuer))
98109
commandManager.register("icd", getICDCommands(controller, credentialsIssuer))
110+
commandManager.register("bdx", getBdxCommands(controller, credentialsIssuer))
99111

100112
try {
101113
commandManager.run(args)

0 commit comments

Comments
 (0)