diff options
author | Nikita Dubrovsky <dubrovsky@google.com> | 2021-05-22 05:06:06 +0000 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2021-05-22 05:06:06 +0000 |
commit | 0eac02491d87bfaa53c004d6dfc32063e14e9562 (patch) | |
tree | 0332108f03c2b5eeb4c345d0d3ed44c36cf93adf | |
parent | a9018531f6cc4835f58d665054f2c24868b11c61 (diff) | |
parent | 522bfd9e7976f0288ef893b3c80ac02d9a75b329 (diff) | |
download | development-0eac02491d87bfaa53c004d6dfc32063e14e9562.tar.gz |
Merge "Update ReceiveContentDemo to follow the API docs for URI permissions" into sc-dev
3 files changed, 53 insertions, 32 deletions
diff --git a/samples/ReceiveContentDemo/src/com/example/android/receivecontent/AttachmentsRecyclerViewAdapter.java b/samples/ReceiveContentDemo/src/com/example/android/receivecontent/AttachmentsRecyclerViewAdapter.java index cfdf32d6d..9e25d0fe0 100644 --- a/samples/ReceiveContentDemo/src/com/example/android/receivecontent/AttachmentsRecyclerViewAdapter.java +++ b/samples/ReceiveContentDemo/src/com/example/android/receivecontent/AttachmentsRecyclerViewAdapter.java @@ -25,6 +25,7 @@ import androidx.appcompat.widget.AppCompatImageView; import androidx.recyclerview.widget.RecyclerView; import java.util.ArrayList; +import java.util.Collection; import java.util.List; final class AttachmentsRecyclerViewAdapter extends @@ -49,6 +50,10 @@ final class AttachmentsRecyclerViewAdapter extends mAttachments.add(uri); } + public void addAttachments(Collection<Uri> uris) { + mAttachments.addAll(uris); + } + public void clearAttachments() { mAttachments.clear(); } diff --git a/samples/ReceiveContentDemo/src/com/example/android/receivecontent/MyReceiver.java b/samples/ReceiveContentDemo/src/com/example/android/receivecontent/MyReceiver.java index bb180e6cc..37612eeb1 100644 --- a/samples/ReceiveContentDemo/src/com/example/android/receivecontent/MyReceiver.java +++ b/samples/ReceiveContentDemo/src/com/example/android/receivecontent/MyReceiver.java @@ -16,7 +16,6 @@ package com.example.android.receivecontent; -import android.content.ClipData; import android.content.ClipDescription; import android.content.ContentResolver; import android.content.Context; @@ -37,6 +36,8 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import java.io.FileNotFoundException; +import java.util.ArrayList; +import java.util.List; /** * Sample {@link OnReceiveContentListener} implementation that accepts all URIs, and delegates @@ -65,13 +66,7 @@ final class MyReceiver implements OnReceiveContentListener { ContentInfo uriContent = split.first; ContentInfo remaining = split.second; if (uriContent != null) { - ContentResolver contentResolver = view.getContext().getContentResolver(); - ClipData clip = uriContent.getClip(); - for (int i = 0; i < clip.getItemCount(); i++) { - Uri uri = clip.getItemAt(i).getUri(); - String mimeType = contentResolver.getType(uri); - receive(view, uri, mimeType); - } + receive(view.getContext(), uriContent); } // Return anything that we didn't handle ourselves. This preserves the default platform // behavior for text and anything else for which we are not implementing custom handling. @@ -82,34 +77,43 @@ final class MyReceiver implements OnReceiveContentListener { * Handles incoming content URIs. If the content is an image, stores it as an attachment in the * app's private storage. If the content is any other type, simply shows a toast with the type * of the content and its size in bytes. + * + * <p><strong>Important:</strong> It is significant that we pass along the {@code payload} + * object to the worker thread that will process the content, because URI permissions are tied + * to the payload object's lifecycle. If that object is not passed along, it could be garbage + * collected and permissions would be revoked prematurely (before we have a chance to process + * the content). */ - private void receive(@NonNull View view, @NonNull Uri uri, @NonNull String mimeType) { - Log.i(Logcat.TAG, "Receiving " + mimeType + ": " + uri); - if (ClipDescription.compareMimeTypes(mimeType, "image/*")) { - createAttachment(uri, mimeType); - } else { - showMessage(view, uri, mimeType); - } - } - - /** - * Reads the image at the given URI and writes it to private storage. Then shows the image in - * the UI by passing the URI pointing to the locally stored copy to the recycler view adapter. - */ - private void createAttachment(@NonNull Uri uri, @NonNull String mimeType) { - ListenableFuture<Uri> addAttachmentFuture = MyExecutors.bg().submit(() -> - mAttachmentsRepo.write(uri) - ); - Futures.addCallback(addAttachmentFuture, new FutureCallback<Uri>() { + private void receive(@NonNull Context context, @NonNull ContentInfo payload) { + Context applicationContext = context.getApplicationContext(); + ContentResolver contentResolver = applicationContext.getContentResolver(); + ListenableFuture<List<Uri>> addAttachmentsFuture = MyExecutors.bg().submit(() -> { + List<Uri> uris = Utils.collectUris(payload.getClip()); + List<Uri> localUris = new ArrayList<>(uris.size()); + for (Uri uri : uris) { + String mimeType = contentResolver.getType(uri); + Log.i(Logcat.TAG, "Processing " + mimeType + ": " + uri); + if (ClipDescription.compareMimeTypes(mimeType, "image/*")) { + // Read the image at the given URI and write it to private storage. + localUris.add(mAttachmentsRepo.write(uri)); + } else { + showMessage(applicationContext, uri, mimeType); + } + } + return localUris; + }); + Futures.addCallback(addAttachmentsFuture, new FutureCallback<List<Uri>>() { @Override - public void onSuccess(Uri result) { - mAttachmentsRecyclerViewAdapter.addAttachment(result); + public void onSuccess(List<Uri> localUris) { + // Show the image in the UI by passing the URI pointing to the locally stored copy + // to the recycler view adapter. + mAttachmentsRecyclerViewAdapter.addAttachments(localUris); mAttachmentsRecyclerViewAdapter.notifyDataSetChanged(); + Log.i(Logcat.TAG, "Processed content: " + payload); } @Override public void onFailure(@NonNull Throwable t) { - Log.e(Logcat.TAG, - "Error receiving content: uri=" + uri + ", mimeType" + mimeType, t); + Log.e(Logcat.TAG,"Error processing content: " + payload, t); } }, MyExecutors.main()); } @@ -118,8 +122,8 @@ final class MyReceiver implements OnReceiveContentListener { * Reads the size of the given content URI and shows a toast with the type of the content and * its size in bytes. */ - private void showMessage(@NonNull View view, @NonNull Uri uri, @NonNull String mimeType) { - Context applicationContext = view.getContext().getApplicationContext(); + private void showMessage(@NonNull Context applicationContext, + @NonNull Uri uri, @NonNull String mimeType) { MyExecutors.bg().execute(() -> { ContentResolver contentResolver = applicationContext.getContentResolver(); long lengthBytes; diff --git a/samples/ReceiveContentDemo/src/com/example/android/receivecontent/Utils.java b/samples/ReceiveContentDemo/src/com/example/android/receivecontent/Utils.java index 5e43f7f60..39662289c 100644 --- a/samples/ReceiveContentDemo/src/com/example/android/receivecontent/Utils.java +++ b/samples/ReceiveContentDemo/src/com/example/android/receivecontent/Utils.java @@ -18,6 +18,7 @@ package com.example.android.receivecontent; import android.content.ClipData; import android.content.ClipDescription; +import android.net.Uri; import android.util.Pair; import android.view.ContentInfo; @@ -71,4 +72,15 @@ final class Utils { } return clip; } + + public static List<Uri> collectUris(ClipData clip) { + List<Uri> uris = new ArrayList<>(clip.getItemCount()); + for (int i = 0; i < clip.getItemCount(); i++) { + Uri uri = clip.getItemAt(i).getUri(); + if (uri != null) { + uris.add(uri); + } + } + return uris; + } } |