Merge pull request #8143 from TiA4f8R/image-preview-sharesheet

Add image preview of the content shared where possible in Android share sheet (for Android 10+ devices only)
This commit is contained in:
Stypox 2022-05-22 21:45:53 +02:00 committed by GitHub
commit 8c870cd3ca
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 118 additions and 12 deletions

View File

@ -7,6 +7,8 @@ import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import androidx.annotation.Nullable;
import com.squareup.picasso.Cache;
import com.squareup.picasso.LruCache;
import com.squareup.picasso.OkHttp3Downloader;
@ -158,6 +160,11 @@ public final class PicassoHelper {
});
}
@Nullable
public static Bitmap getImageFromCacheIfPresent(final String imageUrl) {
// URLs in the internal cache finish with \n so we need to add \n to image URLs
return picassoCache.get(imageUrl + "\n");
}
public static void loadNotificationIcon(final String url,
final Consumer<Bitmap> bitmapConsumer) {

View File

@ -1,5 +1,7 @@
package org.schabi.newpipe.util.external_communication;
import static org.schabi.newpipe.MainActivity.DEBUG;
import android.content.ActivityNotFoundException;
import android.content.ClipData;
import android.content.ClipboardManager;
@ -7,17 +9,28 @@ import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Build;
import android.text.TextUtils;
import android.util.Log;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.core.content.FileProvider;
import org.schabi.newpipe.BuildConfig;
import org.schabi.newpipe.R;
import org.schabi.newpipe.util.PicassoHelper;
import java.io.File;
import java.io.FileOutputStream;
public final class ShareUtils {
private static final String TAG = ShareUtils.class.getSimpleName();
private ShareUtils() {
}
@ -231,9 +244,11 @@ public final class ShareUtils {
/**
* Open the android share sheet to share a content.
*
* <p>
* For Android 10+ users, a content preview is shown, which includes the title of the shared
* content.
* Support sharing the image of the content needs to done, if possible.
* content and an image preview the content, if its URL is not null or empty and its
* corresponding image is in the image cache.
* </p>
*
* @param context the context to use
* @param title the title of the content
@ -252,13 +267,20 @@ public final class ShareUtils {
shareIntent.putExtra(Intent.EXTRA_SUBJECT, title);
}
/* TODO: add the image of the content to Android share sheet with setClipData after
generating a content URI of this image, then use ClipData.newUri(the content resolver,
null, the content URI) and set the ClipData to the share intent with
shareIntent.setClipData(generated ClipData).
if (!imagePreviewUrl.isEmpty()) {
//shareIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
}*/
// Content preview in the share sheet has been added in Android 10, so it's not needed to
// set a content preview which will be never displayed
// See https://developer.android.com/training/sharing/send#adding-rich-content-previews
// If loading of images has been disabled, don't try to generate a content preview
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q
&& !TextUtils.isEmpty(imagePreviewUrl)
&& PicassoHelper.getShouldLoadImages()) {
final ClipData clipData = generateClipDataForImagePreview(context, imagePreviewUrl);
if (clipData != null) {
shareIntent.setClipData(clipData);
shareIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
}
openAppChooser(context, shareIntent, false);
}
@ -266,11 +288,11 @@ public final class ShareUtils {
/**
* Open the android share sheet to share a content.
*
* For Android 10+ users, a content preview is shown, which includes the title of the shared
* content.
* <p>
* This calls {@link #shareText(Context, String, String, String)} with an empty string for the
* imagePreviewUrl parameter.
* {@code imagePreviewUrl} parameter. This method should be used when the shared content has no
* preview thumbnail.
* </p>
*
* @param context the context to use
* @param title the title of the content
@ -301,4 +323,81 @@ public final class ShareUtils {
clipboardManager.setPrimaryClip(ClipData.newPlainText(null, text));
Toast.makeText(context, R.string.msg_copied, Toast.LENGTH_SHORT).show();
}
/**
* Generate a {@link ClipData} with the image of the content shared, if it's in the app cache.
*
* <p>
* In order not to worry about network issues (timeouts, DNS issues, low connection speed, ...)
* when sharing a content, only images in the {@link com.squareup.picasso.LruCache LruCache}
* used by the Picasso library inside {@link PicassoHelper} are used as preview images. If the
* thumbnail image is not in the cache, no {@link ClipData} will be generated and {@code null}
* will be returned.
* </p>
*
* <p>
* In order to display the image in the content preview of the Android share sheet, an URI of
* the content, accessible and readable by other apps has to be generated, so a new file inside
* the application cache will be generated, named {@code android_share_sheet_image_preview.jpg}
* (if a file under this name already exists, it will be overwritten). The thumbnail will be
* compressed in JPEG format, with a {@code 90} compression level.
* </p>
*
* <p>
* Note that if an exception occurs when generating the {@link ClipData}, {@code null} is
* returned.
* </p>
*
* <p>
* This method will call {@link PicassoHelper#getImageFromCacheIfPresent(String)} to get the
* thumbnail of the content in the {@link com.squareup.picasso.LruCache LruCache} used by
* the Picasso library inside {@link PicassoHelper}.
* </p>
*
* <p>
* Using the result of this method when sharing has only an effect on the system share sheet (if
* OEMs didn't change Android system standard behavior) on Android API 29 and higher.
* </p>
*
* @param context the context to use
* @param thumbnailUrl the URL of the content thumbnail
* @return a {@link ClipData} of the content thumbnail, or {@code null}
*/
@Nullable
private static ClipData generateClipDataForImagePreview(
@NonNull final Context context,
@NonNull final String thumbnailUrl) {
try {
final Bitmap bitmap = PicassoHelper.getImageFromCacheIfPresent(thumbnailUrl);
if (bitmap == null) {
return null;
}
// Save the image in memory to the application's cache because we need a URI to the
// image to generate a ClipData which will show the share sheet, and so an image file
final Context applicationContext = context.getApplicationContext();
final String appFolder = applicationContext.getCacheDir().getAbsolutePath();
final File thumbnailPreviewFile = new File(appFolder
+ "/android_share_sheet_image_preview.jpg");
// Any existing file will be overwritten with FileOutputStream
final FileOutputStream fileOutputStream = new FileOutputStream(thumbnailPreviewFile);
bitmap.compress(Bitmap.CompressFormat.JPEG, 90, fileOutputStream);
fileOutputStream.close();
final ClipData clipData = ClipData.newUri(applicationContext.getContentResolver(), "",
FileProvider.getUriForFile(applicationContext,
BuildConfig.APPLICATION_ID + ".provider",
thumbnailPreviewFile));
if (DEBUG) {
Log.d(TAG, "ClipData successfully generated for Android share sheet: " + clipData);
}
return clipData;
} catch (final Exception e) {
Log.w(TAG, "Error when setting preview image for share sheet", e);
return null;
}
}
}