Using the default camera app instead of creating my own


#1

So I’ve decided to go a different route with this chapter and make the ImageButton start up an activity with the default camera app instead of using our own. To get this working was actually pretty easy.

First, add the code to CrimeFragment to create a file that the image will be saved in:

private File createImageFile() throws IOException {
        // Create an image file name
        String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
        String imageFileName = "JPEG_" + timeStamp + "_";
        File storageDir = Environment.getExternalStoragePublicDirectory(
                Environment.DIRECTORY_PICTURES);
        Log.d(TAG, "Directory: " + storageDir);
        if (!storageDir.exists())
            Log.d(TAG, "Directory does not exist; Creating new directory.");
            storageDir.mkdirs();

        File image = File.createTempFile(
                imageFileName,  /* prefix */
                ".jpg",         /* suffix */
                storageDir      /* directory */
        );

        // Save a file: path for use with ACTION_VIEW intents
        mPhotoPath = image.getAbsolutePath();
        Log.d(TAG, "Path for file: " + mPhotoPath);
        return image;
    }

Next, wire up the ImageButton to check if the device has a default camera activity and start the camera activity if it does:

mPhotoButton = (ImageButton) v.findViewById(R.id.crime_imageButton);
        mPhotoButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent takePictureWithCamera = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
                // Ensure that there's a camera activity to handle the intent
                if (takePictureWithCamera.resolveActivity(getActivity().getPackageManager()) != null) {
                    // Create the File where the photo should go
                    try {
                        photoFile = createImageFile();
                        Log.e(TAG, "After createImageFile(): " + mPhotoPath);
                        if (photoFile.exists() && photoFile.isDirectory()) {
                            Log.e(TAG, "File already exists and is a directory");
                        }
                        Log.d(TAG, photoFile.toString());
                    } catch (IOException e) {
                        // Error occurred while creating the File
                        Log.e(TAG, "Error: " + e + ". in mPhotoButton.onClickListener." + photoFile);
                    }
                    // Continue only if the File was successfully created
                    if (photoFile != null) {
                        takePictureWithCamera.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(photoFile));
                        startActivityForResult(takePictureWithCamera, REQUEST_CAMERA_PHOTO);
                    }

                }

                /*Intent i = new Intent(getActivity(), CrimeCameraActivity.class);
                startActivityForResult(i, REQUEST_PHOTO);*/
            }
        });

Now, receive the result from the camera activity:

if (requestCode == REQUEST_CAMERA_PHOTO) {
            Log.d(TAG, "After camera activity, path for file: " + mPhotoPath);
            if (mPhotoPath != null) {
                Photo p = new Photo(mPhotoPath);
                mCrime.setPhoto(p);
                Log.d(TAG, "File: " + p.getFilename());
                showPhoto();
            } else {
                Toast.makeText(getActivity(), "Error saving photo: " + mPhotoPath, Toast.LENGTH_LONG).show();
            }
        }

Add a new method to our PictureUtils class to handle the file and make changes to getScaledDrawable():

public class PictureUtils {
    private ExifInterface exif;

    public static BitmapDrawable getPictureDrawable(Activity a, String path) {
        // For pictures taken with Camera App
        Matrix matrix = new Matrix();
        try {
            ExifInterface exif = new ExifInterface(path);
            int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 1);
            if (orientation == 3) {
                matrix.postRotate(180);
            } else if (orientation == 6) {
                matrix.postRotate(90);
            } else if (orientation == 8) {
                matrix.postRotate(270);
            }
        } catch (Exception ex) {
            Log.e("ERROR!!!!", "the error: " + ex);
        }

        Bitmap b = BitmapFactory.decodeFile(path);
        Bitmap rotatedBitmap = Bitmap.createBitmap(b, 0, 0, b.getWidth(), b.getHeight(), matrix, true);
        return new BitmapDrawable(a.getResources(), rotatedBitmap);
    }

    /**
     * Get a BitmapDrawable from a local file that is scaled down
     * to fit the current Window size.
     */
    @SuppressWarnings("deprecation")
    public static BitmapDrawable getScaledDrawable(Activity a, String path) {
        Display display = a.getWindowManager().getDefaultDisplay();
        float destWidth = display.getWidth();
        float destHeight = display.getHeight();

        //Bitmap b = BitmapFactory.decodeFile(path);
        //Bitmap rotatedBitmap = Bitmap.createBitmap(b, 0, 0, destWidth, destHeight, matrix, true);
        //return new BitmapDrawable(a.getResources(), rotatedBitmap);

        // Read in the dimensions of the image on disk
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFile(path, options);

        float srcWidth = options.outWidth;
        float srcHeight = options.outHeight;

        int inSampleSize = 1;
        if (srcHeight > destHeight || srcWidth > destWidth) {
            if (srcWidth > srcHeight) {
                inSampleSize = Math.round(srcHeight/destHeight);
            } else {
                inSampleSize = Math.round(srcWidth/destWidth);
            }
        }

        options = new BitmapFactory.Options();
        options.inSampleSize = inSampleSize;

        Bitmap bitmap = BitmapFactory.decodeFile(path, options);

        Matrix matrix = new Matrix();
        try {
            ExifInterface exif = new ExifInterface(path);
            int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 1);
            if (orientation == 3) {
                matrix.postRotate(180);
            } else if (orientation == 6) {
                matrix.postRotate(90);
            } else if (orientation == 8) {
                matrix.postRotate(270);
            }
        } catch (Exception ex) {
            Log.e("ERROR!!!!", "the error: " + ex);
        }

        Bitmap rotated = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
        return new BitmapDrawable(a.getResources(), rotated);
    }

    public static BitmapDrawable getPortraitDrawable(ImageView iView, BitmapDrawable origImage) {
        Matrix m = new Matrix();

        m.postRotate(90);
        Bitmap br = Bitmap.createBitmap(origImage.getBitmap(), 0, 0, origImage.getIntrinsicWidth(), origImage.getIntrinsicHeight());
        origImage = new BitmapDrawable(iView.getResources(), br);

        return origImage;
    }

    public static void cleanImageView(ImageView imageView) {
        if (!(imageView.getDrawable() instanceof BitmapDrawable))
            return;

        // Clean up the view's image for the sake of memory
        BitmapDrawable b = (BitmapDrawable) imageView.getDrawable();
        if (b != null)
            try {
                b.getBitmap().recycle();
            } catch (Exception e) {
                Log.e("error", "Error: " + e);
            }
        // Bitmap.recycle() frees the native storage for your bitmap. This is most of the meat of your bitmap object.

        // If you do not free this memory explicitly by calling recycle(), then the memory will still be cleaned up.
        // However, it will be cleaned up at some future point in a finalizer, not when the bitmap itself is garbage-collected.
        // This means that there is a chance that you will run out of memory before the finalizer is called.
        imageView.setImageDrawable(null);
    }
}

Make changes to the ImageFragment:

public class ImageFragment extends DialogFragment {
    private ImageView mImageView;

    public static final String EXTRA_IMAGE_PATH =
            "com.bignerdranch.android.criminalintent.image_path";
    public static final String EXTRA_IMAGE_ORIENTATION =
            "com.bignerdranch.android.criminalintent.image_orientation";

    public static ImageFragment newInstance(String imagePath) {
        Bundle args = new Bundle();
        args.putSerializable(EXTRA_IMAGE_PATH, imagePath);
        //args.putSerializable(EXTRA_IMAGE_ORIENTATION, orientation);

        ImageFragment fragment = new ImageFragment();
        fragment.setArguments(args);
        fragment.setStyle(DialogFragment.STYLE_NO_TITLE, 0);

        return fragment;
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {
        mImageView = new ImageView(getActivity());
        String path = (String) getArguments().getSerializable(EXTRA_IMAGE_PATH);
        //int o = getArguments().getInt(EXTRA_IMAGE_ORIENTATION);
        BitmapDrawable image = PictureUtils.getScaledDrawable(getActivity(), path);

        /*if (o == CrimeCameraActivity.ORIENTATION_PORTRAIT_NORMAL ||
                o == CrimeCameraActivity.ORIENTATION_PORTRAIT_INVERTED ) {
            image = PictureUtils.getPortraitDrawable(mImageView, image);
        }*/

        mImageView.setImageDrawable(image);

        return mImageView;
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        PictureUtils.cleanImageView(mImageView);
    }

Now, change the showPhoto() method:

private void showPhoto() {
        // (Re)set the image button's image based on our photo
        Photo p = mCrime.getPhoto();
        BitmapDrawable b = null;
        if (p != null) {
            String path = p.getFilename();
            if (path != null)
                b = PictureUtils.getPictureDrawable(getActivity(), path);

            /*int orientation = p.getOrientation();
            if (orientation == CrimeCameraActivity.ORIENTATION_PORTRAIT_INVERTED ||
                    orientation == CrimeCameraActivity.ORIENTATION_PORTRAIT_NORMAL) {
                b = PictureUtils.getPortraitDrawable(mPhotoView, b);
            }*/
        }

        mPhotoView.setImageDrawable(b);
    }

And finally, changes to the ImageView:

mPhotoView = (ImageView) v.findViewById(R.id.crime_imageView);
        mPhotoView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Photo p = mCrime.getPhoto();
                if (p == null)
                    return;

                FragmentManager fm = getActivity().getSupportFragmentManager();
                String path = p.getFilename();
                //int orientation = p.getOrientation();
                ImageFragment.newInstance(path).show(fm, DIALOG_IMAGE);
            }
        });

That is the end for Challenge 1. I’ve opted out of deleting the photo from the gallery because I myself like to have the pictures available if taken from inside an app. So for challenge 2, I simply set that Crime’s photo to be null.

@Override
    public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
        getActivity().getMenuInflater().inflate(R.menu.del_photo_context, menu);
    }

    @Override
    public boolean onContextItemSelected(MenuItem item) {
        switch(item.getItemId()) {
            case R.id.menu_item_delete_photo:
                if (mCrime.getPhoto() != null) {
                    //String filename = mCrime.getPhoto().getFilename();
                    //File f = new File(filename);
                    //f.delete();
                    mCrime.setPhoto(null);
                    mPhotoView.setImageDrawable(null);
                }
                return true;
            default:
                return super.onContextItemSelected(item);
        }
    }

If there are any questions, I’d be glad to try and answer them. I liked the idea of using the built in camera app instead of re-inventing the wheel. I believe making your own camera would only be necessary if creating app like Instagram or SnapChat. But that’s just me.

**** EDIT ****
Sorry guys, I forgot to mention that when using this method you must shrink the bitmap or else you will run into an OutOfMemoryError.

In PictureUtils, add the following method to calculate the InSampleSize:

public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
        // Raw height and width of image
        final int height = options.outHeight;
        final int width = options.outWidth;
        int inSampleSize = 1;

        if (height > reqHeight || width > reqWidth) {

            final int halfHeight = height / 2;
            final int halfWidth = width / 2;

            // Calculate the largest inSampleSize value that is a power of 2 and keeps both
            // height and width larger than the requested height and width.
            while ((halfHeight / inSampleSize) > reqHeight
                    && (halfWidth / inSampleSize) > reqWidth) {
                inSampleSize *= 2;
            }
        }

        return inSampleSize;
    }

Now, edit the getPictureDrawable() to be as such:

public static BitmapDrawable getPictureDrawable(Activity a, String path) {
        // For pictures taken with Camera App
        Matrix matrix = new Matrix();
        try {
            ExifInterface exif = new ExifInterface(path);
            int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 1);
            if (orientation == 3) {
                matrix.postRotate(180);
            } else if (orientation == 6) {
                matrix.postRotate(90);
            } else if (orientation == 8) {
                matrix.postRotate(270);
            }
        } catch (Exception ex) {
            Log.e("ERROR!!!!", "the error: " + ex);
        }

        BitmapFactory.Options opts = new BitmapFactory.Options();
        opts.inJustDecodeBounds = true;
        Bitmap b = BitmapFactory.decodeFile(path, opts);

        opts.inSampleSize = calculateInSampleSize(opts, 100, 100);
        opts.inJustDecodeBounds = false;

        Bitmap bitmap = BitmapFactory.decodeFile(path, opts);


        Bitmap rotatedBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
        return new BitmapDrawable(a.getResources(), rotatedBitmap);
    }