Android M Camera Intent + permission bug?

54,116

Solution 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;
}

Solution 2

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://issuetracker.google.com/issues/37063818#comment8

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.

Solution 3

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.

Solution 4

If you are using Google M, go to Settings -> Apps -> your app -> and give the appropriate permissions.

Solution 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.

Share:
54,116
source.rar
Author by

source.rar

Updated on July 05, 2022

Comments

  • source.rar
    source.rar almost 2 years

    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();
            }
        }
    

  • source.rar
    source.rar over 8 years
    but doesn't the permission versus intents section here developer.android.com/preview/features/runtime-permissions.h‌​tml say that we do not need to declare CAMERA permissions if we are using an intent to get an image?
  • source.rar
    source.rar over 8 years
    I wonder if this was in the documentation before I asked this question? (or was it added later) :)
  • 0101100101
    0101100101 about 8 years
    @source.rar Exactly, and still it doesn't work without setting the permission even for Android 4 and 5. WTF Google?
  • hatted
    hatted about 8 years
    Right, even you are developing your own app for testing, you still need to do it for each app.
  • Adrian Krebs
    Adrian Krebs about 8 years
    Thanks! what a weird behavior. I was searching for an issue in my manifest.xml
  • Pravinsingh Waghela
    Pravinsingh Waghela about 8 years
    So now is it fine if I skip the Camera permission from my Manifest.xml file? Will it create any problem to other lower versions like Lollipop, Kitkat and Jellybeans?
  • Marcin Orlowski
    Marcin Orlowski almost 8 years
    Then you do this wrong: "When declaring a feature, remember that you must also request permissions as appropriate" developer.android.com/guide/topics/manifest/…
  • Jeann van Rooyen
    Jeann van Rooyen almost 8 years
    So, if I'm doing this wrong - Android should not be providing me with access to the camera?! And yet - it works perfectly. Also - as per the article developer.android.com/training/camera/photobasics.html you don't need permission... only the feature. (hence the required="true" flag) But thanks for the down vote...
  • Marcin Orlowski
    Marcin Orlowski almost 8 years
    uses-feature does not automatically grants you uses-permission. It just means you want the camera. Will you use it or will access to it be granted by the user is completely other story.
  • Marcin Orlowski
    Marcin Orlowski almost 8 years
    Mind pointing to the place where they say you do not need uses-permission because you have uses-feature?
  • Jeann van Rooyen
    Jeann van Rooyen almost 8 years
    Well - the code breaks if I added it. And it worked when I removed it - and when I followed the article. Simple as that... Mind pointing to the place where they state that it is required?!
  • Jeann van Rooyen
    Jeann van Rooyen almost 8 years
    Also - I downloaded the sample project - and the manifest does not contain a "uses-permission" for the camera... so there... It's not required... Stop bickering, and try to help someone... Nit picking. Why?!
  • Jeann van Rooyen
    Jeann van Rooyen almost 8 years
    But to maybe resolve this peacefully - if you require access to the hardware camera, then sure, the permission is required. If you rely on a default camera app (i.e, as per the question, as the "take picture intent" does) then the persmission is not required, only the feature....
  • Jeann van Rooyen
    Jeann van Rooyen almost 8 years
    Here it also states that permission may be granted automatically... (also, as mentioned in above comments) developer.android.com/training/permissions/index.html
  • Teo Inke
    Teo Inke almost 8 years
    I know it s*cks that you need a lot of code just run a simple camera Intent. The price you pay to use the latest API.
  • Munib
    Munib almost 7 years
    What is no permissions then what would you do? ask for permissions?
  • Singed
    Singed over 5 years
    Hey, why would you check if permission is included in Manifest programmatically if that's something you can know precompile time? You can just check your merged manifest and know whether the permission is there or not, and write your code based on that.
  • Ayaz Alifov
    Ayaz Alifov over 5 years
    @Singed, because later on you can forget about this fix and add/remove camera permission to manifest.
  • oberstet
    oberstet almost 3 years
    this should be the accepted answer: it is the shortest solution that will make it practically work. fwiw, in this case (android), I don't care "what is right" .. only "what works";)