package org.wikipedia.util;
import android.content.Context;
import android.content.Intent;
import android.content.pm.LabeledIntent;
import android.content.pm.ResolveInfo;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Build;
import android.os.Parcelable;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.content.FileProvider;
import android.widget.Toast;
import org.wikipedia.BuildConfig;
import org.wikipedia.R;
import org.wikipedia.concurrency.SaneAsyncTask;
import org.wikipedia.page.PageTitle;
import org.wikipedia.util.log.L;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import static org.apache.commons.lang3.StringUtils.defaultString;
public final class ShareUtil {
public static final String APP_PACKAGE_REGEX = "org\\.wikipedia.*";
private static final String FILE_PROVIDER_AUTHORITY = BuildConfig.APPLICATION_ID + ".fileprovider";
private static final String FILE_PREFIX = "file://";
/**
* Share some text and subject (title) as plain text using an activity chooser,
* so that the user can choose the app with which to share the content.
*/
public static void shareText(final Context context, final String subject, final String text) {
Intent shareIntent = new Intent(Intent.ACTION_SEND);
shareIntent.putExtra(Intent.EXTRA_SUBJECT, subject);
shareIntent.putExtra(Intent.EXTRA_TEXT, text);
shareIntent.setType("text/plain");
Intent chooserIntent = createChooserIntent(shareIntent,
context.getString(R.string.share_via), context);
if (chooserIntent == null) {
showUnresolvableIntentMessage(context);
} else {
context.startActivity(chooserIntent);
}
}
public static void shareText(final Context context, final PageTitle title) {
shareText(context, title.getDisplayText(), UriUtil.getUrlWithProvenance(context, title, R.string.prov_share_link));
}
/**
* Share a bitmap image using an activity chooser, so that the user can choose the
* app with which to share the content.
* This is done by saving the image to a temporary file in external storage, then specifying
* that file in the share intent. The name of the temporary file is kept constant, so that
* it's overwritten every time an image is shared from the app, so that it takes up a
* constant amount of space.
*/
public static void shareImage(final Context context, final Bitmap bmp,
final String imageFileName, final String subject,
final String text) {
new SaneAsyncTask<Uri>() {
@Override
public Uri performTask() throws Throwable {
File processedBitmap = processBitmapForSharing(context, bmp, imageFileName);
return getUri(context, processedBitmap);
}
@Override
public void onFinish(Uri result) {
if (result == null) {
displayShareErrorMessage(context);
return;
}
Intent chooserIntent = buildImageShareChooserIntent(context, subject, text, result);
context.startActivity(chooserIntent);
}
@Override
public void onCatch(Throwable caught) {
displayOnCatchMessage(caught, context);
}
}.execute();
}
public static String getFeaturedImageShareSubject(@NonNull Context context, int age) {
return context.getString(R.string.feed_featured_image_share_subject) + " | " + DateUtil.getFeedCardDateString(age);
}
public static Intent buildImageShareChooserIntent(Context context, String subject, String text, Uri uri) {
Intent shareIntent = createImageShareIntent(subject, text, uri);
return Intent.createChooser(shareIntent,
context.getResources().getString(R.string.share_via));
}
private static Uri getUri(Context context, File processedBitmap) {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
? FileProvider.getUriForFile(context, FILE_PROVIDER_AUTHORITY, processedBitmap)
: Uri.parse(FILE_PREFIX + processedBitmap.getAbsolutePath());
}
private static File processBitmapForSharing(final Context context, final Bitmap bmp,
final String imageFileName) throws IOException {
File shareFolder = getClearShareFolder(context);
if (shareFolder == null) {
return null;
}
shareFolder.mkdirs();
ByteArrayOutputStream bytes = FileUtil.compressBmpToJpg(bmp);
return FileUtil.writeToFile(bytes, new File(shareFolder, cleanFileName(imageFileName)));
}
private static Intent createImageShareIntent(String subject, String text, Uri uri) {
return new Intent(Intent.ACTION_SEND)
.putExtra(Intent.EXTRA_SUBJECT, subject)
.putExtra(Intent.EXTRA_TEXT, text)
.putExtra(Intent.EXTRA_STREAM, uri)
.setType("image/jpeg");
}
private static void displayOnCatchMessage(Throwable caught, Context context) {
Toast.makeText(context,
String.format(context.getString(R.string.gallery_share_error),
caught.getLocalizedMessage()), Toast.LENGTH_SHORT).show();
}
private static void displayShareErrorMessage(Context context) {
Toast.makeText(context,
String.format(context.getString(R.string.gallery_share_error),
context.getString(R.string.err_cannot_save_file)),
Toast.LENGTH_SHORT).show();
}
public static void showUnresolvableIntentMessage(Context context) {
Toast.makeText(context, R.string.error_can_not_process_link, Toast.LENGTH_LONG).show();
}
/**
* Cleans up and returns the internal cache subdirectory for share-a-fact images.
*/
public static File getClearShareFolder(Context context) {
try {
File dir = new File(getShareFolder(context), "share");
FileUtil.clearDirectory(dir);
return dir;
} catch (Throwable caught) {
L.e("Caught " + caught.getMessage(), caught);
}
return null;
}
public static File getShareFolder(Context context) {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? context.getCacheDir() : context.getExternalFilesDir(null);
}
private static String cleanFileName(String fileName) {
// Google+ doesn't like file names that have characters %28, %29, %2C
fileName = fileName.replaceAll("%2[0-9A-F]", "_")
.replaceAll("[^0-9a-zA-Z-_\\.]", "_")
.replaceAll("_+", "_");
// ensure file name ends with .jpg
if (!fileName.endsWith(".jpg")) {
fileName = fileName + ".jpg";
}
return fileName;
}
@Nullable
public static Intent createChooserIntent(@NonNull Intent targetIntent,
@Nullable CharSequence chooserTitle,
@NonNull Context context) {
return createChooserIntent(targetIntent, chooserTitle, context, APP_PACKAGE_REGEX);
}
@Nullable
public static Intent createChooserIntent(@NonNull Intent targetIntent,
@Nullable CharSequence chooserTitle,
@NonNull Context context,
String packageNameBlacklistRegex) {
List<Intent> intents = queryIntents(context, targetIntent, packageNameBlacklistRegex);
if (intents.isEmpty()) {
return null;
}
Intent bestIntent = Intent.createChooser(intents.remove(0), chooserTitle);
bestIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, intents.toArray(new Parcelable[intents.size()]));
return bestIntent;
}
public static List<Intent> queryIntents(@NonNull Context context,
@NonNull Intent targetIntent,
String packageNameBlacklistRegex) {
List<Intent> intents = new ArrayList<>();
Intent queryIntent = new Intent(targetIntent);
if (targetIntent.getAction().equals(Intent.ACTION_VIEW)) {
queryIntent.setData(Uri.parse("http://example.com"));
}
for (ResolveInfo intentActivity : queryIntentActivities(queryIntent, context)) {
if (!isIntentActivityBlacklisted(intentActivity, packageNameBlacklistRegex)) {
intents.add(buildLabeledIntent(targetIntent, intentActivity));
}
}
return intents;
}
public static List<ResolveInfo> queryIntentActivities(Intent intent, @NonNull Context context) {
return context.getPackageManager().queryIntentActivities(intent, 0);
}
public static boolean canOpenUrlInApp(@NonNull Context context, @NonNull String url) {
boolean canOpen = false;
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
for (ResolveInfo intentActivity : queryIntentActivities(intent, context)) {
if (getPackageName(intentActivity).matches(APP_PACKAGE_REGEX)) {
canOpen = true;
break;
}
}
return canOpen;
}
private static boolean isIntentActivityBlacklisted(@Nullable ResolveInfo intentActivity,
@Nullable String packageNameBlacklistRegex) {
return intentActivity != null
&& getPackageName(intentActivity).matches(defaultString(packageNameBlacklistRegex));
}
private static LabeledIntent buildLabeledIntent(Intent intent, ResolveInfo intentActivity) {
LabeledIntent labeledIntent = new LabeledIntent(intent, intentActivity.resolvePackageName,
intentActivity.labelRes, intentActivity.getIconResource());
labeledIntent.setPackage(getPackageName(intentActivity));
labeledIntent.setClassName(getPackageName(intentActivity), intentActivity.activityInfo.name);
return labeledIntent;
}
private static String getPackageName(@NonNull ResolveInfo intentActivity) {
return intentActivity.activityInfo.packageName;
}
private ShareUtil() { }
}