My Experiments with Android : Camera (Part 2/X : Camera Intent API)

pic credits.
As I discussed in my previous Article, the Camera Intent API (Am not sure its the actual terminology, but well.) is a simple and light way of achieving our original aim of receiving a picture captured via camera.

In a nutshell, our app will not have any camera implementation of our own, we would be simply delegating our requirement to the system camera app. On a button click, the system app would open, take a picture and send it to our app.

why is it good? Well:
  1.  It provides a lot of small features  like retaking a picture , toggling front/back/other cameras, toggling flashlight /hdr ,etc . basically it provides access to all features from the default camera app
  2. Since default camera app is being used, we are saved from implementing these features manually and in a Thread safe/ Leak proof manner.
  3. We are able to receive a picture directly with minimum permissions and checks
  4. It even provides us with a way to  either receive a thumbnail* or actual image* {covered later in this article} this is a significant feature based on your app usage.
Why is it bad ? Well :
  1.  We are using system camera and that is that. Its like a sandbox model: it will handle all the process of taking picture itself and would send the result to us at the end. Our app is in no control of the live preview* or other image data.
  2. something i forgot
  3. something i forgot
So let's dive into it. Let's assume that our app consist of a Button , on the click of which the camera should open and user should be able to click a picture. when user presses send, our app is able to recieve that image inside an ImageView. The ui would look something like below, and its java code is here




Getting the captured picture thumbnail.

This would be rather an easy task:
1. We put an optional feature request in your manifest(This will prevent our app from being installed on a device that lacks a camera.)
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="z.y.x.camera">
<uses-feature
android:name="android.hardware.camera.any"
android:required="true" />
...
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher" ...>
...
</application>
</manifest>
view raw manifest.xml hosted with ❤ by GitHub

2. in our main activity , we call the startActivityForResult() with an Intent having action as ACTION_IMAGE_CAPTURE and a request code.
3. we receive image as bitmap in onActivityResult()
public class MainActivity extends AppCompatActivity {
private static final int RQ_CAPTURE_IMG_CODE_THUMB = 100;
ImageView ivTemp;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ivTemp = findViewById(R.id.iv_tmpcheck);
Button btCallCameraThumbnail = findViewById(R.id.bt_call_camera_thumbnail);
btCallCameraThumbnail.setOnClickListener(v -> callCameraForThumbnail());
}
private void callCameraForThumbnail() {
Intent i = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
if (i.resolveActivity(getPackageManager()) != null) {
startActivityForResult(i, RQ_CAPTURE_IMG_CODE_THUMB);
}
// ^--Ensures that there's a camera activity to handle the intent
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == RQ_CAPTURE_IMG_CODE_THUMB && resultCode == RESULT_OK) {
if (data != null && data.getExtras() != null) {
Bitmap imageBitmap = (Bitmap) data.getExtras().get("data");
onBitmapRecieved(imageBitmap);
}
}
}
private void onBitmapRecieved(Bitmap imageBitmap) {
ivTemp.setImageBitmap(imageBitmap);
}
}

The image we receive in onActivity result is actually a very reduced version(thumbnail) of the actual picture. This could be used as a gallery icon or in a small profile pic view, but not elsewhere. To be able to put the actual image in a bigger preview pane or something like that, we have to perform some more steps.



Getting the original, full-HD captured picture.

  • We receive a reduced , small size size copy of our captured image because intent can carry only upto a specific size of data when passed between different apps and activities. The image captured by our camera can be as large as a 6mb bitmap.  Thus we have to make the camera save it in a file so we can access it in our app via its Uri. (Any big file or data is accessed via uri in android) . This would also mean adding read/write permissions in manifest and java code(runtime permission checks).
  • But with the usage of uri comes its own set of problems for android 23+ : The file://uri are banned and causes some exceptions (read more here .) So we have to implement a File Provider in manifest, R.asset.xml and java code.
  • With a file provider, we are pretty much done. Our button click would create a temporary file and pass its uri to system cam, the user would capture the image  via system cam and system will write all the bitmap to this temporary file. and we could simply access and use our now temporary-turned to-image file via its uri
  • But this image could still cause problems for our ui. these images are of very high resolution, and they should be used with a scaling algorithm before p setting it in ui. Most of the time, this image would also be somehow received as rotated by some angle . The rotating algorithm could fix that. I found a stack overflow solution having one such algorithm, but i would definably recommend picasso or glide library for such tasks
  • Thus our code becomes something like this:

public class MainActivity extends AppCompatActivity {
private static final int RQ_CAPTURE_IMG_CODE_HDPIC = 101;
private static final String AUTHORITY = BuildConfig.APPLICATION_ID + ".provider";
private static final String TAG = "$1477$";
Uri tmpPhotoURI;
ImageView ivTemp;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ivTemp = findViewById(R.id.iv_tmpcheck);
Button btCallCameraHdPic = findViewById(R.id.bt_call_camera_hd_pic);
btCallCameraHdPic.setOnClickListener(v -> callCameraForHdPic());
}
private void callCameraForHdPic() {
//todo : add code for runtimePermissionCheck here//
File tempImageFile = createImageFile();// step 2
if (tempImageFile != null) {
Log.e(TAG, "callCameraForHdPic: temp image file is not null");
tmpPhotoURI = FileProvider.getUriForFile(this, AUTHORITY, tempImageFile);
//^--step3
Intent i = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
i.putExtra(MediaStore.EXTRA_OUTPUT, tmpPhotoURI); //step4
if (i.resolveActivity(getPackageManager()) != null) {
startActivityForResult(i, RQ_CAPTURE_IMG_CODE_HDPIC); //step4
} else {
Log.e(TAG, "callCameraForHdPic: couldn't strt cmra activity");
}
} else {
Log.e(TAG, "callCameraForHdPic: temp image file is null ");
}
}
private File createImageFile() {
// Create an image file name
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(new Date());
String imageFileName = "JPEG_" + timeStamp + "_";
File storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES);
File image = null;
try {
image = File.createTempFile(imageFileName, ".jpg", storageDir);
Log.e(TAG, "createImageFile: succesfully created image file");
} catch (IOException e) {
Log.e("$1457$", "createImageFile: error happenned during temp file creation ");
e.printStackTrace();
}
return image;
}
private void openCameraX1() {
startActivity(new Intent(this,Camera1Activity.class));
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == RQ_CAPTURE_IMG_CODE_HDPIC && resultCode == RESULT_OK) {
//if (data != null && data.getExtras() != null) { ...} //not needed to check data
Log.e(TAG, "onActivityResultCodeForHdPic: result recieved from camera activity");
Bitmap b= getBitmapFromUri(this,tmpPhotoURI);
onBitmapRecieved(b);
}
}
private static Bitmap getBitmapFromUri(Context context, Uri photoURI) {
// super cool decoding algorithm at https://stackoverflow.com/a/31720143/7500651 //
....
return rotatedAndDecodedImg;
}
private void onBitmapRecieved(Bitmap imageBitmap) {
ivTemp.setImageBitmap(imageBitmap);
}
}
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="x.y.z.camera1">
<uses-feature
android:name="android.hardware.camera.any"
android:required="true" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application
....
>
<activity android:name=".MainActivity">
...
</activity>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="in.curioustools.camera1.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
</application>
</manifest>
view raw manifest.xml hosted with ❤ by GitHub
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="my_images" path="Android/data/in.curioustools.camera1/files/Pictures" />
</paths>

This code follows along the lines of following steps:
(0. add permission WRITE_STORAGE_EXTERNAL/READ_STORAGE_INTERNAL in manifest)

1. call the code to check this permission at runtime todo
2 create temporary file in public external directory(or your internal directory)
(3-internal: your app should have a file provider , make it in manifest and xml)
3 if succesfully created, generate its uri via file provider.
4 send it to camera app by passing it as Extra data attached to OUTPUT key.
5 Recieve captured image Bitmap in  onActivity or Result Recieve image Uri in OnActivityResult. You will not be receiving the captured hdImage or its uri in onActivity result, because you already have them! The OS will write image data to the same temp file you previously created and its uri will remain the same. This function would just act as an alert about the OS completing its actions and for you to start reading/ using this image uri.
The file it will now point to will have a high res picture. We use a scaling algorithm (https://stackoverflow.com/a/31720143/7500651) before displaying it.


Comments