From d51e06b61fa1138ccf44ef4955f089d625f8ac42 Mon Sep 17 00:00:00 2001 From: sk Date: Wed, 15 Nov 2023 12:06:54 +0100 Subject: [PATCH] save latest crash log closes sk22#932 closes sk22#419 --- .../joinmastodon/android/MainActivity.java | 26 +++++++++++++ .../settings/SettingsAboutAppFragment.java | 39 ++++++++++++++++++- mastodon/src/main/res/values/strings_sk.xml | 5 ++- 3 files changed, 67 insertions(+), 3 deletions(-) diff --git a/mastodon/src/main/java/org/joinmastodon/android/MainActivity.java b/mastodon/src/main/java/org/joinmastodon/android/MainActivity.java index 005d9a382..5050dcee1 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/MainActivity.java +++ b/mastodon/src/main/java/org/joinmastodon/android/MainActivity.java @@ -31,18 +31,44 @@ import org.joinmastodon.android.utils.ProvidesAssistContent; import org.parceler.Parcels; import androidx.annotation.Nullable; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintWriter; +import java.time.Instant; + import me.grishka.appkit.FragmentStackActivity; import me.grishka.appkit.Nav; import me.grishka.appkit.api.Callback; import me.grishka.appkit.api.ErrorResponse; public class MainActivity extends FragmentStackActivity implements ProvidesAssistContent { + private static final String TAG="MainActivity"; + @Override protected void onCreate(@Nullable Bundle savedInstanceState){ AccountSession session=getCurrentSession(); UiUtils.setUserPreferredTheme(this, session); super.onCreate(savedInstanceState); + Thread.UncaughtExceptionHandler defaultHandler=Thread.getDefaultUncaughtExceptionHandler(); + Thread.setDefaultUncaughtExceptionHandler((t, e)->{ + File file=new File(MastodonApp.context.getFilesDir(), "crash.log"); + try(FileOutputStream out=new FileOutputStream(file)){ + PrintWriter writer=new PrintWriter(out); + writer.println(BuildConfig.VERSION_NAME+" ("+BuildConfig.VERSION_CODE+")"); + writer.println(Instant.now().toString()); + writer.println(); + e.printStackTrace(writer); + writer.flush(); + }catch(IOException x){ + Log.e(TAG, "Error writing crash.log", x); + }finally{ + defaultHandler.uncaughtException(t, e); + } + }); + if(savedInstanceState==null){ restartHomeFragment(); } diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/settings/SettingsAboutAppFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/settings/SettingsAboutAppFragment.java index fcc1eb869..da89b6f71 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/settings/SettingsAboutAppFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/settings/SettingsAboutAppFragment.java @@ -2,6 +2,7 @@ package org.joinmastodon.android.fragments.settings; import android.app.Activity; import android.os.Bundle; +import android.util.Log; import android.view.Gravity; import android.view.ViewGroup; import android.widget.TextView; @@ -9,6 +10,7 @@ import android.widget.Toast; import org.joinmastodon.android.BuildConfig; import org.joinmastodon.android.GlobalUserPreferences; +import org.joinmastodon.android.MastodonApp; import org.joinmastodon.android.R; import org.joinmastodon.android.api.MastodonAPIController; import org.joinmastodon.android.api.session.AccountSession; @@ -18,6 +20,19 @@ import org.joinmastodon.android.model.viewmodel.ListItem; import org.joinmastodon.android.ui.utils.UiUtils; import org.joinmastodon.android.updater.GithubSelfUpdater; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.StringReader; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.time.Instant; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.time.format.FormatStyle; import java.util.ArrayList; import java.util.List; @@ -28,10 +43,12 @@ import me.grishka.appkit.utils.SingleViewRecyclerAdapter; import me.grishka.appkit.utils.V; public class SettingsAboutAppFragment extends BaseSettingsFragment{ - private ListItem mediaCacheItem; + private static final String TAG="SettingsAboutAppFragment"; + private ListItem mediaCacheItem, copyCrashLogItem; private CheckableListItem enablePreReleasesItem; private AccountSession session; private boolean timelineCacheCleared=false; + private File crashLogFile=new File(MastodonApp.context.getFilesDir(), "crash.log"); @Override public void onCreate(Bundle savedInstanceState){ @@ -39,19 +56,24 @@ public class SettingsAboutAppFragment extends BaseSettingsFragment{ setTitle(getString(R.string.about_app, getString(R.string.sk_app_name))); session=AccountSessionManager.get(accountID); + String lastModified=crashLogFile.exists() + ? DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG, FormatStyle.SHORT).withZone(ZoneId.systemDefault()).format(Instant.ofEpochMilli(crashLogFile.lastModified())) + : getString(R.string.sk_settings_crash_log_unavailable); List> items=new ArrayList<>(List.of( new ListItem<>(R.string.sk_settings_donate, 0, R.drawable.ic_fluent_heart_24_regular, i->UiUtils.openHashtagTimeline(getActivity(), accountID, getString(R.string.donate_hashtag))), new ListItem<>(R.string.sk_settings_contribute, 0, R.drawable.ic_fluent_open_24_regular, i->UiUtils.launchWebBrowser(getActivity(), getString(R.string.repo_url))), new ListItem<>(R.string.settings_tos, 0, R.drawable.ic_fluent_open_24_regular, i->UiUtils.launchWebBrowser(getActivity(), "https://"+session.domain+"/terms")), new ListItem<>(R.string.settings_privacy_policy, 0, R.drawable.ic_fluent_open_24_regular, i->UiUtils.launchWebBrowser(getActivity(), getString(R.string.privacy_policy_url)), 0, true), mediaCacheItem=new ListItem<>(R.string.settings_clear_cache, 0, this::onClearMediaCacheClick), - new ListItem<>(getString(R.string.sk_settings_clear_timeline_cache), session.domain, this::onClearTimelineCacheClick) + new ListItem<>(getString(R.string.sk_settings_clear_timeline_cache), session.domain, this::onClearTimelineCacheClick), + copyCrashLogItem=new ListItem<>(getString(R.string.sk_settings_copy_crash_log), lastModified, 0, this::onCopyCrashLog) )); if(GithubSelfUpdater.needSelfUpdating()){ items.add(enablePreReleasesItem=new CheckableListItem<>(R.string.sk_updater_enable_pre_releases, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.enablePreReleases, i->toggleCheckableItem(enablePreReleasesItem))); } + copyCrashLogItem.isEnabled=crashLogFile.exists(); onDataLoaded(items); updateMediaCacheItem(); } @@ -107,4 +129,17 @@ public class SettingsAboutAppFragment extends BaseSettingsFragment{ mediaCacheItem.isEnabled=size>0; rebindItem(mediaCacheItem); } + + private void onCopyCrashLog(ListItem item){ + if(!crashLogFile.exists()) return; + try(InputStream is=new FileInputStream(crashLogFile)){ + BufferedReader reader=new BufferedReader(new InputStreamReader(is)); + StringBuilder sb=new StringBuilder(); + String line; + while ((line=reader.readLine())!=null) sb.append(line).append("\n"); + UiUtils.copyText(list, sb.toString()); + } catch(IOException e){ + Log.e(TAG, "Error reading crash log", e); + } + } } diff --git a/mastodon/src/main/res/values/strings_sk.xml b/mastodon/src/main/res/values/strings_sk.xml index 60a0892c7..fcb8f04ad 100644 --- a/mastodon/src/main/res/values/strings_sk.xml +++ b/mastodon/src/main/res/values/strings_sk.xml @@ -430,4 +430,7 @@ Delete personal note about %s? Delete personal note Add personal note - \ No newline at end of file + Copy latest crash log + None available… yet + Crash log copied +