Commit 0ee91a2b authored by nicolasroard's avatar nicolasroard
Browse files

Implements image sharing

bug:7233986
Change-Id: I8feb94d77facf8dbb8da5fab89b49ed7c224116e
parent 51915a62
......@@ -225,6 +225,18 @@
</intent-filter>
</activity>
<permission android:name="com.android.gallery3d.filtershow.permission.READ"
android:protectionLevel="signature" />
<permission android:name="com.android.gallery3d.filtershow.permission.WRITE"
android:protectionLevel="signature" />
<provider
android:name="com.android.gallery3d.filtershow.provider.SharedImageProvider"
android:authorities="com.android.gallery3d.filtershow.provider.SharedImageProvider"
android:grantUriPermissions="true"
android:readPermission="com.android.gallery3d.filtershow.permission.READ"
android:writePermission="com.android.gallery3d.filtershow.permission.WRITE" />
<activity
android:name="com.android.gallery3d.filtershow.FilterShowActivity"
android:label="@string/title_activity_filter_show"
......
package com.android.gallery3d.filtershow;
import java.io.File;
import java.io.IOException;
import java.util.Vector;
import com.android.gallery3d.filtershow.cache.ImageLoader;
......@@ -10,6 +12,8 @@ import com.android.gallery3d.filtershow.imageshow.ImageShow;
import com.android.gallery3d.filtershow.imageshow.ImageSmallFilter;
import com.android.gallery3d.filtershow.imageshow.ImageStraighten;
import com.android.gallery3d.filtershow.presets.*;
import com.android.gallery3d.filtershow.provider.SharedImageProvider;
import com.android.gallery3d.filtershow.tools.SaveCopyTask;
import com.android.gallery3d.filtershow.ui.ImageCurves;
import com.android.gallery3d.R;
......@@ -18,6 +22,7 @@ import android.os.Bundle;
import android.annotation.TargetApi;
import android.app.ActionBar;
import android.app.Activity;
import android.content.ContentValues;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
......@@ -31,7 +36,6 @@ import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnTouchListener;
import android.widget.AbsoluteLayout;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.Button;
......@@ -40,10 +44,13 @@ import android.widget.FrameLayout.LayoutParams;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.ShareActionProvider;
import android.widget.ShareActionProvider.OnShareTargetSelectedListener;
import android.widget.Toast;
@TargetApi(16)
public class FilterShowActivity extends Activity implements OnItemClickListener {
public class FilterShowActivity extends Activity implements OnItemClickListener,
OnShareTargetSelectedListener {
private ImageLoader mImageLoader = null;
private ImageShow mImageShow = null;
......@@ -84,6 +91,11 @@ public class FilterShowActivity extends Activity implements OnItemClickListener
private Vector<ImageButton> mBottomPanelButtons = new Vector<ImageButton>();
private Vector<ImageButton> mColorsPanelButtons = new Vector<ImageButton>();
private ShareActionProvider mShareActionProvider;
private File mSharedOutputFile = null;
private boolean mSharingImage = false;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
......@@ -210,6 +222,46 @@ public class FilterShowActivity extends Activity implements OnItemClickListener
}
}
public void completeSaveImage(Uri saveUri) {
if (mSharingImage && mSharedOutputFile != null) {
// Image saved, we unblock the content provider
Uri uri = Uri.withAppendedPath(SharedImageProvider.CONTENT_URI,
Uri.encode(mSharedOutputFile.getAbsolutePath()));
ContentValues values = new ContentValues();
values.put(SharedImageProvider.PREPARE, false);
getContentResolver().insert(uri, values);
}
setResult(RESULT_OK, new Intent().setData(saveUri));
finish();
}
@Override
public boolean onShareTargetSelected(ShareActionProvider arg0, Intent arg1) {
// First, let's tell the SharedImageProvider that it will need to wait for the image
Uri uri = Uri.withAppendedPath(SharedImageProvider.CONTENT_URI,
Uri.encode(mSharedOutputFile.getAbsolutePath()));
ContentValues values = new ContentValues();
values.put(SharedImageProvider.PREPARE, true);
getContentResolver().insert(uri, values);
mSharingImage = true;
// Process and save the image in the background.
mImageShow.saveImage(this, mSharedOutputFile);
return true;
}
private Intent getDefaultShareIntent() {
Intent intent = new Intent(Intent.ACTION_SEND);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.setType(SharedImageProvider.MIME_TYPE);
mSharedOutputFile = SaveCopyTask.getNewFile(this, mImageLoader.getUri());
Uri uri = Uri.withAppendedPath(SharedImageProvider.CONTENT_URI,
Uri.encode(mSharedOutputFile.getAbsolutePath()));
intent.putExtra(Intent.EXTRA_STREAM, uri);
return intent;
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.filtershow_activity_menu, menu);
......@@ -225,6 +277,10 @@ public class FilterShowActivity extends Activity implements OnItemClickListener
} else {
showState.setTitle(R.string.show_imagestate_panel);
}
mShareActionProvider = (ShareActionProvider) menu.findItem(R.id.menu_share)
.getActionProvider();
mShareActionProvider.setShareIntent(getDefaultShareIntent());
mShareActionProvider.setOnShareTargetSelectedListener(this);
return true;
}
......@@ -865,12 +921,7 @@ public class FilterShowActivity extends Activity implements OnItemClickListener
toast.setGravity(Gravity.CENTER, 0, 0);
toast.show();
mImageShow.saveImage(this);
}
public void completeSaveImage(Uri saveUri) {
setResult(RESULT_OK, new Intent().setData(saveUri));
finish();
mImageShow.saveImage(this, null);
}
static {
......
......@@ -2,6 +2,7 @@
package com.android.gallery3d.filtershow.cache;
import java.io.Closeable;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
......@@ -13,6 +14,7 @@ import com.android.gallery3d.filtershow.HistoryAdapter;
import com.android.gallery3d.filtershow.imageshow.ImageShow;
import com.android.gallery3d.filtershow.presets.ImagePreset;
import com.android.gallery3d.filtershow.tools.SaveCopyTask;
import com.android.gallery3d.filtershow.tools.ProcessedBitmap;
import com.android.gallery3d.R;
import android.content.Context;
......@@ -59,6 +61,10 @@ public class ImageLoader {
updateBitmaps();
}
public Uri getUri() {
return mUri;
}
private int getOrientation(Uri uri) {
Cursor cursor = null;
try {
......@@ -219,7 +225,8 @@ public class ImageLoader {
mCache.reset(imagePreset);
}
public Uri saveImage(ImagePreset preset, final FilterShowActivity filterShowActivity) {
public Uri saveImage(ImagePreset preset, final FilterShowActivity filterShowActivity,
File destination) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inMutable = true;
......@@ -235,15 +242,15 @@ public class ImageLoader {
// TODO: on <3.x we need a copy of the bitmap (inMutable doesn't
// exist)
mSaveCopy = mFullOriginalBitmap;
preset.apply(mSaveCopy);
new SaveCopyTask(mContext, mUri, new SaveCopyTask.Callback() {
ProcessedBitmap processedBitmap = new ProcessedBitmap(mSaveCopy, preset);
new SaveCopyTask(mContext, mUri, destination, new SaveCopyTask.Callback() {
@Override
public void onComplete(Uri result) {
filterShowActivity.completeSaveImage(result);
}
}).execute(mSaveCopy);
}).execute(processedBitmap);
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
......
......@@ -13,6 +13,8 @@ import com.android.gallery3d.R;
import com.android.gallery3d.R.id;
import com.android.gallery3d.R.layout;
import java.io.File;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
......@@ -288,8 +290,8 @@ public class ImageShow extends View implements SliderListener {
mFilteredImage = bitmap;
}
public void saveImage(FilterShowActivity filterShowActivity) {
mImageLoader.saveImage(getImagePreset(), filterShowActivity);
public void saveImage(FilterShowActivity filterShowActivity, File file) {
mImageLoader.saveImage(getImagePreset(), filterShowActivity, file);
}
public boolean onTouchEvent(MotionEvent event) {
......
package com.android.gallery3d.filtershow.provider;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.net.Uri;
import android.os.ConditionVariable;
import android.os.ParcelFileDescriptor;
import android.provider.BaseColumns;
import android.provider.MediaStore;
import android.provider.OpenableColumns;
import android.util.Log;
import java.io.File;
import java.io.FileNotFoundException;
import java.util.Arrays;
public class SharedImageProvider extends ContentProvider {
private static final String LOGTAG = "SharedImageProvider";
public static final String MIME_TYPE = "image/jpeg";
public static final String AUTHORITY = "com.android.gallery3d.filtershow.provider.SharedImageProvider";
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/image");
public static final String PREPARE = "prepare";
private final String[] mMimeStreamType = {
MIME_TYPE
};
private static ConditionVariable mImageReadyCond = new ConditionVariable(false);
@Override
public int delete(Uri arg0, String arg1, String[] arg2) {
return 0;
}
@Override
public String getType(Uri arg0) {
return MIME_TYPE;
}
@Override
public String[] getStreamTypes(Uri arg0, String mimeTypeFilter) {
return mMimeStreamType;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
if (values.containsKey(PREPARE)) {
if (values.getAsBoolean(PREPARE)) {
mImageReadyCond.close();
} else {
mImageReadyCond.open();
}
}
return null;
}
@Override
public int update(Uri arg0, ContentValues arg1, String arg2, String[] arg3) {
return 0;
}
@Override
public boolean onCreate() {
return true;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
String uriPath = uri.getLastPathSegment();
if (uriPath == null) {
return null;
}
if (projection == null) {
projection = new String[] {
BaseColumns._ID,
MediaStore.MediaColumns.DATA,
OpenableColumns.DISPLAY_NAME,
OpenableColumns.SIZE
};
}
// If we receive a query on display name or size,
// we should block until the image is ready
mImageReadyCond.block();
File path = new File(uriPath);
MatrixCursor cursor = new MatrixCursor(projection);
Object[] columns = new Object[projection.length];
for (int i = 0; i < projection.length; i++) {
if (projection[i].equalsIgnoreCase(BaseColumns._ID)) {
columns[i] = 0;
} else if (projection[i].equalsIgnoreCase(MediaStore.MediaColumns.DATA)) {
columns[i] = uri;
} else if (projection[i].equalsIgnoreCase(OpenableColumns.DISPLAY_NAME)) {
columns[i] = path.getName();
} else if (projection[i].equalsIgnoreCase(OpenableColumns.SIZE)) {
columns[i] = path.length();
}
}
cursor.addRow(columns);
return cursor;
}
public ParcelFileDescriptor openFile(Uri uri, String mode)
throws FileNotFoundException {
String uriPath = uri.getLastPathSegment();
if (uriPath == null) {
return null;
}
// Here we need to block until the image is ready
mImageReadyCond.block();
File path = new File(uriPath);
int imode = 0;
imode |= ParcelFileDescriptor.MODE_READ_ONLY;
return ParcelFileDescriptor.open(path, imode);
}
}
package com.android.gallery3d.filtershow.tools;
import android.graphics.Bitmap;
import com.android.gallery3d.filtershow.presets.ImagePreset;
public class ProcessedBitmap {
private Bitmap mBitmap;
private ImagePreset mPreset;
public ProcessedBitmap(Bitmap bitmap, ImagePreset preset) {
mBitmap = bitmap;
mPreset = preset;
}
public Bitmap apply() {
mPreset.apply(mBitmap);
return mBitmap;
}
}
\ No newline at end of file
......@@ -30,6 +30,8 @@ import android.provider.MediaStore.Images.ImageColumns;
import android.view.Gravity;
import android.widget.Toast;
import com.android.gallery3d.filtershow.presets.ImagePreset;
//import com.android.gallery3d.R;
//import com.android.gallery3d.util.BucketNames;
......@@ -45,46 +47,28 @@ import java.text.SimpleDateFormat;
/**
* Asynchronous task for saving edited photo as a new copy.
*/
public class SaveCopyTask extends AsyncTask<Bitmap, Void, Uri> {
public class SaveCopyTask extends AsyncTask<ProcessedBitmap, Void, Uri> {
public static final String DOWNLOAD = "download";
public static final String DEFAULT_SAVE_DIRECTORY = "Download";
private static final int DEFAULT_COMPRESS_QUALITY = 95;
/**
* Saves the bitmap by given directory, filename, and format; if the
* directory is given null, then saves it under the cache directory.
* Saves the bitmap in the final destination
*/
public File saveBitmap(Bitmap bitmap, File directory, String filename,
CompressFormat format) {
if (directory == null) {
directory = context.getCacheDir();
} else {
// Check if the given directory exists or try to create it.
if (!directory.isDirectory() && !directory.mkdirs()) {
return null;
}
}
File file = null;
public static void saveBitmap(Bitmap bitmap, File destination) {
OutputStream os = null;
try {
filename = (format == CompressFormat.PNG) ? filename + ".png"
: filename + ".jpg";
file = new File(directory, filename);
os = new FileOutputStream(file);
bitmap.compress(format, DEFAULT_COMPRESS_QUALITY, os);
os = new FileOutputStream(destination);
bitmap.compress(CompressFormat.JPEG, DEFAULT_COMPRESS_QUALITY, os);
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
closeStream(os);
}
return file;
}
private void closeStream(Closeable stream) {
private static void closeStream(Closeable stream) {
if (stream != null) {
try {
stream.close();
......@@ -113,62 +97,63 @@ public class SaveCopyTask extends AsyncTask<Bitmap, Void, Uri> {
private final Uri sourceUri;
private final Callback callback;
private final String saveFileName;
private String saveFolderName;
private final File destinationFile;
public SaveCopyTask(Context context, Uri sourceUri, Callback callback) {
public SaveCopyTask(Context context, Uri sourceUri, File destination, Callback callback) {
this.context = context;
this.sourceUri = sourceUri;
this.callback = callback;
if (destination == null) {
this.destinationFile = getNewFile(context, sourceUri);
} else {
this.destinationFile = destination;
}
saveFileName = new SimpleDateFormat(TIME_STAMP_NAME).format(new Date(
System.currentTimeMillis()));
}
public static File getNewFile(Context context, Uri sourceUri) {
File saveDirectory = getSaveDirectory(context, sourceUri);
if ((saveDirectory == null) || !saveDirectory.canWrite()) {
saveDirectory = new File(Environment.getExternalStorageDirectory(),
DOWNLOAD);
}
String filename = new SimpleDateFormat(TIME_STAMP_NAME).format(new Date(
System.currentTimeMillis()));
return new File(saveDirectory, filename + ".JPG");
}
/**
* The task should be executed with one given bitmap to be saved.
*/
@Override
protected Uri doInBackground(Bitmap... params) {
protected Uri doInBackground(ProcessedBitmap... params) {
// TODO: Support larger dimensions for photo saving.
if (params[0] == null) {
return null;
}
// Use the default save directory if the source directory cannot be
// saved.
File saveDirectory = getSaveDirectory();
if ((saveDirectory == null) || !saveDirectory.canWrite()) {
saveDirectory = new File(Environment.getExternalStorageDirectory(),
DOWNLOAD);
saveFolderName = DEFAULT_SAVE_DIRECTORY;
} else {
saveFolderName = saveDirectory.getName();
}
Bitmap bitmap = params[0];
ProcessedBitmap processedBitmap = params[0];
File file = saveBitmap(bitmap, saveDirectory, saveFileName,
Bitmap.CompressFormat.JPEG);
Bitmap bitmap = processedBitmap.apply();
saveBitmap(bitmap, this.destinationFile);
Uri uri = (file != null) ? insertContent(file) : null;
Uri uri = insertContent(context, sourceUri, this.destinationFile, saveFileName);
bitmap.recycle();
return uri;
}
@Override
protected void onPostExecute(Uri result) {
/*
* String message = (result == null) ?
* context.getString(R.string.saving_failure) :
* context.getString(R.string.photo_saved, saveFolderName); Toast toast
* = Toast.makeText(context, message, Toast.LENGTH_SHORT);
* toast.setGravity(Gravity.CENTER, 0, 0); toast.show();
*/
if (callback != null) {
callback.onComplete(result);
}
}
private void querySource(String[] projection,
private static void querySource(Context context, Uri sourceUri, String[] projection,
ContentResolverQueryCallback callback) {
ContentResolver contentResolver = context.getContentResolver();
Cursor cursor = null;
......@@ -187,10 +172,10 @@ public class SaveCopyTask extends AsyncTask<Bitmap, Void, Uri> {
}
}
private File getSaveDirectory() {
private static File getSaveDirectory(Context context, Uri sourceUri) {
final File[] dir = new File[1];
querySource(new String[] {
ImageColumns.DATA
querySource(context, sourceUri, new String[] {
ImageColumns.DATA
},
new ContentResolverQueryCallback() {
......@@ -205,7 +190,7 @@ public class SaveCopyTask extends AsyncTask<Bitmap, Void, Uri> {
/**
* Insert the content (saved file) with proper source photo properties.
*/
private Uri insertContent(File file) {
public static Uri insertContent(Context context, Uri sourceUri, File file, String saveFileName) {
long now = System.currentTimeMillis() / 1000;
final ContentValues values = new ContentValues();
......@@ -223,7 +208,7 @@ public class SaveCopyTask extends AsyncTask<Bitmap, Void, Uri> {
ImageColumns.DATE_TAKEN,
ImageColumns.LATITUDE, ImageColumns.LONGITUDE,
};
querySource(projection, new ContentResolverQueryCallback() {
querySource(context, sourceUri, projection, new ContentResolverQueryCallback() {
@Override
public void onCursorResult(Cursor cursor) {
......@@ -243,4 +228,5 @@ public class SaveCopyTask extends AsyncTask<Bitmap, Void, Uri> {
return context.getContentResolver().insert(
Images.Media.EXTERNAL_CONTENT_URI, values);
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment