Camera.open works on Android 4.2.2, but fails on 6

2019-12-16 20:15发布

问题:

I have 2 devices to test my app : an Acer v370 running Android 4.2.2, and a Samsung Galaxy S6 on 6.0.1

The app works fine on the Acer, but crashes instantly on the S6. I'm using _camera = Camera.open(0); and debugging says it crashes at this point.

The error I get is :

09-15 11:24:33.491 15284-15284/com.user.qrReader E/AndroidRuntime: FATAL EXCEPTION: main
  Process: com.user.qrReader, PID: 15284
  java.lang.RuntimeException: Unable to resume activity {com.user.qrReader/com.user.qrReader.MainActivity}:
  java.lang.RuntimeException: Fail to connect to camera service
      at android.app.ActivityThread.performResumeActivity(ActivityThread.java:4156)
      at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:4250)
      at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3361)
      at android.app.ActivityThread.access$1100(ActivityThread.java:222)
      at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1795)
      at android.os.Handler.dispatchMessage(Handler.java:102)
      at android.os.Looper.loop(Looper.java:158)
      at android.app.ActivityThread.main(ActivityThread.java:7229)
      at java.lang.reflect.Method.invoke(Native Method)
      at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1230)
      at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1120)
  Caused by: java.lang.RuntimeException: Fail to connect to camera service
      at android.hardware.Camera.<init>(Camera.java:568)
      at android.hardware.Camera.open(Camera.java:405)
      at com.user.qrReader.CameraPreview.openCamera(CameraPreview.java:206)
      at com.user.qrReader.CameraPreview.captureStart(CameraPreview.java:288)
      at com.user.qrReader.QRReaderAppManager.onResume(QRReaderAppManager.java:208)
      at com.user.qrReader.MainActivity.onResume(MainActivity.java:187)
      at android.app.Instrumentation.callActivityOnResume(Instrumentation.java:1286)
      at android.app.Activity.performResume(Activity.java:6987)
      at android.app.ActivityThread.performResumeActivity(ActivityThread.java:4145)
      at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:4250) 
      at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3361) 
      at android.app.ActivityThread.access$1100(ActivityThread.java:222) 
      at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1795) 
      at android.os.Handler.dispatchMessage(Handler.java:102) 
      at android.os.Looper.loop(Looper.java:158) 
      at android.app.ActivityThread.main(ActivityThread.java:7229) 
      at java.lang.reflect.Method.invoke(Native Method) 
      at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1230) 
      at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1120)

Here are the permissions in my manifest :

<uses-feature android:name="android.hardware.camera" android:required="true" />
<uses-feature android:name="android.hardware.camera.autofocus" />
<uses-permission android:name="android.permission.CAMERA" />

I can't use camera2 because I have to keep the app compatible for at least android 4, and I don't know which direction to go. It must be something related to the new android but I can't find what. Any thoughts ?

EDIT : You can also do it manually, once the apk is installed, by going to settings>applications>application manager>{my app}>permissions> allow camera. Of course it's terrible and useless if you have the solution but it helped me for a bit while I was debugging so I'll leave it here. Thank you for your answers.

回答1:

For Checking permission I created a separate class as below:

  public class MarshMallowPermission {

    public static final int RECORD_PERMISSION_REQUEST_CODE = 1;
    public static final int EXTERNAL_STORAGE_PERMISSION_REQUEST_CODE = 2;
    public static final int CAMERA_PERMISSION_REQUEST_CODE = 3;
    Activity activity;

    public MarshMallowPermission(Activity activity) {
        this.activity = activity;
    }

    public boolean checkPermissionForRecord(){
        int result = ContextCompat.checkSelfPermission(activity, Manifest.permission.RECORD_AUDIO);
        if (result == PackageManager.PERMISSION_GRANTED){
            return true;
        } else {
            return false;
        }
    }

    public boolean checkPermissionForExternalStorage(){
        int result = ContextCompat.checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE);
        if (result == PackageManager.PERMISSION_GRANTED){
            return true;
        } else {
            return false;
        }
    }

    public boolean checkPermissionForCamera(){
        int result = ContextCompat.checkSelfPermission(activity, Manifest.permission.CAMERA);
        if (result == PackageManager.PERMISSION_GRANTED){
            return true;
        } else {
            return false;
        }
    }

    public void requestPermissionForRecord(){
        if (ActivityCompat.shouldShowRequestPermissionRationale(activity, Manifest.permission.RECORD_AUDIO)){
           Toast.makeText(activity, "Microphone permission needed for recording. Please allow in App Settings for additional functionality.", Toast.LENGTH_LONG).show();
        } else {
            ActivityCompat.requestPermissions(activity,new String[]{Manifest.permission.RECORD_AUDIO},RECORD_PERMISSION_REQUEST_CODE);
        }
    }

    public void requestPermissionForExternalStorage(){
        if (ActivityCompat.shouldShowRequestPermissionRationale(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE)){
            Toast.makeText(activity, "External Storage permission needed. Please allow in App Settings for additional functionality.", Toast.LENGTH_LONG).show();
        } else {
            ActivityCompat.requestPermissions(activity,new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},EXTERNAL_STORAGE_PERMISSION_REQUEST_CODE);
        }
    }

    public void requestPermissionForCamera(){
        if (ActivityCompat.shouldShowRequestPermissionRationale(activity, Manifest.permission.CAMERA)){
            Toast.makeText(activity, "Camera permission needed. Please allow in App Settings for additional functionality.", Toast.LENGTH_LONG).show();
        } else {
            ActivityCompat.requestPermissions(activity,new String[]{Manifest.permission.CAMERA},CAMERA_PERMISSION_REQUEST_CODE);
        }
    }
}

Then, for getting

MarshMallowPermission marshMallowPermission = new MarshMallowPermission(this);


public void getPhotoFromCamera() {

    if (!marshMallowPermission.checkPermissionForCamera()) {
        marshMallowPermission.requestPermissionForCamera();
    } else {
        if (!marshMallowPermission.checkPermissionForExternalStorage()) {
            marshMallowPermission.requestPermissionForExternalStorage();
        } else {
            Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
            File mediaStorageDir = new File(
                    Environment.getExternalStorageDirectory()
                            + File.separator
                            + getString(R.string.directory_name_corp_chat)
                            + File.separator
                            + getString(R.string.directory_name_images)
            );

            if (!mediaStorageDir.exists()) {
                mediaStorageDir.mkdirs();
            }

            String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss",
                    Locale.getDefault()).format(new Date());
            try {
                mediaFile = File.createTempFile(
                        "IMG_" + timeStamp,  /* prefix */
                        ".jpg",         /* suffix */
                        mediaStorageDir      /* directory */
                );
                takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(mediaFile));
                startActivityForResult(takePictureIntent, PICK_FROM_CAMERA);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}


回答2:

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

}



回答3:

Camera class is deprecated since API 21.

Camera class documentation

recommend using the new android.hardware.camera2 API for new applications.

Sadly I haven't experienced it yet, so I won't be able to help more.

Still a minor chance to take, try:

    Camera.open(); // Without argument

Regards



回答4:

this is in Activity. I check permissions and save it, change some of my permissions to permission about CAMERA. This is working example.

private static final int REQUEST_CODE_GET_ACCOUNTS = 101;
    private static final int REQUEST_AUDIO_PERMISSION = 102;

 @TargetApi(23)
public void checkAudioPermission() {
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
        playerFragment.setupVisualizerFxAndUI();
        return;
    }
    if (this.checkSelfPermission(Manifest.permission.RECORD_AUDIO) != PackageManager
            .PERMISSION_GRANTED) {
        requestPermissions(new String[]{Manifest.permission.RECORD_AUDIO},
                REQUEST_AUDIO_PERMISSION);
    } else {
        playerFragment.setupVisualizerFxAndUI();
    }
}

@TargetApi(23)
public void checkGmailPermission() {
    if (isDeviceOnline()) {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
            showGmailRecFragment(true);
            return;
        }
        if (this.checkSelfPermission(Manifest.permission.GET_ACCOUNTS) != PackageManager
                .PERMISSION_GRANTED) {
            requestPermissions(new String[]{Manifest.permission.GET_ACCOUNTS},
                    REQUEST_CODE_GET_ACCOUNTS);
            return;
        } else {
            showGmailRecFragment(true);
        }
    } else {
        Utils.showToast(this, getString(R.string.no_internet));
    }
}

@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[]
        grantResults) {
    switch (requestCode) {
        case REQUEST_CODE_GET_ACCOUNTS:
            if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                showGmailRecFragment(true);
            } else {
                Utils.showToast(this, getString(R.string.accounts_permision_denied));
            }
            break;
        case REQUEST_AUDIO_PERMISSION:
            if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                playerFragment.setupVisualizerFxAndUI();
            } else {
                Utils.showToast(this, getString(R.string.audio_permission_denied));
            }
        default:
            super.onRequestPermissionsResult(requestCode, permissions, grantResults);
            break;
    }
}

and this is on fragment when i want check permission again or if user denied it. Also this save the permissions

private void setupViewVisualizer() {
    if (!isLiveTv && !homeVideo.isVideoType()) {
        ((PlayerActivity) activity).checkAudioPermission();
    } else {
        return;
    }
}