Change API of BugReporter (make it suspend)
This commit is contained in:
parent
b68830faf1
commit
30eebe0b2e
4 changed files with 284 additions and 302 deletions
|
|
@ -16,15 +16,10 @@
|
||||||
|
|
||||||
package io.element.android.features.rageshake.api.reporter
|
package io.element.android.features.rageshake.api.reporter
|
||||||
|
|
||||||
import io.element.android.features.rageshake.api.reporter.BugReporterListener
|
|
||||||
import io.element.android.features.rageshake.api.reporter.ReportType
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
|
|
||||||
interface BugReporter {
|
interface BugReporter {
|
||||||
/**
|
/**
|
||||||
* Send a bug report.
|
* Send a bug report.
|
||||||
*
|
*
|
||||||
* @param coroutineScope The coroutine scope
|
|
||||||
* @param reportType The report type (bug, suggestion, feedback)
|
* @param reportType The report type (bug, suggestion, feedback)
|
||||||
* @param withDevicesLogs true to include the device log
|
* @param withDevicesLogs true to include the device log
|
||||||
* @param withCrashLogs true to include the crash logs
|
* @param withCrashLogs true to include the crash logs
|
||||||
|
|
@ -36,8 +31,7 @@ interface BugReporter {
|
||||||
* @param customFields fields which will be sent with the report
|
* @param customFields fields which will be sent with the report
|
||||||
* @param listener the listener
|
* @param listener the listener
|
||||||
*/
|
*/
|
||||||
fun sendBugReport(
|
suspend fun sendBugReport(
|
||||||
coroutineScope: CoroutineScope,
|
|
||||||
reportType: ReportType,
|
reportType: ReportType,
|
||||||
withDevicesLogs: Boolean,
|
withDevicesLogs: Boolean,
|
||||||
withCrashLogs: Boolean,
|
withCrashLogs: Boolean,
|
||||||
|
|
|
||||||
|
|
@ -132,7 +132,6 @@ class BugReportPresenter @Inject constructor(
|
||||||
listener: BugReporterListener,
|
listener: BugReporterListener,
|
||||||
) = launch {
|
) = launch {
|
||||||
bugReporter.sendBugReport(
|
bugReporter.sendBugReport(
|
||||||
coroutineScope = this,
|
|
||||||
reportType = ReportType.BUG_REPORT,
|
reportType = ReportType.BUG_REPORT,
|
||||||
withDevicesLogs = formState.sendLogs,
|
withDevicesLogs = formState.sendLogs,
|
||||||
withCrashLogs = hasCrashLogs && formState.sendCrashLogs,
|
withCrashLogs = hasCrashLogs && formState.sendCrashLogs,
|
||||||
|
|
|
||||||
|
|
@ -21,13 +21,13 @@ import android.os.Build
|
||||||
import androidx.core.net.toFile
|
import androidx.core.net.toFile
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import com.squareup.anvil.annotations.ContributesBinding
|
import com.squareup.anvil.annotations.ContributesBinding
|
||||||
|
import io.element.android.features.rageshake.api.crash.CrashDataStore
|
||||||
import io.element.android.features.rageshake.api.reporter.BugReporter
|
import io.element.android.features.rageshake.api.reporter.BugReporter
|
||||||
import io.element.android.features.rageshake.api.reporter.BugReporterListener
|
import io.element.android.features.rageshake.api.reporter.BugReporterListener
|
||||||
import io.element.android.features.rageshake.api.reporter.ReportType
|
import io.element.android.features.rageshake.api.reporter.ReportType
|
||||||
import io.element.android.features.rageshake.impl.R
|
|
||||||
import io.element.android.features.rageshake.api.crash.CrashDataStore
|
|
||||||
import io.element.android.features.rageshake.impl.logs.VectorFileLogger
|
|
||||||
import io.element.android.features.rageshake.api.screenshot.ScreenshotHolder
|
import io.element.android.features.rageshake.api.screenshot.ScreenshotHolder
|
||||||
|
import io.element.android.features.rageshake.impl.R
|
||||||
|
import io.element.android.features.rageshake.impl.logs.VectorFileLogger
|
||||||
import io.element.android.libraries.androidutils.file.compressFile
|
import io.element.android.libraries.androidutils.file.compressFile
|
||||||
import io.element.android.libraries.androidutils.file.safeDelete
|
import io.element.android.libraries.androidutils.file.safeDelete
|
||||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||||
|
|
@ -35,9 +35,7 @@ import io.element.android.libraries.core.extensions.toOnOff
|
||||||
import io.element.android.libraries.core.mimetype.MimeTypes
|
import io.element.android.libraries.core.mimetype.MimeTypes
|
||||||
import io.element.android.libraries.di.AppScope
|
import io.element.android.libraries.di.AppScope
|
||||||
import io.element.android.libraries.di.ApplicationContext
|
import io.element.android.libraries.di.ApplicationContext
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import okhttp3.Call
|
import okhttp3.Call
|
||||||
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||||
|
|
@ -116,7 +114,6 @@ class DefaultBugReporter @Inject constructor(
|
||||||
/**
|
/**
|
||||||
* Send a bug report.
|
* Send a bug report.
|
||||||
*
|
*
|
||||||
* @param coroutineScope The coroutine scope
|
|
||||||
* @param reportType The report type (bug, suggestion, feedback)
|
* @param reportType The report type (bug, suggestion, feedback)
|
||||||
* @param withDevicesLogs true to include the device log
|
* @param withDevicesLogs true to include the device log
|
||||||
* @param withCrashLogs true to include the crash logs
|
* @param withCrashLogs true to include the crash logs
|
||||||
|
|
@ -128,8 +125,7 @@ class DefaultBugReporter @Inject constructor(
|
||||||
* @param customFields fields which will be sent with the report
|
* @param customFields fields which will be sent with the report
|
||||||
* @param listener the listener
|
* @param listener the listener
|
||||||
*/
|
*/
|
||||||
override fun sendBugReport(
|
override suspend fun sendBugReport(
|
||||||
coroutineScope: CoroutineScope,
|
|
||||||
reportType: ReportType,
|
reportType: ReportType,
|
||||||
withDevicesLogs: Boolean,
|
withDevicesLogs: Boolean,
|
||||||
withCrashLogs: Boolean,
|
withCrashLogs: Boolean,
|
||||||
|
|
@ -144,282 +140,280 @@ class DefaultBugReporter @Inject constructor(
|
||||||
// enumerate files to delete
|
// enumerate files to delete
|
||||||
val mBugReportFiles: MutableList<File> = ArrayList()
|
val mBugReportFiles: MutableList<File> = ArrayList()
|
||||||
|
|
||||||
coroutineScope.launch {
|
var serverError: String? = null
|
||||||
var serverError: String? = null
|
var reportURL: String? = null
|
||||||
var reportURL: String? = null
|
withContext(coroutineDispatchers.io) {
|
||||||
withContext(coroutineDispatchers.io) {
|
var bugDescription = theBugDescription
|
||||||
var bugDescription = theBugDescription
|
val crashCallStack = crashDataStore.crashInfo().first()
|
||||||
val crashCallStack = crashDataStore.crashInfo().first()
|
|
||||||
|
|
||||||
if (crashCallStack.isNotEmpty() && withCrashLogs) {
|
if (crashCallStack.isNotEmpty() && withCrashLogs) {
|
||||||
bugDescription += "\n\n\n\n--------------------------------- crash call stack ---------------------------------\n"
|
bugDescription += "\n\n\n\n--------------------------------- crash call stack ---------------------------------\n"
|
||||||
bugDescription += crashCallStack
|
bugDescription += crashCallStack
|
||||||
}
|
}
|
||||||
|
|
||||||
val gzippedFiles = ArrayList<File>()
|
val gzippedFiles = ArrayList<File>()
|
||||||
|
|
||||||
val vectorFileLogger = VectorFileLogger.getFromTimber()
|
val vectorFileLogger = VectorFileLogger.getFromTimber()
|
||||||
if (withDevicesLogs && vectorFileLogger != null) {
|
if (withDevicesLogs && vectorFileLogger != null) {
|
||||||
val files = vectorFileLogger.getLogFiles()
|
val files = vectorFileLogger.getLogFiles()
|
||||||
files.mapNotNullTo(gzippedFiles) { f ->
|
files.mapNotNullTo(gzippedFiles) { f ->
|
||||||
if (!mIsCancelled) {
|
if (!mIsCancelled) {
|
||||||
compressFile(f)
|
compressFile(f)
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!mIsCancelled && (withCrashLogs || withDevicesLogs)) {
|
|
||||||
val gzippedLogcat = saveLogCat(false)
|
|
||||||
|
|
||||||
if (null != gzippedLogcat) {
|
|
||||||
if (gzippedFiles.size == 0) {
|
|
||||||
gzippedFiles.add(gzippedLogcat)
|
|
||||||
} else {
|
|
||||||
gzippedFiles.add(0, gzippedLogcat)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
activeSessionHolder.getSafeActiveSession()
|
|
||||||
?.takeIf { !mIsCancelled && withKeyRequestHistory }
|
|
||||||
?.cryptoService()
|
|
||||||
?.getGossipingEvents()
|
|
||||||
?.let { GossipingEventsSerializer().serialize(it) }
|
|
||||||
?.toByteArray()
|
|
||||||
?.let { rawByteArray ->
|
|
||||||
File(context.cacheDir.absolutePath, KEY_REQUESTS_FILENAME)
|
|
||||||
.also {
|
|
||||||
it.outputStream()
|
|
||||||
.use { os -> os.write(rawByteArray) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
?.let { compressFile(it) }
|
|
||||||
?.let { gzippedFiles.add(it) }
|
|
||||||
*/
|
|
||||||
|
|
||||||
var deviceId = "undefined"
|
|
||||||
var userId = "undefined"
|
|
||||||
var olmVersion = "undefined"
|
|
||||||
|
|
||||||
/*
|
|
||||||
activeSessionHolder.getSafeActiveSession()?.let { session ->
|
|
||||||
userId = session.myUserId
|
|
||||||
deviceId = session.sessionParams.deviceId ?: "undefined"
|
|
||||||
olmVersion = session.cryptoService().getCryptoVersion(context, true)
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
if (!mIsCancelled) {
|
|
||||||
val text = when (reportType) {
|
|
||||||
ReportType.BUG_REPORT -> bugDescription
|
|
||||||
ReportType.SUGGESTION -> "[Suggestion] $bugDescription"
|
|
||||||
ReportType.SPACE_BETA_FEEDBACK -> "[spaces-feedback] $bugDescription"
|
|
||||||
ReportType.THREADS_BETA_FEEDBACK -> "[threads-feedback] $bugDescription"
|
|
||||||
ReportType.AUTO_UISI_SENDER,
|
|
||||||
ReportType.AUTO_UISI -> bugDescription
|
|
||||||
}
|
|
||||||
|
|
||||||
// build the multi part request
|
|
||||||
val builder = BugReporterMultipartBody.Builder()
|
|
||||||
.addFormDataPart("text", text)
|
|
||||||
.addFormDataPart("app", rageShakeAppNameForReport(reportType))
|
|
||||||
// .addFormDataPart("user_agent", matrix.getUserAgent())
|
|
||||||
.addFormDataPart("user_id", userId)
|
|
||||||
.addFormDataPart("can_contact", canContact.toString())
|
|
||||||
.addFormDataPart("device_id", deviceId)
|
|
||||||
// .addFormDataPart("version", versionProvider.getVersion(longFormat = true))
|
|
||||||
// .addFormDataPart("branch_name", buildMeta.gitBranchName)
|
|
||||||
// .addFormDataPart("matrix_sdk_version", Matrix.getSdkVersion())
|
|
||||||
.addFormDataPart("olm_version", olmVersion)
|
|
||||||
.addFormDataPart("device", Build.MODEL.trim())
|
|
||||||
// .addFormDataPart("verbose_log", vectorPreferences.labAllowedExtendedLogging().toOnOff())
|
|
||||||
.addFormDataPart("multi_window", inMultiWindowMode.toOnOff())
|
|
||||||
// .addFormDataPart(
|
|
||||||
// "os", Build.VERSION.RELEASE + " (API " + sdkIntProvider.get() + ") " +
|
|
||||||
// Build.VERSION.INCREMENTAL + "-" + Build.VERSION.CODENAME
|
|
||||||
// )
|
|
||||||
.addFormDataPart("locale", Locale.getDefault().toString())
|
|
||||||
// .addFormDataPart("app_language", vectorLocale.applicationLocale.toString())
|
|
||||||
// .addFormDataPart("default_app_language", systemLocaleProvider.getSystemLocale().toString())
|
|
||||||
// .addFormDataPart("theme", ThemeUtils.getApplicationTheme(context))
|
|
||||||
.addFormDataPart("server_version", serverVersion)
|
|
||||||
.apply {
|
|
||||||
customFields?.forEach { (name, value) ->
|
|
||||||
addFormDataPart(name, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// add the gzipped files
|
|
||||||
for (file in gzippedFiles) {
|
|
||||||
builder.addFormDataPart("compressed-log", file.name, file.asRequestBody(MimeTypes.OctetStream.toMediaTypeOrNull()))
|
|
||||||
}
|
|
||||||
|
|
||||||
mBugReportFiles.addAll(gzippedFiles)
|
|
||||||
|
|
||||||
if (withScreenshot) {
|
|
||||||
screenshotHolder.getFileUri()
|
|
||||||
?.toUri()
|
|
||||||
?.toFile()
|
|
||||||
?.let { screenshotFile ->
|
|
||||||
try {
|
|
||||||
builder.addFormDataPart(
|
|
||||||
"file",
|
|
||||||
screenshotFile.name, screenshotFile.asRequestBody(MimeTypes.OctetStream.toMediaTypeOrNull())
|
|
||||||
)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Timber.e(e, "## sendBugReport() : fail to write screenshot")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// add some github labels
|
|
||||||
// builder.addFormDataPart("label", buildMeta.versionName)
|
|
||||||
// builder.addFormDataPart("label", buildMeta.flavorDescription)
|
|
||||||
// builder.addFormDataPart("label", buildMeta.gitBranchName)
|
|
||||||
|
|
||||||
// Possible values for BuildConfig.BUILD_TYPE: "debug", "nightly", "release".
|
|
||||||
// builder.addFormDataPart("label", BuildConfig.BUILD_TYPE)
|
|
||||||
|
|
||||||
when (reportType) {
|
|
||||||
ReportType.BUG_REPORT -> {
|
|
||||||
/* nop */
|
|
||||||
}
|
|
||||||
ReportType.SUGGESTION -> builder.addFormDataPart("label", "[Suggestion]")
|
|
||||||
ReportType.SPACE_BETA_FEEDBACK -> builder.addFormDataPart("label", "spaces-feedback")
|
|
||||||
ReportType.THREADS_BETA_FEEDBACK -> builder.addFormDataPart("label", "threads-feedback")
|
|
||||||
ReportType.AUTO_UISI -> {
|
|
||||||
builder.addFormDataPart("label", "Z-UISI")
|
|
||||||
builder.addFormDataPart("label", "android")
|
|
||||||
builder.addFormDataPart("label", "uisi-recipient")
|
|
||||||
}
|
|
||||||
ReportType.AUTO_UISI_SENDER -> {
|
|
||||||
builder.addFormDataPart("label", "Z-UISI")
|
|
||||||
builder.addFormDataPart("label", "android")
|
|
||||||
builder.addFormDataPart("label", "uisi-sender")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (crashCallStack.isNotEmpty() && withCrashLogs) {
|
|
||||||
builder.addFormDataPart("label", "crash")
|
|
||||||
}
|
|
||||||
|
|
||||||
val requestBody = builder.build()
|
|
||||||
|
|
||||||
// add a progress listener
|
|
||||||
requestBody.setWriteListener { totalWritten, contentLength ->
|
|
||||||
val percentage = if (-1L != contentLength) {
|
|
||||||
if (totalWritten > contentLength) {
|
|
||||||
100
|
|
||||||
} else {
|
|
||||||
(totalWritten * 100 / contentLength).toInt()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mIsCancelled && null != mBugReportCall) {
|
|
||||||
mBugReportCall!!.cancel()
|
|
||||||
}
|
|
||||||
|
|
||||||
Timber.v("## onWrite() : $percentage%")
|
|
||||||
try {
|
|
||||||
listener?.onProgress(percentage)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Timber.e(e, "## onProgress() : failed")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// build the request
|
|
||||||
val request = Request.Builder()
|
|
||||||
.url(context.getString(R.string.bug_report_url))
|
|
||||||
.post(requestBody)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
var responseCode = HttpURLConnection.HTTP_INTERNAL_ERROR
|
|
||||||
var response: Response? = null
|
|
||||||
var errorMessage: String? = null
|
|
||||||
|
|
||||||
// trigger the request
|
|
||||||
try {
|
|
||||||
mBugReportCall = okHttpClient.newCall(request)
|
|
||||||
response = mBugReportCall!!.execute()
|
|
||||||
responseCode = response.code
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Timber.e(e, "response")
|
|
||||||
errorMessage = e.localizedMessage
|
|
||||||
}
|
|
||||||
|
|
||||||
// if the upload failed, try to retrieve the reason
|
|
||||||
if (responseCode != HttpURLConnection.HTTP_OK) {
|
|
||||||
if (null != errorMessage) {
|
|
||||||
serverError = "Failed with error $errorMessage"
|
|
||||||
} else if (response?.body == null) {
|
|
||||||
serverError = "Failed with error $responseCode"
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
val inputStream = response.body!!.byteStream()
|
|
||||||
|
|
||||||
serverError = inputStream.use {
|
|
||||||
buildString {
|
|
||||||
var ch = it.read()
|
|
||||||
while (ch != -1) {
|
|
||||||
append(ch.toChar())
|
|
||||||
ch = it.read()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if the error message
|
|
||||||
serverError?.let {
|
|
||||||
try {
|
|
||||||
val responseJSON = JSONObject(it)
|
|
||||||
serverError = responseJSON.getString("error")
|
|
||||||
} catch (e: JSONException) {
|
|
||||||
Timber.e(e, "doInBackground ; Json conversion failed")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// should never happen
|
|
||||||
if (null == serverError) {
|
|
||||||
serverError = "Failed with error $responseCode"
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Timber.e(e, "## sendBugReport() : failed to parse error")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
/*
|
null
|
||||||
reportURL = response?.body?.string()?.let { stringBody ->
|
|
||||||
adapter.fromJson(stringBody)?.get("report_url")?.toString()
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
withContext(coroutineDispatchers.main) {
|
if (!mIsCancelled && (withCrashLogs || withDevicesLogs)) {
|
||||||
mBugReportCall = null
|
val gzippedLogcat = saveLogCat(false)
|
||||||
|
|
||||||
// delete when the bug report has been successfully sent
|
if (null != gzippedLogcat) {
|
||||||
for (file in mBugReportFiles) {
|
if (gzippedFiles.size == 0) {
|
||||||
file.safeDelete()
|
gzippedFiles.add(gzippedLogcat)
|
||||||
|
} else {
|
||||||
|
gzippedFiles.add(0, gzippedLogcat)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
activeSessionHolder.getSafeActiveSession()
|
||||||
|
?.takeIf { !mIsCancelled && withKeyRequestHistory }
|
||||||
|
?.cryptoService()
|
||||||
|
?.getGossipingEvents()
|
||||||
|
?.let { GossipingEventsSerializer().serialize(it) }
|
||||||
|
?.toByteArray()
|
||||||
|
?.let { rawByteArray ->
|
||||||
|
File(context.cacheDir.absolutePath, KEY_REQUESTS_FILENAME)
|
||||||
|
.also {
|
||||||
|
it.outputStream()
|
||||||
|
.use { os -> os.write(rawByteArray) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?.let { compressFile(it) }
|
||||||
|
?.let { gzippedFiles.add(it) }
|
||||||
|
*/
|
||||||
|
|
||||||
|
var deviceId = "undefined"
|
||||||
|
var userId = "undefined"
|
||||||
|
var olmVersion = "undefined"
|
||||||
|
|
||||||
|
/*
|
||||||
|
activeSessionHolder.getSafeActiveSession()?.let { session ->
|
||||||
|
userId = session.myUserId
|
||||||
|
deviceId = session.sessionParams.deviceId ?: "undefined"
|
||||||
|
olmVersion = session.cryptoService().getCryptoVersion(context, true)
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!mIsCancelled) {
|
||||||
|
val text = when (reportType) {
|
||||||
|
ReportType.BUG_REPORT -> bugDescription
|
||||||
|
ReportType.SUGGESTION -> "[Suggestion] $bugDescription"
|
||||||
|
ReportType.SPACE_BETA_FEEDBACK -> "[spaces-feedback] $bugDescription"
|
||||||
|
ReportType.THREADS_BETA_FEEDBACK -> "[threads-feedback] $bugDescription"
|
||||||
|
ReportType.AUTO_UISI_SENDER,
|
||||||
|
ReportType.AUTO_UISI -> bugDescription
|
||||||
}
|
}
|
||||||
|
|
||||||
if (null != listener) {
|
// build the multi part request
|
||||||
try {
|
val builder = BugReporterMultipartBody.Builder()
|
||||||
if (mIsCancelled) {
|
.addFormDataPart("text", text)
|
||||||
listener.onUploadCancelled()
|
.addFormDataPart("app", rageShakeAppNameForReport(reportType))
|
||||||
} else if (null == serverError) {
|
// .addFormDataPart("user_agent", matrix.getUserAgent())
|
||||||
listener.onUploadSucceed(reportURL)
|
.addFormDataPart("user_id", userId)
|
||||||
} else {
|
.addFormDataPart("can_contact", canContact.toString())
|
||||||
listener.onUploadFailed(serverError)
|
.addFormDataPart("device_id", deviceId)
|
||||||
|
// .addFormDataPart("version", versionProvider.getVersion(longFormat = true))
|
||||||
|
// .addFormDataPart("branch_name", buildMeta.gitBranchName)
|
||||||
|
// .addFormDataPart("matrix_sdk_version", Matrix.getSdkVersion())
|
||||||
|
.addFormDataPart("olm_version", olmVersion)
|
||||||
|
.addFormDataPart("device", Build.MODEL.trim())
|
||||||
|
// .addFormDataPart("verbose_log", vectorPreferences.labAllowedExtendedLogging().toOnOff())
|
||||||
|
.addFormDataPart("multi_window", inMultiWindowMode.toOnOff())
|
||||||
|
// .addFormDataPart(
|
||||||
|
// "os", Build.VERSION.RELEASE + " (API " + sdkIntProvider.get() + ") " +
|
||||||
|
// Build.VERSION.INCREMENTAL + "-" + Build.VERSION.CODENAME
|
||||||
|
// )
|
||||||
|
.addFormDataPart("locale", Locale.getDefault().toString())
|
||||||
|
// .addFormDataPart("app_language", vectorLocale.applicationLocale.toString())
|
||||||
|
// .addFormDataPart("default_app_language", systemLocaleProvider.getSystemLocale().toString())
|
||||||
|
// .addFormDataPart("theme", ThemeUtils.getApplicationTheme(context))
|
||||||
|
.addFormDataPart("server_version", serverVersion)
|
||||||
|
.apply {
|
||||||
|
customFields?.forEach { (name, value) ->
|
||||||
|
addFormDataPart(name, value)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
|
||||||
Timber.e(e, "## onPostExecute() : failed")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// add the gzipped files
|
||||||
|
for (file in gzippedFiles) {
|
||||||
|
builder.addFormDataPart("compressed-log", file.name, file.asRequestBody(MimeTypes.OctetStream.toMediaTypeOrNull()))
|
||||||
|
}
|
||||||
|
|
||||||
|
mBugReportFiles.addAll(gzippedFiles)
|
||||||
|
|
||||||
|
if (withScreenshot) {
|
||||||
|
screenshotHolder.getFileUri()
|
||||||
|
?.toUri()
|
||||||
|
?.toFile()
|
||||||
|
?.let { screenshotFile ->
|
||||||
|
try {
|
||||||
|
builder.addFormDataPart(
|
||||||
|
"file",
|
||||||
|
screenshotFile.name, screenshotFile.asRequestBody(MimeTypes.OctetStream.toMediaTypeOrNull())
|
||||||
|
)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Timber.e(e, "## sendBugReport() : fail to write screenshot")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// add some github labels
|
||||||
|
// builder.addFormDataPart("label", buildMeta.versionName)
|
||||||
|
// builder.addFormDataPart("label", buildMeta.flavorDescription)
|
||||||
|
// builder.addFormDataPart("label", buildMeta.gitBranchName)
|
||||||
|
|
||||||
|
// Possible values for BuildConfig.BUILD_TYPE: "debug", "nightly", "release".
|
||||||
|
// builder.addFormDataPart("label", BuildConfig.BUILD_TYPE)
|
||||||
|
|
||||||
|
when (reportType) {
|
||||||
|
ReportType.BUG_REPORT -> {
|
||||||
|
/* nop */
|
||||||
|
}
|
||||||
|
ReportType.SUGGESTION -> builder.addFormDataPart("label", "[Suggestion]")
|
||||||
|
ReportType.SPACE_BETA_FEEDBACK -> builder.addFormDataPart("label", "spaces-feedback")
|
||||||
|
ReportType.THREADS_BETA_FEEDBACK -> builder.addFormDataPart("label", "threads-feedback")
|
||||||
|
ReportType.AUTO_UISI -> {
|
||||||
|
builder.addFormDataPart("label", "Z-UISI")
|
||||||
|
builder.addFormDataPart("label", "android")
|
||||||
|
builder.addFormDataPart("label", "uisi-recipient")
|
||||||
|
}
|
||||||
|
ReportType.AUTO_UISI_SENDER -> {
|
||||||
|
builder.addFormDataPart("label", "Z-UISI")
|
||||||
|
builder.addFormDataPart("label", "android")
|
||||||
|
builder.addFormDataPart("label", "uisi-sender")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (crashCallStack.isNotEmpty() && withCrashLogs) {
|
||||||
|
builder.addFormDataPart("label", "crash")
|
||||||
|
}
|
||||||
|
|
||||||
|
val requestBody = builder.build()
|
||||||
|
|
||||||
|
// add a progress listener
|
||||||
|
requestBody.setWriteListener { totalWritten, contentLength ->
|
||||||
|
val percentage = if (-1L != contentLength) {
|
||||||
|
if (totalWritten > contentLength) {
|
||||||
|
100
|
||||||
|
} else {
|
||||||
|
(totalWritten * 100 / contentLength).toInt()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mIsCancelled && null != mBugReportCall) {
|
||||||
|
mBugReportCall!!.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
Timber.v("## onWrite() : $percentage%")
|
||||||
|
try {
|
||||||
|
listener?.onProgress(percentage)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Timber.e(e, "## onProgress() : failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// build the request
|
||||||
|
val request = Request.Builder()
|
||||||
|
.url(context.getString(R.string.bug_report_url))
|
||||||
|
.post(requestBody)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
var responseCode = HttpURLConnection.HTTP_INTERNAL_ERROR
|
||||||
|
var response: Response? = null
|
||||||
|
var errorMessage: String? = null
|
||||||
|
|
||||||
|
// trigger the request
|
||||||
|
try {
|
||||||
|
mBugReportCall = okHttpClient.newCall(request)
|
||||||
|
response = mBugReportCall!!.execute()
|
||||||
|
responseCode = response.code
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Timber.e(e, "response")
|
||||||
|
errorMessage = e.localizedMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the upload failed, try to retrieve the reason
|
||||||
|
if (responseCode != HttpURLConnection.HTTP_OK) {
|
||||||
|
if (null != errorMessage) {
|
||||||
|
serverError = "Failed with error $errorMessage"
|
||||||
|
} else if (response?.body == null) {
|
||||||
|
serverError = "Failed with error $responseCode"
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
val inputStream = response.body!!.byteStream()
|
||||||
|
|
||||||
|
serverError = inputStream.use {
|
||||||
|
buildString {
|
||||||
|
var ch = it.read()
|
||||||
|
while (ch != -1) {
|
||||||
|
append(ch.toChar())
|
||||||
|
ch = it.read()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if the error message
|
||||||
|
serverError?.let {
|
||||||
|
try {
|
||||||
|
val responseJSON = JSONObject(it)
|
||||||
|
serverError = responseJSON.getString("error")
|
||||||
|
} catch (e: JSONException) {
|
||||||
|
Timber.e(e, "doInBackground ; Json conversion failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// should never happen
|
||||||
|
if (null == serverError) {
|
||||||
|
serverError = "Failed with error $responseCode"
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Timber.e(e, "## sendBugReport() : failed to parse error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/*
|
||||||
|
reportURL = response?.body?.string()?.let { stringBody ->
|
||||||
|
adapter.fromJson(stringBody)?.get("report_url")?.toString()
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
withContext(coroutineDispatchers.main) {
|
||||||
|
mBugReportCall = null
|
||||||
|
|
||||||
|
// delete when the bug report has been successfully sent
|
||||||
|
for (file in mBugReportFiles) {
|
||||||
|
file.safeDelete()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null != listener) {
|
||||||
|
try {
|
||||||
|
if (mIsCancelled) {
|
||||||
|
listener.onUploadCancelled()
|
||||||
|
} else if (null == serverError) {
|
||||||
|
listener.onUploadSucceed(reportURL)
|
||||||
|
} else {
|
||||||
|
listener.onUploadFailed(serverError)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Timber.e(e, "## onPostExecute() : failed")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,13 +20,10 @@ import io.element.android.features.rageshake.api.reporter.BugReporter
|
||||||
import io.element.android.features.rageshake.api.reporter.BugReporterListener
|
import io.element.android.features.rageshake.api.reporter.BugReporterListener
|
||||||
import io.element.android.features.rageshake.api.reporter.ReportType
|
import io.element.android.features.rageshake.api.reporter.ReportType
|
||||||
import io.element.android.libraries.matrix.test.A_FAILURE_REASON
|
import io.element.android.libraries.matrix.test.A_FAILURE_REASON
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
class FakeBugReporter(val mode: FakeBugReporterMode = FakeBugReporterMode.Success) : BugReporter {
|
class FakeBugReporter(val mode: FakeBugReporterMode = FakeBugReporterMode.Success) : BugReporter {
|
||||||
override fun sendBugReport(
|
override suspend fun sendBugReport(
|
||||||
coroutineScope: CoroutineScope,
|
|
||||||
reportType: ReportType,
|
reportType: ReportType,
|
||||||
withDevicesLogs: Boolean,
|
withDevicesLogs: Boolean,
|
||||||
withCrashLogs: Boolean,
|
withCrashLogs: Boolean,
|
||||||
|
|
@ -38,27 +35,25 @@ class FakeBugReporter(val mode: FakeBugReporterMode = FakeBugReporterMode.Succes
|
||||||
customFields: Map<String, String>?,
|
customFields: Map<String, String>?,
|
||||||
listener: BugReporterListener?,
|
listener: BugReporterListener?,
|
||||||
) {
|
) {
|
||||||
coroutineScope.launch {
|
delay(100)
|
||||||
delay(100)
|
listener?.onProgress(0)
|
||||||
listener?.onProgress(0)
|
delay(100)
|
||||||
delay(100)
|
listener?.onProgress(50)
|
||||||
listener?.onProgress(50)
|
delay(100)
|
||||||
delay(100)
|
when (mode) {
|
||||||
when (mode) {
|
FakeBugReporterMode.Success -> Unit
|
||||||
FakeBugReporterMode.Success -> Unit
|
FakeBugReporterMode.Failure -> {
|
||||||
FakeBugReporterMode.Failure -> {
|
listener?.onUploadFailed(A_FAILURE_REASON)
|
||||||
listener?.onUploadFailed(A_FAILURE_REASON)
|
return
|
||||||
return@launch
|
}
|
||||||
}
|
FakeBugReporterMode.Cancel -> {
|
||||||
FakeBugReporterMode.Cancel -> {
|
listener?.onUploadCancelled()
|
||||||
listener?.onUploadCancelled()
|
return
|
||||||
return@launch
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
listener?.onProgress(100)
|
|
||||||
delay(100)
|
|
||||||
listener?.onUploadSucceed(null)
|
|
||||||
}
|
}
|
||||||
|
listener?.onProgress(100)
|
||||||
|
delay(100)
|
||||||
|
listener?.onUploadSucceed(null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue