Подтвердить что ты не робот

Android M Camera Intent + ошибка разрешения?

Я пытаюсь подготовить свое приложение к новым изменениям прав на Android M и обнаружил какое-то странное поведение. Мое приложение использует механизм намерения камеры, чтобы позволить пользователю получить изображение из камеры. Но в другом действии необходимо использовать саму камеру с разрешением камеры (из-за того, что для нее требуется карточка-зависимая библиотека.)

Однако с M в активности, которая нужна только для камеры, когда я пытаюсь запустить намерение камеры, я вижу следующий сбой (этого не происходит, если я удалю разрешение камеры из манифеста),

> 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

Это известная проблема с Android M? И что еще более важно, как мне обойти это?

в манифесте у меня есть следующее:

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

и это код, который я использую, чтобы позволить пользователю щелкнуть рис. с помощью камеры и/или выбрать изображение

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

Я вызываю openImageIntent() нажатием кнопки в моей активности. Когда у меня нет разрешения CAMERA в моем приложении, он работает нормально, но с добавлением я получаю исключение, указанное выше.

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

4b9b3361

Ответ 1

У меня была такая же проблема, и я нашел этот документ из google: https://developer.android.com/reference/android/provider/MediaStore.html#ACTION_IMAGE_CAPTURE

"Примечание. Если вы используете приложение M и выше и объявляете с использованием разрешения CAMERA, которое не предоставляется, то попытка использовать это действие приведет к исключению SecurityException."

Это действительно странно. Не имеешь смысла. Приложение объявляет разрешение камеры, используя намерение с действием IMAGE_CAPTURE, просто запускается в SecurityException. Но если ваше приложение не объявляет разрешение камеры, используя намерение с действием, IMAGE_CAPTURE может запускать приложение для камеры без проблем.

Обходным решением будет проверка, что приложение имеет разрешение камеры, включенное в манифест, если оно запрашивает разрешение камеры до запуска намерения.

Вот способ проверить, включено ли разрешение в манифест, не имеет значения, предоставлено разрешение или нет.

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

Если вы используете модель разрешения Android M, сначала вам нужно проверить, имеет ли приложение это разрешение во время выполнения, и пригласить пользователя на это разрешение во время выполнения. Разрешение, которое вы определяете в своем манифесте, автоматически не будет предоставлено во время установки.

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

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

MY_REQUEST_CODE - это статическая константа, которую вы можете определить, которая будет использоваться снова для обратного вызова диалога requestPermission.

Вам понадобится обратный вызов для результата диалога:

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

изменить

Чтение из трассировки стека, похоже, что у камеры Google не разрешено разрешение CAMERA. В конце концов, это может выглядеть как обратная совместимость.

Предположим, что для камеры Google (или любого другого приложения, обрабатывающего ваши намерения ACTION) требуется определенное разрешение.

Когда ваше приложение не имеет разрешения CAMERA, оно просто позволяет Google Camera делать это со старой моделью разрешений.

Однако, с разрешением CAMERA, объявленным в вашем манифесте, он также обеспечивает разрешение CAMERA в Google Camera (на котором отсутствует модель разрешений Android M), чтобы использовать модель разрешений Android M (я думаю.)

Таким образом, это означает использование вышеописанного метода, вам нужно будет предоставить разрешение вашего приложения во время выполнения, что означает, что его дочерняя задача (в данном случае камера Google) также получит это разрешение.

Ответ 3

Что касается вашего вопроса "Это известная проблема в M?" Google-разработчик ответил, что кто-то сообщил об этой проблеме как ошибке.

Смотрите здесь: https://code.google.com/p/android/issues/detail?id=188073&q=label%3APriority-Medium&colspec=ID%20Type%20Status%20Owner%20Summary%20Stars&start=100

Вот слово от парня Google: "Это предполагаемое поведение, чтобы избежать разочарования пользователя, когда они отозвали разрешение камеры от приложения, и приложение все еще может фотографировать с помощью намерения. Пользователи не знают, что фотография после того, как аннулирование разрешения происходит с помощью другого механизма и будет подвергать сомнению правильность модели разрешения. Это относится к MediaStore.ACTION_IMAGE_CAPTURE, MediaStore.ACTION_VIDEO_CAPTURE и Intent.ACTION_CALL документам, для которых документ изменяет поведение для приложений, ориентированных на M."

Поскольку Google не возражает против абстрагирования механики использования камеры от вашего пользователя, вы можете стратегически инициировать первый запрос на разрешение камеры и ссылаться на функциональность Activity, которая использует камеру в качестве аргумента для запроса. Если вы разрешите своему приложению сначала сделать этот запрос на разрешение, когда пользователь просто пытается сделать снимок, пользователь может подумать, что ваше приложение ведет себя странно, поскольку получение фотографии обычно не требует разрешения.

Ответ 4

Если вы используете Google M, перейдите в Настройки → Приложения → ваше приложение → и укажите соответствующие разрешения.

Ответ 5

Я застрял в этой проблеме, и я уже использовал JTY-ответ. Проблема в том, что в какой-то момент в диалоговом окне разрешения запроса была отмечена опция "Никогда не спрашивать снова". Я разрабатываю SDK 24.

Мой полный код для обработки разрешений (камера в моем случае):

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

затем

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

а затем

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

Предполагаемое поведение заключается в том, что если пользователь по ошибке проверил "Никогда не спрашивать снова", ваше приложение застревает (диалоговое окно запроса не отображается), и пользователь может почувствовать разочарование. Таким образом, сообщение сообщает ему, что ему нужно это разрешение.

Ответ 6

Я удалил:

uses-permission android:name="android.permission.CAMERA"

и полагался только на:

uses-feature android:name="android.hardware.camera" android:required="true"

в файле манифеста.

Ответ 7

Этот метод не проверяет только камеру, но все разрешения, необходимые моему приложению во время запуска... У меня есть это в моем файле Helper.java. Также обратите внимание, что для диалога я использую эту библиотеку: 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

поэтому вы добавите или удалите свои списки разрешений здесь:

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

В моем файле "Активность" я проверяю разрешение, подобное этому, класс "Помощник" - это место, где я сохранил метод hasPermissions

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

Это означает, что нам не нужно продолжать выполнение, если разрешение не предоставлено. Снова нам нужно будет послушать запрос разрешения после его завершения, чтобы сделать это, добавьте следующий код в свой файл активности (необязательно)

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

немного поздно. но я хочу добавить еще одну вещь. всякий раз, когда вы вызываете методы, которые содержат функции камеры, используйте его в блоке try catch. если приложение не будет работать на некоторых устройствах, таких как Moto G4 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: убедитесь, что вы не скопировали вставку переопределенного метода.

Ответ 9

Вы должны включить разрешение приложения для использования камеры. Я предпочитаю добавлять этот метод добавить команду, которая активирует камеру:

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