From e9ca109c57b5c76d5050f94794c2d292280f92bd Mon Sep 17 00:00:00 2001 From: Kacper Ziubryniewicz Date: Tue, 31 Dec 2019 18:48:06 +0100 Subject: [PATCH] [Errors] Rewrite crash activity in Kotlin and add error reporting. --- .../edziennik/data/api/Errors.kt | 2 + .../ui/modules/base/CrashActivity.java | 180 ------------------ .../ui/modules/base/CrashActivity.kt | 180 ++++++++++++++++++ app/src/main/res/values/errors.xml | 4 + 4 files changed, 186 insertions(+), 180 deletions(-) delete mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/base/CrashActivity.java create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/base/CrashActivity.kt diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Errors.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Errors.kt index 01f3f3d1..bd4f47a0 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Errors.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Errors.kt @@ -31,6 +31,8 @@ const val CODE_SYNERGIA_NOT_ACTIVATED = 32 const val CODE_LIBRUS_DISCONNECTED = 31 const val CODE_PROFILE_ARCHIVED = 30*/ +const val ERROR_APP_CRASH = 1 + const val ERROR_REQUEST_FAILURE = 50 const val ERROR_REQUEST_HTTP_400 = 51 const val ERROR_REQUEST_HTTP_401 = 52 diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/base/CrashActivity.java b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/base/CrashActivity.java deleted file mode 100644 index f159282a..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/base/CrashActivity.java +++ /dev/null @@ -1,180 +0,0 @@ -package pl.szczodrzynski.edziennik.ui.modules.base; - -/* - * Copyright 2014-2017 Eduard Ereza Martínez - * - * 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. - */ - -import android.annotation.SuppressLint; -import android.content.ClipData; -import android.content.ClipboardManager; -import android.content.Intent; -import android.os.Build; -import android.os.Bundle; -import androidx.annotation.Nullable; -import androidx.appcompat.app.AppCompatActivity; -import android.text.Html; -import android.util.Base64; -import android.view.View; -import android.widget.Button; -import android.widget.Toast; - -import com.afollestad.materialdialogs.MaterialDialog; - -import cat.ereza.customactivityoncrash.CustomActivityOnCrash; -import cat.ereza.customactivityoncrash.config.CaocConfig; -import pl.szczodrzynski.edziennik.App; -import pl.szczodrzynski.edziennik.BuildConfig; -import pl.szczodrzynski.edziennik.R; -import pl.szczodrzynski.edziennik.network.ServerRequest; -import pl.szczodrzynski.edziennik.utils.Themes; - -import static pl.szczodrzynski.edziennik.App.APP_URL; -import static pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile.REGISTRATION_ENABLED; - -public final class CrashActivity extends AppCompatActivity { - - private App app; - - @SuppressLint("PrivateResource") - @Override - protected void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - this.app = (App)getApplication(); - setTheme(Themes.INSTANCE.getAppTheme()); - - setContentView(R.layout.activity_crash); - - final CaocConfig config = CustomActivityOnCrash.getConfigFromIntent(getIntent()); - - if (config == null) { - //This should never happen - Just finish the activity to avoid a recursive crash. - finish(); - return; - } - - //Close/restart button logic: - //If a class if set, use restart. - //Else, use close and just finish the app. - //It is recommended that you follow this logic if implementing a custom error activity. - Button restartButton = findViewById(R.id.crash_restart_btn); - restartButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - CustomActivityOnCrash.restartApplication(CrashActivity.this, config); - } - }); - - - Button devMessageButton = findViewById(R.id.crash_dev_message_btn); - devMessageButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - Intent i = new Intent(CrashActivity.this, CrashGtfoActivity.class); - startActivity(i); - } - }); - - final Button reportButton = findViewById(R.id.crash_report_btn); - reportButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - if (!app.networkUtils.isOnline()) - { - new MaterialDialog.Builder(CrashActivity.this) - .title(R.string.network_you_are_offline_title) - .content(R.string.network_you_are_offline_text) - .positiveText(R.string.ok) - .show(); - } - else - { - //app.networkUtils.setSelfSignedSSL(CrashActivity.this, null); - new ServerRequest(app, app.requestScheme + APP_URL + "main.php?report", "CrashActivity") - .setBodyParameter("base64_encoded", Base64.encodeToString(getErrorString(getIntent(), true).getBytes(), Base64.DEFAULT)) - .run((e, result) -> { - if (result != null) - { - if (result.get("success").getAsBoolean()) { - Toast.makeText(CrashActivity.this, getString(R.string.crash_report_sent), Toast.LENGTH_SHORT).show(); - reportButton.setEnabled(false); - reportButton.setTextColor(getResources().getColor(android.R.color.darker_gray)); - } - else { - Toast.makeText(CrashActivity.this, getString(R.string.crash_report_cannot_send) + ": " + result.get("reason").getAsString(), Toast.LENGTH_LONG).show(); - } - } - else - { - Toast.makeText(CrashActivity.this, getString(R.string.crash_report_cannot_send)+" JsonObject equals null", Toast.LENGTH_LONG).show(); - } - }); - } - } - }); - - Button moreInfoButton = findViewById(R.id.crash_details_btn); - moreInfoButton.setOnClickListener(v -> new MaterialDialog.Builder(CrashActivity.this) - .title(R.string.crash_details) - .content(Html.fromHtml(getErrorString(getIntent(), false))) - .typeface(null, "RobotoMono-Regular.ttf") - .positiveText(R.string.close) - .neutralText(R.string.copy_to_clipboard) - .onNeutral((dialog, which) -> copyErrorToClipboard()) - .show()); - - String errorInformation = CustomActivityOnCrash.getAllErrorDetailsFromIntent(CrashActivity.this, getIntent()); - if (errorInformation.contains("MANUAL CRASH")) - { - findViewById(R.id.crash_notice).setVisibility(View.GONE); - findViewById(R.id.crash_report_btn).setVisibility(View.GONE); - findViewById(R.id.crash_feature).setVisibility(View.VISIBLE); - } - else - { - findViewById(R.id.crash_notice).setVisibility(View.VISIBLE); - findViewById(R.id.crash_report_btn).setVisibility(View.VISIBLE); - findViewById(R.id.crash_feature).setVisibility(View.GONE); - } - } - - private String getErrorString(Intent intent, boolean plain) { - // build a string containing the stack trace and the device name + user's registration data - String contentPlain = "Crash report:\n\n"+CustomActivityOnCrash.getStackTraceFromIntent(intent); - String content = ""+contentPlain+""; - content = content.replaceAll(getPackageName(), ""+getPackageName()+""); - content = content.replaceAll("\n", "
"); - - contentPlain += "\n"+Build.MANUFACTURER+"\n"+Build.BRAND+"\n"+Build.MODEL+"\n"+Build.DEVICE+"\n"; - if (app.profile != null && app.profile.getRegistration() == REGISTRATION_ENABLED) { - contentPlain += "U: "+app.profile.getUsernameId()+"\nS: "+ app.profile.getStudentNameLong() +"\n"; - } - contentPlain += BuildConfig.VERSION_NAME+" "+BuildConfig.BUILD_TYPE; - - return plain ? contentPlain : content; - } - - private void copyErrorToClipboard() { - String errorInformation = CustomActivityOnCrash.getAllErrorDetailsFromIntent(CrashActivity.this, getIntent()); - - ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE); - - //Are there any devices without clipboard...? - if (clipboard != null) { - ClipData clip = ClipData.newPlainText(getString(R.string.customactivityoncrash_error_activity_error_details_clipboard_label), errorInformation); - clipboard.setPrimaryClip(clip); - Toast.makeText(CrashActivity.this, R.string.copied_to_clipboard, Toast.LENGTH_SHORT).show(); - } - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/base/CrashActivity.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/base/CrashActivity.kt new file mode 100644 index 00000000..229d5926 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/base/CrashActivity.kt @@ -0,0 +1,180 @@ +package pl.szczodrzynski.edziennik.ui.modules.base + +import android.content.ClipData +import android.content.ClipboardManager +import android.content.Context +import android.content.Intent +import android.os.Build +import android.os.Bundle +import android.text.Html +import android.view.View +import android.widget.Button +import android.widget.Toast +import androidx.appcompat.app.AppCompatActivity +import cat.ereza.customactivityoncrash.CustomActivityOnCrash +import com.afollestad.materialdialogs.MaterialDialog +import kotlinx.coroutines.* +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.BuildConfig +import pl.szczodrzynski.edziennik.R +import pl.szczodrzynski.edziennik.data.api.ERROR_APP_CRASH +import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi +import pl.szczodrzynski.edziennik.data.api.szkolny.request.ErrorReportRequest +import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile +import pl.szczodrzynski.edziennik.ifNotEmpty +import pl.szczodrzynski.edziennik.utils.Themes.appTheme +import kotlin.coroutines.CoroutineContext + +/* + * Copyright 2014-2017 Eduard Ereza Martínez + * + * 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. + */ + +class CrashActivity : AppCompatActivity(), CoroutineScope { + companion object { + const val TAG = "CrashActivity" + } + + private val app by lazy { application as App } + private val api by lazy { SzkolnyApi(app) } + + private var job = Job() + override val coroutineContext: CoroutineContext + get() = job + Dispatchers.Main + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setTheme(appTheme) + setContentView(R.layout.activity_crash) + val config = CustomActivityOnCrash.getConfigFromIntent(intent) + if (config == null) { //This should never happen - Just finish the activity to avoid a recursive crash. + finish() + return + } + + //Close/restart button logic: + //If a class if set, use restart. + //Else, use close and just finish the app. + //It is recommended that you follow this logic if implementing a custom error activity. + val restartButton = findViewById