0

I have this activity in which the user can either choose one image from Gallery or just take a picture and (along with other data) upload it to a website.

So far I've encountered 2 different problems:

1) If I try it with a picture from the gallery, I get an IOException with message /external/images/media/2305: open failed: ENOENT (No such file or directory) That happens when it comes to open the file stream.

2) If I try it by taking the picture, it goes ok, but the encoded data string is composed of "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" (really longer, but only A's) and I guess that's not a good sign. This is only a guess since I still cannot properly upload it to the website, but different pictures showing the same data string just smells funny.

The code here

@Override

public void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    switch (requestCode) {
    case TAKE_PICTURE:
        if (resultCode == Activity.RESULT_OK) {
            //Uri selectedImage = imageUri;
            loadImage(imageUri);

    }
    break;
case SELECT_PHOTO:
    if(resultCode == Activity.RESULT_OK){  
        imageUri = data.getData();
        loadImage(imageUri);
    }
}
}

This is how I load the image (either pic taken or from the gallery) onto the ImageView. It works ok.

public void loadImage(Uri selectedImage){
    mActivity.getContentResolver().notifyChange(selectedImage, null);

    ContentResolver cr = mActivity.getContentResolver();
    Bitmap bitmap;
    try {
        bitmap = android.provider.MediaStore.Images.Media
                .getBitmap(cr, selectedImage);

        ivPicture.setImageBitmap(bitmap);
        ivPicture.setVisibility(View.VISIBLE);
        mActivity.croutonInfo(selectedImage.toString());

    } catch (Exception e) {
        mActivity.croutonAlert("Failed to load");
        e("Camera " + e.toString());
    }
}

This is the method I use to mock the data upload. When I get the API it will have an asynctask to deal with the http transfer, so far it only puts the data into a logicless transfer object

public void uploadTapa() throws IOException{
    mActivity.croutonInfo("subiendo tapa ");
    d("uploadTapa new ");
    TapaUploadParametros tup = new TapaUploadParametros();
    d("uploadTapa bar: " + nombreBar);
    tup.setBarNombre(etBarName.getText().toString());
    d("uploadTapa tapa: " + nombreTapa);
    tup.setNombre(etTapaName.getText().toString());
    d("uploadTapa municipio: " + municipio);
    tup.setLocalidad(municipio);
    d("uploadTapa provincia: " + provincia);
    tup.setProvincia(provincia);
    d("uploadTapa tipologiaId: " + tipologiaId);
    tup.setTipo(tipologiaId);
    d("uploadTapa precioId: " + precioId);
    tup.setPrecio(precioId);

    String encodedImage = encodeImgForHTTP(imageUri);
    d("uploadTapa encoded image: " + encodedImage);
    tup.setPic(encodedImage);
    d("uploadTapa direccionBar: " + direccionBar);
    tup.setBarDireccion(direccionBar);
}

And this is the method to encode the image for http transfer. Images from gallery fail just after "before opening stream"

   private String encodeImgForHTTP (Uri imageUri) throws IOException{
        ContentResolver cr = mActivity.getContentResolver();
        d("encodeImgForHTTP before opening stream ");
        FileInputStream fis = new FileInputStream(imageUri.getPath());
        d("encodeImgForHTTP after opening stream ");
        // Get binary bytes for encode
        byte[] imageBytes = new byte[fis.available()];
        d("encodeImgForHTTP after getting byte array ");
        // base 64 encode for text transmission (HTTP)
        d("encodeImgForHTTP pre 64: " + imageBytes);
        String data_string = Base64.encodeToString(imageBytes, Base64.URL_SAFE); 
        d("encodeImgForHTTP before returning the encoded data string " + data_string);
        return data_string;
    }

What am I doing wrong with the gallery images? Why does the encoding of different pictures look the same?

stinepike
  • 54,068
  • 14
  • 92
  • 112
Frank
  • 2,777
  • 5
  • 18
  • 30

2 Answers2

1

I think you should buffer your input stream into smaller byte arrays, and not use the available function as it is an estimate, in your encoding function, to start with.

In order to take a picture you have to determine a path where you would like the image saved and pass that as an extra in the intent, for example:

private void capture(){
    String directoryPath = Environment.getExternalStorageDirectory() + "/" + IMAGE_DIRECTORY + "/";
    String filePath = directoryPath+Long.toHexString(System.currentTimeMillis())+".jpg";
    File directory = new File(directoryPath);
    if (!directory.exists()) {
        directory.mkdirs();
    }
    this.capturePath = filePath; // you will process the image from this path if the capture goes well
    Intent intent = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE);
    intent.putExtra( MediaStore.EXTRA_OUTPUT, Uri.fromFile( new File(filePath) ) );
    startActivityForResult(intent, REQUEST_CAPTURE);                

}
Emil Davtyan
  • 13,808
  • 5
  • 44
  • 66
  • Yeah, I'm more than sure (since I'm completely green at encoding files and such) that there's lot of room for improvement there. I just wanted to achieve it using native resources, since most examples I found used external libraries from apache and the like (and I'm already using quite a few in this app). About the pictures: do I need to save the image to upload it? Isn't valid the temporary uri I get from the camera intent? – Frank Mar 15 '13 at 12:24
  • @Frank I don't recall any temporary URI from the camera intent but I will tell you that there is a lot inconsistencies with this capture behavior between devices. For example here an issue I ran into when trying to implement it: http://stackoverflow.com/questions/6390163/deleting-a-gallery-image-after-camera-intent-photo-taken/8555925#8555925 – Emil Davtyan Mar 15 '13 at 12:30
  • I'm trying this snippet and now it won't respond to my pressing "check" after shooting the pic. The screen remains on the pic taken. It does work if I press on "X" to cancel and go back to my activity... – Frank Mar 18 '13 at 12:03
  • any idea about my question ? https://stackoverflow.com/questions/57598276/how-do-i-pass-a-multi-part-body-parameter-for-rxjava2-androidnetworking-post-req –  Aug 24 '19 at 10:32
0

I guess I finally got it to work. First I used Emil's advice and saved the image. DCIM_PATH is the path to the DCIM folder.

public void takePhoto() {
    String directoryPath = DCIM_PATH;
    d("takePhoto directoryPath: " + directoryPath);
    this.pictureFileName = Long.toHexString(System.currentTimeMillis())+".jpg";
    String filePath = directoryPath + pictureFileName ;
    File directory = new File(directoryPath);
    if (!directory.exists()) { // in case there's no DCIM folder
        directory.mkdirs(); // just create it
    }
    d("takePhoto filePath: " + filePath);
    this.imageUri = Uri.parse(filePath);
    d("takePhoto imageUri: " + filePath);
    Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
            // here's where I tell the intent where to save the file
    intent.putExtra( 
            MediaStore.EXTRA_OUTPUT,Uri.fromFile( new File(filePath) )
            );

    startActivityForResult(intent, TAKE_PICTURE);

}

I had to use two different methods for loading the picture to the imageview. If it's a picture just taken, I use this one:

public void loadImageJustTaken(Uri selectedImage) {
    mActivity.getContentResolver().notifyChange(selectedImage, null);

    Bitmap bitmap = 
            BitmapFactory.decodeFile(imageUri.getPath());

    ivPicture.setImageBitmap(bitmap);
    ivPicture.setVisibility(View.VISIBLE);
}

But to use one from the gallery I have to use the contentResolver

public void loadImage(Uri selectedImage){

    imageUri = selectedImage;
    mActivity.getContentResolver().notifyChange(selectedImage, null);

    ContentResolver cr = mActivity.getContentResolver();
    Bitmap bitmap;
    try {
        bitmap = android.provider.MediaStore.Images.Media
                .getBitmap(cr, imageUri);

        ivPicture.setImageBitmap(bitmap);
        ivPicture.setVisibility(View.VISIBLE);

        mActivity.croutonInfo(imageUri.getPath());

    } catch (Exception e) {
        e("Camera " + e.toString());
    }
}

When I want to upload the image, I have to encode it. This method works as long as you provide it with the right file path

  private String encodeImgForHTTP (Uri imageUri) throws IOException{
    String realPicPath = getPath(imageUri);
    d("encodeImgForHTTP before opening stream " + realPicPath);
    FileInputStream fis = new FileInputStream(realPicPath);
    d("encodeImgForHTTP after opening stream ");
    // Get binary bytes for encode
    byte[] imageBytes = IOUtils.toByteArray(fis);
    d("encodeImgForHTTP after getting byte array ");

    // base 64 encode for text transmission (HTTP)
    //String data_string = Base64.encodeToString(data, Base64.URL_SAFE);
    d("encodeImgForHTTP pre 64: " + imageBytes);
    String data_string = Base64.encodeToString(imageBytes, Base64.URL_SAFE); 
    d("encodeImgForHTTP before returning the encoded data string " + data_string);
    return data_string;
}

And here's how I get the "real path" for the picture:

public String getPath(Uri uri) throws IOException {
    String[] projection = { MediaStore.Images.Media.DATA };
    Cursor cursor = mActivity.
            managedQuery(uri, projection, null, null, null);
    if (cursor == null){ // with pictures just taken, the uri returned by the onActivityResult makes cursor to be null. Following method takes care of that
        uri = saveMediaEntry(imageUri.getPath(), pictureFileName, "");
        d("cursor nulo, segundo cursor con uri " + uri.getPath());
        cursor = mActivity.
                managedQuery(uri, projection, null, null, null);
    }
    int column_index = cursor
            .getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
    cursor.moveToFirst();
    return cursor.getString(column_index);
}

The method saveMediaEntry creates an entry to the device's media database returning its Uri. Using that Uri, the cursor now will point to the picture file we want

  private Uri saveMediaEntry(
        String imagePath,String title,String description) throws IOException {

    ExifInterface exif = new ExifInterface(imagePath);

    ContentValues v = new ContentValues();
    v.put(Images.Media.TITLE, title);
    v.put(Images.Media.DISPLAY_NAME, title);
    v.put(Images.Media.DESCRIPTION, description);
    v.put(Images.Media.DATE_ADDED, System.currentTimeMillis());
    v.put(Images.Media.DATE_TAKEN, exif.getAttribute(ExifInterface.TAG_DATETIME));
    //v.put(Images.Media.DATE_MODIFIED, dateTaken) ;
    v.put(Images.Media.MIME_TYPE, "image/jpeg");
    v.put(Images.Media.ORIENTATION, exif.getAttribute(ExifInterface.TAG_ORIENTATION));
    File f = new File(imagePath) ;
    File parent = f.getParentFile() ;
    String path = parent.toString().toLowerCase() ;
    String name = parent.getName().toLowerCase() ;
    v.put(Images.ImageColumns.BUCKET_ID, path.hashCode());
    v.put(Images.ImageColumns.BUCKET_DISPLAY_NAME, name);
    v.put(Images.Media.SIZE,f.length()) ;
    f = null ;

    v.put(Images.Media.LATITUDE, exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE));
    v.put(Images.Media.LONGITUDE, exif.getAttribute(ExifInterface.TAG_GPS_LONGITUDE));

    v.put("_data",imagePath) ;
    ContentResolver c = mActivity.getContentResolver() ;
    return c.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, v);
}

After all this, pictures get loaded OK, and the Base64.encodeToString returns are different for different pictures :)

Hope it helps someone :)

Frank
  • 2,777
  • 5
  • 18
  • 30
  • any idea about my question? (sort of similar to this one ) : https://stackoverflow.com/questions/57598276/how-do-i-pass-a-multi-part-body-parameter-for-rxjava2-androidnetworking-post-req –  Aug 24 '19 at 10:32