From 3faf7ed57da18e132d8d08b1fe6a5539e143459f Mon Sep 17 00:00:00 2001 From: M66B Date: Wed, 4 Jun 2025 15:44:53 +0200 Subject: [PATCH] Typed URIs --- .../eu/faircode/email/ActivityCompose.java | 13 +-- .../java/eu/faircode/email/EntityAnswer.java | 2 +- .../eu/faircode/email/FragmentCompose.java | 90 +++++++++---------- .../main/java/eu/faircode/email/Helper.java | 8 +- .../main/java/eu/faircode/email/UriType.java | 90 +++++++++++++++++++ 5 files changed, 147 insertions(+), 56 deletions(-) create mode 100644 app/src/main/java/eu/faircode/email/UriType.java diff --git a/app/src/main/java/eu/faircode/email/ActivityCompose.java b/app/src/main/java/eu/faircode/email/ActivityCompose.java index 8c22516aec..01d925a67e 100644 --- a/app/src/main/java/eu/faircode/email/ActivityCompose.java +++ b/app/src/main/java/eu/faircode/email/ActivityCompose.java @@ -21,6 +21,7 @@ package eu.faircode.email; import android.app.NotificationManager; import android.content.ClipData; +import android.content.ClipDescription; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -248,15 +249,17 @@ public class ActivityCompose extends ActivityBase implements FragmentManager.OnB if (!TextUtils.isEmpty(html)) args.putString("body", html); - ArrayList uris = new ArrayList<>(); + ArrayList uris = new ArrayList<>(); ClipData clip = intent.getClipData(); + ClipDescription description = (clip == null ? null : clip.getDescription()); if (clip != null) for (int i = 0; i < clip.getItemCount(); i++) { ClipData.Item item = clip.getItemAt(i); Uri stream = (item == null ? null : item.getUri()); if (stream != null) - uris.add(stream); + uris.add(new UriType(stream, + description != null && i < description.getMimeTypeCount() ? description.getMimeType(i) : null)); } if (intent.hasExtra(Intent.EXTRA_STREAM)) { @@ -268,13 +271,13 @@ public class ActivityCompose extends ActivityBase implements FragmentManager.OnB for (Uri stream : streams) if (stream != null) { boolean found = false; - for (Uri e : uris) - if (stream.equals(e)) { + for (UriType e : uris) + if (stream.equals(e.getUri())) { found = true; break; } if (!found) - uris.add(stream); + uris.add(new UriType(stream, streams.size() == 1 ? intent.getType() : null)); } } } diff --git a/app/src/main/java/eu/faircode/email/EntityAnswer.java b/app/src/main/java/eu/faircode/email/EntityAnswer.java index af0c254454..567ddc0e36 100644 --- a/app/src/main/java/eu/faircode/email/EntityAnswer.java +++ b/app/src/main/java/eu/faircode/email/EntityAnswer.java @@ -580,7 +580,7 @@ public class EntityAnswer implements Serializable { for (Uri file : attachments) try { EntityAttachment attachment = new EntityAttachment(); - Helper.UriInfo info = Helper.getInfo(file, context); + Helper.UriInfo info = Helper.getInfo(new UriType(file, null), context); attachment.message = id; attachment.sequence = db.attachment().getAttachmentSequence(id) + 1; diff --git a/app/src/main/java/eu/faircode/email/FragmentCompose.java b/app/src/main/java/eu/faircode/email/FragmentCompose.java index 8547441451..593db48c7c 100644 --- a/app/src/main/java/eu/faircode/email/FragmentCompose.java +++ b/app/src/main/java/eu/faircode/email/FragmentCompose.java @@ -28,6 +28,7 @@ import android.Manifest; import android.app.Activity; import android.app.PendingIntent; import android.content.ClipData; +import android.content.ClipDescription; import android.content.ClipboardManager; import android.content.ContentResolver; import android.content.Context; @@ -392,7 +393,7 @@ public class FragmentCompose extends FragmentBase { pickImages = registerForActivityResult(new ActivityResultContracts.PickMultipleVisualMedia(max), uris -> { if (!uris.isEmpty()) - onAddImageFile(uris, false); + onAddImageFile(UriType.getList(uris), false); }); } @@ -654,8 +655,7 @@ public class FragmentCompose extends FragmentBase { int resize = prefs.getInt("resize", FragmentCompose.REDUCED_IMAGE_SIZE); boolean resize_width_only = prefs.getBoolean("resize_width_only", false); onAddAttachment( - Arrays.asList(uri), - type == null ? null : new String[]{type}, + Arrays.asList(new UriType(uri, type)), true, resize_paste ? resize : 0, resize_width_only, @@ -3382,13 +3382,13 @@ public class FragmentCompose extends FragmentBase { case REQUEST_TAKE_PHOTO: if (resultCode == RESULT_OK) { if (photoURI != null) - onAddImageFile(Arrays.asList(photoURI), false); + onAddImageFile(Arrays.asList(new UriType(photoURI, null)), false); } break; case REQUEST_ATTACHMENT: case REQUEST_RECORD_AUDIO: if (resultCode == RESULT_OK && data != null) - onAddAttachment(getUris(data), null, false, 0, false, false, false); + onAddAttachment(getUris(data), false, 0, false, false, false); break; case REQUEST_OPENPGP: if (resultCode == RESULT_OK && data != null) @@ -3733,21 +3733,20 @@ public class FragmentCompose extends FragmentBase { } } - private void onAddImageFile(List uri, boolean focus) { + private void onAddImageFile(List uri, boolean focus) { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext()); boolean add_inline = prefs.getBoolean("add_inline", true); boolean resize_images = prefs.getBoolean("resize_images", true); boolean resize_width_only = prefs.getBoolean("resize_width_only", false); boolean privacy_images = prefs.getBoolean("privacy_images", false); int resize = prefs.getInt("resize", FragmentCompose.REDUCED_IMAGE_SIZE); - onAddAttachment(uri, null, add_inline, resize_images ? resize : 0, resize_width_only, privacy_images, focus); + onAddAttachment(uri, add_inline, resize_images ? resize : 0, resize_width_only, privacy_images, focus); } - private void onAddAttachment(List uris, String[] types, boolean image, int resize, boolean resize_width_only, boolean privacy, boolean focus) { + private void onAddAttachment(List uris, boolean image, int resize, boolean resize_width_only, boolean privacy, boolean focus) { Bundle args = new Bundle(); args.putLong("id", working); args.putParcelableArrayList("uris", new ArrayList<>(uris)); - args.putStringArray("types", types); args.putBoolean("image", image); args.putInt("resize", resize); args.putBoolean("resize_width_only", resize_width_only); @@ -3761,8 +3760,7 @@ public class FragmentCompose extends FragmentBase { @Override protected Spanned onExecute(Context context, Bundle args) throws IOException, SecurityException { final long id = args.getLong("id"); - List uris = args.getParcelableArrayList("uris"); - String[] types = args.getStringArray("types"); + List uris = args.getParcelableArrayList("uris"); boolean image = args.getBoolean("image"); int resize = args.getInt("resize"); boolean resize_width_only = args.getBoolean("resize_width_only"); @@ -3779,10 +3777,9 @@ public class FragmentCompose extends FragmentBase { start = s.length(); for (int i = 0; i < uris.size(); i++) { - Uri uri = uris.get(i); - String type = (types != null && i < types.length ? types[i] : null); + UriType uri = uris.get(i); - EntityAttachment attachment = addAttachment(context, id, uri, type, image, resize, resize_width_only, privacy); + EntityAttachment attachment = addAttachment(context, id, uri, image, resize, resize_width_only, privacy); if (attachment == null) continue; if (!image || !attachment.isImage()) @@ -3884,25 +3881,25 @@ public class FragmentCompose extends FragmentBase { }.serial().execute(this, args, "compose:attachment:add"); } - void onSharedAttachments(ArrayList uris) { + void onSharedAttachments(ArrayList uris) { Bundle args = new Bundle(); args.putLong("id", working); args.putParcelableArrayList("uris", uris); - new SimpleTask>() { + new SimpleTask>() { @Override - protected ArrayList onExecute(Context context, Bundle args) throws Throwable { + protected ArrayList onExecute(Context context, Bundle args) throws Throwable { long id = args.getLong("id"); - List uris = args.getParcelableArrayList("uris"); + List uris = args.getParcelableArrayList("uris"); - ArrayList images = new ArrayList<>(); - for (Uri uri : uris) + ArrayList images = new ArrayList<>(); + for (UriType uri : uris) try { Helper.UriInfo info = Helper.getInfo(uri, context); if (info.isImage()) images.add(uri); else - addAttachment(context, id, uri, null, false, 0, false, false); + addAttachment(context, id, uri, false, 0, false, false); } catch (IOException ex) { Log.e(ex); } @@ -3911,7 +3908,7 @@ public class FragmentCompose extends FragmentBase { } @Override - protected void onExecuted(Bundle args, ArrayList images) { + protected void onExecuted(Bundle args, ArrayList images) { if (images.size() == 0) return; @@ -3940,20 +3937,22 @@ public class FragmentCompose extends FragmentBase { }.serial().execute(this, args, "compose:shared"); } - private List getUris(Intent data) { - List result = new ArrayList<>(); + private List getUris(Intent data) { + List result = new ArrayList<>(); ClipData clipData = data.getClipData(); if (clipData == null) { Uri uri = data.getData(); if (uri != null) - result.add(uri); + result.add(new UriType(uri, null)); } else { + ClipDescription description = clipData.getDescription(); for (int i = 0; i < clipData.getItemCount(); i++) { ClipData.Item item = clipData.getItemAt(i); Uri uri = item.getUri(); if (uri != null) - result.add(uri); + result.add(new UriType(uri, + description != null && i < description.getMimeTypeCount() ? description.getMimeType(i) : null)); } } @@ -3963,7 +3962,7 @@ public class FragmentCompose extends FragmentBase { if (result.size() == 0 && data.hasExtra("media-uri-list")) try { List uris = data.getParcelableArrayListExtra("media-uri-list"); - result.addAll(uris); + result.addAll(UriType.getList(uris)); } catch (Throwable ex) { Log.e(ex); } @@ -5377,25 +5376,22 @@ public class FragmentCompose extends FragmentBase { } private static EntityAttachment addAttachment( - Context context, long id, Uri uri, String type, boolean image, int resize, boolean resize_width_only, boolean privacy) throws IOException, SecurityException { + Context context, long id, UriType uri, boolean image, int resize, boolean resize_width_only, boolean privacy) throws IOException, SecurityException { Log.w("Add attachment uri=" + uri + " image=" + image + " resize=" + resize + "/" + resize_width_only + " privacy=" + privacy); - NoStreamException.check(uri, context); + NoStreamException.check(uri.getUri(), context); EntityAttachment attachment = new EntityAttachment(); Helper.UriInfo info = Helper.getInfo(uri, context); EntityLog.log(context, "Add attachment" + - " uri=" + uri + " type=" + type + " image=" + image + " resize=" + resize + " privacy=" + privacy + + " uri=" + uri + " image=" + image + " resize=" + resize + " privacy=" + privacy + " name=" + info.name + " type=" + info.type + " size=" + info.size); - if (type == null) - type = info.type; - String ext = Helper.getExtension(info.name); - if (info.name != null && ext == null && type != null) { + if (info.name != null && ext == null && uri.getType() != null) { String guessed = MimeTypeMap.getSingleton() - .getExtensionFromMimeType(type.toLowerCase(Locale.ROOT)); + .getExtensionFromMimeType(uri.getType().toLowerCase(Locale.ROOT)); if (!TextUtils.isEmpty(guessed)) { ext = guessed; info.name += '.' + ext; @@ -5418,7 +5414,7 @@ public class FragmentCompose extends FragmentBase { attachment.name = "img" + attachment.sequence + (ext == null ? "" : "." + ext); else attachment.name = info.name; - attachment.type = type; + attachment.type = info.type; attachment.disposition = (image ? Part.INLINE : Part.ATTACHMENT); attachment.size = info.size; attachment.progress = 0; @@ -5439,7 +5435,7 @@ public class FragmentCompose extends FragmentBase { InputStream is = null; OutputStream os = null; try { - is = context.getContentResolver().openInputStream(uri); + is = context.getContentResolver().openInputStream(uri.getUri()); os = new FileOutputStream(file); if (is == null) @@ -5494,15 +5490,15 @@ public class FragmentCompose extends FragmentBase { db.attachment().setDownloaded(attachment.id, size); - if (BuildConfig.APPLICATION_ID.equals(uri.getAuthority()) && - uri.getPathSegments().size() > 0 && - "photo".equals(uri.getPathSegments().get(0))) { + if (BuildConfig.APPLICATION_ID.equals(uri.getUri().getAuthority()) && + uri.getUri().getPathSegments().size() > 0 && + "photo".equals(uri.getUri().getPathSegments().get(0))) { // content://eu.faircode.email/photo/nnn.jpg - File tmp = new File(context.getFilesDir(), uri.getPath()); + File tmp = new File(context.getFilesDir(), uri.getUri().getPath()); Log.i("Deleting " + tmp); Helper.secureDelete(tmp); } else - Log.i("Authority=" + uri.getAuthority()); + Log.i("Authority=" + uri.getUri().getAuthority()); if (resize > 0) resizeAttachment(context, attachment, resize, resize_width_only); @@ -5710,7 +5706,7 @@ public class FragmentCompose extends FragmentBase { String external_body = args.getString("body", ""); String external_text = args.getString("text"); CharSequence selected_text = args.getCharSequence("selected"); - ArrayList uris = args.getParcelableArrayList("attachments"); + ArrayList uris = args.getParcelableArrayList("attachments"); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); boolean plain_only = prefs.getBoolean("plain_only", false); @@ -6453,14 +6449,14 @@ public class FragmentCompose extends FragmentBase { } if ("new".equals(action) && uris != null) { - ArrayList images = new ArrayList<>(); - for (Uri uri : uris) + ArrayList images = new ArrayList<>(); + for (UriType uri : uris) try { Helper.UriInfo info = Helper.getInfo(uri, context); if (info.isImage()) images.add(uri); else - addAttachment(context, data.draft.id, uri, null, false, 0, false, false); + addAttachment(context, data.draft.id, uri, false, 0, false, false); } catch (IOException | SecurityException ex) { Log.e(ex); } @@ -6896,7 +6892,7 @@ public class FragmentCompose extends FragmentBase { if (draft.content && state == State.NONE) { Runnable postShow = null; if (args.containsKey("images")) { - ArrayList images = args.getParcelableArrayList("images"); + ArrayList images = args.getParcelableArrayList("images"); args.remove("images"); // once postShow = new Runnable() { diff --git a/app/src/main/java/eu/faircode/email/Helper.java b/app/src/main/java/eu/faircode/email/Helper.java index e5a157a437..b89422e59b 100644 --- a/app/src/main/java/eu/faircode/email/Helper.java +++ b/app/src/main/java/eu/faircode/email/Helper.java @@ -2962,13 +2962,13 @@ public class Helper { } @NonNull - static UriInfo getInfo(Uri uri, Context context) { + static UriInfo getInfo(UriType uri, Context context) { UriInfo result = new UriInfo(); // https://stackoverflow.com/questions/76094229/android-13-photo-video-picker-file-name-from-the-uri-is-garbage DocumentFile dfile = null; try { - dfile = DocumentFile.fromSingleUri(context, uri); + dfile = DocumentFile.fromSingleUri(context, uri.getUri()); if (dfile != null) { result.name = dfile.getName(); result.type = dfile.getType(); @@ -2981,9 +2981,11 @@ public class Helper { // Check name if (TextUtils.isEmpty(result.name)) - result.name = uri.getLastPathSegment(); + result.name = uri.getUri().getLastPathSegment(); // Check type + if (uri.getType() != null) + result.type = uri.getType(); if (!TextUtils.isEmpty(result.type)) try { new ContentType(result.type); diff --git a/app/src/main/java/eu/faircode/email/UriType.java b/app/src/main/java/eu/faircode/email/UriType.java new file mode 100644 index 0000000000..dbaac36aef --- /dev/null +++ b/app/src/main/java/eu/faircode/email/UriType.java @@ -0,0 +1,90 @@ +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-2025 by Marcel Bokhorst (M66B) +*/ + +import android.net.Uri; +import android.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; + +import androidx.annotation.NonNull; + +import java.util.ArrayList; +import java.util.List; + +public class UriType implements Parcelable { + private Uri uri; + private String type; + + protected UriType(Parcel in) { + this.uri = in.readParcelable(Uri.class.getClassLoader()); + this.type = in.readString(); + } + + public UriType(Uri uri, String type) { + this.uri = uri; + if (!TextUtils.isEmpty(type)) + this.type = type; + } + + public Uri getUri() { + return this.uri; + } + + public String getType() { + return this.type; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel parcel, int flags) { + parcel.writeParcelable(this.uri, flags); + parcel.writeString(this.type); + } + + public static final Creator CREATOR = new Creator() { + @Override + public UriType createFromParcel(Parcel in) { + return new UriType(in); + } + + @Override + public UriType[] newArray(int size) { + return new UriType[size]; + } + }; + + public static List getList(List uris) { + List result = new ArrayList<>(); + for (Uri uri : uris) + result.add(new UriType(uri, null)); + return result; + } + + @NonNull + @Override + public String toString() { + return uri + " type=" + type; + } +}