-
Notifications
You must be signed in to change notification settings - Fork 6.4k
Accessing the Camera and Stored Media
This guide covers how to work with the camera and how to access media stored on the phone.
The camera implementation depends on the level of customization required:
- The easy way - launch the camera with an intent, designating a file path, and handle the onActivityResult.
- The hard way - use the Camera API to embed the camera preview within your app, adding your own custom controls.
You must configure a FileProvider
as show in this section. The example below uses com.codepath.fileprovider
and should match the authorities
XML tag specified.
If you see a "INSTALL_FAILED_CONFLICTING_PROVIDER" error when attempting to run the app, change this to something unique, such as com.codepath.fileprovider.YOUR_APP_NAME_HERE
, and also update the value in your XML tag to match.
In your AndroidManifest.xml, add the following queries
block:
<manifest ...>
<queries>
<!-- Camera -->
<intent>
<action android:name="android.media.action.IMAGE_CAPTURE" />
</intent>
</queries>
</manifest>
Easy way works in most cases, using the intent to launch the camera:
public final String APP_TAG = "MyCustomApp";
public final static int CAPTURE_IMAGE_ACTIVITY_REQUEST_CODE = 1034;
public String photoFileName = "photo.jpg";
File photoFile;
public void onLaunchCamera(View view) {
// create Intent to take a picture and return control to the calling application
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
// Create a File reference for future access
photoFile = getPhotoFileUri(photoFileName);
// wrap File object into a content provider
// required for API >= 24
// See https://guides.codepath.com/android/Sharing-Content-with-Intents#sharing-files-with-api-24-or-higher
Uri fileProvider = FileProvider.getUriForFile(MyActivity.this, "com.codepath.fileprovider", photoFile);
intent.putExtra(MediaStore.EXTRA_OUTPUT, fileProvider);
// If you call startActivityForResult() using an intent that no app can handle, your app will crash.
// So as long as the result is not null, it's safe to use the intent.
if (intent.resolveActivity(getPackageManager()) != null) {
// Start the image capture intent to take photo
startActivityForResult(intent, CAPTURE_IMAGE_ACTIVITY_REQUEST_CODE);
}
}
val APP_TAG = "MyCustomApp"
val CAPTURE_IMAGE_ACTIVITY_REQUEST_CODE = 1034
val photoFileName = "photo.jpg"
var photoFile: File? = null
fun onLaunchCamera() {
// create Intent to take a picture and return control to the calling application
val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
// Create a File reference for future access
photoFile = getPhotoFileUri(photoFileName)
// wrap File object into a content provider
// required for API >= 24
// See https://guides.codepath.com/android/Sharing-Content-with-Intents#sharing-files-with-api-24-or-higher
if (photoFile != null) {
val fileProvider: Uri =
FileProvider.getUriForFile(this, "com.codepath.fileprovider", photoFile!!)
intent.putExtra(MediaStore.EXTRA_OUTPUT, fileProvider)
// If you call startActivityForResult() using an intent that no app can handle, your app will crash.
// So as long as the result is not null, it's safe to use the intent.
// If you call startActivityForResult() using an intent that no app can handle, your app will crash.
// So as long as the result is not null, it's safe to use the intent.
if (intent.resolveActivity(packageManager) != null) {
// Start the image capture intent to take photo
startActivityForResult(intent, CAPTURE_IMAGE_ACTIVITY_REQUEST_CODE)
}
}
}
We need to define the getPhotoFileUri()
function:
// Returns the File for a photo stored on disk given the fileName
public File getPhotoFileUri(String fileName) {
// Get safe storage directory for photos
// Use `getExternalFilesDir` on Context to access package-specific directories.
// This way, we don't need to request external read/write runtime permissions.
File mediaStorageDir = new File(getExternalFilesDir(Environment.DIRECTORY_PICTURES), APP_TAG);
// Create the storage directory if it does not exist
if (!mediaStorageDir.exists() && !mediaStorageDir.mkdirs()){
Log.d(APP_TAG, "failed to create directory");
}
// Return the file target for the photo based on filename
File file = new File(mediaStorageDir.getPath() + File.separator + fileName);
return file;
}
// Returns the File for a photo stored on disk given the fileName
fun getPhotoFileUri(fileName: String): File {
// Get safe storage directory for photos
// Use `getExternalFilesDir` on Context to access package-specific directories.
// This way, we don't need to request external read/write runtime permissions.
val mediaStorageDir =
File(getExternalFilesDir(Environment.DIRECTORY_PICTURES), APP_TAG)
// Create the storage directory if it does not exist
if (!mediaStorageDir.exists() && !mediaStorageDir.mkdirs()) {
Log.d(APP_TAG, "failed to create directory")
}
// Return the file target for the photo based on filename
return File(mediaStorageDir.path + File.separator + fileName)
}
When the camera app finishes, the registerForActivityResult()
method will be called:
ActivityResultLauncher<Intent> cameraResultLauncher;
//Within the onCreate method
cameraResultLauncher = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
result -> {
if (result.getResultCode() == RESULT_OK) {
// by this point we have the camera photo on disk
Bitmap takenImage = BitmapFactory.decodeFile(photoFile.getAbsolutePath());
// RESIZE BITMAP, see section below
// Load the taken image into a preview
ImageView ivPreview = ivPostImg;
ivPreview.setImageBitmap(takenImage);
} else { // Result was a failure
Toast.makeText(this, "Error taking picture", Toast.LENGTH_SHORT).show();
}
}
);
var cameraResultLauncher: ActivityResultLauncher<Intent>? = null
//Within the onCreate method
cameraResultLauncher = registerForActivityResult(StartActivityForResult()) {
result: ActivityResult ->
if (result.resultCode == RESULT_OK) {
// by this point we have the camera photo on disk
val takenImage = BitmapFactory.decodeFile(photoFile!!.absolutePath)
// RESIZE BITMAP, see section below
// Load the taken image into a preview
val ivPreview = ivPostImage
ivPreview!!.setImageBitmap(takenImage)
} else { // Result was a failure
Toast.makeText(this, "Error taking picture", Toast.LENGTH_SHORT).show()
}
}
Check out the official Photo Basics guide for more details.
In certain cases, when loading a bitmap with BitmapFactory.decodeFile(file)
decoding the Bitmap in memory may actually cause a crash with a OutOfMemoryError: Failed to allocate
error. Check out the Loading Bitmaps Efficiently guide and this stackoverflow post for an overview of the solutions.
Photos taken with the Camera intent are often quite large and take a very long time to load from disk. After taking a photo, you should strongly consider resizing the Bitmap to a more manageable size and then storing that smaller bitmap to disk. We can then use that resized bitmap before displaying in an ImageView
.
Resizing a large bitmap and writing to disk can be done with:
// See code above
Uri takenPhotoUri = Uri.fromFile(getPhotoFileUri(photoFileName));
// by this point we have the camera photo on disk
Bitmap rawTakenImage = BitmapFactory.decodeFile(takenPhotoUri.getPath());
// See BitmapScaler.java: https://gist.github.com/nesquena/3885707fd3773c09f1bb
Bitmap resizedBitmap = BitmapScaler.scaleToFitWidth(rawTakenImage, SOME_WIDTH);
Then we can write that smaller bitmap back to disk with:
// Configure byte output stream
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
// Compress the image further
resizedBitmap.compress(Bitmap.CompressFormat.JPEG, 40, bytes);
// Create a new file for the resized bitmap (`getPhotoFileUri` defined above)
File resizedFile = getPhotoFileUri(photoFileName + "_resized");
resizedFile.createNewFile();
FileOutputStream fos = new FileOutputStream(resizedFile);
// Write the bytes of the bitmap to file
fos.write(bytes.toByteArray());
fos.close();
Now, we can store the path to that resized image and load that from disk instead for much faster load times.
When using the Camera intent to capture a photo, the picture is always taken in the orientation the camera is built into the device. To get your image rotated correctly you'll have to read the orientation information that is stored into the picture (EXIF meta data) and perform the following transformation using the ExifInterface Support Library:
public Bitmap rotateBitmapOrientation(String photoFilePath) {
// Create and configure BitmapFactory
BitmapFactory.Options bounds = new BitmapFactory.Options();
bounds.inJustDecodeBounds = true;
BitmapFactory.decodeFile(photoFilePath, bounds);
BitmapFactory.Options opts = new BitmapFactory.Options();
Bitmap bm = BitmapFactory.decodeFile(photoFilePath, opts);
// Read EXIF Data
ExifInterface exif = null;
try {
exif = new ExifInterface(photoFilePath);
} catch (IOException e) {
e.printStackTrace();
}
String orientString = exif.getAttribute(ExifInterface.TAG_ORIENTATION);
int orientation = orientString != null ? Integer.parseInt(orientString) : ExifInterface.ORIENTATION_NORMAL;
int rotationAngle = 0;
if (orientation == ExifInterface.ORIENTATION_ROTATE_90) rotationAngle = 90;
if (orientation == ExifInterface.ORIENTATION_ROTATE_180) rotationAngle = 180;
if (orientation == ExifInterface.ORIENTATION_ROTATE_270) rotationAngle = 270;
// Rotate Bitmap
Matrix matrix = new Matrix();
matrix.setRotate(rotationAngle, (float) bm.getWidth() / 2, (float) bm.getHeight() / 2);
Bitmap rotatedBitmap = Bitmap.createBitmap(bm, 0, 0, bounds.outWidth, bounds.outHeight, matrix, true);
// Return result
return rotatedBitmap;
}
See this guide for the source for this answer. Be aware that on certain devices even the EXIF data isn't set properly, in which case you should checkout this workaround for a fix. You can read more here about the ExifInterface Support Library.
If you need to lookup the image type, there is the guessContentTypeFromStream()
in the Java library that allows you to get back the mime type (i.e. image/jpeg
). It will read the first 16 bytes to determine the type of file. In order to use this API call, you must pass in a BufferedInputStream()
which supports the mark()
and reset()
method calls required for the guessContentTypeFromStream()
to work.
// need BufferedInputStream() to satisfy the `markSupported()` condition described in
// http://hg.openjdk.java.net/jdk9/dev/jdk/file/3f00e1b74464/src/java.base/share/classes/java/net/URLConnection.java#l1447
BufferedInputStream bis = BufferedInputStream(FileInputStream(file));
String contentType = URLConnection.guessContentTypeFromStream(bis);
For applying filters to your captured images, check out the following libraries:
- CameraFilter - Realtime camera filters. Process frames by OpenGL shaders.
- photofilter - Apply filters to images after they are captured.
If you sure to enable access to the external storage to save to the public image, you must add this permission to your AndroidManifest.xml
file:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
...>
<!- ... -->
<!--- requesting the WRITE_EXTERNAL_STORAGE permission will also include READ_EXTERNAL_STORAGE permission -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<!- ... -->
</manifest>
Note: The permissions model has changed starting in Marshmallow. If your targetSdkVersion
>= 23
and you are running on a Marshmallow (or later) device, you may need to enable runtime permissions. You should also read more about the runtime permissions changes.
Instead of using the capture intent to capture photos "the easy way", a custom camera can be used within your app directly leveraging the Camera2 API. This custom camera is much more complicated to implement but sample code can be found here and this CameraView from Google aims to help Android developers easily integrate Camera features. There is also a Google video overview of Camera2 which explains how Camera2
works.
There are a number of Camera2
tutorials you can review:
- Android Lollipop's New Camera
- Android Camera Tutorial.'
- Android Camera2 Tutorial
- Android Camera2 Video Tutorial
- Camera2 Explained
There are also a number of third-party libraries available to make custom cameras easier:
- CameraKit
- EasyCamera (Camera 1 API)
- SquareCamera (Camera 1 API)
- Many other libraries
Leveraging Camera2
or the libraries above, apps can develop a camera that functions in anyway required including custom overlays for depositing checks, taking pictures with a particular form factor, or scanning custom barcodes.
Similar to the camera, the media picker implementation depends on the level of customization required:
- The easy way - launch the Gallery with an intent, and get the media URI in onActivityResult.
- The hard way - fetch thumbnail and full-size URIs from the MediaStore ContentProvider.
Make sure to enable access to the external storage first before using the camera (Note: The permissions model has changed starting in Marshmallow. If your targetSdkVersion
>= 23
and you are running on a Marshmallow (or later) device, you may need to enable runtime permissions. You should also read more about the runtime permissions changes):
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
...>
<!- ... -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<!- ... -->
</manifest>
Easy way is to use an intent to launch the gallery:
// PICK_PHOTO_CODE is a constant integer
public final static int PICK_PHOTO_CODE = 1046;
// Trigger gallery selection for a photo
public void onPickPhoto(View view) {
// Create intent for picking a photo from the gallery
Intent intent = new Intent(Intent.ACTION_PICK,
MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
// If you call startActivityForResult() using an intent that no app can handle, your app will crash.
// So as long as the result is not null, it's safe to use the intent.
if (intent.resolveActivity(getPackageManager()) != null) {
// Bring up gallery to select a photo
startActivityForResult(intent, PICK_PHOTO_CODE);
}
}
public Bitmap loadFromUri(Uri photoUri) {
Bitmap image = null;
try {
// check version of Android on device
if(Build.VERSION.SDK_INT > 27){
// on newer versions of Android, use the new decodeBitmap method
ImageDecoder.Source source = ImageDecoder.createSource(this.getContentResolver(), photoUri);
image = ImageDecoder.decodeBitmap(source);
} else {
// support older versions of Android by using getBitmap
image = MediaStore.Images.Media.getBitmap(this.getContentResolver(), photoUri);
}
} catch (IOException e) {
e.printStackTrace();
}
return image;
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if ((data != null) && requestCode == PICK_PHOTO_CODE) {
Uri photoUri = data.getData();
// Load the image located at photoUri into selectedImage
Bitmap selectedImage = loadFromUri(photoUri);
// Load the selected image into a preview
ImageView ivPreview = (ImageView) findViewById(R.id.ivPreview);
ivPreview.setImageBitmap(selectedImage);
}
}
Check out this Stack Overflow post for an alternate approach using mimetypes to restrict content user can select.
First, from the above example, we can add the Intent.EXTRA_ALLOW_MULTIPLE
flag to the intent:
Intent intent = new Intent(Intent.ACTION_PICK);
intent.setType("image/*");
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
startActivityForResult(Intent.createChooser(intent, "Select Picture"), PICK_PHOTO_CODE);
and then inside of onActivityResult
, we can access all the photos selected with:
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (data.getClipData() != null) {
ClipData mClipData = data.getClipData();
mArrayUri = new ArrayList<Uri>();
mBitmapsSelected = new ArrayList<Bitmap>();
for (int i = 0; i < mClipData.getItemCount(); i++) {
ClipData.Item item = mClipData.getItemAt(i);
Uri uri = item.getUri();
mArrayUri.add(uri);
// Use the loadFromUri method from above
Bitmap bitmap = loadFromUri(photoUri);
mBitmapsSelected.add(bitmap);
}
}
}
Note: that you may need to load the selected bitmaps efficiently or resize them if they are large images to avoid encountering OutOfMemoryError
exceptions.
Alternatively, we can use a custom gallery selector that is implemented inside of our application to take full control over the gallery picking user experience. Check out this custom gallery source code gist or older libraries wrapping this up for reference. You can also take a look at older tutorials on custom galleries.
For allowing users to pick files from their system or online services, check out these helpful filepicker libraries:
These allow users to pick files beyond just images and are easy to drop into any app.
Created by CodePath with much help from the community. Contributed content licensed under cc-wiki with attribution required. You are free to remix and reuse, as long as you attribute and use a similar license.
Finding these guides helpful?
We need help from the broader community to improve these guides, add new topics and keep the topics up-to-date. See our contribution guidelines here and our topic issues list for great ways to help out.
Check these same guides through our standalone viewer for a better browsing experience and an improved search. Follow us on twitter @codepath for access to more useful Android development resources.