diff --git a/vector/src/main/java/im/vector/app/features/rageshake/BugReporter.kt b/vector/src/main/java/im/vector/app/features/rageshake/BugReporter.kt index fa037a5435..1a03fc6c47 100755 --- a/vector/src/main/java/im/vector/app/features/rageshake/BugReporter.kt +++ b/vector/src/main/java/im/vector/app/features/rageshake/BugReporter.kt @@ -102,7 +102,7 @@ class BugReporter @Inject constructor( var screenshot: Bitmap? = null private set - private val coroutineScope = CoroutineScope(SupervisorJob()) + private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main) private val LOGCAT_CMD_ERROR = arrayOf("logcat", // /< Run 'logcat' command "-d", // /< Dump the log rather than continue outputting it @@ -169,284 +169,277 @@ class BugReporter @Inject constructor( // enumerate files to delete val mBugReportFiles: MutableList = ArrayList() - coroutineScope.executeAsyncTask( - onPreExecute = { - // NOOP - }, - doInBackground = { publishProgress: suspend (progress: Int) -> Unit -> - var bugDescription = theBugDescription - var serverError: String? = null - val crashCallStack = getCrashDescription(context) + coroutineScope.launch { + var serverError: String? = null + withContext(Dispatchers.IO) { + var bugDescription = theBugDescription + val crashCallStack = getCrashDescription(context) - if (null != crashCallStack) { - bugDescription += "\n\n\n\n--------------------------------- crash call stack ---------------------------------\n" - bugDescription += crashCallStack + if (null != crashCallStack) { + bugDescription += "\n\n\n\n--------------------------------- crash call stack ---------------------------------\n" + bugDescription += crashCallStack + } + + val gzippedFiles = ArrayList() + + if (withDevicesLogs) { + val files = vectorFileLogger.getLogFiles() + files.mapNotNullTo(gzippedFiles) { f -> + if (!mIsCancelled) { + compressFile(f) + } else { + null + } } + } - val gzippedFiles = ArrayList() + if (!mIsCancelled && (withCrashLogs || withDevicesLogs)) { + val gzippedLogcat = saveLogCat(context, false) - if (withDevicesLogs) { - val files = vectorFileLogger.getLogFiles() - files.mapNotNullTo(gzippedFiles) { f -> - if (!mIsCancelled) { - compressFile(f) - } else { - null - } + if (null != gzippedLogcat) { + if (gzippedFiles.size == 0) { + gzippedFiles.add(gzippedLogcat) + } else { + gzippedFiles.add(0, gzippedLogcat) } } - if (!mIsCancelled && (withCrashLogs || withDevicesLogs)) { - val gzippedLogcat = saveLogCat(context, false) + val crashDescription = getCrashFile(context) + if (crashDescription.exists()) { + val compressedCrashDescription = compressFile(crashDescription) - if (null != gzippedLogcat) { + if (null != compressedCrashDescription) { if (gzippedFiles.size == 0) { - gzippedFiles.add(gzippedLogcat) + gzippedFiles.add(compressedCrashDescription) } else { - gzippedFiles.add(0, gzippedLogcat) - } - } - - 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) - } + gzippedFiles.add(0, compressedCrashDescription) } } } + } - 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 = "[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) + 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) } } - - 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 - builder.addFormDataPart("label", BuildConfig.VERSION_NAME) - builder.addFormDataPart("label", BuildConfig.FLAVOR_DESCRIPTION) - builder.addFormDataPart("label", context.getString(R.string.git_branch_name)) + activeSessionHolder.getSafeActiveSession()?.let { session -> + userId = session.myUserId + deviceId = session.sessionParams.deviceId ?: "undefined" + olmVersion = session.cryptoService().getCryptoVersion(context, true) + } - // 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() - } + if (!mIsCancelled) { + val text = "[Element] " + + if (forSuggestion) { + "[Suggestion] " } 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) { - mBugReportCall!!.cancel() - } + try { + logCatScreenshotFile.outputStream().use { + bitmap.compress(Bitmap.CompressFormat.PNG, 100, it) + } - Timber.v("## onWrite() : $percentage%") - suspend { publishProgress(percentage) } + builder.addFormDataPart("file", + 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 - val request = Request.Builder() - .url(context.getString(R.string.bug_report_url)) - .post(requestBody) - .build() + if (mIsCancelled && null != mBugReportCall) { + mBugReportCall!!.cancel() + } - var responseCode = HttpURLConnection.HTTP_INTERNAL_ERROR - var response: Response? = null - var errorMessage: String? = null - - // trigger the request + Timber.v("## onWrite() : $percentage%") try { - mBugReportCall = mOkHttpClient.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 (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) + listener?.onProgress(percentage) } catch (e: Exception) { Timber.e(e, "## onProgress() : failed") } } - }, - onPostExecute = { reason: String? -> - mBugReportCall = null - // delete when the bug report has been successfully sent - for (file in mBugReportFiles) { - file.delete() + // 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 = mOkHttpClient.newCall(request) + response = mBugReportCall!!.execute() + responseCode = response.code + } catch (e: Exception) { + Timber.e(e, "response") + errorMessage = e.localizedMessage } - if (null != listener) { - try { - if (mIsCancelled) { - listener.onUploadCancelled() - } else if (null == reason) { - listener.onUploadSucceed() - } else { - listener.onUploadFailed(reason) + // 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 + 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) } - // ============================================================================================================== - // crash report management - // ============================================================================================================== +// ============================================================================================================== +// crash report management +// ============================================================================================================== /** * Provides the crash file @@ -536,9 +529,9 @@ class BugReporter @Inject constructor( return null } - // ============================================================================================================== - // Screenshot management - // ============================================================================================================== +// ============================================================================================================== +// Screenshot management +// ============================================================================================================== /** * Take a screenshot of the display. @@ -605,9 +598,9 @@ class BugReporter @Inject constructor( } } - // ============================================================================================================== - // Logcat management - // ============================================================================================================== +// ============================================================================================================== +// Logcat management +// ============================================================================================================== /** * Save the logcat @@ -667,9 +660,9 @@ class BugReporter @Inject constructor( } } - // ============================================================================================================== - // File compression management - // ============================================================================================================== +// ============================================================================================================== +// File compression management +// ============================================================================================================== /** * GZip a file @@ -703,21 +696,4 @@ class BugReporter @Inject constructor( return null } - - fun 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) - } }