Sunday, September 2, 2012

Load local and remote image from gallery by Intent

In this article you will learn, how images from gallery can be load into your activity no matter if they are local or stored remotely (i.e. in Picasa).

If you are interested in how to load image from gallery to your activity, you almost certainly know, that you have to use Intent.ACTION_PICK and startActivityForResult. You probably know, that the result intent, which you will obtain by onActivityResult contains Uri of the selected image and you can get the Uri by the getData method. Maybe you have learned, that Uri got by the getData is not Uri of actual file, but only a kind of "database key".

Commonly used (wrong) approach uses ContentResolver query, which is supposed to return cursor, that holds the image file path in the MediaStore.Images.Media.DATA column.

This method will work well for local files. For files stored remotely, i.e. in Picasa gallery this method will work only on some (older) versions of Android. I have learned, that my Android 2.3 on my LG P350 downloads remote images which I select in gallery to Downloads folder, so they become local. But Android 3.2 on my PackardBell tablet does not do any download automatically and therefore above mentioned approach fails and I get Null pointer error when I try to work with remotely stored images.

Following code shows another approach, which seems to work with both local and remote images well. It uses ContentProviderClient and ParcelFileDescriptor. It loads Image to temp file and works with this local copy. Note, thet _path[0] holds Uri got by getData method from the result Intent.
@Override
    protected Drawable doInBackground(Uri... _path) {
      file = null;
      // This is the key line. Content provider client gives us access to
      // file no matter if it is a local or a remote one
      ContentProviderClient client = getContentResolver()
          .acquireContentProviderClient(_path[0]);
      try {
        // Here we save copy of the file to temporary
        ParcelFileDescriptor descriptor = client.openFile(_path[0], "r");
        AutoCloseInputStream is = new AutoCloseInputStream(descriptor);
        file = File.createTempFile("image", ".jpg", getDir(null, MODE_PRIVATE));
        OutputStream outS = new FileOutputStream(file);
        byte[] buf = new byte[1024];
        int len = 0;
        while ((len = is.read(buf)) > 0) {
          outS.write(buf, 0, len);
        }
        is.close();
        outS.close();
Complete code of test activity follows. To make it work, you have to create activity_galery_get.xml which will provide ImageView and Button.
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/previewFrame"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <ImageView
            android:id="@+id/imagePreview"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:contentDescription="Image to share" >
        </ImageView>
        
        <Button
            android:id="@+id/bFromGallery"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="left"
            android:onClick="handleFromGallery"
            android:text="Select image from gallery" />
        
    </FrameLayout>

Here is the complete activity code:
public class GaleryGet extends Activity {
  private final String LOGTAG = getClass().getName();
  private final int REQUEST_IMAGE = 33;
  private ProgressDialog progressDialog;
  private File file = null;
  private ImageView imagePreview;
  private Context mContext;

  /**
   * Usual onCreate - nothing special there
   */
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    mContext=this;
    setContentView(R.layout.activity_galery_get);
    imagePreview = (ImageView) findViewById(R.id.imagePreview);
  }

  /**
   * Button handler. Also very common one. Just create Intent and run it.
   */
  public void handleFromGallery(View _view) {
    Intent i = new Intent(Intent.ACTION_PICK,
        android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
    startActivityForResult(i, REQUEST_IMAGE);
  }

  /**
   * Get result from gallery. Well we extract URI from intent and run
   * ImageDisplayer
   */
  @Override
  public void onActivityResult(int requestCode, int resultCode, Intent _intent) {
    if (resultCode == Activity.RESULT_OK && requestCode == REQUEST_IMAGE) {
      ImageDisplayer displayer = new ImageDisplayer();
      if(_intent.getData()!=null){
      displayer.execute(_intent.getData());
      }
    }
  }

  /**
   * This is it. here we can see, how to process Uri from gallery result
   * in a proper way to get image displayed.
   * 
   */
  private class ImageDisplayer extends AsyncTask<Uri, Drawable,Drawable> {
    @Override
    protected void onPreExecute(){
      progressDialog = ProgressDialog.show(mContext, "Loading", "");
    }
    @Override
    protected Drawable doInBackground(Uri... _path) {
      file = null;
      // This is the key line. Content provider client gives us access to
      // file no matter if it is a local or a remote one
      ContentProviderClient client = getContentResolver()
          .acquireContentProviderClient(_path[0]);
      try {
        // Here we save copy of the file to temporary
        ParcelFileDescriptor descriptor = client.openFile(_path[0], "r");
        AutoCloseInputStream is = new AutoCloseInputStream(descriptor);
        file = File.createTempFile("image", ".jpg", getDir(null, MODE_PRIVATE));
        OutputStream outS = new FileOutputStream(file);
        byte[] buf = new byte[1024];
        int len = 0;
        while ((len = is.read(buf)) > 0) {
          outS.write(buf, 0, len);
        }
        is.close();
        outS.close();
      } catch (RemoteException e1) {
        // TODO Auto-generated catch block
        e1.printStackTrace();
      } catch (FileNotFoundException e1) {
        // TODO Auto-generated catch block
        e1.printStackTrace();
      } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
      }
      // And finally, we display the image
      String filePath = file.getAbsolutePath();
      return Drawable.createFromPath(filePath);
    }

    @Override
    protected void onPostExecute(Drawable _drawable) {
      imagePreview.setImageDrawable(_drawable);
      progressDialog.dismiss();
    }
  }
}
Hope this will work for you.

No comments:

Post a Comment