From: Soren Stoutner Date: Thu, 18 Nov 2021 22:06:05 +0000 (-0700) Subject: Remove the SaveDialog. https://redmine.stoutner.com/issues/769 X-Git-Tag: v3.9~2 X-Git-Url: https://gitweb.stoutner.com/?p=PrivacyBrowserAndroid.git;a=commitdiff_plain;h=d53f0640263cf0799e7304fa459864c542ab0d2a;hp=c2350d6be15259bc560c623a09b9ac98cca1c876 Remove the SaveDialog. https://redmine.stoutner.com/issues/769 --- diff --git a/app/build.gradle b/app/build.gradle index 8d3fd799..b4e8d9cd 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -78,8 +78,8 @@ dependencies { implementation 'androidx.viewpager:viewpager:1.0.0' implementation 'androidx.webkit:webkit:1.4.0' - // Include the Kotlin standard libraries. This should be the same version number listed in project build.gradle. - implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.5.31' + // Include the Kotlin standard library. This should be the same version number listed in project build.gradle. + implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.6.0' // Include the Google material library. implementation 'com.google.android.material:material:1.4.0' diff --git a/app/src/main/java/com/stoutner/privacybrowser/activities/AboutActivity.kt b/app/src/main/java/com/stoutner/privacybrowser/activities/AboutActivity.kt index b275e6e0..d52d95ec 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/activities/AboutActivity.kt +++ b/app/src/main/java/com/stoutner/privacybrowser/activities/AboutActivity.kt @@ -19,35 +19,20 @@ package com.stoutner.privacybrowser.activities -import android.content.Intent -import android.net.Uri import android.os.Bundle import android.view.WindowManager -import android.widget.EditText -import android.widget.LinearLayout import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.widget.Toolbar -import androidx.fragment.app.DialogFragment import androidx.preference.PreferenceManager import androidx.viewpager.widget.ViewPager -import com.google.android.material.snackbar.Snackbar import com.google.android.material.tabs.TabLayout import com.stoutner.privacybrowser.R import com.stoutner.privacybrowser.adapters.AboutPagerAdapter -import com.stoutner.privacybrowser.asynctasks.SaveAboutVersionImage -import com.stoutner.privacybrowser.dialogs.SaveDialog -import com.stoutner.privacybrowser.dialogs.SaveDialog.SaveListener -import com.stoutner.privacybrowser.fragments.AboutVersionFragment -import java.io.ByteArrayInputStream -import java.io.InputStream -import java.lang.Exception -import java.nio.charset.StandardCharsets - -class AboutActivity : AppCompatActivity(), SaveListener { +class AboutActivity : AppCompatActivity() { // Declare the class variables. private lateinit var aboutPagerAdapter: AboutPagerAdapter @@ -114,85 +99,4 @@ class AboutActivity : AppCompatActivity(), SaveListener { // Connect the tab layout to the view pager. aboutTabLayout.setupWithViewPager(aboutViewPager) } - - // The activity result is called after browsing for a file in the save alert dialog. - public override fun onActivityResult(requestCode: Int, resultCode: Int, returnedIntent: Intent?) { - // Run the default commands. - super.onActivityResult(requestCode, resultCode, returnedIntent) - - // Only do something if the user didn't press back from the file picker. - if (resultCode == RESULT_OK) { - // Get a handle for the save dialog fragment. - val saveDialogFragment = supportFragmentManager.findFragmentByTag(getString(R.string.save_dialog)) as DialogFragment? - - // Only update the file name if the dialog still exists. - if (saveDialogFragment != null) { - // Get a handle for the save dialog. - val saveDialog = saveDialogFragment.dialog!! - - // Get a handle for the file name edit text. - val fileNameEditText = saveDialog.findViewById(R.id.file_name_edittext) - - // Get the file name URI from the intent. - val fileNameUri = returnedIntent!!.data - - // Get the file name string from the URI. - val fileNameString = fileNameUri.toString() - - // Set the file name text. - fileNameEditText.setText(fileNameString) - - // Move the cursor to the end of the file name edit text. - fileNameEditText.setSelection(fileNameString.length) - } - } - } - - override fun onSave(saveType: Int, dialogFragment: DialogFragment) { - // Get a handle for the dialog. - val dialog = dialogFragment.dialog!! - - // Get a handle for the file name edit text. - val fileNameEditText = dialog.findViewById(R.id.file_name_edittext) - - // Get the file name string. - val fileNameString = fileNameEditText.text.toString() - - // Get a handle for the about version linear layout. - val aboutVersionLinearLayout = findViewById(R.id.about_version_linearlayout) - - // Process the save event according to the type. - when (saveType) { - SaveDialog.SAVE_ABOUT_VERSION_TEXT -> try { - // Get a handle for the about version fragment. - val aboutVersionFragment = aboutPagerAdapter.getTabFragment(0) as AboutVersionFragment - - // Get the about version text. - val aboutVersionString = aboutVersionFragment.aboutVersionString - - // Create an input stream with the contents of about version. - val aboutVersionInputStream: InputStream = ByteArrayInputStream(aboutVersionString.toByteArray(StandardCharsets.UTF_8)) - - // Open an output stream. - val outputStream = contentResolver.openOutputStream(Uri.parse(fileNameString))!! - - // Copy the input stream to the output stream. - aboutVersionInputStream.copyTo(outputStream, 2048) - - // Close the streams. - aboutVersionInputStream.close() - outputStream.close() - - // Display a snackbar with the saved about version information. - Snackbar.make(aboutVersionLinearLayout, getString(R.string.file_saved) + " " + fileNameString, Snackbar.LENGTH_SHORT).show() - } catch (exception: Exception) { - // Display a snackbar with the error message. - Snackbar.make(aboutVersionLinearLayout, getString(R.string.error_saving_file) + " " + exception.toString(), Snackbar.LENGTH_INDEFINITE).show() - } - - SaveDialog.SAVE_ABOUT_VERSION_IMAGE -> - // Save the about version image. - SaveAboutVersionImage(this, fileNameString, aboutVersionLinearLayout).execute() - } - } } 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 23e6d525..e555f194 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/activities/LogcatActivity.kt +++ b/app/src/main/java/com/stoutner/privacybrowser/activities/LogcatActivity.kt @@ -21,7 +21,6 @@ package com.stoutner.privacybrowser.activities import android.content.ClipData import android.content.ClipboardManager -import android.content.Intent import android.net.Uri import android.os.Build import android.os.Bundle @@ -30,20 +29,19 @@ import android.util.TypedValue import android.view.Menu import android.view.MenuItem import android.view.WindowManager -import android.widget.EditText import android.widget.TextView import android.widget.ScrollView +import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.widget.Toolbar -import androidx.fragment.app.DialogFragment import androidx.preference.PreferenceManager import androidx.swiperefreshlayout.widget.SwipeRefreshLayout import com.google.android.material.snackbar.Snackbar +import com.stoutner.privacybrowser.BuildConfig import com.stoutner.privacybrowser.R -import com.stoutner.privacybrowser.dialogs.SaveDialog import java.io.BufferedReader import java.io.IOException @@ -54,7 +52,7 @@ import java.nio.charset.StandardCharsets // Define the class constants. private const val SCROLLVIEW_POSITION = "scrollview_position" -class LogcatActivity : AppCompatActivity(), SaveDialog.SaveListener { +class LogcatActivity : AppCompatActivity() { // Define the class variables. private var scrollViewYPositionInt = 0 @@ -63,6 +61,50 @@ class LogcatActivity : AppCompatActivity(), SaveDialog.SaveListener { private lateinit var logcatScrollView: ScrollView private lateinit var logcatTextView: TextView + // 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()) { fileNameUri: Uri? -> + // Only save the file if the URI is not null, which happens if the user exited the file picker by pressing back. + if (fileNameUri != null) { + try { + // Get the logcat string. + val logcatString = logcatTextView.text.toString() + + // Open an output stream. + val outputStream = contentResolver.openOutputStream(fileNameUri)!! + + // Write the logcat string to the output stream. + outputStream.write(logcatString.toByteArray(StandardCharsets.UTF_8)) + + // Close the output stream. + outputStream.close() + + // Initialize the file name string from the file name URI last path segment. + var fileNameString = fileNameUri.lastPathSegment + + // Query the exact file name if the API >= 26. + if (Build.VERSION.SDK_INT >= 26) { + // Get a cursor from the content resolver. + val contentResolverCursor = contentResolver.query(fileNameUri, null, null, null)!! + + // Move to the fist row. + contentResolverCursor.moveToFirst() + + // Get the file name from the cursor. + fileNameString = contentResolverCursor.getString(contentResolverCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)) + + // Close the cursor. + contentResolverCursor.close() + } + + // Display a snackbar with the saved logcat information. + Snackbar.make(logcatTextView, getString(R.string.saved, fileNameString), Snackbar.LENGTH_SHORT).show() + } catch (exception: Exception) { + // Display a snackbar with the error message. + Snackbar.make(logcatTextView, getString(R.string.error_saving_logcat, exception.toString()), Snackbar.LENGTH_INDEFINITE).show() + } + } + } + public override fun onCreate(savedInstanceState: Bundle?) { // Get a handle for the shared preferences. val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this) @@ -164,11 +206,8 @@ class LogcatActivity : AppCompatActivity(), SaveDialog.SaveListener { } R.id.save -> { // Save was selected. - // Instantiate the save alert dialog. - val saveDialogFragment: DialogFragment = SaveDialog.save(SaveDialog.SAVE_LOGCAT) - - // Show the save alert dialog. - saveDialogFragment.show(supportFragmentManager, getString(R.string.save_logcat)) + // Open the file picker. + saveLogcatActivityResultLauncher.launch(getString(R.string.privacy_browser_logcat_txt, BuildConfig.VERSION_NAME)) // Consume the event. true @@ -239,83 +278,4 @@ class LogcatActivity : AppCompatActivity(), SaveDialog.SaveListener { // Stop the swipe to refresh animation if it is displayed. swipeRefreshLayout.isRefreshing = false } - - // The activity result is called after browsing for a file in the save alert dialog. - public override fun onActivityResult(requestCode: Int, resultCode: Int, returnedIntent: Intent?) { - // Run the default commands. - super.onActivityResult(requestCode, resultCode, returnedIntent) - - // Only do something if the user didn't press back from the file picker. - if (resultCode == RESULT_OK) { - // Get a handle for the save dialog fragment. - val saveDialogFragment = supportFragmentManager.findFragmentByTag(getString(R.string.save_logcat)) as DialogFragment? - - // Only update the file name if the dialog still exists. - if (saveDialogFragment != null) { - // Get a handle for the save dialog. - val saveDialog = saveDialogFragment.dialog!! - - // Get a handle for the file name edit text. - val fileNameEditText = saveDialog.findViewById(R.id.file_name_edittext) - - // Get the file name URI from the intent. - val fileNameUri = returnedIntent!!.data - - // Get the file name string from the URI. - val fileNameString = fileNameUri.toString() - - // Set the file name text. - fileNameEditText.setText(fileNameString) - - // Move the cursor to the end of the file name edit text. - fileNameEditText.setSelection(fileNameString.length) - } - } - } - - override fun onSave(saveType: Int, dialogFragment: DialogFragment) { - // Get a handle for the dialog. - val dialog = dialogFragment.dialog!! - - // Get a handle for the file name edit text. - val fileNameEditText = dialog.findViewById(R.id.file_name_edittext) - - // Get the file path string. - var fileNameString = fileNameEditText.text.toString() - - try { - // Get the logcat as a string. - val logcatString = logcatTextView.text.toString() - - // Open an output stream. - val outputStream = contentResolver.openOutputStream(Uri.parse(fileNameString))!! - - // Write the logcat string to the output stream. - outputStream.write(logcatString.toByteArray(StandardCharsets.UTF_8)) - - // Close the output stream. - outputStream.close() - - // Get the actual file name if the API >= 26. - if (Build.VERSION.SDK_INT >= 26) { - // Get a cursor from the content resolver. - val contentResolverCursor = contentResolver.query(Uri.parse(fileNameString), null, null, null)!! - - // Move to the first row. - contentResolverCursor.moveToFirst() - - // Get the file name from the cursor. - fileNameString = contentResolverCursor.getString(contentResolverCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)) - - // Close the cursor. - contentResolverCursor.close() - } - - // Display a snackbar with the saved logcat information. - Snackbar.make(logcatTextView, getString(R.string.file_saved) + " " + fileNameString, Snackbar.LENGTH_SHORT).show() - } catch (exception: Exception) { - // Display a snackbar with the error message. - Snackbar.make(logcatTextView, getString(R.string.error_saving_file) + " " + exception.toString(), Snackbar.LENGTH_INDEFINITE).show() - } - } } \ No newline at end of file diff --git a/app/src/main/java/com/stoutner/privacybrowser/asynctasks/SaveAboutVersionImage.java b/app/src/main/java/com/stoutner/privacybrowser/asynctasks/SaveAboutVersionImage.java index 377ce7be..1c15bc9c 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/asynctasks/SaveAboutVersionImage.java +++ b/app/src/main/java/com/stoutner/privacybrowser/asynctasks/SaveAboutVersionImage.java @@ -20,10 +20,13 @@ package com.stoutner.privacybrowser.asynctasks; import android.app.Activity; +import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.Canvas; import android.net.Uri; import android.os.AsyncTask; +import android.os.Build; +import android.provider.OpenableColumns; import android.widget.LinearLayout; import com.google.android.material.snackbar.Snackbar; @@ -35,26 +38,45 @@ import java.io.OutputStream; import java.lang.ref.WeakReference; public class SaveAboutVersionImage extends AsyncTask { + // Declare the class constants. + private final String SUCCESS = "Success"; + // Declare the weak references. private final WeakReference activityWeakReference; private final WeakReference aboutVersionLinearLayoutWeakReference; - // Declare the class constants. - private final String SUCCESS = "Success"; - // Declare the class variables. private Snackbar savingImageSnackbar; private Bitmap aboutVersionBitmap; + private final Uri fileNameUri; private final String fileNameString; // The public constructor. - public SaveAboutVersionImage(Activity activity, String fileNameString, LinearLayout aboutVersionLinearLayout) { + public SaveAboutVersionImage(Activity activity, Uri fileNameUri, LinearLayout aboutVersionLinearLayout) { // Populate the weak references. activityWeakReference = new WeakReference<>(activity); aboutVersionLinearLayoutWeakReference = new WeakReference<>(aboutVersionLinearLayout); // Store the class variables. - this.fileNameString = fileNameString; + this.fileNameUri = fileNameUri; + + // Query the exact file name if the API >= 26. + if (Build.VERSION.SDK_INT >= 26) { + // Get a cursor from the content resolver. + Cursor contentResolverCursor = activity.getContentResolver().query(fileNameUri, null, null, null); + + // Move to the first row. + contentResolverCursor.moveToFirst(); + + // Get the file name from the cursor. + fileNameString = contentResolverCursor.getString(contentResolverCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)); + + // Close the cursor. + contentResolverCursor.close(); + } else { + // Use the URI last path segment as the file name string. + fileNameString = fileNameUri.getLastPathSegment(); + } } // `onPreExecute()` operates on the UI thread. @@ -107,7 +129,7 @@ public class SaveAboutVersionImage extends AsyncTask { try { // Open an output stream. - OutputStream outputStream = activity.getContentResolver().openOutputStream(Uri.parse(fileNameString)); + OutputStream outputStream = activity.getContentResolver().openOutputStream(fileNameUri); // Write the webpage image to the image file. aboutVersionByteArrayOutputStream.writeTo(outputStream); diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/SaveDialog.kt b/app/src/main/java/com/stoutner/privacybrowser/dialogs/SaveDialog.kt deleted file mode 100644 index 4c8b75a3..00000000 --- a/app/src/main/java/com/stoutner/privacybrowser/dialogs/SaveDialog.kt +++ /dev/null @@ -1,204 +0,0 @@ -/* - * Copyright © 2016-2021 Soren Stoutner . - * - * This file is part of Privacy Browser . - * - * Privacy Browser is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Privacy Browser is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Privacy Browser. If not, see . - */ - -package com.stoutner.privacybrowser.dialogs - -import android.app.Dialog -import android.content.Context -import android.content.DialogInterface -import android.content.Intent -import android.os.Bundle -import android.text.Editable -import android.text.TextWatcher -import android.view.WindowManager -import android.widget.Button -import android.widget.EditText - -import androidx.appcompat.app.AlertDialog -import androidx.fragment.app.DialogFragment -import androidx.preference.PreferenceManager - -import com.stoutner.privacybrowser.R - -// Define the class constants. -private const val SAVE_TYPE = "save_type" - -class SaveDialog : DialogFragment() { - // Declare the class variables. - private lateinit var saveListener: SaveListener - private lateinit var fileName: String - - // The public interface is used to send information back to the parent activity. - interface SaveListener { - fun onSave(saveType: Int, dialogFragment: DialogFragment) - } - - override fun onAttach(context: Context) { - // Run the default commands. - super.onAttach(context) - - // Get a handle for the save listener from the launching context. - saveListener = context as SaveListener - } - - companion object { - // Define the companion object constants. These can be moved to class constants once all of the code has transitioned to Kotlin. - const val SAVE_LOGCAT = 0 - const val SAVE_ABOUT_VERSION_TEXT = 1 - const val SAVE_ABOUT_VERSION_IMAGE = 2 - - // `@JvmStatic` will no longer be required once all the code has transitioned to Kotlin. - @JvmStatic - fun save(saveType: Int): SaveDialog { - // Create an arguments bundle. - val argumentsBundle = Bundle() - - // Store the arguments in the bundle. - argumentsBundle.putInt(SAVE_TYPE, saveType) - - // Create a new instance of the save dialog. - val saveDialog = SaveDialog() - - // Add the arguments bundle to the new dialog. - saveDialog.arguments = argumentsBundle - - // Return the new dialog. - return saveDialog - } - } - - override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { - // Get the arguments from the bundle. - val saveType = requireArguments().getInt(SAVE_TYPE) - - // Use an alert dialog builder to create the alert dialog. - val dialogBuilder = AlertDialog.Builder(requireContext(), R.style.PrivacyBrowserAlertDialog) - - // Set the title and the icon according to the save type. - when (saveType) { - SAVE_LOGCAT -> { - // Set the title. - dialogBuilder.setTitle(R.string.save_logcat) - - // Set the icon according to the theme. - dialogBuilder.setIconAttribute(R.attr.saveBlueIcon) - } - - SAVE_ABOUT_VERSION_TEXT -> { - // Set the title. - dialogBuilder.setTitle(R.string.save_text) - - // Set the icon according to the theme. - dialogBuilder.setIconAttribute(R.attr.saveTextBlueIcon) - } - - SAVE_ABOUT_VERSION_IMAGE -> { - // Set the title. - dialogBuilder.setTitle(R.string.save_image) - - // Set the icon according to the theme. - dialogBuilder.setIconAttribute(R.attr.imagesBlueIcon) - } - } - - // Set the view. - dialogBuilder.setView(R.layout.save_dialog) - - // Set the cancel button listener. Using `null` as the listener closes the dialog without doing anything else. - dialogBuilder.setNegativeButton(R.string.cancel, null) - - // Set the save button listener. - dialogBuilder.setPositiveButton(R.string.save) { _: DialogInterface?, _: Int -> - // Return the dialog fragment to the parent activity. - saveListener.onSave(saveType, this) - } - - // Create an alert dialog from the builder. - val alertDialog = dialogBuilder.create() - - // Get a handle for the shared preferences. - val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context) - - // Get the screenshot preference. - val allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false) - - // Disable screenshots if not allowed. - if (!allowScreenshots) { - alertDialog.window!!.addFlags(WindowManager.LayoutParams.FLAG_SECURE) - } - - // The alert dialog must be shown before items in the layout can be modified. - alertDialog.show() - - // Get handles for the layout items. - val fileNameEditText = alertDialog.findViewById(R.id.file_name_edittext)!! - val browseButton = alertDialog.findViewById