问题
I'm trying to get my app ready for the new Android M permissions changes and found some weird behaviour. My app uses the Camera intent mechanism to allow the user to get a picture form the camera. But in another activity needs to make use of the camera itself with Camera permission (because of a library dependency card.io that requires this).
However with M in the activity that only needs a camera intent when I try to launch the Camera intent I see the following crash (this does not happen if I remove the Camera permission from the Manifest),
> 09-25 21:57:55.260 774-8053/? I/ActivityManager: START u0
> {act=android.media.action.IMAGE_CAPTURE flg=0x3000003
> pkg=com.google.android.GoogleCamera
> cmp=com.google.android.GoogleCamera/com.android.camera.CaptureActivity
> (has clip) (has extras)} from uid 10098 on display 0 09-25
> 21:57:55.261 774-8053/? W/ActivityManager: Permission Denial: starting
> Intent { act=android.media.action.IMAGE_CAPTURE flg=0x3000003
> pkg=com.google.android.GoogleCamera
> cmp=com.google.android.GoogleCamera/com.android.camera.CaptureActivity
> (has clip) (has extras) } from null (pid=-1, uid=10098) with revoked
> permission android.permission.CAMERA 09-25 21:57:55.263 32657-32657/?
> E/ResolverActivity: Unable to launch as uid 10098 package
> com.example.me.mycamerselectapp, while running in android:ui 09-25
> 21:57:55.263 32657-32657/? E/ResolverActivity:
> java.lang.SecurityException: Permission Denial: starting Intent {
> act=android.media.action.IMAGE_CAPTURE flg=0x3000003
> pkg=com.google.android.GoogleCamera
> cmp=com.google.android.GoogleCamera/com.android.camera.CaptureActivity
> (has clip) (has extras) } from null (pid=-1, uid=10098) with revoked
> permission android.permission.CAMERA 09-25 21:57:55.263 32657-32657/?
> E/ResolverActivity: at
> android.os.Parcel.readException(Parcel.java:1599) 09-25 21:57:55.263
> 32657-32657/? E/ResolverActivity: at
> android.os.Parcel.readException(Parcel.java:1552) 09-25 21:57:55.263
> 32657-32657/? E/ResolverActivity: at
> android.app.ActivityManagerProxy.startActivityAsCaller(ActivityManagerNative.java:2730)
> 09-25 21:57:55.263 32657-32657/? E/ResolverActivity: at
> android.app.Instrumentation.execStartActivityAsCaller(Instrumentation.java:1725)
> 09-25 21:57:55.263 32657-32657/? E/ResolverActivity: at
> android.app.Activity.startActivityAsCaller(Activity.java:4047) 09-25
> 21:57:55.263 32657-32657/? E/ResolverActivity: at
> com.android.internal.app.ResolverActivity$DisplayResolveInfo.startAsCaller(ResolverActivity.java:983)
> 09-25 21:57:55.263 32657-32657/? E/ResolverActivity: at
> com.android.internal.app.ResolverActivity.safelyStartActivity(ResolverActivity.java:772)
> 09-25 21:57:55.263 32657-32657/? E/ResolverActivity: at
> com.android.internal.app.ResolverActivity.onTargetSelected(ResolverActivity.java:754)
> 09-25 21:57:55.263 32657-32657/? E/ResolverActivity: at
> com.android.internal.app.ChooserActivity.onTargetSelected(ChooserActivity.java:305)
> 09-25 21:57:55.263 32657-32657/? E/ResolverActivity: at
> com.android.internal.app.ResolverActivity.startSelected(ResolverActivity.java:603)
> 09-25 21:57:55.263 32657-32657/? E/ResolverActivity: at
> com.android.internal.app.ChooserActivity.startSelected(ChooserActivity.java:310)
> 09-25 21:57:55.263 32657-32657/? E/ResolverActivity: at
> com.android.internal.app.ChooserActivity$ChooserRowAdapter$2.onClick(ChooserActivity.java:990)
> 09-25 21:57:55.263 32657-32657/? E/ResolverActivity: at
> android.view.View.performClick(View.java:5198) 09-25 21:57:55.263
> 32657-32657/? E/ResolverActivity: at
> android.view.View$PerformClick.run(View.java:21147) 09-25 21:57:55.263
> 32657-32657/? E/ResolverActivity: at
> android.os.Handler.handleCallback(Handler.java:739) 09-25 21:57:55.263
> 32657-32657/? E/ResolverActivity: at
> android.os.Handler.dispatchMessage(Handler.java:95) 09-25 21:57:55.263
> 32657-32657/? E/ResolverActivity: at
> android.os.Looper.loop(Looper.java:148) 09-25 21:57:55.263
> 32657-32657/? E/ResolverActivity: at
> android.app.ActivityThread.main(ActivityThread.java:5417) 09-25
> 21:57:55.263 32657-32657/? E/ResolverActivity: at
> java.lang.reflect.Method.invoke(Native Method) 09-25 21:57:55.263
> 32657-32657/? E/ResolverActivity: at
> com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
> 09-25 21:57:55.263 32657-32657/? E/ResolverActivity: at
> com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616) 09-25
> 21:57:55.286 1159-1159/? I/Keyboard.Facilitator: onFinishInput() 09-25
> 21:57:55.297 32657-32676/? E/Surface: getSlotFromBufferLocked: unknown
> buffer: 0xaec352e0 09-25 21:57:55.344 325-349/? V/RenderScript:
> 0xb3693000 Launching thread(s), CPUs 4 09-25 21:57:57.290 325-349/?
> E/Surface: getSlotFromBufferLocked: unknown buffer: 0xb3f88240
Is this a known problem with Android M? And more importantly how do I work around this?
in the manifest I have the following,
<uses-permission android:name="android.permission.CAMERA" />
and this is the code I use to let the user click a pic with the Camera and/or select an image
public static Intent openImageIntent(Context context, Uri cameraOutputFile) {
// Camera.
final List<Intent> cameraIntents = new ArrayList<Intent>();
final Intent captureIntent = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE);
final PackageManager packageManager = context.getPackageManager();
final List<ResolveInfo> listCam = packageManager.queryIntentActivities(captureIntent, 0);
for(ResolveInfo res : listCam) {
final String packageName = res.activityInfo.packageName;
final Intent intent = new Intent(captureIntent);
intent.setComponent(new ComponentName(res.activityInfo.packageName, res.activityInfo.name));
intent.setPackage(packageName);
intent.putExtra(MediaStore.EXTRA_OUTPUT, cameraOutputFile);
cameraIntents.add(intent);
}
// Filesystem.
final Intent galleryIntent = new Intent();
galleryIntent.setType("image/*");
galleryIntent.setAction(Intent.ACTION_GET_CONTENT);
// Chooser of filesystem options.
final Intent chooserIntent = Intent.createChooser(galleryIntent, "Take or select pic");
// Add the camera options.
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, cameraIntents.toArray(new Parcelable[]{}));
return chooserIntent;
}
I call the openImageIntent()
on a button click in my activity. When I do not have the CAMERA permission in my app it works fine, but with that added I get the exception posted above.
@Override
public void onClick(View v) {
Intent picCaptureIntenet = openImageIntent(MainActivity.this, getTempImageFileUri(MainActivity.this));
try {
startActivityForResult(picCaptureIntenet, 100);
} catch(Exception e) {
Toast.makeText(MainActivity.this, e.getMessage(), Toast.LENGTH_SHORT).show();
}
}
回答1:
I had the same issue and find this doc from google: https://developer.android.com/reference/android/provider/MediaStore.html#ACTION_IMAGE_CAPTURE
"Note: if you app targets M and above and declares as using the CAMERA permission which is not granted, then atempting to use this action will result in a SecurityException."
This is really weird. Don't make sense at all. The app declares Camera permission using intent with action IMAGE_CAPTURE just run into SecurityException. But if your app doesn't declare Camera permission using intent with action IMAGE_CAPTURE can launch Camera app without issue.
The workaround would be check is the app has camera permission included in the manifest, if it's , request camera permission before launching intent.
Here is the way to check if the permission is included in the manifest, doesn't matter the permission is granted or not.
public boolean hasPermissionInManifest(Context context, String permissionName) {
final String packageName = context.getPackageName();
try {
final PackageInfo packageInfo = context.getPackageManager()
.getPackageInfo(packageName, PackageManager.GET_PERMISSIONS);
final String[] declaredPermisisons = packageInfo.requestedPermissions;
if (declaredPermisisons != null && declaredPermisisons.length > 0) {
for (String p : declaredPermisisons) {
if (p.equals(permissionName)) {
return true;
}
}
}
} catch (NameNotFoundException e) {
}
return false;
}
回答2:
If you are using the Android M permission model, you first need to check if the app has this permission during runtime, and have to prompt the user for this permission during runtime. The permission you define on your manifest will not automatically be granted on install time.
if (checkSelfPermission(Manifest.permission.CAMERA)
!= PackageManager.PERMISSION_GRANTED) {
requestPermissions(new String[]{Manifest.permission.CAMERA},
MY_REQUEST_CODE);
}
MY_REQUEST_CODE is a static constant that you can define, which will be used again for the requestPermission dialog callback.
You will need a callback for the dialog result:
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == MY_REQUEST_CODE) {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// Now user should be able to use camera
}
else {
// Your app will not have this permission. Turn off all functions
// that require this permission or it will force close like your
// original question
}
}
}
edit
Reading from the stack trace, it looks like Google Camera doesn't have the CAMERA permission enabled. This might actually look like a backwards compatibility thing after all.
Let's assume that Google Camera (or whatever other application handling your ACTION intent) requires a certain permission.
When your app does not have the CAMERA permission, it is just letting Google Camera do it's thing with the old permissions model.
However, with the CAMERA permission declared in your manifest, it is also enforcing the CAMERA permission within Google Camera (which does not have Android M permissions model) to use the Android M permissions model (I think.)
So that means using the above method, you will need to provide your app permission during runtime, which means its child task (in this case Google Camera) will now also have that permission.
回答3:
As far as your question 'Is this a known problem in M?' A google dev responded to someone reporting this issue as a bug.
See here: https://code.google.com/p/android/issues/detail?id=188073&q=label%3APriority-Medium&colspec=ID%20Type%20Status%20Owner%20Summary%20Stars&start=100
Here is the word from the Google guy: “ This is intended behavior to avoid user frustration where they revoked the camera permission from an app and the app still being able to take photos via the intent. Users are not aware that the photo taken after the permission revocation happens via different mechanism and would question the correctness of the permission model. This applies to MediaStore.ACTION_IMAGE_CAPTURE, MediaStore.ACTION_VIDEO_CAPTURE, and Intent.ACTION_CALL the docs for which document the behavior change for apps targeting M."
Since Google doesn't mind abstracting the mechanics of using the camera from your user, you might as well strategically trigger the first request for camera permission and reference the functionality of the Activity that uses the Camera as your reasoning for the request. If you allow your app to first make this permission request when the user is simply attempting to take a picture, the user may think your app is behaving strangely as taking a photo does not typically require granting permission.
回答4:
If you are using Google M, go to Settings -> Apps -> your app -> and give the appropriate permissions.
回答5:
I got stuck on this problem and I was already using JTY's answer. The problem is that at some point the request permission dialog was checked on "Never ask again". I'm developing on SDK 24.
My full code to handle permissions (the camera in my case) was the following:
public void checksCameraPermission(View view) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
Log.d("MyApp", "SDK >= 23");
if (this.checkSelfPermission(Manifest.permission.CAMERA)
!= PackageManager.PERMISSION_GRANTED) {
Log.d("MyApp", "Request permission");
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.CAMERA},
MY_REQUEST_CODE);
if (! shouldShowRequestPermissionRationale(Manifest.permission.CAMERA)) {
showMessageOKCancel("You need to allow camera usage",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
ActivityCompat.requestPermissions(FotoPerfil.this, new String[] {Manifest.permission.CAMERA},
MY_REQUEST_CODE);
}
});
}
}
else {
Log.d("MyApp", "Permission granted: taking pic");
takePicture();
}
}
else {
Log.d("MyApp", "Android < 6.0");
}
}
then
private void showMessageOKCancel(String message, DialogInterface.OnClickListener okListener) {
new AlertDialog.Builder(this)
.setMessage(message)
.setPositiveButton("OK", okListener)
.setNegativeButton("Cancel", null)
.create()
.show();
}
and then
@Override
public void onRequestPermissionsResult(int requestCode,
String permissions[], int[] grantResults) {
switch (requestCode) {
case MY_REQUEST_CODE: {
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
criarFoto();
} else {
Toast.makeText(this, "You did not allow camera usage :(", Toast.LENGTH_SHORT).show();
noFotoTaken();
}
return;
}
}
}
The intended behavior is that in case the user by mistake checked "Never ask again" your app gets stuck (the request Dialog is not shown) and the user might feel frustrated. This way a message tells him that he needs this permission.
回答6:
I removed:
uses-permission android:name="android.permission.CAMERA"
and only relied on:
uses-feature android:name="android.hardware.camera" android:required="true"
in the manifest file.
回答7:
This method of mine doesnt check for only Camera but all the permissions required by my app during startup ... I have this in my Helper.java file , Also Note that for the dialog I am using this Library : https://github.com/afollestad/material-dialogs
///check camera permission
public static boolean hasPermissions(final Activity activity){
//add your permissions here
String[] AppPermissions = {
Manifest.permission.CAMERA,
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE
};
//ungranted permissions
ArrayList<String> ungrantedPerms = new ArrayList<String>();
//loop
//lets set a boolean of hasUngrantedPerm to false
Boolean needsPermRequest = false;
//permissionGranted
int permGranted = PackageManager.PERMISSION_GRANTED;
//permission required content
String permRequestStr = activity.getString(R.string.the_following_perm_required);
//loop
for(String permission : AppPermissions){
//check if perm is granted
int checkPerm = ContextCompat.checkSelfPermission(activity,permission);
//if the permission is not granted
if(ContextCompat.checkSelfPermission(activity,permission) != permGranted){
needsPermRequest = true;
//add the permission to the ungranted permission list
ungrantedPerms.add(permission);
//permssion name
String[] splitPerm = permission.split(Pattern.quote("."));
String permName = splitPerm[splitPerm.length-1].concat("\n");
permRequestStr = permRequestStr.concat(permName);
}//end if
}//end loop
//if all permission is granted end exec
//then continue code exec
if(!needsPermRequest) {
return true;
}//end if
//convert array list to array string
final String[] ungrantedPermsArray = ungrantedPerms.toArray(new String[ungrantedPerms.size()]);
//show alert Dialog requesting permission
new MaterialDialog.Builder(activity)
.title(R.string.permission_required)
.content(permRequestStr)
.positiveText(R.string.enable)
.negativeText(R.string.cancel)
.onPositive(new MaterialDialog.SingleButtonCallback(){
@Override
public void onClick(@NonNull MaterialDialog dialog,@NonNull DialogAction which){
//request the permission now
ActivityCompat.requestPermissions(activity,ungrantedPermsArray,0);
}
})
.show();
//return false so that code exec in that script will not be allowed
//to continue
return false;
}//end checkPermissions
so You will add or remove your permission Lists here:
//add your permissions here
String[] AppPermissions = {
Manifest.permission.CAMERA,
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE
};
In my activity File I check the permission like this, the Helper class is where I kept the hasPermissions method
if(Helper.hasPermissions(this) == false){
return;
}//end if
Means we dont need to continue execution if no permission is granted.. Again we will need to listen to the permission request after it has finished ,to do that, add the code below to your activity file (optional)
//Listen to Permission request completion
//put in your activity file
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults)
{
int permGranted = PackageManager.PERMISSION_GRANTED;
Boolean permissionRequired = false;
for(int perm : grantResults){
if(perm != permGranted){
permissionRequired = true;
}
}
//if permission is still required
if(permissionRequired){
//recheck and enforce permission again
Helper.hasPermissions(this);
}//end if
}//end method
回答8:
it's a little late. but i want to add one more thing. whenever you call methods which contains camera functionality use it in try catch block. if not app will crash on some devices like Moto G4 plus or one plus.
private static final int CAMERA_REQUEST_CODE = 10;
//TODO add camera opening functionality here.
try {
captureImage();
Intent intent = new Intent("android.media.action.IMAGE_CAPTURE");
startActivityForResult(intent,CAMERA_REQUEST_CODE);
} catch (Exception e){
e.printStackTrace();
}
private void captureImage(){
if( ContextCompat.checkSelfPermission(getContext(), android.Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
requestPermissions(new String[]{android.Manifest.permission.CAMERA},
CAMERA_REQUEST_CODE);
}
else {
// Open your camera here.
}
}
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == CAMERA_REQUEST_CODE) {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// Now user should be able to use camera
}
else {
// Your app will not have this permission. Turn off all functions
// that require this permission or it will force close like your
// original question
}
}
}
P.S: make sure not to copy paste the overridden method.
回答9:
You have to enable App permission for Camera usage. I prefer to add this method add command that activate camera :
public static async Task<bool> HasPermission()
{
var status = await CrossPermissions.Current.CheckPermissionStatusAsync(Permission.Camera);
if (status == PermissionStatus.Granted) return true;
if (await CrossPermissions.Current.ShouldShowRequestPermissionRationaleAsync(Permission.Camera))
{
ShowDialogOk("Error", "Please allow access to the camera.");//that is my custom method for allert
}
var results = await CrossPermissions.Current.RequestPermissionsAsync(Permission.Camera);
status = results[Permission.Camera];
return status == PermissionStatus.Granted;
}
来源:https://stackoverflow.com/questions/32789027/android-m-camera-intent-permission-bug