diff --git a/app/src/main/java/eu/faircode/email/ActivityAMP.java b/app/src/main/java/eu/faircode/email/ActivityAMP.java index d28b0643f1..08b6e0d2df 100644 --- a/app/src/main/java/eu/faircode/email/ActivityAMP.java +++ b/app/src/main/java/eu/faircode/email/ActivityAMP.java @@ -22,15 +22,12 @@ package eu.faircode.email; import static androidx.webkit.WebSettingsCompat.FORCE_DARK_OFF; import static androidx.webkit.WebSettingsCompat.FORCE_DARK_ON; -import android.Manifest; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.net.Uri; -import android.os.Build; import android.os.Bundle; -import android.text.TextUtils; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; @@ -45,12 +42,9 @@ import androidx.preference.PreferenceManager; import androidx.webkit.WebSettingsCompat; import androidx.webkit.WebViewFeature; -import com.google.android.material.snackbar.Snackbar; - import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; -import java.io.FileNotFoundException; import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.util.Arrays; @@ -199,14 +193,7 @@ public class ActivityAMP extends ActivityBase { Uri uri = args.getParcelable("uri"); long id = args.getLong("id"); - if (uri == null) - throw new FileNotFoundException(); - - if (!"content".equals(uri.getScheme()) && - !Helper.hasPermission(context, Manifest.permission.READ_EXTERNAL_STORAGE)) { - Log.w("AMP uri=" + uri); - throw new IllegalArgumentException(context.getString(R.string.title_no_stream)); - } + NoStreamException.check(uri, context); DB db = DB.getInstance(context); EntityMessage message = db.message().getMessage(id); @@ -249,9 +236,8 @@ public class ActivityAMP extends ActivityBase { @Override protected void onException(Bundle args, @NonNull Throwable ex) { - if (ex instanceof IllegalArgumentException) - Snackbar.make(findViewById(android.R.id.content), ex.getMessage(), Snackbar.LENGTH_LONG) - .setGestureInsetBottomIgnored(true).show(); + if (ex instanceof NoStreamException) + ((NoStreamException) ex).report(ActivityAMP.this); else Log.unexpectedError(getSupportFragmentManager(), ex, false); } diff --git a/app/src/main/java/eu/faircode/email/ActivityDSN.java b/app/src/main/java/eu/faircode/email/ActivityDSN.java index c4a9cbaea5..1f17d192b7 100644 --- a/app/src/main/java/eu/faircode/email/ActivityDSN.java +++ b/app/src/main/java/eu/faircode/email/ActivityDSN.java @@ -19,7 +19,6 @@ package eu.faircode.email; Copyright 2018-2022 by Marcel Bokhorst (M66B) */ -import android.Manifest; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; @@ -32,10 +31,7 @@ import android.widget.TextView; import androidx.constraintlayout.widget.Group; -import com.google.android.material.snackbar.Snackbar; - import java.io.ByteArrayOutputStream; -import java.io.FileNotFoundException; import java.io.InputStream; import java.nio.charset.StandardCharsets; @@ -94,14 +90,7 @@ public class ActivityDSN extends ActivityBase { protected Result onExecute(Context context, Bundle args) throws Throwable { Uri uri = args.getParcelable("uri"); - if (uri == null) - throw new FileNotFoundException(); - - if (!"content".equals(uri.getScheme()) && - !Helper.hasPermission(context, Manifest.permission.READ_EXTERNAL_STORAGE)) { - Log.w("DSN uri=" + uri); - throw new IllegalArgumentException(context.getString(R.string.title_no_stream)); - } + NoStreamException.check(uri, context); Result result = new Result(); @@ -128,9 +117,8 @@ public class ActivityDSN extends ActivityBase { @Override protected void onException(Bundle args, Throwable ex) { - if (ex instanceof IllegalArgumentException) - Snackbar.make(findViewById(android.R.id.content), ex.getMessage(), Snackbar.LENGTH_LONG) - .setGestureInsetBottomIgnored(true).show(); + if (ex instanceof NoStreamException) + ((NoStreamException) ex).report(ActivityDSN.this); else Log.unexpectedError(getSupportFragmentManager(), ex, false); } diff --git a/app/src/main/java/eu/faircode/email/ActivityDmarc.java b/app/src/main/java/eu/faircode/email/ActivityDmarc.java index 1572326b24..46045531b9 100644 --- a/app/src/main/java/eu/faircode/email/ActivityDmarc.java +++ b/app/src/main/java/eu/faircode/email/ActivityDmarc.java @@ -19,7 +19,6 @@ package eu.faircode.email; Copyright 2018-2022 by Marcel Bokhorst (M66B) */ -import android.Manifest; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; @@ -43,7 +42,6 @@ import androidx.constraintlayout.widget.Group; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserFactory; -import java.io.FileNotFoundException; import java.io.InputStream; import java.io.StringReader; import java.net.InetAddress; @@ -111,14 +109,7 @@ public class ActivityDmarc extends ActivityBase { protected Spanned onExecute(Context context, Bundle args) throws Throwable { Uri uri = args.getParcelable("uri"); - if (uri == null) - throw new FileNotFoundException(); - - if (!"content".equals(uri.getScheme()) && - !Helper.hasPermission(context, Manifest.permission.READ_EXTERNAL_STORAGE)) { - Log.w("DMARC uri=" + uri); - throw new IllegalArgumentException(context.getString(R.string.title_no_stream)); - } + NoStreamException.check(uri, context); DateFormat DTF = Helper.getDateTimeInstance(context, DateFormat.SHORT, DateFormat.SHORT); int colorWarning = Helper.resolveColor(context, R.attr.colorWarning); @@ -489,7 +480,10 @@ public class ActivityDmarc extends ActivityBase { @Override protected void onException(Bundle args, @NonNull Throwable ex) { - tvDmarc.setText(ex + "\n" + android.util.Log.getStackTraceString(ex)); + if (ex instanceof NoStreamException) + ((NoStreamException) ex).report(ActivityDmarc.this); + else + tvDmarc.setText(ex + "\n" + android.util.Log.getStackTraceString(ex)); grpReady.setVisibility(View.VISIBLE); } diff --git a/app/src/main/java/eu/faircode/email/ActivityEML.java b/app/src/main/java/eu/faircode/email/ActivityEML.java index b6e77c8f63..c5753fa618 100644 --- a/app/src/main/java/eu/faircode/email/ActivityEML.java +++ b/app/src/main/java/eu/faircode/email/ActivityEML.java @@ -19,7 +19,6 @@ package eu.faircode.email; Copyright 2018-2022 by Marcel Bokhorst (M66B) */ -import android.Manifest; import android.content.ContentResolver; import android.content.Context; import android.content.DialogInterface; @@ -257,14 +256,7 @@ public class ActivityEML extends ActivityBase { protected Result onExecute(Context context, Bundle args) throws Throwable { Uri uri = args.getParcelable("uri"); - if (uri == null) - throw new FileNotFoundException(); - - if (!"content".equals(uri.getScheme()) && - !Helper.hasPermission(context, Manifest.permission.READ_EXTERNAL_STORAGE)) { - Log.w("EML uri=" + uri); - throw new IllegalArgumentException(context.getString(R.string.title_no_stream)); - } + NoStreamException.check(uri, context); Result result = new Result(); @@ -389,9 +381,8 @@ public class ActivityEML extends ActivityBase { @Override protected void onException(Bundle args, @NonNull Throwable ex) { - if (ex instanceof IllegalArgumentException) - Snackbar.make(findViewById(android.R.id.content), ex.getMessage(), Snackbar.LENGTH_LONG) - .setGestureInsetBottomIgnored(true).show(); + if (ex instanceof NoStreamException) + ((NoStreamException) ex).report(ActivityEML.this); else Log.unexpectedError(getSupportFragmentManager(), ex, false); } diff --git a/app/src/main/java/eu/faircode/email/ActivitySetup.java b/app/src/main/java/eu/faircode/email/ActivitySetup.java index 2e2994434f..215fe4147e 100644 --- a/app/src/main/java/eu/faircode/email/ActivitySetup.java +++ b/app/src/main/java/eu/faircode/email/ActivitySetup.java @@ -21,7 +21,6 @@ package eu.faircode.email; import static eu.faircode.email.ServiceAuthenticator.AUTH_TYPE_GMAIL; -import android.Manifest; import android.app.Dialog; import android.app.NotificationChannel; import android.app.NotificationChannelGroup; @@ -848,14 +847,7 @@ public class ActivitySetup extends ActivityBase implements FragmentManager.OnBac " answers=" + import_answers + " settings=" + import_settings); - if (uri == null) - throw new FileNotFoundException(); - - if (!"content".equals(uri.getScheme()) && - !Helper.hasPermission(context, Manifest.permission.READ_EXTERNAL_STORAGE)) { - Log.w("Import uri=" + uri); - throw new IllegalArgumentException(context.getString(R.string.title_no_stream)); - } + NoStreamException.check(uri, context); StringBuilder data = new StringBuilder(); Log.i("Reading URI=" + uri); @@ -1256,18 +1248,22 @@ public class ActivitySetup extends ActivityBase implements FragmentManager.OnBac @Override protected void onException(Bundle args, Throwable ex) { - SpannableStringBuilder ssb = new SpannableStringBuilder(); - if (ex.getCause() instanceof BadPaddingException) - ssb.append(getString(R.string.title_setup_password_invalid)); - else if (ex instanceof IOException && ex.getCause() instanceof IllegalBlockSizeException) - ssb.append(getString(R.string.title_setup_import_invalid)); - if (ssb.length() > 0) { - ssb.setSpan(new StyleSpan(Typeface.BOLD), 0, ssb.length(), 0); - ssb.setSpan(new ForegroundColorSpan(colorWarning), 0, ssb.length(), 0); - ssb.append("\n\n"); + if (ex instanceof NoStreamException) + ((NoStreamException) ex).report(ActivitySetup.this); + else { + SpannableStringBuilder ssb = new SpannableStringBuilder(); + if (ex.getCause() instanceof BadPaddingException) + ssb.append(getString(R.string.title_setup_password_invalid)); + else if (ex instanceof IOException && ex.getCause() instanceof IllegalBlockSizeException) + ssb.append(getString(R.string.title_setup_import_invalid)); + if (ssb.length() > 0) { + ssb.setSpan(new StyleSpan(Typeface.BOLD), 0, ssb.length(), 0); + ssb.setSpan(new ForegroundColorSpan(colorWarning), 0, ssb.length(), 0); + ssb.append("\n\n"); + } + ssb.append(ex.toString()); + onProgress(ssb, null); } - ssb.append(ex.toString()); - onProgress(ssb, null); } }.execute(this, args, "setup:import"); } diff --git a/app/src/main/java/eu/faircode/email/ActivitySignature.java b/app/src/main/java/eu/faircode/email/ActivitySignature.java index a29783038b..672161d0cf 100644 --- a/app/src/main/java/eu/faircode/email/ActivitySignature.java +++ b/app/src/main/java/eu/faircode/email/ActivitySignature.java @@ -52,7 +52,6 @@ import androidx.appcompat.app.AlertDialog; import androidx.preference.PreferenceManager; import com.google.android.material.bottomnavigation.BottomNavigationView; -import com.google.android.material.snackbar.Snackbar; import org.jsoup.nodes.Document; @@ -376,6 +375,8 @@ public class ActivitySignature extends ActivityBase { private void onImageSelected(Uri uri) { try { + NoStreamException.check(uri, this); + getContentResolver().takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION); int start = etText.getSelectionStart(); @@ -406,16 +407,8 @@ public class ActivitySignature extends ActivityBase { }) .show(); } - } catch (SecurityException ex) { - Snackbar sb = Snackbar.make(view, R.string.title_no_stream, Snackbar.LENGTH_INDEFINITE) - .setGestureInsetBottomIgnored(true); - sb.setAction(R.string.title_info, new View.OnClickListener() { - @Override - public void onClick(View v) { - Helper.viewFAQ(ActivitySignature.this, 49); - } - }); - sb.show(); + } catch (NoStreamException ex) { + ex.report(this); } catch (Throwable ex) { Log.unexpectedError(getSupportFragmentManager(), ex); } diff --git a/app/src/main/java/eu/faircode/email/FragmentAnswer.java b/app/src/main/java/eu/faircode/email/FragmentAnswer.java index 381ed5b4fe..05f4e8b27f 100644 --- a/app/src/main/java/eu/faircode/email/FragmentAnswer.java +++ b/app/src/main/java/eu/faircode/email/FragmentAnswer.java @@ -500,6 +500,8 @@ public class FragmentAnswer extends FragmentBase { private void onImageSelected(Uri uri) { try { + NoStreamException.check(uri, getContext()); + getContext().getContentResolver().takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION); int start = etText.getSelectionStart(); @@ -511,16 +513,8 @@ public class FragmentAnswer extends FragmentBase { ssb.setSpan(is, start + 1, start + 2, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); etText.setText(ssb); etText.setSelection(start + 2); - } catch (SecurityException ex) { - Snackbar sb = Snackbar.make(view, R.string.title_no_stream, Snackbar.LENGTH_INDEFINITE) - .setGestureInsetBottomIgnored(true); - sb.setAction(R.string.title_info, new View.OnClickListener() { - @Override - public void onClick(View v) { - Helper.viewFAQ(v.getContext(), 49); - } - }); - sb.show(); + } catch (NoStreamException ex) { + ex.report(getContext()); } catch (Throwable ex) { Log.unexpectedError(getParentFragmentManager(), ex); } diff --git a/app/src/main/java/eu/faircode/email/FragmentCompose.java b/app/src/main/java/eu/faircode/email/FragmentCompose.java index ee86c7cd20..91e4ef7d32 100644 --- a/app/src/main/java/eu/faircode/email/FragmentCompose.java +++ b/app/src/main/java/eu/faircode/email/FragmentCompose.java @@ -2870,8 +2870,8 @@ public class FragmentCompose extends FragmentBase { @Override protected void onException(Bundle args, Throwable ex) { // External app sending absolute file - if (ex instanceof SecurityException) - handleFileShare(); + if (ex instanceof NoStreamException) + ((NoStreamException) ex).report(getContext()); else if (ex instanceof FileNotFoundException || ex instanceof IllegalArgumentException || ex instanceof IllegalStateException) { @@ -2965,7 +2965,10 @@ public class FragmentCompose extends FragmentBase { @Override protected void onException(Bundle args, Throwable ex) { - Log.unexpectedError(getParentFragmentManager(), ex); + if (ex instanceof NoStreamException) + ((NoStreamException) ex).report(getContext()); + else + Log.unexpectedError(getParentFragmentManager(), ex); } }.execute(this, args, "compose:shared"); } @@ -3948,11 +3951,7 @@ public class FragmentCompose extends FragmentBase { Context context, long id, Uri uri, boolean image, int resize, boolean privacy) throws IOException { Log.w("Add attachment uri=" + uri + " image=" + image + " resize=" + resize + " privacy=" + privacy); - if (!"content".equals(uri.getScheme()) && - !Helper.hasPermission(context, Manifest.permission.READ_EXTERNAL_STORAGE)) { - Log.w("Add attachment uri=" + uri); - throw new SecurityException("Add attachment with file scheme"); - } + NoStreamException.check(uri, context); EntityAttachment attachment = new EntityAttachment(); UriInfo info = getInfo(uri, context); @@ -5335,8 +5334,8 @@ public class FragmentCompose extends FragmentBase { // External app sending absolute file if (ex instanceof MessageRemovedException) finish(); - else if (ex instanceof SecurityException) - handleFileShare(); + if (ex instanceof NoStreamException) + ((NoStreamException) ex).report(getContext()); else if (ex instanceof FileNotFoundException || ex instanceof IllegalArgumentException || ex instanceof IllegalStateException) @@ -5360,18 +5359,6 @@ public class FragmentCompose extends FragmentBase { } }.setExecutor(executor); - private void handleFileShare() { - Snackbar sb = Snackbar.make(view, R.string.title_no_stream, Snackbar.LENGTH_INDEFINITE) - .setGestureInsetBottomIgnored(true); - sb.setAction(R.string.title_info, new View.OnClickListener() { - @Override - public void onClick(View v) { - Helper.viewFAQ(v.getContext(), 49); - } - }); - sb.show(); - } - private SimpleTask actionLoader = new SimpleTask() { @Override protected void onPreExecute(Bundle args) { diff --git a/app/src/main/java/eu/faircode/email/FragmentContacts.java b/app/src/main/java/eu/faircode/email/FragmentContacts.java index 1d158ebcc0..635ff94709 100644 --- a/app/src/main/java/eu/faircode/email/FragmentContacts.java +++ b/app/src/main/java/eu/faircode/email/FragmentContacts.java @@ -21,7 +21,6 @@ package eu.faircode.email; import static android.app.Activity.RESULT_OK; -import android.Manifest; import android.app.Dialog; import android.content.ContentResolver; import android.content.Context; @@ -328,14 +327,7 @@ public class FragmentContacts extends FragmentBase { Uri uri = args.getParcelable("uri"); long account = args.getLong("account"); - if (uri == null) - throw new FileNotFoundException(); - - if (!"content".equals(uri.getScheme()) && - !Helper.hasPermission(context, Manifest.permission.READ_EXTERNAL_STORAGE)) { - Log.w("Import uri=" + uri); - throw new IllegalArgumentException(context.getString(R.string.title_no_stream)); - } + NoStreamException.check(uri, context); long now = new Date().getTime(); @@ -380,7 +372,10 @@ public class FragmentContacts extends FragmentBase { @Override protected void onException(Bundle args, Throwable ex) { - Log.unexpectedError(getParentFragmentManager(), ex); + if (ex instanceof NoStreamException) + ((NoStreamException) ex).report(getContext()); + else + Log.unexpectedError(getParentFragmentManager(), ex); } }.execute(this, args, "setup:import"); } diff --git a/app/src/main/java/eu/faircode/email/FragmentRules.java b/app/src/main/java/eu/faircode/email/FragmentRules.java index ea12d3af7d..2690217140 100644 --- a/app/src/main/java/eu/faircode/email/FragmentRules.java +++ b/app/src/main/java/eu/faircode/email/FragmentRules.java @@ -21,7 +21,6 @@ package eu.faircode.email; import static android.app.Activity.RESULT_OK; -import android.Manifest; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; @@ -392,14 +391,7 @@ public class FragmentRules extends FragmentBase { long fid = args.getLong("folder"); Uri uri = args.getParcelable("uri"); - if (uri == null) - throw new FileNotFoundException(); - - if (!"content".equals(uri.getScheme()) && - !Helper.hasPermission(context, Manifest.permission.READ_EXTERNAL_STORAGE)) { - Log.w("Import uri=" + uri); - throw new IllegalArgumentException(context.getString(R.string.title_no_stream)); - } + NoStreamException.check(uri, context); StringBuilder data = new StringBuilder(); @@ -462,8 +454,9 @@ public class FragmentRules extends FragmentBase { @Override protected void onException(Bundle args, Throwable ex) { - if (ex instanceof IllegalArgumentException || - ex instanceof FileNotFoundException || + if (ex instanceof NoStreamException) + ((NoStreamException) ex).report(getContext()); + else if (ex instanceof FileNotFoundException || ex instanceof JSONException) ToastEx.makeText(getContext(), ex.getMessage(), Toast.LENGTH_LONG).show(); else diff --git a/app/src/main/java/eu/faircode/email/Helper.java b/app/src/main/java/eu/faircode/email/Helper.java index 1e23fcdb73..a294175ce0 100644 --- a/app/src/main/java/eu/faircode/email/Helper.java +++ b/app/src/main/java/eu/faircode/email/Helper.java @@ -74,7 +74,6 @@ import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.ViewParent; -import android.view.WindowManager; import android.view.accessibility.AccessibilityManager; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; @@ -118,6 +117,7 @@ import org.openintents.openpgp.util.OpenPgpApi; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; diff --git a/app/src/main/java/eu/faircode/email/NoStreamException.java b/app/src/main/java/eu/faircode/email/NoStreamException.java new file mode 100644 index 0000000000..caeae3ed4a --- /dev/null +++ b/app/src/main/java/eu/faircode/email/NoStreamException.java @@ -0,0 +1,88 @@ +package eu.faircode.email; + +/* + This file is part of FairEmail. + + FairEmail 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. + + FairEmail 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 FairEmail. If not, see . + + Copyright 2018-2022 by Marcel Bokhorst (M66B) +*/ + +import android.Manifest; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.net.Uri; +import android.provider.Settings; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.ImageButton; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; + +import java.io.FileNotFoundException; + +public class NoStreamException extends SecurityException { + private Uri uri; + + private NoStreamException(@NonNull Uri uri) { + Log.w("Read uri=" + uri); + this.uri = uri; + } + + static void check(Uri uri, Context context) throws FileNotFoundException { + if (uri == null) + throw new FileNotFoundException("Selected file"); + + if ("content".equals(uri.getScheme())) + return; + if (Helper.hasPermission(context, Manifest.permission.READ_EXTERNAL_STORAGE)) + return; + + throw new NoStreamException(uri); + } + + void report(Context context) { + View dview = LayoutInflater.from(context).inflate(R.layout.dialog_no_stream, null); + + TextView tvUri = dview.findViewById(R.id.tvUri); + ImageButton ibInfo = dview.findViewById(R.id.ibInfo); + + tvUri.setText(uri == null ? null : uri.toString()); + + ibInfo.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Helper.viewFAQ(v.getContext(), 49); + } + }); + + new AlertDialog.Builder(context) + .setView(dview) + .setNegativeButton(android.R.string.cancel, null) + .setPositiveButton(R.string.title_setup_grant, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + Uri uri = Uri.fromParts("package", context.getPackageName(), null); + intent.setData(uri); + context.startActivity(intent); + } + }) + .show(); + } +} diff --git a/app/src/main/res/layout/dialog_no_stream.xml b/app/src/main/res/layout/dialog_no_stream.xml new file mode 100644 index 0000000000..1ab5ba7247 --- /dev/null +++ b/app/src/main/res/layout/dialog_no_stream.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ca820e8206..c04583b4e7 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1204,7 +1204,8 @@ No suitable audio recorder app available No suitable camera app available Storage access framework not available - An outdated app sent a file path instead of a file stream + FairEmail does not have permission to read this file directly + This can be solved by selecting the file with a more modern file manager app or by granting permission to read files No or no suitable internet connection Connecting to one or more accounts … Folder does not exist