diff --git a/app/build.gradle b/app/build.gradle index acd7bf3355..bb4e528e1c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -25,8 +25,8 @@ android { defaultConfig { resValue "string", "manifest_package_id", "net.gsantner.markor" applicationId "net.gsantner.markor" - versionName "2.13.0" - versionCode 155 + versionName "2.13.1" + versionCode 156 multiDexEnabled true minSdkVersion rootProject.ext.version_minSdk diff --git a/app/src/main/java/net/gsantner/markor/activity/DocumentActivity.java b/app/src/main/java/net/gsantner/markor/activity/DocumentActivity.java index fd563564b9..4dfc8bc2ed 100644 --- a/app/src/main/java/net/gsantner/markor/activity/DocumentActivity.java +++ b/app/src/main/java/net/gsantner/markor/activity/DocumentActivity.java @@ -16,6 +16,7 @@ import android.os.Build; import android.os.Bundle; import android.text.Html; +import android.util.Log; import android.view.KeyEvent; import android.view.MotionEvent; @@ -95,8 +96,8 @@ private static void launch( intent = new Intent(activity, DocumentActivity.class); if (!(activity instanceof DocumentActivity) && - Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && - as.isMultiWindowEnabled() + Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && + as.isMultiWindowEnabled() ) { intent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT | Intent.FLAG_ACTIVITY_MULTIPLE_TASK); } @@ -254,7 +255,8 @@ public boolean dispatchTouchEvent(MotionEvent event) { } try { return super.dispatchTouchEvent(event); - } catch (IndexOutOfBoundsException ignored) { + } catch (Exception e) { + Log.e(getClass().getName(), "Error in super.dispatchTouchEvent: " + e); return false; } } diff --git a/app/src/main/java/net/gsantner/markor/activity/DocumentEditAndViewFragment.java b/app/src/main/java/net/gsantner/markor/activity/DocumentEditAndViewFragment.java index 39f02c6fbd..5cec9c9409 100644 --- a/app/src/main/java/net/gsantner/markor/activity/DocumentEditAndViewFragment.java +++ b/app/src/main/java/net/gsantner/markor/activity/DocumentEditAndViewFragment.java @@ -286,7 +286,7 @@ public void onPause() { _appSettings.setDocumentPreviewState(_document.path, _isPreviewVisible); _appSettings.setLastEditPosition(_document.path, TextViewUtils.getSelection(_hlEditor)[0]); - if(_document.path.equals(_appSettings.getTodoFile().getAbsolutePath())){ + if (_document.path.equals(_appSettings.getTodoFile().getAbsolutePath())) { TodoWidgetProvider.updateTodoWidgets(); } super.onPause(); @@ -862,7 +862,12 @@ private boolean isDisplayedAtMainActivity() { } public void updateViewModeText() { - _format.getConverter().convertMarkupShowInWebView(_document, getTextString(), getActivity(), _webView, _nextConvertToPrintMode, _hlEditor.isLineNumbersEnabled()); + // Don't let text to view mode crash app + try { + _format.getConverter().convertMarkupShowInWebView(_document, getTextString(), getActivity(), _webView, _nextConvertToPrintMode, _hlEditor.isLineNumbersEnabled()); + } catch (OutOfMemoryError e) { + _format.getConverter().convertMarkupShowInWebView(_document, "updateViewModeText getTextString(): OutOfMemory " + e, getActivity(), _webView, _nextConvertToPrintMode, _hlEditor.isLineNumbersEnabled()); + } } public void setViewModeVisibility(final boolean show) { diff --git a/app/src/main/java/net/gsantner/markor/activity/DocumentShareIntoFragment.java b/app/src/main/java/net/gsantner/markor/activity/DocumentShareIntoFragment.java index 4a688fbb7e..1e8f9469a1 100644 --- a/app/src/main/java/net/gsantner/markor/activity/DocumentShareIntoFragment.java +++ b/app/src/main/java/net/gsantner/markor/activity/DocumentShareIntoFragment.java @@ -17,6 +17,7 @@ import android.os.Build; import android.os.Bundle; import android.text.TextUtils; +import android.util.Log; import android.util.Pair; import android.util.Patterns; import android.util.TypedValue; @@ -375,7 +376,7 @@ private String formatShare(final String shared) { } // Put the shared text in the right place - parts.add(1, shared); + parts.add(parts.isEmpty() ? 0 : 1, shared); return TextUtils.join("", parts); } @@ -433,7 +434,9 @@ public void onFsViewerConfig(GsFileBrowserOptions.Options dopt) { @Override public void onFsViewerSelected(final String request, final File sel, final Integer lineNumber) { - if (sel.isDirectory()) { + if (sel == null) { + Log.e(getClass().getName(), "onFsViewerSelected: selected file is null"); + } else if (sel.isDirectory()) { NewFileDialog.newInstance(sel, false, f -> { if (f.isFile()) { appendToExistingDocumentAndClose(f, true); diff --git a/app/src/main/java/net/gsantner/markor/format/ActionButtonBase.java b/app/src/main/java/net/gsantner/markor/format/ActionButtonBase.java index f58de8fd99..48c22775ba 100644 --- a/app/src/main/java/net/gsantner/markor/format/ActionButtonBase.java +++ b/app/src/main/java/net/gsantner/markor/format/ActionButtonBase.java @@ -297,7 +297,7 @@ public void recreateActionButtons(final ViewGroup barLayout, final ActionItem.Di @SuppressLint("ClickableViewAccessibility") private void setupRepeat(final View btn) { // Velocity and acceleration parameters - final int INITIAL_DELAY = 400, DELTA_DELAY = 50, MIN_DELAY = 100; + final int INITIAL_DELAY = 300, DELTA_DELAY = 100, MIN_DELAY = 100; final Handler handler = new Handler(); final Runnable repeater = new Runnable() { diff --git a/app/src/main/java/net/gsantner/markor/frontend/textview/HighlightingEditor.java b/app/src/main/java/net/gsantner/markor/frontend/textview/HighlightingEditor.java index 42dec9e891..1202fc5e1a 100644 --- a/app/src/main/java/net/gsantner/markor/frontend/textview/HighlightingEditor.java +++ b/app/src/main/java/net/gsantner/markor/frontend/textview/HighlightingEditor.java @@ -19,12 +19,14 @@ import android.text.TextWatcher; import android.util.AttributeSet; import android.view.ActionMode; +import android.util.Log; import android.view.KeyEvent; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.ViewTreeObserver; import android.view.accessibility.AccessibilityEvent; +import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.RequiresApi; @@ -41,6 +43,7 @@ import java.util.Objects; import java.util.concurrent.ExecutorService; +import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.SynchronousQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; @@ -123,12 +126,22 @@ public void afterTextChanged(final Editable s) { @Override public boolean onPreDraw() { _lineNumbersDrawer.setTextSize(getTextSize()); - return super.onPreDraw(); + try { + return super.onPreDraw(); + } catch (OutOfMemoryError ignored) { + return false; // return false to cancel current drawing pass/round + } } @Override protected void onDraw(Canvas canvas) { - super.onDraw(canvas); + try { + super.onDraw(canvas); + } catch (Exception e) { + // Hinder drawing from crashing the app + Log.e(getClass().getName(), "HighlightingEdtior onDraw->super.onDraw crash" + e); + Toast.makeText(getContext(), e.toString(), Toast.LENGTH_SHORT).show(); + } if (_numEnabled) { _lineNumbersDrawer.draw(canvas); @@ -188,7 +201,10 @@ public void recomputeHighlighting() { */ private void recomputeHighlightingAsync() { if (runHighlight(true)) { - executor.execute(this::_recomputeHighlightingWorker); + try { + executor.execute(this::_recomputeHighlightingWorker); + } catch (RejectedExecutionException ignored) { + } } } @@ -296,14 +312,12 @@ public boolean bringPointIntoView(int i) { private int rowStart(final int y) { final Layout layout = getLayout(); - final int line = layout.getLineForVertical(y); - return layout.getLineStart(line); + return layout == null ? 0 : layout.getLineStart(layout.getLineForVertical(y)); } private int rowEnd(final int y) { final Layout layout = getLayout(); - final int line = layout.getLineForVertical(y); - return layout.getLineEnd(line); + return layout == null ? 0 : layout.getLineEnd(layout.getLineForVertical(y)); } // Text-Casing @@ -405,7 +419,12 @@ public boolean onTextContextMenuItem(int id) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && id == android.R.id.paste) { id = android.R.id.pasteAsPlainText; } - return super.onTextContextMenuItem(id); + try { + // i.e. DeadSystemRuntimeException can happen here + return super.onTextContextMenuItem(id); + } catch (Exception ignored) { + return true; + } } // Accessibility code is blocked during rapid update events diff --git a/app/src/main/java/net/gsantner/markor/frontend/textview/SyntaxHighlighterBase.java b/app/src/main/java/net/gsantner/markor/frontend/textview/SyntaxHighlighterBase.java index fdd5a36ba5..00756b5b21 100644 --- a/app/src/main/java/net/gsantner/markor/frontend/textview/SyntaxHighlighterBase.java +++ b/app/src/main/java/net/gsantner/markor/frontend/textview/SyntaxHighlighterBase.java @@ -62,9 +62,9 @@ * Spans are further divided into two categories: dynamic and static. * - Dynamic spans are updated as one scrolls, as described above * - Static spans are applied once and never updated. These are typically used for - * spans which affect the text layout. - * - For example, a span which makes text bigger. - * - Updating these dynamically would make the text jump around as one scrolls + * spans which affect the text layout. + * - For example, a span which makes text bigger. + * - Updating these dynamically would make the text jump around as one scrolls *

* Fixup: * - As the user types we shift all spans to accomodate the changed text. @@ -75,19 +75,19 @@ * - Derived classes should override generateSpans() to generate all spans * - New spans are added by calling addSpanGroup() * - The HighlightingEditor will trigger the generation of spans when the text changes. - * - This is debounced so that changes are batched - * - Span generation is done on a background thread + * - This is debounced so that changes are batched + * - Span generation is done on a background thread *

* Other performance tips: * - Performance is heavily dependent on the number of spans applied to the text. * - Combine related spans into a single span if possible - * - HighlightSpan is a helper class which can be used to create a span with multiple attributes - * - For example, a span which makes text bold and italic + * - HighlightSpan is a helper class which can be used to create a span with multiple attributes + * - For example, a span which makes text bold and italic * - Absolutely minimize the number of spans implementing `UpdateLayout` - * - These spans trigger a text layout update when changed in any way - * - Instead consider using a span implementing `StaticSpan` - * - If StaticSpans are present, the text is reflowed after applying them - * - This happens once, and not for each span, which is much more efficient + * - These spans trigger a text layout update when changed in any way + * - Instead consider using a span implementing `StaticSpan` + * - If StaticSpans are present, the text is reflowed after applying them + * - This happens once, and not for each span, which is much more efficient */ public abstract class SyntaxHighlighterBase { @@ -230,7 +230,7 @@ public SyntaxHighlighterBase clearStatic() { boolean hasStatic = false; for (int i = _groups.size() - 1; i >= 0; i--) { final SpanGroup group = _groups.get(i); - if (group.isStatic) { + if (group != null && group.isStatic) { hasStatic = true; _spannable.removeSpan(group.span); } @@ -348,7 +348,7 @@ public SyntaxHighlighterBase applyDynamic(final int[] range) { for (int i = 0; i < _groups.size(); i++) { final SpanGroup group = _groups.get(i); - if (group.isStatic) { + if (group == null || group.isStatic) { continue; } @@ -368,7 +368,6 @@ public SyntaxHighlighterBase applyDynamic(final int[] range) { } - public SyntaxHighlighterBase applyStatic() { if (_spannable != null && !_staticApplied) { applyFixup(); diff --git a/app/src/main/java/net/gsantner/markor/model/AppSettings.java b/app/src/main/java/net/gsantner/markor/model/AppSettings.java index 7c83c5095f..f354681012 100644 --- a/app/src/main/java/net/gsantner/markor/model/AppSettings.java +++ b/app/src/main/java/net/gsantner/markor/model/AppSettings.java @@ -855,7 +855,7 @@ public boolean isExperimentalFeaturesEnabled() { } public boolean isHighlightBiggerHeadings() { - return getBool(R.string.pref_key__editor_markdown_bigger_headings_2, false); + return getBool(R.string.pref_key__editor_markdown_bigger_headings_3, false); } public String getViewModeLinkColor() { diff --git a/app/src/main/java/net/gsantner/opoc/frontend/filebrowser/GsFileBrowserListAdapter.java b/app/src/main/java/net/gsantner/opoc/frontend/filebrowser/GsFileBrowserListAdapter.java index e665d0547c..1dee2e5ae8 100644 --- a/app/src/main/java/net/gsantner/opoc/frontend/filebrowser/GsFileBrowserListAdapter.java +++ b/app/src/main/java/net/gsantner/opoc/frontend/filebrowser/GsFileBrowserListAdapter.java @@ -56,6 +56,7 @@ import java.util.Set; import java.util.Stack; import java.util.concurrent.ExecutorService; +import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.SynchronousQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; @@ -152,7 +153,7 @@ public Map getVirtualFolders() { } for (final File file : ContextCompat.getExternalFilesDirs(_context, null)) { - if (file == null || file.getParentFile() == null) { + if (file == null || (file != null && file.getParentFile() == null)) { continue; } final File remap = new File(VIRTUAL_STORAGE_ROOT, "AppData (" + file.getParentFile().toString().replace("/", "-").substring(1) + ")"); @@ -678,7 +679,10 @@ private void loadFolder(final File folder, final File show) { final File toShow = show == null ? _fileToShowAfterNextLoad : show; _fileToShowAfterNextLoad = null; - executorService.execute(() -> _loadFolder(toLoad, toShow)); + try { + executorService.execute(() -> _loadFolder(toLoad, toShow)); + } catch (RejectedExecutionException ignored) { // during exit + } } // This function is not called on the main thread, so post to the UI thread diff --git a/app/src/main/java/net/gsantner/opoc/frontend/textview/TextViewUndoRedo.java b/app/src/main/java/net/gsantner/opoc/frontend/textview/TextViewUndoRedo.java index 9a4f6c254b..f90325092f 100644 --- a/app/src/main/java/net/gsantner/opoc/frontend/textview/TextViewUndoRedo.java +++ b/app/src/main/java/net/gsantner/opoc/frontend/textview/TextViewUndoRedo.java @@ -43,7 +43,9 @@ public void onClick(View v) { import android.text.Selection; import android.text.TextWatcher; import android.text.style.UnderlineSpan; +import android.util.Log; import android.widget.TextView; +import android.widget.Toast; import net.gsantner.markor.frontend.textview.TextViewUtils; @@ -150,8 +152,10 @@ public void undo() { mIsUndoOrRedo = true; try { text.replace(start, end, edit.before); - } catch (Exception ex){ + } catch (Exception ex) { // In case a undo would crash the app, don't do it instead + Log.e(getClass().getName(), "undo() Error in text.replace" + ex); + Toast.makeText(mTextView.getContext(), "undo() Error in text.replace" + ex, Toast.LENGTH_LONG).show(); return; } mIsUndoOrRedo = false; diff --git a/app/src/main/java/net/gsantner/opoc/util/GsContextUtils.java b/app/src/main/java/net/gsantner/opoc/util/GsContextUtils.java index c243af4224..7bff4dad0c 100644 --- a/app/src/main/java/net/gsantner/opoc/util/GsContextUtils.java +++ b/app/src/main/java/net/gsantner/opoc/util/GsContextUtils.java @@ -1861,7 +1861,7 @@ public void extractResultFromActivityResult(final Activity context, final int re if (resultCode == Activity.RESULT_OK && intent != null && intent.getData() != null) { final Uri uri = intent.getData(); final String uriPath = uri.getPath(); - final String ext = uriPath.substring(uriPath.lastIndexOf(".")); + final String ext = uriPath == null || !uriPath.contains(".") ? "" : uriPath.substring(uriPath.lastIndexOf(".")); final String datestr = new SimpleDateFormat("yyyy-MM-dd'T'HH-mm-ss", Locale.ENGLISH).format(new Date()); final File temp = new File(context.getCacheDir(), datestr + ext); GsFileUtils.copyUriToFile(context, uri, temp); @@ -2743,12 +2743,12 @@ public void dialogFullWidth(AlertDialog dialog, boolean fullWidth, boolean showK } public static void windowAspectRatio( - final Window window, - final DisplayMetrics displayMetrics, - float portraitWidthRatio, - float portraitHeightRatio, - float landscapeWidthRatio, - float landscapeHeightRatio + final Window window, + final DisplayMetrics displayMetrics, + float portraitWidthRatio, + float portraitHeightRatio, + float landscapeWidthRatio, + float landscapeHeightRatio ) { if (window == null) { return; diff --git a/app/src/main/res/values/string-not_translatable.xml b/app/src/main/res/values/string-not_translatable.xml index a790a20abb..d30f3f612c 100644 --- a/app/src/main/res/values/string-not_translatable.xml +++ b/app/src/main/res/values/string-not_translatable.xml @@ -180,7 +180,7 @@ work. If not, see . pref_key__editor_disable_spelling_red_underline pref_key__highlight_code_monospace_font pref_key__highlight_code_block_disabled - pref_key__editor_markdown_bigger_headings_2 + pref_key__editor_markdown_bigger_headings_3 pref_key__editor_unordered_list_character pref_key__recent_documents pref_key__popular_documents diff --git a/app/src/main/res/xml/preferences_master.xml b/app/src/main/res/xml/preferences_master.xml index 8fc6e6e26d..dbc0522597 100644 --- a/app/src/main/res/xml/preferences_master.xml +++ b/app/src/main/res/xml/preferences_master.xml @@ -340,7 +340,7 @@ diff --git a/metadata/en-US/full_description.txt b/metadata/en-US/full_description.txt index b4221a5c9c..7266c8c513 100644 --- a/metadata/en-US/full_description.txt +++ b/metadata/en-US/full_description.txt @@ -17,9 +17,9 @@ 👌 No ads or unnecessary permissions 🌎 Language selection -- use other language than on the system -💡 Unlike other office suites (like LibreOffice) or to-do apps (like Wunderlist), Markor has one streamlined text editor with no other editing UI. Markor shows how powerful and expressive simple text can be. View, edit, manipulate and convert plaintext! +💡 Unlike other office suites (like LibreOffice) or to-do apps (like Wunderlist), Markor has one streamlined text editor with no other editing UI. Markor shows how powerful and expressive simple text can be. View, edit, manipulate and convert plaintext! -🔃 Markor works with sync apps, but they have to do syncing respectively. Sync clients known to work in combination include BitTorrent Sync, Dropbox, FolderSync, OwnCloud, NextCloud, Seafile, Syncthing, Syncopoli +🔃 Markor works with sync apps, but they have to do syncing respectively. Sync clients known to work in combination include BitTorrent Sync, Dropbox, FolderSync, OwnCloud, NextCloud, Seafile, Syncthing, Syncopoli 👀 These apps may also be in your interest if you like Markor: OneNote, EverNote, Google Keep, Wunderlist, Read-It-Later, Pocket, Epsilon Notes, iA Writer, Todoist, Shaarli, Wallabag, Simple Notes, Simpletask, Share to clipboard, NextCloud Bookmarks, Easy Open Link