OSDN Git Service

Implements image sharing
authornicolasroard <nicolasroard@google.com>
Mon, 1 Oct 2012 07:25:11 +0000 (00:25 -0700)
committernicolasroard <nicolasroard@google.com>
Wed, 3 Oct 2012 00:27:00 +0000 (17:27 -0700)
bug:7233986
Change-Id: I8feb94d77facf8dbb8da5fab89b49ed7c224116e

AndroidManifest.xml
src/com/android/gallery3d/filtershow/FilterShowActivity.java
src/com/android/gallery3d/filtershow/cache/ImageLoader.java
src/com/android/gallery3d/filtershow/imageshow/ImageShow.java
src/com/android/gallery3d/filtershow/provider/SharedImageProvider.java [new file with mode: 0644]
src/com/android/gallery3d/filtershow/tools/ProcessedBitmap.java [new file with mode: 0644]
src/com/android/gallery3d/filtershow/tools/SaveCopyTask.java

index 3a3ac92..166d75e 100644 (file)
             </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"
index 4570eb0..b50ab94 100644 (file)
@@ -1,6 +1,8 @@
 
 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 {
index 2c8fff9..100a17b 100644 (file)
@@ -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 {
index 14bc0be..2caa2d5 100644 (file)
@@ -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) {
diff --git a/src/com/android/gallery3d/filtershow/provider/SharedImageProvider.java b/src/com/android/gallery3d/filtershow/provider/SharedImageProvider.java
new file mode 100644 (file)
index 0000000..dff1516
--- /dev/null
@@ -0,0 +1,123 @@
+
+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);
+    }
+}
diff --git a/src/com/android/gallery3d/filtershow/tools/ProcessedBitmap.java b/src/com/android/gallery3d/filtershow/tools/ProcessedBitmap.java
new file mode 100644 (file)
index 0000000..2e23b92
--- /dev/null
@@ -0,0 +1,18 @@
+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
index 8ca21e2..49cc33a 100644 (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);
     }
+
 }