[Android 10+] Add image preview of the content shared where possible

These previews will be only available for images cached in the cache used by Picasso.
The Bitmap of the content is compressed in JPEG 90 and saved inside the application cache folder under the name android_share_sheet_image_preview.jpg.
The current image will be, of course, always overwritten by the next one and cleared when the application cache is cleared.
This commit is contained in:
TiA4f8R 2022-02-10 10:05:16 +01:00
parent 2dd4f8b04a
commit 761c0ff9ac
No known key found for this signature in database
GPG Key ID: E6D3E7F5949450DD
2 changed files with 117 additions and 9 deletions

View File

@ -24,6 +24,9 @@ import java.util.function.Consumer;
import okhttp3.OkHttpClient;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
public final class PicassoHelper {
public static final String PLAYER_THUMBNAIL_TAG = "PICASSO_PLAYER_THUMBNAIL_TAG";
private static final String PLAYER_THUMBNAIL_TRANSFORMATION_KEY
@ -158,6 +161,10 @@ public final class PicassoHelper {
});
}
@Nullable
public static Bitmap getImageFromCacheIfPresent(@NonNull final String imageUrl) {
return picassoCache.get(imageUrl);
}
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() {
}
@ -252,13 +265,16 @@ 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()) {
shareIntent.setClipData(generateClipDataForImagePreview(context, imagePreviewUrl));
shareIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
openAppChooser(context, shareIntent, false);
}
@ -266,11 +282,16 @@ 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.
* 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>
*
* <p>
* This calls {@link #shareText(Context, String, String, String)} with an empty string for the
* imagePreviewUrl parameter.
* {@code imagePreviewUrl} parameter.
* </p>
*
* @param context the context to use
* @param title the title of the content
@ -301,4 +322,84 @@ 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 to not manage 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 yet loaded, no {@link ClipData} will be generated and {@code null}
* will be returned in this case.
* </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 100} 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>
* This method 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 {
// URLs in the internal cache finish with \n so we need to add \n to image URLs
final Bitmap bitmap = PicassoHelper.getImageFromCacheIfPresent(thumbnailUrl + "\n");
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;
}
}