Skip to content

Commit

Permalink
feat: share image directly via share menu item
Browse files Browse the repository at this point in the history
Solves issue murraycu#30. Now added some documentation, removed some generated files and hardcoded strings.

Previously, there were permission problems from trying to share the image to image sharing apps, according to the issue tracker. The following changes were made:
FileProvider class is defined in AndroidManifest, along with the required meta-data and associated files (a string was added to strings.xml for the provider authority). This is apparently required for sharing streams in Android M and above.
Temporary file of the image is written to keep track of exact file location, as well as have a temporary cache of previously shared images. This is done in an AsyncTask that is defined at the bottom of SubjectFragment and executed in the updateShareIntent method.
In devices with Android M or above, explicit permission to write and read images is obtained if not already provided.
The FileProvider class is used to create the URI that will eventually be sent in the intent.
An intent flag is added for purposes of magic (I'm not really sure what it does, but it works).

In the limited use case of trying to share images, this code works. Additional changes might be required to distinguish text from image intents to share, because the code right now will set the share intent to an image type if mUriImageRemote is not null.
  • Loading branch information
avitaker committed Apr 7, 2017
1 parent 6c52efc commit 8d31c99
Show file tree
Hide file tree
Showing 5 changed files with 34 additions and 15 deletions.
7 changes: 0 additions & 7 deletions app/app.iml
Original file line number Diff line number Diff line change
Expand Up @@ -70,13 +70,6 @@
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/rs" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/shaders" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/test/res" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/test/resources" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/test/assets" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/test/aidl" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/test/rs" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/test/shaders" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/assets" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/blame" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/classes" />
Expand Down
7 changes: 7 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -140,11 +140,18 @@
android:resource="@xml/syncadapter" />
</service>

<!--Generates a FileProvider class for the app with the appropriate authority.
This allows the system to securely share files by creating content URI for files rather than file URI,
thus granting temporary file access permissions to sharing apps.
This is required for devices with Android M or above, while it wasn't necessary before. Without a file provider class,
the OS will deny other apps the permission to access GalaxyZoo's files, and sharing locally stored images will be impossible-->
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="@string/authority_fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<!--This meta data and the associated XML file specify that the files will be written to the root folder
of the app's external storage area-->
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
Expand Down
28 changes: 21 additions & 7 deletions app/src/main/java/com/murrayc/galaxyzoo/app/SubjectFragment.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@

package com.murrayc.galaxyzoo.app;

import android.*;
import android.Manifest;
import android.app.Activity;
import android.app.DownloadManager;
import android.content.Context;
Expand Down Expand Up @@ -438,6 +440,11 @@ private void updateShareActionIntent() {
//shareIntent.putExtra(Intent.EXTRA_STREAM, mUriImageStandard);
//shareIntent.setType("image/*");

/**
* if the image URI is not null, a GetImageBitmapAsyncTask is executed for it, and the returned URI is set as
* a stream extra on the intent that will be shared, along with an explicit permission for recieving contexts to
* read the content URI, enabling them to access the generated image.
*/
if (mUriStandardRemote!=null) {
GetImageBitmapAsyncTask getImageBitmapAsyncTask = new GetImageBitmapAsyncTask(){
@Override
Expand All @@ -451,18 +458,20 @@ protected void onPostExecute(Uri uri) {
getImageBitmapAsyncTask.execute(mUriStandardRemote);
}


mShareActionProvider.setShareIntent(shareIntent);
}

/**
*These constants and the verifyStoragePermissions method will gain explicit permission from users to read and write
* files on their devices. This will allow us to save an image, that can then be shared to other apps.
*/
private static final int REQUEST_EXTERNAL_STORAGE = 1;
private static String[] PERMISSIONS_STORAGE = {
"android.permission.WRITE_EXTERNAL_STORAGE",
"android.permission.READ_EXTERNAL_STORAGE"
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_EXTERNAL_STORAGE
};

public static void verifyStoragePermissions(Activity activity) {
int permission = ActivityCompat.checkSelfPermission(activity, "android.permission.WRITE_EXTERNAL_STORAGE");
int permission = ActivityCompat.checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE);

if (permission != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(
Expand All @@ -473,15 +482,20 @@ public static void verifyStoragePermissions(Activity activity) {
}
}

private class GetImageBitmapAsyncTask extends AsyncTask<String,Integer,Uri> {
/**
* This AsyncTask, when executed, takes a String that represents a URI, creates the appropriate Bitmap, saves it to
* the external storage area of the app as a jpg, and returns the content Uri of that file as generated by the FileProvider class.
* Keeping a single file name ensures that future tasks will write over images previously generated by this task instead
* of creating new ones.
*/
private class GetImageBitmapAsyncTask extends AsyncTask<String, Integer, Uri> {
@Override
protected Uri doInBackground(String... params) {
try {
Bitmap image = Picasso.with(getContext()).load(params[0]).get();
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
image.compress(Bitmap.CompressFormat.JPEG, 100, bytes);

//TODO: PROVIDE A MORE DESCRIPTIVE FILENAME HERE IF PLAUSIBLE. Filename will be visible when sharing via certain apps like Gmail etc.
String filename = "galaxy_zoo_image.jpg";

String pathname = Environment.getExternalStorageDirectory() + File.separator + filename;
Expand Down
6 changes: 6 additions & 0 deletions app/src/main/res/xml/provider_paths.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<!--The FileProvider class will draw from the root directory of the app's external storage area (specified by path="."),
and the generated content URI will indicate that this directory is called "external_files" for security reasons.-->
<external-path name="external_files" path="."/>
</paths>

0 comments on commit 8d31c99

Please sign in to comment.