Merge "Update ReceiveContentDemo to follow the API docs for URI permissions" into sc-dev

This commit is contained in:
Nikita Dubrovsky
2021-05-22 05:06:06 +00:00
committed by Android (Google) Code Review
3 changed files with 53 additions and 32 deletions

View File

@@ -25,6 +25,7 @@ import androidx.appcompat.widget.AppCompatImageView;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.List; import java.util.List;
final class AttachmentsRecyclerViewAdapter extends final class AttachmentsRecyclerViewAdapter extends
@@ -49,6 +50,10 @@ final class AttachmentsRecyclerViewAdapter extends
mAttachments.add(uri); mAttachments.add(uri);
} }
public void addAttachments(Collection<Uri> uris) {
mAttachments.addAll(uris);
}
public void clearAttachments() { public void clearAttachments() {
mAttachments.clear(); mAttachments.clear();
} }

View File

@@ -16,7 +16,6 @@
package com.example.android.receivecontent; package com.example.android.receivecontent;
import android.content.ClipData;
import android.content.ClipDescription; import android.content.ClipDescription;
import android.content.ContentResolver; import android.content.ContentResolver;
import android.content.Context; import android.content.Context;
@@ -37,6 +36,8 @@ import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.List;
/** /**
* Sample {@link OnReceiveContentListener} implementation that accepts all URIs, and delegates * Sample {@link OnReceiveContentListener} implementation that accepts all URIs, and delegates
@@ -65,13 +66,7 @@ final class MyReceiver implements OnReceiveContentListener {
ContentInfo uriContent = split.first; ContentInfo uriContent = split.first;
ContentInfo remaining = split.second; ContentInfo remaining = split.second;
if (uriContent != null) { if (uriContent != null) {
ContentResolver contentResolver = view.getContext().getContentResolver(); receive(view.getContext(), uriContent);
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);
}
} }
// Return anything that we didn't handle ourselves. This preserves the default platform // 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. // 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 * 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 * 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. * 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) { private void receive(@NonNull Context context, @NonNull ContentInfo payload) {
Log.i(Logcat.TAG, "Receiving " + mimeType + ": " + uri); Context applicationContext = context.getApplicationContext();
if (ClipDescription.compareMimeTypes(mimeType, "image/*")) { ContentResolver contentResolver = applicationContext.getContentResolver();
createAttachment(uri, mimeType); ListenableFuture<List<Uri>> addAttachmentsFuture = MyExecutors.bg().submit(() -> {
} else { List<Uri> uris = Utils.collectUris(payload.getClip());
showMessage(view, uri, mimeType); 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/*")) {
* Reads the image at the given URI and writes it to private storage. Then shows the image in // Read the image at the given URI and write it to private storage.
* the UI by passing the URI pointing to the locally stored copy to the recycler view adapter. localUris.add(mAttachmentsRepo.write(uri));
*/ } else {
private void createAttachment(@NonNull Uri uri, @NonNull String mimeType) { showMessage(applicationContext, uri, mimeType);
ListenableFuture<Uri> addAttachmentFuture = MyExecutors.bg().submit(() -> }
mAttachmentsRepo.write(uri) }
); return localUris;
Futures.addCallback(addAttachmentFuture, new FutureCallback<Uri>() { });
Futures.addCallback(addAttachmentsFuture, new FutureCallback<List<Uri>>() {
@Override @Override
public void onSuccess(Uri result) { public void onSuccess(List<Uri> localUris) {
mAttachmentsRecyclerViewAdapter.addAttachment(result); // 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(); mAttachmentsRecyclerViewAdapter.notifyDataSetChanged();
Log.i(Logcat.TAG, "Processed content: " + payload);
} }
@Override @Override
public void onFailure(@NonNull Throwable t) { public void onFailure(@NonNull Throwable t) {
Log.e(Logcat.TAG, Log.e(Logcat.TAG,"Error processing content: " + payload, t);
"Error receiving content: uri=" + uri + ", mimeType" + mimeType, t);
} }
}, MyExecutors.main()); }, 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 * Reads the size of the given content URI and shows a toast with the type of the content and
* its size in bytes. * its size in bytes.
*/ */
private void showMessage(@NonNull View view, @NonNull Uri uri, @NonNull String mimeType) { private void showMessage(@NonNull Context applicationContext,
Context applicationContext = view.getContext().getApplicationContext(); @NonNull Uri uri, @NonNull String mimeType) {
MyExecutors.bg().execute(() -> { MyExecutors.bg().execute(() -> {
ContentResolver contentResolver = applicationContext.getContentResolver(); ContentResolver contentResolver = applicationContext.getContentResolver();
long lengthBytes; long lengthBytes;

View File

@@ -18,6 +18,7 @@ package com.example.android.receivecontent;
import android.content.ClipData; import android.content.ClipData;
import android.content.ClipDescription; import android.content.ClipDescription;
import android.net.Uri;
import android.util.Pair; import android.util.Pair;
import android.view.ContentInfo; import android.view.ContentInfo;
@@ -71,4 +72,15 @@ final class Utils {
} }
return clip; 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;
}
} }