Fix bug reporter progress.

This commit is contained in:
Onuray Sahin 2021-01-05 14:44:02 +03:00
parent 23623b8895
commit fa311f4ce2

View File

@ -102,7 +102,7 @@ class BugReporter @Inject constructor(
var screenshot: Bitmap? = null var screenshot: Bitmap? = null
private set private set
private val coroutineScope = CoroutineScope(SupervisorJob()) private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
private val LOGCAT_CMD_ERROR = arrayOf("logcat", // /< Run 'logcat' command private val LOGCAT_CMD_ERROR = arrayOf("logcat", // /< Run 'logcat' command
"-d", // /< Dump the log rather than continue outputting it "-d", // /< Dump the log rather than continue outputting it
@ -169,284 +169,277 @@ class BugReporter @Inject constructor(
// enumerate files to delete // enumerate files to delete
val mBugReportFiles: MutableList<File> = ArrayList() val mBugReportFiles: MutableList<File> = ArrayList()
coroutineScope.executeAsyncTask( coroutineScope.launch {
onPreExecute = { var serverError: String? = null
// NOOP withContext(Dispatchers.IO) {
}, var bugDescription = theBugDescription
doInBackground = { publishProgress: suspend (progress: Int) -> Unit -> val crashCallStack = getCrashDescription(context)
var bugDescription = theBugDescription
var serverError: String? = null
val crashCallStack = getCrashDescription(context)
if (null != crashCallStack) { if (null != crashCallStack) {
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>()
if (withDevicesLogs) {
val files = vectorFileLogger.getLogFiles()
files.mapNotNullTo(gzippedFiles) { f ->
if (!mIsCancelled) {
compressFile(f)
} else {
null
}
} }
}
val gzippedFiles = ArrayList<File>() if (!mIsCancelled && (withCrashLogs || withDevicesLogs)) {
val gzippedLogcat = saveLogCat(context, false)
if (withDevicesLogs) { if (null != gzippedLogcat) {
val files = vectorFileLogger.getLogFiles() if (gzippedFiles.size == 0) {
files.mapNotNullTo(gzippedFiles) { f -> gzippedFiles.add(gzippedLogcat)
if (!mIsCancelled) { } else {
compressFile(f) gzippedFiles.add(0, gzippedLogcat)
} else {
null
}
} }
} }
if (!mIsCancelled && (withCrashLogs || withDevicesLogs)) { val crashDescription = getCrashFile(context)
val gzippedLogcat = saveLogCat(context, false) if (crashDescription.exists()) {
val compressedCrashDescription = compressFile(crashDescription)
if (null != gzippedLogcat) { if (null != compressedCrashDescription) {
if (gzippedFiles.size == 0) { if (gzippedFiles.size == 0) {
gzippedFiles.add(gzippedLogcat) gzippedFiles.add(compressedCrashDescription)
} else { } else {
gzippedFiles.add(0, gzippedLogcat) gzippedFiles.add(0, compressedCrashDescription)
}
}
val crashDescription = getCrashFile(context)
if (crashDescription.exists()) {
val compressedCrashDescription = compressFile(crashDescription)
if (null != compressedCrashDescription) {
if (gzippedFiles.size == 0) {
gzippedFiles.add(compressedCrashDescription)
} else {
gzippedFiles.add(0, compressedCrashDescription)
}
} }
} }
} }
}
activeSessionHolder.getSafeActiveSession() activeSessionHolder.getSafeActiveSession()
?.takeIf { !mIsCancelled && withKeyRequestHistory } ?.takeIf { !mIsCancelled && withKeyRequestHistory }
?.cryptoService() ?.cryptoService()
?.getGossipingEvents() ?.getGossipingEvents()
?.let { GossipingEventsSerializer().serialize(it) } ?.let { GossipingEventsSerializer().serialize(it) }
?.toByteArray() ?.toByteArray()
?.let { rawByteArray -> ?.let { rawByteArray ->
File(context.cacheDir.absolutePath, KEY_REQUESTS_FILENAME) File(context.cacheDir.absolutePath, KEY_REQUESTS_FILENAME)
.also { .also {
it.outputStream() it.outputStream()
.use { os -> os.write(rawByteArray) } .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 = "[Element] " +
if (forSuggestion) {
"[Suggestion] "
} else {
""
} +
bugDescription
// build the multi part request
val builder = BugReporterMultipartBody.Builder()
.addFormDataPart("text", text)
.addFormDataPart("app", "riot-android")
.addFormDataPart("user_agent", Matrix.getInstance(context).getUserAgent())
.addFormDataPart("user_id", userId)
.addFormDataPart("device_id", deviceId)
.addFormDataPart("version", versionProvider.getVersion(longFormat = true, useBuildNumber = false))
.addFormDataPart("branch_name", context.getString(R.string.git_branch_name))
.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 " + Build.VERSION.SDK_INT + ") "
+ 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))
val buildNumber = context.getString(R.string.build_number)
if (buildNumber.isNotEmpty() && buildNumber != "0") {
builder.addFormDataPart("build_number", buildNumber)
}
// add the gzipped files
for (file in gzippedFiles) {
builder.addFormDataPart("compressed-log", file.name, file.asRequestBody(MimeTypes.OctetStream.toMediaTypeOrNull()))
}
mBugReportFiles.addAll(gzippedFiles)
if (withScreenshot) {
val bitmap = screenshot
if (null != bitmap) {
val logCatScreenshotFile = File(context.cacheDir.absolutePath, LOG_CAT_SCREENSHOT_FILENAME)
if (logCatScreenshotFile.exists()) {
logCatScreenshotFile.delete()
}
try {
logCatScreenshotFile.outputStream().use {
bitmap.compress(Bitmap.CompressFormat.PNG, 100, it)
} }
builder.addFormDataPart("file",
logCatScreenshotFile.name, logCatScreenshotFile.asRequestBody(MimeTypes.OctetStream.toMediaTypeOrNull()))
} catch (e: Exception) {
Timber.e(e, "## sendBugReport() : fail to write screenshot$e")
}
}
} }
?.let { compressFile(it) }
?.let { gzippedFiles.add(it) }
screenshot = null var deviceId = "undefined"
var userId = "undefined"
var olmVersion = "undefined"
// add some github labels activeSessionHolder.getSafeActiveSession()?.let { session ->
builder.addFormDataPart("label", BuildConfig.VERSION_NAME) userId = session.myUserId
builder.addFormDataPart("label", BuildConfig.FLAVOR_DESCRIPTION) deviceId = session.sessionParams.deviceId ?: "undefined"
builder.addFormDataPart("label", context.getString(R.string.git_branch_name)) olmVersion = session.cryptoService().getCryptoVersion(context, true)
}
// Special for RiotX if (!mIsCancelled) {
builder.addFormDataPart("label", "[Element]") val text = "[Element] " +
if (forSuggestion) {
// Suggestion "[Suggestion] "
if (forSuggestion) {
builder.addFormDataPart("label", "[Suggestion]")
}
if (getCrashFile(context).exists()) {
builder.addFormDataPart("label", "crash")
deleteCrashFile(context)
}
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 { } else {
0 ""
} +
bugDescription
// build the multi part request
val builder = BugReporterMultipartBody.Builder()
.addFormDataPart("text", text)
.addFormDataPart("app", "riot-android")
.addFormDataPart("user_agent", Matrix.getInstance(context).getUserAgent())
.addFormDataPart("user_id", userId)
.addFormDataPart("device_id", deviceId)
.addFormDataPart("version", versionProvider.getVersion(longFormat = true, useBuildNumber = false))
.addFormDataPart("branch_name", context.getString(R.string.git_branch_name))
.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 " + Build.VERSION.SDK_INT + ") "
+ 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))
val buildNumber = context.getString(R.string.build_number)
if (buildNumber.isNotEmpty() && buildNumber != "0") {
builder.addFormDataPart("build_number", buildNumber)
}
// add the gzipped files
for (file in gzippedFiles) {
builder.addFormDataPart("compressed-log", file.name, file.asRequestBody(MimeTypes.OctetStream.toMediaTypeOrNull()))
}
mBugReportFiles.addAll(gzippedFiles)
if (withScreenshot) {
val bitmap = screenshot
if (null != bitmap) {
val logCatScreenshotFile = File(context.cacheDir.absolutePath, LOG_CAT_SCREENSHOT_FILENAME)
if (logCatScreenshotFile.exists()) {
logCatScreenshotFile.delete()
} }
if (mIsCancelled && null != mBugReportCall) { try {
mBugReportCall!!.cancel() logCatScreenshotFile.outputStream().use {
} bitmap.compress(Bitmap.CompressFormat.PNG, 100, it)
}
Timber.v("## onWrite() : $percentage%") builder.addFormDataPart("file",
suspend { publishProgress(percentage) } logCatScreenshotFile.name, logCatScreenshotFile.asRequestBody(MimeTypes.OctetStream.toMediaTypeOrNull()))
} catch (e: Exception) {
Timber.e(e, "## sendBugReport() : fail to write screenshot$e")
}
}
}
screenshot = null
// add some github labels
builder.addFormDataPart("label", BuildConfig.VERSION_NAME)
builder.addFormDataPart("label", BuildConfig.FLAVOR_DESCRIPTION)
builder.addFormDataPart("label", context.getString(R.string.git_branch_name))
// Special for RiotX
builder.addFormDataPart("label", "[Element]")
// Suggestion
if (forSuggestion) {
builder.addFormDataPart("label", "[Suggestion]")
}
if (getCrashFile(context).exists()) {
builder.addFormDataPart("label", "crash")
deleteCrashFile(context)
}
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
} }
// build the request if (mIsCancelled && null != mBugReportCall) {
val request = Request.Builder() mBugReportCall!!.cancel()
.url(context.getString(R.string.bug_report_url)) }
.post(requestBody)
.build()
var responseCode = HttpURLConnection.HTTP_INTERNAL_ERROR Timber.v("## onWrite() : $percentage%")
var response: Response? = null
var errorMessage: String? = null
// trigger the request
try { try {
mBugReportCall = mOkHttpClient.newCall(request) listener?.onProgress(percentage)
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 (null == response || null == response.body) {
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
try {
val responseJSON = JSONObject(serverError)
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")
}
}
}
}
serverError
},
onProgressUpdate = { progress ->
if (null != listener) {
try {
listener.onProgress(progress)
} catch (e: Exception) { } catch (e: Exception) {
Timber.e(e, "## onProgress() : failed") Timber.e(e, "## onProgress() : failed")
} }
} }
},
onPostExecute = { reason: String? ->
mBugReportCall = null
// delete when the bug report has been successfully sent // build the request
for (file in mBugReportFiles) { val request = Request.Builder()
file.delete() .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 = mOkHttpClient.newCall(request)
response = mBugReportCall!!.execute()
responseCode = response.code
} catch (e: Exception) {
Timber.e(e, "response")
errorMessage = e.localizedMessage
} }
if (null != listener) { // if the upload failed, try to retrieve the reason
try { if (responseCode != HttpURLConnection.HTTP_OK) {
if (mIsCancelled) { if (null != errorMessage) {
listener.onUploadCancelled() serverError = "Failed with error $errorMessage"
} else if (null == reason) { } else if (null == response || null == response.body) {
listener.onUploadSucceed() serverError = "Failed with error $responseCode"
} else { } else {
listener.onUploadFailed(reason) 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")
} }
} catch (e: Exception) {
Timber.e(e, "## onPostExecute() : failed")
} }
} }
} }
) }
withContext(Dispatchers.Main) {
mBugReportCall = null
// delete when the bug report has been successfully sent
for (file in mBugReportFiles) {
file.delete()
}
if (null != listener) {
try {
if (mIsCancelled) {
listener.onUploadCancelled()
} else if (null == serverError) {
listener.onUploadSucceed()
} else {
listener.onUploadFailed(serverError)
}
} catch (e: Exception) {
Timber.e(e, "## onPostExecute() : failed")
}
}
}
}
} }
/** /**
@ -464,9 +457,9 @@ class BugReporter @Inject constructor(
activity.startActivity(intent) activity.startActivity(intent)
} }
// ============================================================================================================== // ==============================================================================================================
// crash report management // crash report management
// ============================================================================================================== // ==============================================================================================================
/** /**
* Provides the crash file * Provides the crash file
@ -536,9 +529,9 @@ class BugReporter @Inject constructor(
return null return null
} }
// ============================================================================================================== // ==============================================================================================================
// Screenshot management // Screenshot management
// ============================================================================================================== // ==============================================================================================================
/** /**
* Take a screenshot of the display. * Take a screenshot of the display.
@ -605,9 +598,9 @@ class BugReporter @Inject constructor(
} }
} }
// ============================================================================================================== // ==============================================================================================================
// Logcat management // Logcat management
// ============================================================================================================== // ==============================================================================================================
/** /**
* Save the logcat * Save the logcat
@ -667,9 +660,9 @@ class BugReporter @Inject constructor(
} }
} }
// ============================================================================================================== // ==============================================================================================================
// File compression management // File compression management
// ============================================================================================================== // ==============================================================================================================
/** /**
* GZip a file * GZip a file
@ -703,21 +696,4 @@ class BugReporter @Inject constructor(
return null return null
} }
fun <P, R> CoroutineScope.executeAsyncTask(
onPreExecute: () -> Unit,
doInBackground: suspend (suspend (P) -> Unit) -> R,
onPostExecute: (R) -> Unit,
onProgressUpdate: (P) -> Unit
) = launch {
onPreExecute()
val result = withContext(Dispatchers.IO) {
doInBackground {
withContext(Dispatchers.Main) { onProgressUpdate(it) }
}
}
onPostExecute(result)
}
} }