diff --git a/samples/ReceiveContentDemo/Android.bp b/samples/ReceiveContentDemo/Android.bp
index 7a42e2d8e..3c7f12e26 100644
--- a/samples/ReceiveContentDemo/Android.bp
+++ b/samples/ReceiveContentDemo/Android.bp
@@ -8,6 +8,9 @@ android_app {
static_libs: [
"guava",
"jsr305",
+ "androidx.appcompat_appcompat",
+ "androidx.recyclerview_recyclerview",
+ "com.google.android.material_material",
],
sdk_version: "current",
dex_preopt: {
diff --git a/samples/ReceiveContentDemo/AndroidManifest.xml b/samples/ReceiveContentDemo/AndroidManifest.xml
index e765a0f9c..6b51ac423 100644
--- a/samples/ReceiveContentDemo/AndroidManifest.xml
+++ b/samples/ReceiveContentDemo/AndroidManifest.xml
@@ -15,21 +15,29 @@
~ limitations under the License.
-->
-
-
-
+
+
-
-
+
+
+
+
+
+
diff --git a/samples/ReceiveContentDemo/res/drawable/container_background.xml b/samples/ReceiveContentDemo/res/drawable/container_background.xml
new file mode 100644
index 000000000..4b4d37291
--- /dev/null
+++ b/samples/ReceiveContentDemo/res/drawable/container_background.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
diff --git a/samples/ReceiveContentDemo/res/drawable/thumbnail_background.xml b/samples/ReceiveContentDemo/res/drawable/thumbnail_background.xml
new file mode 100644
index 000000000..354029afc
--- /dev/null
+++ b/samples/ReceiveContentDemo/res/drawable/thumbnail_background.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
diff --git a/samples/ReceiveContentDemo/res/layout/activity_main.xml b/samples/ReceiveContentDemo/res/layout/activity_main.xml
new file mode 100644
index 000000000..3c6835bf9
--- /dev/null
+++ b/samples/ReceiveContentDemo/res/layout/activity_main.xml
@@ -0,0 +1,70 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/ReceiveContentDemo/res/layout/attachment.xml b/samples/ReceiveContentDemo/res/layout/attachment.xml
new file mode 100644
index 000000000..0d9792712
--- /dev/null
+++ b/samples/ReceiveContentDemo/res/layout/attachment.xml
@@ -0,0 +1,26 @@
+
+
+
+
diff --git a/samples/ReceiveContentDemo/res/layout/demo.xml b/samples/ReceiveContentDemo/res/layout/demo.xml
deleted file mode 100644
index 49a0f356e..000000000
--- a/samples/ReceiveContentDemo/res/layout/demo.xml
+++ /dev/null
@@ -1,46 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
diff --git a/samples/ReceiveContentDemo/res/menu/app_menu.xml b/samples/ReceiveContentDemo/res/menu/app_menu.xml
new file mode 100644
index 000000000..52f9cc678
--- /dev/null
+++ b/samples/ReceiveContentDemo/res/menu/app_menu.xml
@@ -0,0 +1,22 @@
+
+
+
+
diff --git a/samples/ReceiveContentDemo/res/values/colors.xml b/samples/ReceiveContentDemo/res/values/colors.xml
new file mode 100644
index 000000000..376199d65
--- /dev/null
+++ b/samples/ReceiveContentDemo/res/values/colors.xml
@@ -0,0 +1,21 @@
+
+
+
+
+ #5f6368
+ #ffffff
+
diff --git a/samples/ReceiveContentDemo/res/values/strings.xml b/samples/ReceiveContentDemo/res/values/strings.xml
index 57c7a6043..f7d1bfe05 100644
--- a/samples/ReceiveContentDemo/res/values/strings.xml
+++ b/samples/ReceiveContentDemo/res/values/strings.xml
@@ -6,7 +6,7 @@
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
- ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
@@ -16,12 +16,8 @@
-->
- Receive Content Demo
- Default EditText
-
- EditText with a listener that handles images
-
-
- EditText with a listener that handles all content
-
+ Android: Receive Content Demo
+ Clear attachments
+ Try inserting text, images and other content
+ using copy/paste or drag-and-drop
diff --git a/samples/ReceiveContentDemo/res/values/styles.xml b/samples/ReceiveContentDemo/res/values/styles.xml
new file mode 100644
index 000000000..73ed947e4
--- /dev/null
+++ b/samples/ReceiveContentDemo/res/values/styles.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
diff --git a/samples/ReceiveContentDemo/res/xml/file_paths.xml b/samples/ReceiveContentDemo/res/xml/file_paths.xml
new file mode 100644
index 000000000..ffb103e61
--- /dev/null
+++ b/samples/ReceiveContentDemo/res/xml/file_paths.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
diff --git a/samples/ReceiveContentDemo/src/com/example/android/receivecontent/AttachmentsRecyclerViewAdapter.java b/samples/ReceiveContentDemo/src/com/example/android/receivecontent/AttachmentsRecyclerViewAdapter.java
new file mode 100644
index 000000000..cfdf32d6d
--- /dev/null
+++ b/samples/ReceiveContentDemo/src/com/example/android/receivecontent/AttachmentsRecyclerViewAdapter.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.receivecontent;
+
+import android.net.Uri;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.appcompat.widget.AppCompatImageView;
+import androidx.recyclerview.widget.RecyclerView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+final class AttachmentsRecyclerViewAdapter extends
+ RecyclerView.Adapter {
+
+ static final class MyViewHolder extends RecyclerView.ViewHolder {
+ public AppCompatImageView mAttachmentThumbnailView;
+
+ MyViewHolder(AppCompatImageView attachmentThumbnailView) {
+ super(attachmentThumbnailView);
+ mAttachmentThumbnailView = attachmentThumbnailView;
+ }
+ }
+
+ private final List mAttachments;
+
+ AttachmentsRecyclerViewAdapter(List attachments) {
+ mAttachments = new ArrayList<>(attachments);
+ }
+
+ public void addAttachment(Uri uri) {
+ mAttachments.add(uri);
+ }
+
+ public void clearAttachments() {
+ mAttachments.clear();
+ }
+
+ @Override
+ public int getItemCount() {
+ return mAttachments.size();
+ }
+
+ @NonNull
+ @Override
+ public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ AppCompatImageView view = (AppCompatImageView) LayoutInflater.from(parent.getContext())
+ .inflate(R.layout.attachment, parent, false);
+ return new MyViewHolder(view);
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
+ Uri uri = mAttachments.get(position);
+ holder.mAttachmentThumbnailView.setImageURI(uri);
+ holder.mAttachmentThumbnailView.setClipToOutline(true);
+ }
+}
diff --git a/samples/ReceiveContentDemo/src/com/example/android/receivecontent/AttachmentsRepo.java b/samples/ReceiveContentDemo/src/com/example/android/receivecontent/AttachmentsRepo.java
new file mode 100644
index 000000000..228db9351
--- /dev/null
+++ b/samples/ReceiveContentDemo/src/com/example/android/receivecontent/AttachmentsRepo.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.receivecontent;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.net.Uri;
+import android.util.Log;
+import android.webkit.MimeTypeMap;
+
+import androidx.annotation.NonNull;
+import androidx.core.content.FileProvider;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.io.ByteStreams;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.UUID;
+
+/**
+ * Stores attachments as files in the app's private storage directory (see
+ * {@link Context#getDataDir()}, {@link Context#getFilesDir()}, etc).
+ */
+final class AttachmentsRepo {
+
+ // This matches the name declared in AndroidManifest.xml
+ private static final String FILE_PROVIDER_AUTHORITY =
+ "com.example.android.receivecontent.fileprovider";
+
+ private final Context mContext;
+ private final File mAttachmentsDir;
+
+ AttachmentsRepo(@NonNull Context context) {
+ mContext = context;
+ mAttachmentsDir = new File(mContext.getFilesDir(), "attachments");
+ }
+
+ /**
+ * Reads the content at the given URI and writes it to private storage. Then returns a content
+ * URI referencing the newly written file.
+ */
+ @NonNull
+ public Uri write(@NonNull Uri uri) {
+ ContentResolver contentResolver = mContext.getContentResolver();
+ String mimeType = contentResolver.getType(uri);
+ String ext = MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType);
+ try (InputStream is = contentResolver.openInputStream(uri)) {
+ if (is == null) {
+ throw new IllegalArgumentException(String.valueOf(uri));
+ }
+ mAttachmentsDir.mkdirs();
+ String fileName = "a-" + UUID.randomUUID().toString() + "." + ext;
+ File newAttachment = new File(mAttachmentsDir, fileName);
+ try (OutputStream os = new FileOutputStream(newAttachment);) {
+ ByteStreams.copy(is, os);
+ }
+ Log.i(Logcat.TAG,
+ "Wrote file [" + fileName + "]: " + newAttachment.length() + " bytes");
+ return getUriForFile(newAttachment);
+ } catch (IOException e) {
+ throw new IllegalStateException(e);
+ }
+
+ }
+
+ public void deleteAll() {
+ File[] files = mAttachmentsDir.listFiles();
+ if (files == null) {
+ return;
+ }
+ for (File file : files) {
+ file.delete();
+ }
+ }
+
+ @NonNull
+ public ImmutableList getAllUris() {
+ File[] files = mAttachmentsDir.listFiles();
+ if (files == null || files.length == 0) {
+ return ImmutableList.of();
+ }
+ ImmutableList.Builder uris = ImmutableList.builderWithExpectedSize(files.length);
+ for (File file : files) {
+ uris.add(getUriForFile(file));
+ }
+ return uris.build();
+ }
+
+ @NonNull
+ private Uri getUriForFile(@NonNull File file) {
+ return FileProvider.getUriForFile(mContext, FILE_PROVIDER_AUTHORITY, file);
+ }
+}
diff --git a/samples/ReceiveContentDemo/src/com/example/android/receivecontent/Logcat.java b/samples/ReceiveContentDemo/src/com/example/android/receivecontent/Logcat.java
new file mode 100644
index 000000000..2bafa31e7
--- /dev/null
+++ b/samples/ReceiveContentDemo/src/com/example/android/receivecontent/Logcat.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.receivecontent;
+
+final class Logcat {
+ private Logcat() {}
+
+ public static final String TAG = "ReceiveContentDemo";
+}
diff --git a/samples/ReceiveContentDemo/src/com/example/android/receivecontent/MyExecutors.java b/samples/ReceiveContentDemo/src/com/example/android/receivecontent/MyExecutors.java
index e2c73b2f8..9dbef3b3a 100644
--- a/samples/ReceiveContentDemo/src/com/example/android/receivecontent/MyExecutors.java
+++ b/samples/ReceiveContentDemo/src/com/example/android/receivecontent/MyExecutors.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,13 +16,39 @@
package com.example.android.receivecontent;
-import java.util.concurrent.ExecutorService;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import com.google.common.util.concurrent.ListeningScheduledExecutorService;
+import com.google.common.util.concurrent.MoreExecutors;
+
+import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
-public class MyExecutors {
- private static final ExecutorService mBg = Executors.newSingleThreadExecutor();
+final class MyExecutors {
+ private MyExecutors() {}
- public static ExecutorService getBg() {
- return mBg;
+ private static final ListeningScheduledExecutorService BG =
+ MoreExecutors.listeningDecorator(Executors.newSingleThreadScheduledExecutor());
+
+ private static final Handler MAIN_HANDLER = new Handler(Looper.getMainLooper());
+ private static final Executor MAIN_EXECUTOR =
+ runnable -> {
+ if (!MAIN_HANDLER.post(runnable)) {
+ Log.e(Logcat.TAG, "Failed to post runnable on main thread");
+ }
+ };
+
+ @NonNull
+ public static ListeningScheduledExecutorService bg() {
+ return BG;
+ }
+
+ @NonNull
+ public static Executor main() {
+ return MAIN_EXECUTOR;
}
}
diff --git a/samples/ReceiveContentDemo/src/com/example/android/receivecontent/MyListenerAllContent.java b/samples/ReceiveContentDemo/src/com/example/android/receivecontent/MyListenerAllContent.java
deleted file mode 100644
index 3cc7efa24..000000000
--- a/samples/ReceiveContentDemo/src/com/example/android/receivecontent/MyListenerAllContent.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.example.android.receivecontent;
-
-import static com.example.android.receivecontent.ReceiveContentDemoActivity.LOG_TAG;
-import static com.example.android.receivecontent.Utils.matchesAny;
-import static com.example.android.receivecontent.Utils.showMessage;
-
-import android.content.ClipData;
-import android.content.ClipDescription;
-import android.content.ContentResolver;
-import android.net.Uri;
-import android.util.Log;
-import android.view.ContentInfo;
-import android.view.OnReceiveContentListener;
-import android.view.View;
-
-import java.util.ArrayList;
-
-/**
- * Sample implementation that:
- *
- * - Accepts images and mp4 videos.
- *
- Rejects other content URIs.
- *
- Coerces all other content to lower-case, plain text and delegates its insertion to the
- * platform.
- *
- */
-public class MyListenerAllContent implements OnReceiveContentListener {
- static final String[] SUPPORTED_MIME_TYPES = new String[]{"image/*", "video/mp4"};
-
- @Override
- public ContentInfo onReceiveContent(View view, ContentInfo payload) {
- ClipData clip = payload.getClip();
- ClipDescription description = clip.getDescription();
- ArrayList remainingItems = new ArrayList<>();
- for (int i = 0; i < clip.getItemCount(); i++) {
- ClipData.Item item = clip.getItemAt(i);
- Uri uri = item.getUri();
- if (uri != null) {
- receive(view, uri);
- continue;
- }
- CharSequence text = item.coerceToText(view.getContext());
- text = text.toString().toLowerCase();
- remainingItems.add(new ClipData.Item(
- text, item.getHtmlText(), item.getIntent(), item.getUri()));
- }
-
- if (!remainingItems.isEmpty()) {
- Log.i(LOG_TAG, "Delegating " + remainingItems.size() + " item(s) to platform");
- ClipData newClip = new ClipData(description, remainingItems.get(0));
- for (int i = 1; i < remainingItems.size(); i++) {
- newClip.addItem(remainingItems.get(i));
- }
- return new ContentInfo.Builder(payload).setClip(newClip).build();
- }
-
- return null;
- }
-
- private static void receive(View view, Uri contentUri) {
- final String viewClassName = view.getClass().getSimpleName();
- MyExecutors.getBg().submit(() -> {
- ContentResolver contentResolver = view.getContext().getContentResolver();
- String mimeType = contentResolver.getType(contentUri);
- if (!matchesAny(mimeType, SUPPORTED_MIME_TYPES)) {
- showMessage(view, "Content of type " + mimeType + " is not supported");
- return;
- }
- showMessage(view, viewClassName + ": Received " + mimeType + ": " + contentUri);
- });
- }
-}
diff --git a/samples/ReceiveContentDemo/src/com/example/android/receivecontent/MyListenerImages.java b/samples/ReceiveContentDemo/src/com/example/android/receivecontent/MyListenerImages.java
deleted file mode 100644
index b64e9ff4d..000000000
--- a/samples/ReceiveContentDemo/src/com/example/android/receivecontent/MyListenerImages.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.example.android.receivecontent;
-
-import static com.example.android.receivecontent.Utils.matchesAny;
-import static com.example.android.receivecontent.Utils.showMessage;
-
-import android.content.ClipData;
-import android.content.ContentResolver;
-import android.net.Uri;
-import android.util.Pair;
-import android.view.ContentInfo;
-import android.view.OnReceiveContentListener;
-import android.view.View;
-
-/**
- * Sample implementation that accepts images, rejects other URIs, and delegates handling for all
- * non-URI content to the platform.
- */
-public class MyListenerImages implements OnReceiveContentListener {
- static final String[] SUPPORTED_MIME_TYPES = new String[]{"image/*"};
-
- @Override
- public ContentInfo onReceiveContent(View view, ContentInfo payload) {
- Pair split = Utils.partition(payload,
- item -> item.getUri() != null);
- ContentInfo uriContent = split.first;
- ContentInfo remaining = split.second;
- if (uriContent != null) {
- ClipData clip = uriContent.getClip();
- for (int i = 0; i < clip.getItemCount(); i++) {
- receive(view, clip.getItemAt(i).getUri());
- }
- }
- return remaining;
- }
-
- private static void receive(View view, Uri contentUri) {
- MyExecutors.getBg().submit(() -> {
- ContentResolver contentResolver = view.getContext().getContentResolver();
- String mimeType = contentResolver.getType(contentUri);
- if (!matchesAny(mimeType, SUPPORTED_MIME_TYPES)) {
- showMessage(view, "Content of type " + mimeType + " is not supported");
- return;
- }
- showMessage(view, "Received " + mimeType + ": " + contentUri);
- });
- }
-}
diff --git a/samples/ReceiveContentDemo/src/com/example/android/receivecontent/MyReceiver.java b/samples/ReceiveContentDemo/src/com/example/android/receivecontent/MyReceiver.java
new file mode 100644
index 000000000..bb180e6cc
--- /dev/null
+++ b/samples/ReceiveContentDemo/src/com/example/android/receivecontent/MyReceiver.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.receivecontent;
+
+import android.content.ClipData;
+import android.content.ClipDescription;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.res.AssetFileDescriptor;
+import android.net.Uri;
+import android.util.Log;
+import android.util.Pair;
+import android.view.ContentInfo;
+import android.view.OnReceiveContentListener;
+import android.view.View;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.google.common.util.concurrent.FutureCallback;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+
+import java.io.FileNotFoundException;
+
+/**
+ * Sample {@link OnReceiveContentListener} implementation that accepts all URIs, and delegates
+ * handling for all other content to the platform.
+ */
+final class MyReceiver implements OnReceiveContentListener {
+ public static final String[] SUPPORTED_MIME_TYPES = new String[]{"image/*"};
+
+ private final AttachmentsRepo mAttachmentsRepo;
+ private final AttachmentsRecyclerViewAdapter mAttachmentsRecyclerViewAdapter;
+
+ MyReceiver(@NonNull AttachmentsRepo attachmentsRepo,
+ @NonNull AttachmentsRecyclerViewAdapter attachmentsRecyclerViewAdapter) {
+ mAttachmentsRepo = attachmentsRepo;
+ mAttachmentsRecyclerViewAdapter = attachmentsRecyclerViewAdapter;
+ }
+
+ @Nullable
+ @Override
+ public ContentInfo onReceiveContent(@NonNull View view,
+ @NonNull ContentInfo contentInfo) {
+ // Split the incoming content into two groups: content URIs and everything else.
+ // This way we can implement custom handling for URIs and delegate the rest.
+ Pair split = Utils.partition(contentInfo,
+ item -> item.getUri() != null);
+ 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);
+ }
+ }
+ // 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.
+ return remaining;
+ }
+
+ /**
+ * 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.
+ */
+ 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 addAttachmentFuture = MyExecutors.bg().submit(() ->
+ mAttachmentsRepo.write(uri)
+ );
+ Futures.addCallback(addAttachmentFuture, new FutureCallback() {
+ @Override
+ public void onSuccess(Uri result) {
+ mAttachmentsRecyclerViewAdapter.addAttachment(result);
+ mAttachmentsRecyclerViewAdapter.notifyDataSetChanged();
+ }
+ @Override
+ public void onFailure(@NonNull Throwable t) {
+ Log.e(Logcat.TAG,
+ "Error receiving content: uri=" + uri + ", mimeType" + mimeType, t);
+ }
+ }, MyExecutors.main());
+ }
+
+ /**
+ * 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();
+ MyExecutors.bg().execute(() -> {
+ ContentResolver contentResolver = applicationContext.getContentResolver();
+ long lengthBytes;
+ try {
+ AssetFileDescriptor fd = contentResolver.openAssetFileDescriptor(uri, "r");
+ lengthBytes = fd.getLength();
+ } catch (FileNotFoundException e) {
+ Log.e(Logcat.TAG, "Error opening content URI: " + uri, e);
+ return;
+ }
+ String msg = "Received " + mimeType + " (" + lengthBytes + " bytes): " + uri;
+ Log.i(Logcat.TAG, msg);
+ MyExecutors.main().execute(() -> {
+ Toast.makeText(applicationContext, msg, Toast.LENGTH_LONG).show();
+ });
+ });
+ }
+}
diff --git a/samples/ReceiveContentDemo/src/com/example/android/receivecontent/ReceiveContentDemoActivity.java b/samples/ReceiveContentDemo/src/com/example/android/receivecontent/ReceiveContentDemoActivity.java
index 5f6dfb51d..6aa205921 100644
--- a/samples/ReceiveContentDemo/src/com/example/android/receivecontent/ReceiveContentDemoActivity.java
+++ b/samples/ReceiveContentDemo/src/com/example/android/receivecontent/ReceiveContentDemoActivity.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,32 +16,87 @@
package com.example.android.receivecontent;
-import android.app.Activity;
+import android.net.Uri;
import android.os.Bundle;
-import android.view.View;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
import android.widget.EditText;
+import android.widget.LinearLayout;
-public class ReceiveContentDemoActivity extends Activity {
- public static final String LOG_TAG = "ReceiveContentDemo";
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.widget.Toolbar;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.util.concurrent.FutureCallback;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+
+/** Main activity for the demo. */
+public class ReceiveContentDemoActivity extends AppCompatActivity {
+ private AttachmentsRepo mAttachmentsRepo;
+ private AttachmentsRecyclerViewAdapter mAttachmentsRecyclerViewAdapter;
@Override
- public void onCreate(Bundle savedInstanceState) {
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- setContentView(R.layout.demo);
+ setContentView(R.layout.activity_main);
- EditText editTextImagesOnly = findViewById(R.id.edittext_images);
- editTextImagesOnly.setOnReceiveContentListener(
- MyListenerImages.SUPPORTED_MIME_TYPES,
- new MyListenerImages());
+ // Setup the app toolbar.
+ Toolbar toolbar = findViewById(R.id.app_toolbar);
+ setSupportActionBar(toolbar);
- EditText editTextAllTypes = findViewById(R.id.edittext_all_types);
- editTextAllTypes.setOnReceiveContentListener(
- MyListenerAllContent.SUPPORTED_MIME_TYPES,
- new MyListenerAllContent());
+ // Setup the repository and recycler view for attachments.
+ mAttachmentsRepo = new AttachmentsRepo(this);
+ ImmutableList attachments = mAttachmentsRepo.getAllUris();
+ RecyclerView attachmentsRecyclerView = findViewById(R.id.attachments_recycler_view);
+ attachmentsRecyclerView.setHasFixedSize(true);
+ mAttachmentsRecyclerViewAdapter = new AttachmentsRecyclerViewAdapter(attachments);
+ attachmentsRecyclerView.setAdapter(mAttachmentsRecyclerViewAdapter);
- View container = findViewById(R.id.container);
- container.setOnReceiveContentListener(
- MyListenerAllContent.SUPPORTED_MIME_TYPES,
- new MyListenerAllContent());
+ // Setup the listener for receiving content.
+ MyReceiver receiver = new MyReceiver(mAttachmentsRepo, mAttachmentsRecyclerViewAdapter);
+ LinearLayout container = findViewById(R.id.container);
+ container.setOnReceiveContentListener(MyReceiver.SUPPORTED_MIME_TYPES, receiver);
+ EditText textInput = findViewById(R.id.text_input);
+ textInput.setOnReceiveContentListener(MyReceiver.SUPPORTED_MIME_TYPES, receiver);
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(@NonNull Menu menu) {
+ MenuInflater inflater = getMenuInflater();
+ inflater.inflate(R.menu.app_menu, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(@NonNull MenuItem item) {
+ if (item.getItemId() == R.id.action_clear_attachments) {
+ deleteAllAttachments();
+ return true;
+ }
+ return false;
+ }
+
+ private void deleteAllAttachments() {
+ ListenableFuture deleteAllFuture = MyExecutors.bg().submit(() -> {
+ mAttachmentsRepo.deleteAll();
+ return null;
+ });
+ Futures.addCallback(deleteAllFuture, new FutureCallback() {
+ @Override
+ public void onSuccess(@Nullable Void result) {
+ mAttachmentsRecyclerViewAdapter.clearAttachments();
+ mAttachmentsRecyclerViewAdapter.notifyDataSetChanged();
+ }
+ @Override
+ public void onFailure(@NonNull Throwable t) {
+ Log.e(Logcat.TAG, "Error deleting attachments", t);
+ }
+ }, MyExecutors.main());
}
}
diff --git a/samples/ReceiveContentDemo/src/com/example/android/receivecontent/Utils.java b/samples/ReceiveContentDemo/src/com/example/android/receivecontent/Utils.java
index c97ab9a76..5e43f7f60 100644
--- a/samples/ReceiveContentDemo/src/com/example/android/receivecontent/Utils.java
+++ b/samples/ReceiveContentDemo/src/com/example/android/receivecontent/Utils.java
@@ -16,15 +16,10 @@
package com.example.android.receivecontent;
-import static com.example.android.receivecontent.ReceiveContentDemoActivity.LOG_TAG;
-
import android.content.ClipData;
import android.content.ClipDescription;
-import android.util.Log;
import android.util.Pair;
import android.view.ContentInfo;
-import android.view.View;
-import android.widget.Toast;
import java.util.ArrayList;
import java.util.List;
@@ -33,24 +28,8 @@ import java.util.function.Predicate;
final class Utils {
private Utils() {}
- public static boolean matchesAny(String mimeType, String[] targetMimeTypes) {
- for (String targetMimeType : targetMimeTypes) {
- if (ClipDescription.compareMimeTypes(mimeType, targetMimeType)) {
- return true;
- }
- }
- return false;
- }
-
- public static void showMessage(View view, String msg) {
- Log.i(LOG_TAG, msg);
- view.getHandler().post(() ->
- Toast.makeText(view.getContext(), msg, Toast.LENGTH_LONG).show()
- );
- }
-
/**
- * If you use the support library, use {@code androidx.core.view.ContentInfoCompat.partition()}.
+ * If you use Jetpack, use {@code androidx.core.view.ContentInfoCompat.partition()}.
*/
public static Pair partition(ContentInfo payload,
Predicate itemPredicate) {