Skip to content

Commit

Permalink
Share media from within Media Preview and share QR code image.
Browse files Browse the repository at this point in the history
  • Loading branch information
alan-signal committed Nov 4, 2020
1 parent 5e536c3 commit 2f69a9c
Show file tree
Hide file tree
Showing 12 changed files with 418 additions and 61 deletions.
5 changes: 5 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -673,6 +673,11 @@
android:exported="false"
android:authorities="${applicationId}.part" />

<provider android:name=".providers.BlobContentProvider"
android:authorities="${applicationId}.blob"
android:exported="false"
android:grantUriPermissions="true" />

<provider android:name=".providers.MmsBodyProvider"
android:grantUriPermissions="true"
android:exported="false"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,14 @@

import android.Manifest;
import android.annotation.SuppressLint;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.database.ContentObserver;
import android.database.Cursor;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.view.Menu;
Expand All @@ -37,6 +39,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.core.app.ShareCompat;
import androidx.core.util.Pair;
import androidx.core.view.ViewCompat;
import androidx.fragment.app.Fragment;
Expand All @@ -61,6 +64,7 @@
import org.thoughtcrime.securesms.mediapreview.MediaPreviewViewModel;
import org.thoughtcrime.securesms.mediapreview.MediaRailAdapter;
import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.mms.PartAuthority;
import org.thoughtcrime.securesms.permissions.Permissions;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
Expand Down Expand Up @@ -378,6 +382,27 @@ private void forward() {
}
}

private void share() {
MediaItem mediaItem = getCurrentMediaItem();

if (mediaItem != null) {
Uri publicUri = PartAuthority.getAttachmentPublicUri(mediaItem.uri);
String mimeType = Intent.normalizeMimeType(mediaItem.type);
Intent shareIntent = ShareCompat.IntentBuilder.from(this)
.setStream(publicUri)
.setType(mimeType)
.createChooserIntent()
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

try {
startActivity(shareIntent);
} catch (ActivityNotFoundException e) {
Log.w(TAG, "No activity existed to share the media.", e);
Toast.makeText(this, R.string.MediaPreviewActivity_cant_find_an_app_able_to_share_this_media, Toast.LENGTH_LONG).show();
}
}
}

@SuppressWarnings("CodeBlock2Expr")
@SuppressLint("InlinedApi")
private void saveToDisk() {
Expand Down Expand Up @@ -455,6 +480,9 @@ public boolean onPrepareOptionsMenu(Menu menu) {
menu.findItem(R.id.delete).setVisible(false);
}

// Restricted to API26 because of MemoryFileUtil not supporting lower API levels well
menu.findItem(R.id.media_preview__share).setVisible(Build.VERSION.SDK_INT >= 26);

if (cameFromAllMedia) {
menu.findItem(R.id.media_preview__overview).setVisible(false);
}
Expand All @@ -464,16 +492,17 @@ public boolean onPrepareOptionsMenu(Menu menu) {
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
super.onOptionsItemSelected(item);

switch (item.getItemId()) {
case R.id.media_preview__overview: showOverview(); return true;
case R.id.media_preview__forward: forward(); return true;
case R.id.save: saveToDisk(); return true;
case R.id.delete: deleteMedia(); return true;
case android.R.id.home: finish(); return true;
}
int itemId = item.getItemId();

if (itemId == R.id.media_preview__overview) { showOverview(); return true; }
if (itemId == R.id.media_preview__forward) { forward(); return true; }
if (itemId == R.id.media_preview__share) { share(); return true; }
if (itemId == R.id.save) { saveToDisk(); return true; }
if (itemId == R.id.delete) { deleteMedia(); return true; }
if (itemId == android.R.id.home) { finish(); return true; }

return false;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package org.thoughtcrime.securesms.providers;

import android.content.ContentProvider;
import android.content.Context;
import android.content.pm.ProviderInfo;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.provider.OpenableColumns;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import java.util.ArrayList;

abstract class BaseContentProvider extends ContentProvider {

private static final String[] COLUMNS = {OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE};

/**
* Sanity checks the security like FileProvider does.
*/
@Override
public void attachInfo(@NonNull Context context, @NonNull ProviderInfo info) {
super.attachInfo(context, info);

if (info.exported) {
throw new SecurityException("Provider must not be exported");
}
if (!info.grantUriPermissions) {
throw new SecurityException("Provider must grant uri permissions");
}
}

protected static Cursor createCursor(@Nullable String[] projection, @NonNull String fileName, long fileSize) {
if (projection == null || projection.length == 0) {
projection = COLUMNS;
}

ArrayList<String> cols = new ArrayList<>(projection.length);
ArrayList<Object> values = new ArrayList<>(projection.length);

for (String col : projection) {
if (OpenableColumns.DISPLAY_NAME.equals(col)) {
cols.add(OpenableColumns.DISPLAY_NAME);
values.add(fileName);
} else if (OpenableColumns.SIZE.equals(col)) {
cols.add(OpenableColumns.SIZE);
values.add(fileSize);
}
}

MatrixCursor cursor = new MatrixCursor(cols.toArray(new String[0]), 1);

cursor.addRow(values.toArray(new Object[0]));

return cursor;
}

protected static String createFileNameForMimeType(String mimeType) {
return mimeType.replace('/', '.');
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package org.thoughtcrime.securesms.providers;

import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.os.MemoryFile;
import android.os.ParcelFileDescriptor;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.util.MemoryFileUtil;
import org.thoughtcrime.securesms.util.Util;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

public final class BlobContentProvider extends BaseContentProvider {

private static final String TAG = Log.tag(BlobContentProvider.class);

@Override
public boolean onCreate() {
Log.i(TAG, "onCreate()");
return true;
}

@Override
public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode) throws FileNotFoundException {
Log.i(TAG, "openFile() called: " + uri);

try {
try (InputStream stream = BlobProvider.getInstance().getStream(ApplicationDependencies.getApplication(), uri)) {
Long fileSize = BlobProvider.getFileSize(uri);
if (fileSize == null) {
Log.w(TAG, "No file size available");
throw new FileNotFoundException();
}

return getParcelStreamForStream(stream, Util.toIntExact(fileSize));
}
} catch (IOException e) {
throw new FileNotFoundException();
}
}

private static @NonNull ParcelFileDescriptor getParcelStreamForStream(@NonNull InputStream in, int fileSize) throws IOException {
MemoryFile memoryFile = new MemoryFile(null, fileSize);

try (OutputStream out = memoryFile.getOutputStream()) {
Util.copy(in, out);
}

return MemoryFileUtil.getParcelFileDescriptor(memoryFile);
}

@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
Log.i(TAG, "query() called: " + uri);

if (projection == null || projection.length <= 0) return null;

String mimeType = BlobProvider.getMimeType(uri);
String fileName = BlobProvider.getFileName(uri);
Long fileSize = BlobProvider.getFileSize(uri);

if (fileSize == null) {
Log.w(TAG, "No file size");
return null;
}

if (mimeType == null) {
Log.w(TAG, "No mime type");
return null;
}

if (fileName == null) {
fileName = createFileNameForMimeType(mimeType);
}

return createCursor(projection, fileName, fileSize);
}

@Nullable
@Override
public String getType(@NonNull Uri uri) {
return BlobProvider.getMimeType(uri);
}

@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
return null;
}

@Override
public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
return 0;
}

@Override
public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
return 0;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public class BlobProvider {
private static final String MULTI_SESSION_DIRECTORY = "multi_session_blobs";
private static final String SINGLE_SESSION_DIRECTORY = "single_session_blobs";

public static final String AUTHORITY = BuildConfig.APPLICATION_ID;
public static final String AUTHORITY = BuildConfig.APPLICATION_ID + ".blob";
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/blob");
public static final String PATH = "blob/*/*/*/*/*";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
*/
package org.thoughtcrime.securesms.providers;

import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
Expand All @@ -35,7 +34,7 @@
import java.io.InputStream;
import java.io.OutputStream;

public class MmsBodyProvider extends ContentProvider {
public final class MmsBodyProvider extends BaseContentProvider {
private static final String TAG = MmsBodyProvider.class.getSimpleName();
private static final String CONTENT_AUTHORITY = BuildConfig.APPLICATION_ID + ".mms";
private static final String CONTENT_URI_STRING = "content://" + CONTENT_AUTHORITY + "/mms";
Expand Down

0 comments on commit 2f69a9c

Please sign in to comment.