Android M Camera Intent + permission bug?
P

9

74

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

Parceling answered 25/9, 2015 at 19:5 Comment(5)
Can you provide any code? Are you requesting the permission at runtime or just expecting the system to do this for you? The reason, this is probably not happening when you remove the permission from the manifest is it won't actually even attempt to bring up the camera. Please see the following blog for a tutorial on Android Runtime permissions in M: captechconsulting.com/blogs/…Farman
Can you provide some more information such as code where you invoking camera, as this is not sufficient to answer you questionCaliphate
I've added the relevant code that gives the problem.Parceling
@TDev: if I understand correctly we do not need CAMERA permissions for getting images with an INTENTParceling
@Parceling The permissions are now grouped together in categories. You need the camera category permission in order to access the pictures.Farman
L
93

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;
}
Lecce answered 30/9, 2015 at 0:58 Comment(6)
I wonder if this was in the documentation before I asked this question? (or was it added later) :)Parceling
Thanks! what a weird behavior. I was searching for an issue in my manifest.xmlAswan
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?Embrocate
What is no permissions then what would you do? ask for permissions?Attach
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.Thibeault
@Singed, because later on you can forget about this fix and add/remove camera permission to manifest.Undue
M
21

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.

Mabuse answered 22/10, 2015 at 21:17 Comment(0)
F
19

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.

Frisette answered 26/9, 2015 at 11:55 Comment(2)
but doesn't the permission versus intents section here developer.android.com/preview/features/runtime-permissions.html say that we do not need to declare CAMERA permissions if we are using an intent to get an image?Parceling
@Parceling Exactly, and still it doesn't work without setting the permission even for Android 4 and 5. WTF Google?Shunt
E
8

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

Ecbatana answered 9/11, 2015 at 3:37 Comment(1)
Right, even you are developing your own app for testing, you still need to do it for each app.Gavel
M
3

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.

Masefield answered 30/6, 2016 at 6:38 Comment(1)
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.Masefield
S
1

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.

Selfdriven answered 26/6, 2016 at 8:21 Comment(9)
Then you do this wrong: "When declaring a feature, remember that you must also request permissions as appropriate" developer.android.com/guide/topics/manifest/…Andromada
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...Selfdriven
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.Andromada
Mind pointing to the place where they say you do not need uses-permission because you have uses-feature?Andromada
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?!Selfdriven
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?!Selfdriven
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....Selfdriven
Here it also states that permission may be granted automatically... (also, as mentioned in above comments) developer.android.com/training/permissions/index.htmlSelfdriven
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";)Swingletree
S
0

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
Selfheal answered 17/3, 2017 at 1:33 Comment(0)
E
0

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.

Evulsion answered 24/7, 2017 at 10:56 Comment(0)
S
0

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;
    }
Stegall answered 4/9, 2019 at 7:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.