start - take image camera android




Android M Camera Intent+bug de permission? (6)

J'essaie de préparer mon application pour les nouvelles modifications des autorisations Android M et j'ai constaté un comportement étrange. Mon application utilise le mécanisme d'intention de l'appareil photo pour permettre à l'utilisateur d'obtenir une image de l'appareil photo. Mais dans une autre activité, vous devez utiliser la caméra elle-même avec son autorisation (en raison d'une carte de dépendance de bibliothèque qui l'exige).

Cependant, avec M dans l'activité qui ne nécessite qu'une intention de caméra lorsque j'essaie de lancer l'objectif de caméra, je vois le blocage suivant (cela ne se produit pas si je supprime la permission de caméra du manifeste),

> 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

Est-ce un problème connu avec Android M? Et plus important encore, comment puis-je contourner ce problème?

dans le manifeste, j'ai ce qui suit,

<uses-permission android:name="android.permission.CAMERA" />

et c’est le code que j’utilise pour laisser l’utilisateur cliquer sur une photo avec la caméra et / ou sélectionner une 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;
}

J'appelle openImageIntent() sur un clic de bouton dans mon activité. Lorsque je ne dispose pas de l'autorisation CAMERA dans l'application, cela fonctionne bien, mais avec cette exception, l'exception ci-dessus est affichée.

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


Cette méthode ne vérifie pas uniquement l'appareil photo, mais toutes les autorisations requises par mon application au démarrage ... Je l'ai dans mon fichier Helper.java. Notez également que pour le dialogue, j'utilise cette bibliothèque: 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

donc vous allez ajouter ou supprimer vos listes de permission ici:

//add your permissions here
            String[] AppPermissions = {
                    Manifest.permission.CAMERA,
                    Manifest.permission.READ_EXTERNAL_STORAGE,
                    Manifest.permission.WRITE_EXTERNAL_STORAGE
            };

Dans mon fichier d'activité, je vérifie l'autorisation comme ceci: c'est dans la classe Helper que j'ai conservé la méthode hasPermissions.

 if(Helper.hasPermissions(this) == false){
            return;
  }//end if

Cela signifie que nous n'avons pas besoin de continuer l'exécution si aucune autorisation n'est accordée. De nouveau, nous devrons écouter la demande d'autorisation une fois celle-ci terminée. Pour ce faire, ajoutez le code ci-dessous à votre fichier d'activité (facultatif).

//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

En ce qui concerne votre question "Est-ce un problème connu dans M?" Un développeur de Google a répondu à une personne signalant ce problème comme un bogue.

Voir ici: https://code.google.com/p/android/issues/detail?id=188073&q=label%3APriority-Medium&colspec=ID%20Type%20Status%20Owner%20Summary%20Stars&start=100

Voici le mot du gars de Google: «Ce comportement est destiné à éviter la frustration des utilisateurs qui ont révoqué l'autorisation de l'appareil photo d'une application et que l'application pouvait toujours prendre des photos via l'intention. Les utilisateurs ne sont pas conscients que la photo prise après la révocation des autorisations se produit via un mécanisme différent et remettrait en question le bien-fondé du modèle d'autorisation. Cela s'applique à MediaStore.ACTION_IMAGE_CAPTURE, MediaStore.ACTION_VIDEO_CAPTURE et Intent.ACTION_CALL les documents pour lesquels documenter le changement de comportement des applications ciblant M. "

Étant donné que Google ne craint pas d'abstraction des mécanismes d'utilisation de l'appareil photo de votre utilisateur, vous pouvez également déclencher de manière stratégique la première demande d'autorisation d'appareil photo et faire référence à la fonctionnalité de l'activité qui utilise l'appareil photo comme raison de votre demande. Si vous autorisez d'abord votre application à faire cette demande d'autorisation lorsque l'utilisateur tente simplement de prendre une photo, celui-ci peut penser que votre application se comporte étrangement, car prendre une photo ne nécessite généralement pas l'autorisation.


J'ai eu le même problème et trouver cette doc de google: https://developer.android.com/reference/android/provider/MediaStore.html#ACTION_IMAGE_CAPTURE

"Remarque: si vous appliquez les cibles M et supérieures et déclare utiliser la permission CAMERA non octroyée, toute tentative d'utilisation de cette action donnera lieu à une exception SecurityException."

C'est vraiment bizarre. Ça n'a pas de sens du tout. L'application déclare l'autorisation Camera en utilisant l'intention avec l'action IMAGE_CAPTURE qui vient d'exécuter SecurityException. Mais si votre application ne déclare pas l'autorisation Camera en utilisant l'intention avec l'action, IMAGE_CAPTURE peut lancer l'application Camera sans problème.

La solution de contournement serait de vérifier si l'application dispose d'une autorisation caméra incluse dans le manifeste. Si c'est le cas, demandez une autorisation caméra avant de lancer l'intention.

Voici le moyen de vérifier si l'autorisation est incluse dans le manifeste, que l'autorisation soit accordée ou non.

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

Je suis resté bloqué sur ce problème et j'utilisais déjà la réponse de JTY. Le problème est qu’à un moment donné, la boîte de dialogue d’autorisation de demande a été cochée sur «Plus jamais demander». Je développe sur le SDK 24.

Mon code complet pour gérer les autorisations (la caméra dans mon cas) était le suivant:

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

ensuite

private void showMessageOKCancel(String message, DialogInterface.OnClickListener okListener) {
    new AlertDialog.Builder(this)
            .setMessage(message)
            .setPositiveButton("OK", okListener)
            .setNegativeButton("Cancel", null)
            .create()
            .show();
}

puis

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

Le comportement souhaité est que, si l'utilisateur coche par erreur "Ne plus demander", votre application reste bloquée (le dialogue de requête n'est pas affiché) et que l'utilisateur risque de se sentir frustré. De cette façon, un message lui dit qu'il a besoin de cette permission.


Si vous utilisez le modèle d'autorisation Android M, vous devez d'abord vérifier si l'application dispose de cette autorisation lors de l'exécution, et demander à l'utilisateur cette autorisation lors de l'exécution. La permission que vous définissez sur votre manifeste ne sera pas automatiquement accordée au moment de l'installation.

if (checkSelfPermission(Manifest.permission.CAMERA)
        != PackageManager.PERMISSION_GRANTED) {

    requestPermissions(new String[]{Manifest.permission.CAMERA},
            MY_REQUEST_CODE);
}

MY_REQUEST_CODE est une constante statique que vous pouvez définir, qui sera utilisée à nouveau pour le rappel de la boîte de dialogue requestPermission.

Vous aurez besoin d'un rappel pour le résultat de la boîte de dialogue:

@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
        }
    }
}

modifier

D'après la trace de la pile, il semble que Google Camera ne dispose pas de l'autorisation CAMERA. Cela pourrait en fait ressembler à une compatibilité ascendante après tout.

Supposons que Google Camera (ou toute autre application gérant votre intention ACTION) nécessite une autorisation donnée.

Lorsque votre application ne dispose pas de l'autorisation CAMERA, elle laisse simplement Google Camera se charger de l'ancien modèle d'autorisations.

Cependant, avec l'autorisation CAMERA déclarée dans votre manifeste, l'application de l'autorisation CAMERA au sein de Google Camera (sans modèle d'autorisations Android M) permet également d'utiliser le modèle d'autorisations Android M (je pense.).

Cela signifie donc que, en utilisant la méthode ci-dessus, vous devrez fournir l'autorisation de votre application pendant l'exécution, ce qui signifie que sa tâche enfant (dans ce cas, Google Camera) aura désormais également cette autorisation.


Vous devez activer l'autorisation App pour l'utilisation de l'appareil photo. Je préfère ajouter cette méthode à la commande add qui active la caméra:

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




android-6.0-marshmallow