From d10e8d5d2aa8b82c8884c8c3e964d010e3519107 Mon Sep 17 00:00:00 2001 From: Soren Stoutner Date: Mon, 8 Apr 2024 16:33:52 -0700 Subject: [PATCH] Enable searching of the logcat. https://redmine.stoutner.com/issues/381 --- app/build.gradle | 2 +- .../activities/LogcatActivity.kt | 140 +++++++++++++++++- .../activities/MainWebViewActivity.kt | 27 ++-- .../views/NestedScrollWebView.kt | 2 +- app/src/main/res/drawable/search.xml | 6 +- app/src/main/res/drawable/search_blue.xml | 14 ++ .../main/res/layout/logcat_bottom_appbar.xml | 74 +++++++++ app/src/main/res/layout/logcat_top_appbar.xml | 75 ++++++++++ .../layout/main_framelayout_bottom_appbar.xml | 6 +- .../layout/main_framelayout_top_appbar.xml | 6 +- app/src/main/res/menu/logcat_options_menu.xml | 19 ++- app/src/main/res/values-de/strings.xml | 6 + app/src/main/res/values-fr/strings.xml | 23 ++- app/src/main/res/values-night-v27/styles.xml | 5 +- app/src/main/res/values-night/styles.xml | 5 +- app/src/main/res/values-v27/styles.xml | 5 +- app/src/main/res/values/attrs.xml | 5 +- app/src/main/res/values/strings.xml | 2 +- app/src/main/res/values/styles.xml | 5 +- app/src/main/res/xml/preferences.xml | 4 +- build.gradle | 2 +- 21 files changed, 371 insertions(+), 62 deletions(-) create mode 100644 app/src/main/res/drawable/search_blue.xml diff --git a/app/build.gradle b/app/build.gradle index 861e9165..900ddf9b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,7 +1,7 @@ /* * Copyright 2016-2024 Soren Stoutner . * - * This file is part of Privacy Browser Android . + * This file is part of Privacy Browser Android . * * Privacy Browser Android is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/app/src/main/java/com/stoutner/privacybrowser/activities/LogcatActivity.kt b/app/src/main/java/com/stoutner/privacybrowser/activities/LogcatActivity.kt index c12c74ec..214ebf21 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/activities/LogcatActivity.kt +++ b/app/src/main/java/com/stoutner/privacybrowser/activities/LogcatActivity.kt @@ -24,12 +24,20 @@ import android.content.ClipboardManager import android.os.Build import android.os.Bundle import android.provider.OpenableColumns +import android.text.Editable +import android.text.TextWatcher import android.util.Base64 import android.util.TypedValue +import android.view.KeyEvent import android.view.Menu import android.view.MenuItem +import android.view.View import android.view.WindowManager +import android.view.inputmethod.InputMethodManager import android.webkit.WebView +import android.widget.EditText +import android.widget.LinearLayout +import android.widget.TextView import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AppCompatActivity @@ -58,11 +66,15 @@ private const val SCROLL_Y = "A" class LogcatActivity : AppCompatActivity() { // Declare the class variables. + private lateinit var inputMethodManager: InputMethodManager private lateinit var logcatPlainTextStringBuilder: StringBuilder // Declare the class views. - private lateinit var swipeRefreshLayout: SwipeRefreshLayout private lateinit var logcatWebView: WebView + private lateinit var searchEditText: EditText + private lateinit var searchLinearLayout: LinearLayout + private lateinit var swipeRefreshLayout: SwipeRefreshLayout + private lateinit var toolbar: Toolbar // Define the save logcat activity result launcher. It must be defined before `onCreate()` is run or the app will crash. private val saveLogcatActivityResultLauncher = registerForActivityResult(ActivityResultContracts.CreateDocument("text/plain")) { fileUri -> @@ -105,6 +117,9 @@ class LogcatActivity : AppCompatActivity() { } public override fun onCreate(savedInstanceState: Bundle?) { + // Run the default commands. + super.onCreate(savedInstanceState) + // Get a handle for the shared preferences. val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this) @@ -116,9 +131,6 @@ class LogcatActivity : AppCompatActivity() { if (!allowScreenshots) window.addFlags(WindowManager.LayoutParams.FLAG_SECURE) - // Run the default commands. - super.onCreate(savedInstanceState) - // Set the content view. if (bottomAppBar) setContentView(R.layout.logcat_bottom_appbar) @@ -126,7 +138,10 @@ class LogcatActivity : AppCompatActivity() { setContentView(R.layout.logcat_top_appbar) // Get handles for the views. - val toolbar = findViewById(R.id.toolbar) + toolbar = findViewById(R.id.toolbar) + val searchCountTextView = findViewById(R.id.search_count_textview) + searchLinearLayout = findViewById(R.id.search_linearlayout) + searchEditText = findViewById(R.id.search_edittext) swipeRefreshLayout = findViewById(R.id.swiperefreshlayout) logcatWebView = findViewById(R.id.logcat_webview) @@ -160,13 +175,63 @@ class LogcatActivity : AppCompatActivity() { // Set the swipe refresh background color. swipeRefreshLayout.setProgressBackgroundColorSchemeColor(colorBackgroundInt) + // Get a handle for the input method manager. + inputMethodManager = (getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager) + + // Search for the string on the page whenever a character changes in the search edit text. + searchEditText.addTextChangedListener(object : TextWatcher { + override fun beforeTextChanged(charSequence: CharSequence, start: Int, count: Int, after: Int) { + // Do nothing. + } + + override fun onTextChanged(charSequence: CharSequence, start: Int, before: Int, count: Int) { + // Do nothing. + } + + override fun afterTextChanged(editable: Editable) { + // Search for the text in the WebView. + logcatWebView.findAllAsync(searchEditText.text.toString()) + } + }) + + // Set the `check mark` button for the search edit text keyboard to close the soft keyboard. + searchEditText.setOnKeyListener { _: View?, keyCode: Int, keyEvent: KeyEvent -> + if ((keyEvent.action == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) { // The `enter` key was pressed. + // Search for the text in the WebView. + logcatWebView.findAllAsync(searchEditText.text.toString()) + + // Hide the soft keyboard. + inputMethodManager.hideSoftInputFromWindow(logcatWebView.windowToken, 0) + + // Consume the event. + return@setOnKeyListener true + } else { // A different key was pressed. + // Do not consume the event. + return@setOnKeyListener false + } + } + + // Update the find on page count. + logcatWebView.setFindListener { activeMatchOrdinal, numberOfMatches, isDoneCounting -> + if (isDoneCounting && (numberOfMatches == 0)) { // There are no matches. + // Set the search count text view to be `0/0`. + searchCountTextView.setText(R.string.zero_of_zero) + } else if (isDoneCounting) { // There are matches. + // The active match ordinal is zero-based. + val activeMatch = activeMatchOrdinal + 1 + + // Build the match string. + val matchString = "$activeMatch/$numberOfMatches" + + // Update the search count text view. + searchCountTextView.text = matchString + } + } + // Restore the WebView scroll position if the activity has been restarted. if (savedInstanceState != null) logcatWebView.scrollY = savedInstanceState.getInt(SCROLL_Y) - // Allow loading of file:// URLs. - logcatWebView.settings.allowFileAccess = true - // Populate the logcat. populateLogcat() } @@ -182,6 +247,35 @@ class LogcatActivity : AppCompatActivity() { override fun onOptionsItemSelected(menuItem: MenuItem): Boolean { // Run the commands that correlate to the selected menu item. return when (menuItem.itemId) { + R.id.search -> { // Search was selected. + // Set the minimum height of the search linear layout to match the toolbar. + searchLinearLayout.minimumHeight = toolbar.height + + // Hide the toolbar. + toolbar.visibility = View.GONE + + // Show the search linear layout. + searchLinearLayout.visibility = View.VISIBLE + + // Display the keyboard once the UI has quiesced. + searchLinearLayout.post { + // Set the focus on the find on page edit text. + searchEditText.requestFocus() + + // Get a handle for the input method manager. + val inputMethodManager = (getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager) + + // Display the keyboard. `0` sets no input flags. + inputMethodManager.showSoftInput(searchEditText, 0) + } + + // Resume the WebView timers. For some reason they get automatically paused, which prevents searching. + logcatWebView.resumeTimers() + + // Consume the event. + true + } + R.id.copy -> { // Copy was selected. // Get a handle for the clipboard manager. val clipboardManager = getSystemService(CLIPBOARD_SERVICE) as ClipboardManager @@ -241,6 +335,24 @@ class LogcatActivity : AppCompatActivity() { savedInstanceState.putInt(SCROLL_Y, logcatWebView.scrollY) } + // The view parameter cannot be removed because it is called from the layout onClick. + fun closeSearch(@Suppress("UNUSED_PARAMETER")view: View?) { + // Delete the contents of the search edit text. + searchEditText.text = null + + // Clear the highlighted phrases in the logcat WebView. + logcatWebView.clearMatches() + + // Hide the search linear layout. + searchLinearLayout.visibility = View.GONE + + // Show the toolbar. + toolbar.visibility = View.VISIBLE + + // Hide the keyboard. + inputMethodManager.hideSoftInputFromWindow(toolbar.windowToken, 0) + } + private fun populateLogcat() { try { // Get the logcat. `-b all` gets all the buffers (instead of just crash, main, and system). `-v long` produces more complete information. `-d` dumps the logcat and exits. @@ -336,4 +448,16 @@ class LogcatActivity : AppCompatActivity() { // Stop the swipe to refresh animation if it is displayed. swipeRefreshLayout.isRefreshing = false } + + // The view parameter cannot be removed because it is called from the layout onClick. + fun searchNext(@Suppress("UNUSED_PARAMETER")view: View?) { + // Go to the next highlighted phrase on the page. `true` goes forwards instead of backwards. + logcatWebView.findNext(true) + } + + // The view parameter cannot be removed because it is called from the layout onClick. + fun searchPrevious(@Suppress("UNUSED_PARAMETER")view: View?) { + // Go to the previous highlighted phrase on the page. `false` goes backwards instead of forwards. + logcatWebView.findNext(false) + } } diff --git a/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.kt b/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.kt index 75aa38be..042af73d 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.kt +++ b/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.kt @@ -3,7 +3,7 @@ * * Download cookie code contributed 2017 Hendrik Knackstedt. Copyright assigned to Soren Stoutner . * - * This file is part of Privacy Browser Android . + * This file is part of Privacy Browser Android . * * Privacy Browser Android is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -278,6 +278,7 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook private lateinit var findOnPageLinearLayout: LinearLayout private lateinit var fullScreenVideoFrameLayout: FrameLayout private lateinit var initialGrayColorSpan: ForegroundColorSpan + private lateinit var inputMethodManager: InputMethodManager private lateinit var navigationBackMenuItem: MenuItem private lateinit var navigationForwardMenuItem: MenuItem private lateinit var navigationHistoryMenuItem: MenuItem @@ -1853,9 +1854,6 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook // Set the focus on the find on page edit text. findOnPageEditText.requestFocus() - // Get a handle for the input method manager. - val inputMethodManager = (getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager) - // Display the keyboard. `0` sets no input flags. inputMethodManager.showSoftInput(findOnPageEditText, 0) }, 200) @@ -3859,9 +3857,6 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook // Show the toolbar. toolbar.visibility = View.VISIBLE - // Get a handle for the input method manager. - val inputMethodManager = (getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager) - // Hide the keyboard. inputMethodManager.hideSoftInputFromWindow(toolbar.windowToken, 0) } @@ -4155,7 +4150,7 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook @SuppressLint("ClickableViewAccessibility") private fun initializeApp() { // Get a handle for the input method. - val inputMethodManager = (getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager) + inputMethodManager = (getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager) // Initialize the color spans for highlighting the URLs. initialGrayColorSpan = ForegroundColorSpan(getColor(R.color.gray_500)) @@ -4344,11 +4339,15 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook // Search for the string on the page whenever a character changes in the find on page edit text. findOnPageEditText.addTextChangedListener(object : TextWatcher { - override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {} + override fun beforeTextChanged(charSequence: CharSequence, start: Int, count: Int, after: Int) { + // Do nothing. + } - override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {} + override fun onTextChanged(charSequence: CharSequence, start: Int, before: Int, count: Int) { + // Do nothing. + } - override fun afterTextChanged(s: Editable) { + override fun afterTextChanged(editable: Editable) { // Search for the text in the WebView if it is not null. Sometimes on resume after a period of non-use the WebView will be null. currentWebView?.findAllAsync(findOnPageEditText.text.toString()) } @@ -4603,9 +4602,6 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook } } - // Get a handle for the input method manager. - val inputMethodManager = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager - // Set the app bar scrolling. nestedScrollWebView.isNestedScrollingEnabled = scrollAppBar @@ -6192,9 +6188,6 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook // Update the privacy icons. `true` redraws the icons in the app bar. updatePrivacyIcons(true) - // Get a handle for the input method manager. - val inputMethodManager = (getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager) - // Get the current URL. val urlString = currentWebView!!.url diff --git a/app/src/main/java/com/stoutner/privacybrowser/views/NestedScrollWebView.kt b/app/src/main/java/com/stoutner/privacybrowser/views/NestedScrollWebView.kt index 60769502..b5b805d8 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/views/NestedScrollWebView.kt +++ b/app/src/main/java/com/stoutner/privacybrowser/views/NestedScrollWebView.kt @@ -1,7 +1,7 @@ /* * Copyright 2019-2024 Soren Stoutner . * - * This file is part of Privacy Browser Android . + * This file is part of Privacy Browser Android . * * Privacy Browser Android is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/app/src/main/res/drawable/search.xml b/app/src/main/res/drawable/search.xml index d95d178b..4c5b7348 100644 --- a/app/src/main/res/drawable/search.xml +++ b/app/src/main/res/drawable/search.xml @@ -1,4 +1,4 @@ - + - \ No newline at end of file + diff --git a/app/src/main/res/drawable/search_blue.xml b/app/src/main/res/drawable/search_blue.xml new file mode 100644 index 00000000..0cb55606 --- /dev/null +++ b/app/src/main/res/drawable/search_blue.xml @@ -0,0 +1,14 @@ + + + + + + diff --git a/app/src/main/res/layout/logcat_bottom_appbar.xml b/app/src/main/res/layout/logcat_bottom_appbar.xml index cc34fb3c..1db3a353 100644 --- a/app/src/main/res/layout/logcat_bottom_appbar.xml +++ b/app/src/main/res/layout/logcat_bottom_appbar.xml @@ -21,6 +21,7 @@ @@ -54,6 +55,79 @@ android:id="@+id/toolbar" android:layout_height="wrap_content" android:layout_width="match_parent" /> + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/logcat_top_appbar.xml b/app/src/main/res/layout/logcat_top_appbar.xml index 1eb6a675..a11dcd93 100644 --- a/app/src/main/res/layout/logcat_top_appbar.xml +++ b/app/src/main/res/layout/logcat_top_appbar.xml @@ -20,6 +20,8 @@ @@ -40,6 +42,79 @@ android:id="@+id/toolbar" android:layout_height="wrap_content" android:layout_width="match_parent" /> + + + + + + + + + + + + + + + + + + + app:tint="@color/blue_icon" /> + app:tint="@color/blue_icon" /> + app:tint="@color/blue_icon" /> diff --git a/app/src/main/res/layout/main_framelayout_top_appbar.xml b/app/src/main/res/layout/main_framelayout_top_appbar.xml index cd407a29..3b43be9d 100644 --- a/app/src/main/res/layout/main_framelayout_top_appbar.xml +++ b/app/src/main/res/layout/main_framelayout_top_appbar.xml @@ -96,7 +96,7 @@ android:background="?attr/selectableItemBackground" android:contentDescription="@string/previous" android:onClick="findPreviousOnPage" - app:tint="?attr/findOnPageIconTintColor" /> + app:tint="@color/blue_icon" /> + app:tint="@color/blue_icon" /> + app:tint="@color/blue_icon" /> diff --git a/app/src/main/res/menu/logcat_options_menu.xml b/app/src/main/res/menu/logcat_options_menu.xml index 26e1d04f..4da47973 100644 --- a/app/src/main/res/menu/logcat_options_menu.xml +++ b/app/src/main/res/menu/logcat_options_menu.xml @@ -1,9 +1,9 @@ + Lesezeichen und Einstellungen + SQLite-Datenbank-Format + HTML-Format Verschlüsselung keine @@ -452,9 +455,12 @@ entschlüsseln Privacy Browser Android %1$s Einstellungen - Schema %2$d.pbs Privacy Browser Android %1$s Einstellungen - Schema %2$d.pbs.aes + Privacy Browser Lesezeichen.html Export erfolgreich. Export fehlgeschlagen:\u0020 %1$s Import fehlgeschlagen:\u0020 %1$s + %1$d Ordner und Lesezeichen importiert. + %1$d Ordner und Lesezeichen exportiert. diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index ae6d75c1..0bc640e2 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -5,7 +5,7 @@ Translation 2019-2024 Kévin L. . Copyright assigned to Soren Stoutner . - This file is part of Privacy Browser Android . + This file is part of Privacy Browser Android . Privacy Browser Android is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -203,6 +203,12 @@ octets taille inconnue URL invalide + Privacy Browser ne peut actuellement pas télécharger les URL de type blob. + Le gestionnaire de téléchargement d\'Android ne peut pas gérer les URL de type data. + Répertoire de téléchargement + Documents + Images + Musique Enregistrement du fichier : %1$d%% - %2$s Enregistrement du fichier : %1$s octets - %2$s Enregistrement du fichier : %1$d%% - %2$s octets / %3$s octets - %4$s @@ -253,6 +259,8 @@ Créer un dossier Icône actuelle Icône actuelle + Icône de signet personnalisée + Icône de dossier personnalisée Icône par défaut Icône de la page web courante Nom du favori @@ -262,6 +270,7 @@ Editer dossier Déplacer vers dossier Déplacer + Un fichier SVG ne peut actuellement pas être utilisé comme icône de signet favorite. @@ -643,6 +652,18 @@ Les intentions sont des liens envoyés à partir d\'autres applications. Glisser pour rafraîchir Certains sites Web ne fonctionnent pas bien lorsque "Glisser pour rafraîchir" est activé. + Fournisseur de téléchargement + + Privacy Browser + Gestionnaire de téléchargement d\'Android + Application externe + + Privacy Browser - Le téléchargeur intégré de Privacy Browser est simple, mais il a l\'avantage de respecter le proxy et d\'utiliser les cookies (si activés), + ainsi que de pouvoir enregistrer les URL de données. + Gestionnaire de téléchargement d\'Android - Le gestionnaire de téléchargement d\'Android ne respecte pas les paramètres de proxy du navigateur + de confidentialité, mais il a accès aux cookies (ce qui signifie que les fichiers téléchargés à partir de sites nécessitant une connexion fonctionneront probablement). + Application externe - Les applications externes ne respectent pas les paramètres de proxy de Privacy Browser et n\'ont pas accès aux cookies + (ce qui signifie qu\'il est peu probable que les fichiers téléchargés à partir de sites nécessitant une connexion fonctionnent). Défilement barre d\'applications Faites défiler la barre d\'applications en haut de l\'écran lorsque WebView défile vers le bas. Barre d\'application en bas diff --git a/app/src/main/res/values-night-v27/styles.xml b/app/src/main/res/values-night-v27/styles.xml index d1e8aaa4..7edb9a6e 100644 --- a/app/src/main/res/values-night-v27/styles.xml +++ b/app/src/main/res/values-night-v27/styles.xml @@ -1,9 +1,9 @@ @color/gray_875 - @color/violet_500 @color/violet_500 @color/violet_500 diff --git a/app/src/main/res/values-night/styles.xml b/app/src/main/res/values-night/styles.xml index 513f3f2d..06703b20 100644 --- a/app/src/main/res/values-night/styles.xml +++ b/app/src/main/res/values-night/styles.xml @@ -1,9 +1,9 @@ @color/gray_875 - @color/violet_500 @color/violet_500 @color/violet_500 diff --git a/app/src/main/res/values-v27/styles.xml b/app/src/main/res/values-v27/styles.xml index f66d95fe..a78f4c79 100644 --- a/app/src/main/res/values-v27/styles.xml +++ b/app/src/main/res/values-v27/styles.xml @@ -1,9 +1,9 @@ @color/white - @color/blue_800 @color/blue_800 @color/blue_700 diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml index db6a6efa..ecce2323 100644 --- a/app/src/main/res/values/attrs.xml +++ b/app/src/main/res/values/attrs.xml @@ -1,9 +1,9 @@ - diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 20a5e9a3..845e2c1c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -3,7 +3,7 @@ @color/white - @color/blue_800 @color/blue_800 @color/blue_700 diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index 85fb5e8e..d2654963 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -3,7 +3,7 @@