Hello friends
Android Nougat is almost be publicly released. And as an Android developer, we need to prepare ourself to adjust targetSdkVersion to the latest one, 24, to let everything works perfectly on the newest release of Android.
You may be curious why Android team decide to change this behavior. Actually there is a good reason behind.
If file path is sent to the target application (Camera app in this case), file will be fully accessed through the Camera app's process not the sender one.
But let's consider thoroughly, actually Camera is launched by our application to take a photo and save as a file on our app's behalf. So the access right to that file should be our app's not Camera's. Every operation did with the file should be done through our application not by Camera app itself.
And that's why file:// is now prohibited on targetSdkVersion 24 to force every developer to do this task in the proper way.
Solution
So if file:// is not allowed anymore, which approach should we go for?
The answer is we should send the URI through content:// scheme instead which is the URI scheme for Content Provider. In this case, we would like to share an access to a file through our app so FileProvider is needed to be implemented. Flow is now changed like below:
And now, with FileProvider, file operation would be done through our app process like it supposes to be !
It is quite easy to implement FileProvider on your application.
First you need to add a FileProvider <provider> tag in AndroidManifest.xml under <application> tag like below:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
...
<application
...
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths"/>
</provider>
</application>
</manifest>
And then create a provider_paths.xml file in xml folder under res folder. Folder may be needed to create if it doesn't exist.
The content of the file is shown below. It describes that we would like to share access to the External Storage at root folder (path=".") with the name external_files.
res/xml/provider_paths.xml
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="external_files" path="."/>
</paths>
Done! FileProvider is now declared and be ready to use.
The final step is to change the line of code below in MainActivity.java
Uri photoURI = Uri.fromFile(createImageFile());
to
Uri photoURI = FileProvider.getUriForFile(MainActivity.this,
BuildConfig.APPLICATION_ID + ".provider",
createImageFile());
And .... done ! Your application should now work perfectly fine on any Android version including Android Nougat
If you not understand above process don't worry i describe with sample follow some easy steps....
MainActivity.java
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.provider.MediaStore;
import android.support.v4.content.FileProvider;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import static android.R.attr.path;
import static android.provider.MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE;
import static android.provider.MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO;
public class MainActivity extends AppCompatActivity {
private ImageView imageView;
private int REQUEST_CAMERA = 101;
private int PIC_CROP = 102;
private Uri photoURI = null;
private String IMAGE_DIRECTORY_NAME = "sample";
//String path;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
imageView = (ImageView) findViewById(R.id.iv);
findViewById(R.id.btncamera).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
takePicture();
}
});
}
private void takePicture() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
File photoFile = getOutputMediaFile();
photoURI = FileProvider.getUriForFile(MainActivity.this, getString(R.string.file_provider_authority), photoFile);
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI);
startActivityForResult(takePictureIntent, REQUEST_CAMERA);
}
} else {
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
photoURI = getOutputMediaFileUri();
intent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI);
startActivityForResult(intent, REQUEST_CAMERA);
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == AppCompatActivity.RESULT_OK) {
if (requestCode == REQUEST_CAMERA) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
imageView.setImageURI(photoURI);
} else {
try {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 8;
final Bitmap bitmap = BitmapFactory.decodeFile(photoURI.getPath(), options);
//imageView.setImageBitmap(bitmap);
// If you want croping use this
performCrop(photoURI);
} catch (NullPointerException e) {
e.printStackTrace();
}
}
} else if (requestCode == PIC_CROP) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
Uri selectedImageUri = data.getData();
imageView.setImageURI(selectedImageUri);
Log.e("TAG", " onResult after crop pixel " + selectedImageUri);
} else {
Uri selectedImageUri = data.getData();
if (selectedImageUri != null) {
imageView.setImageURI(selectedImageUri);
} else {
Bitmap selectedBitmap = (Bitmap) data.getExtras().get("data");
imageView.setImageBitmap(selectedBitmap);
}
}
}
}
}
private void performCrop(Uri selectedImageUri) {
try {
Log.e("TAG", " get data crop " + selectedImageUri);
Intent cropIntent = new Intent("com.android.camera.action.CROP");
// indicate image type and Uri
cropIntent.setDataAndType(selectedImageUri, "image/*");
// set crop properties
cropIntent.putExtra("crop", "true");
// indicate aspect of desired crop
cropIntent.putExtra("aspectX", 1);
cropIntent.putExtra("aspectY", 1);
// indicate output X and Y
cropIntent.putExtra("outputX", 128);
cropIntent.putExtra("outputY", 128);
// retrieve data on return
cropIntent.putExtra("return-data", true);
// start the activity - we handle returning in onActivityResult
if (getPackageManager() != null) {
startActivityForResult(cropIntent, PIC_CROP);
}
} catch (Exception anfe) {
anfe.printStackTrace();
String errorMessage = "Whoops - your device doesn't support the crop action!";
Log.e("TAG", " Croping error " + errorMessage);
}
}
/**
* Creating file uri to store image/video
*/
public Uri getOutputMediaFileUri() {
return Uri.fromFile(getOutputMediaFile());
}
/*
* returning image / video
*/
private File getOutputMediaFile() {
// External sdcard location
File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), IMAGE_DIRECTORY_NAME);
// Create the storage directory if it does not exist
if (!mediaStorageDir.exists()) {
if (!mediaStorageDir.mkdirs()) {
Log.d(IMAGE_DIRECTORY_NAME, "Oops! Failed create "+ IMAGE_DIRECTORY_NAME + " directory");
return null;
}
}
// Create a media file name
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss",
Locale.getDefault()).format(new Date());
File mediaFile;
mediaFile = new File(mediaStorageDir.getPath() + File.separator + "IMG_" + timeStamp + ".jpg");
return mediaFile;
}
}
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.provider.MediaStore;
import android.support.v4.content.FileProvider;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import static android.R.attr.path;
import static android.provider.MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE;
import static android.provider.MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO;
public class MainActivity extends AppCompatActivity {
private ImageView imageView;
private int REQUEST_CAMERA = 101;
private int PIC_CROP = 102;
private Uri photoURI = null;
private String IMAGE_DIRECTORY_NAME = "sample";
//String path;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
imageView = (ImageView) findViewById(R.id.iv);
findViewById(R.id.btncamera).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
takePicture();
}
});
}
private void takePicture() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
File photoFile = getOutputMediaFile();
photoURI = FileProvider.getUriForFile(MainActivity.this, getString(R.string.file_provider_authority), photoFile);
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI);
startActivityForResult(takePictureIntent, REQUEST_CAMERA);
}
} else {
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
photoURI = getOutputMediaFileUri();
intent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI);
startActivityForResult(intent, REQUEST_CAMERA);
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == AppCompatActivity.RESULT_OK) {
if (requestCode == REQUEST_CAMERA) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
imageView.setImageURI(photoURI);
} else {
try {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 8;
final Bitmap bitmap = BitmapFactory.decodeFile(photoURI.getPath(), options);
//imageView.setImageBitmap(bitmap);
// If you want croping use this
performCrop(photoURI);
} catch (NullPointerException e) {
e.printStackTrace();
}
}
} else if (requestCode == PIC_CROP) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
Uri selectedImageUri = data.getData();
imageView.setImageURI(selectedImageUri);
Log.e("TAG", " onResult after crop pixel " + selectedImageUri);
} else {
Uri selectedImageUri = data.getData();
if (selectedImageUri != null) {
imageView.setImageURI(selectedImageUri);
} else {
Bitmap selectedBitmap = (Bitmap) data.getExtras().get("data");
imageView.setImageBitmap(selectedBitmap);
}
}
}
}
}
private void performCrop(Uri selectedImageUri) {
try {
Log.e("TAG", " get data crop " + selectedImageUri);
Intent cropIntent = new Intent("com.android.camera.action.CROP");
// indicate image type and Uri
cropIntent.setDataAndType(selectedImageUri, "image/*");
// set crop properties
cropIntent.putExtra("crop", "true");
// indicate aspect of desired crop
cropIntent.putExtra("aspectX", 1);
cropIntent.putExtra("aspectY", 1);
// indicate output X and Y
cropIntent.putExtra("outputX", 128);
cropIntent.putExtra("outputY", 128);
// retrieve data on return
cropIntent.putExtra("return-data", true);
// start the activity - we handle returning in onActivityResult
if (getPackageManager() != null) {
startActivityForResult(cropIntent, PIC_CROP);
}
} catch (Exception anfe) {
anfe.printStackTrace();
String errorMessage = "Whoops - your device doesn't support the crop action!";
Log.e("TAG", " Croping error " + errorMessage);
}
}
/**
* Creating file uri to store image/video
*/
public Uri getOutputMediaFileUri() {
return Uri.fromFile(getOutputMediaFile());
}
/*
* returning image / video
*/
private File getOutputMediaFile() {
// External sdcard location
File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), IMAGE_DIRECTORY_NAME);
// Create the storage directory if it does not exist
if (!mediaStorageDir.exists()) {
if (!mediaStorageDir.mkdirs()) {
Log.d(IMAGE_DIRECTORY_NAME, "Oops! Failed create "+ IMAGE_DIRECTORY_NAME + " directory");
return null;
}
}
// Create a media file name
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss",
Locale.getDefault()).format(new Date());
File mediaFile;
mediaFile = new File(mediaStorageDir.getPath() + File.separator + "IMG_" + timeStamp + ".jpg");
return mediaFile;
}
}
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.demo.camerademos">
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.INTERNET" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="@string/file_provider_authority"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_provider_path"/>
</provider>
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.demo.camerademos">
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.INTERNET" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="@string/file_provider_authority"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_provider_path"/>
</provider>
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
Thank you
FullSourceCode DownloadNougatCamera
Sample
No comments:
Post a Comment