Android M Camera Intent + ошибка разрешения?
Я пытаюсь подготовить свое приложение к новым изменениям разрешений Android M и обнаружил странное поведение. Мое приложение использует механизм намерения камеры, чтобы позволить пользователю получить изображение с камеры. Но в другом упражнении необходимо использовать саму камеру с разрешением камеры (из-за библиотеки зависимостей card.io, которая требует этого).
Тем не менее, с 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();
}
}
8 ответов
У меня была такая же проблема, и я нашел этот документ от Google: https://developer.android.com/reference/android/provider/MediaStore.html
"Примечание: если ваше приложение предназначено для 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;
}
Насколько ваш вопрос "Это известная проблема в М?" Разработчик Google ответил, что кто-то сообщил об этой проблеме как об ошибке.
Вот слово от парня из Google: "Это намеренное поведение, чтобы избежать разочарования пользователей, когда они отозвали разрешение камеры из приложения и приложение все еще могло делать фотографии через намерение. Пользователи не знают, что фотография, сделанная после отзыва разрешения, происходит с помощью другого механизма, и могут поставить под сомнение правильность модели разрешения. Это относится к MediaStore.ACTION_IMAGE_CAPTURE, MediaStore.ACTION_VIDEO_CAPTURE и Intent.ACTION_CALL документам, для которых документ изменяет поведение приложений, ориентированных на M."
Поскольку Google не возражает абстрагироваться от механизма использования камеры от вашего пользователя, вы также можете стратегически инициировать первый запрос на разрешение камеры и ссылаться на функциональность Активности, которая использует Камеру в качестве вашего обоснования для запроса. Если вы разрешаете своему приложению сначала сделать этот запрос на разрешение, когда пользователь просто пытается сделать снимок, пользователь может подумать, что ваше приложение ведет себя странно, поскольку для съемки фотографии обычно не требуется разрешение.
Если вы используете модель разрешений 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 не включено разрешение CAMERA. В конце концов, это может выглядеть как вещь обратной совместимости.
Предположим, что для Google Camera (или любого другого приложения, обрабатывающего ваше намерение ACTION) требуется определенное разрешение.
Когда ваше приложение не имеет разрешения CAMERA, оно просто позволяет Google Camera делать то же самое со старой моделью разрешений.
Однако с разрешением CAMERA, объявленным в вашем манифесте, оно также принудительно разрешает разрешение CAMERA в Google Camera (у которого нет модели разрешений Android M) для использования модели разрешений Android M (я думаю).
Таким образом, это означает, что при использовании вышеуказанного метода вам нужно будет предоставить разрешение своему приложению во время выполнения, что означает, что его дочерняя задача (в данном случае Google Camera) теперь также будет иметь это разрешение.
Если вы используете Google M, перейдите в Настройки -> Приложения -> ваше приложение -> и дайте соответствующие разрешения.
Я застрял в этой проблеме, и я уже использовал ответ 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;
}
}
}
Предполагаемое поведение состоит в том, что в случае, если пользователь по ошибке установил флажок "Никогда больше не спрашивать", ваше приложение застревает (диалоговое окно запроса не отображается), и пользователь может чувствовать разочарование. Таким образом, сообщение говорит ему, что ему нужно это разрешение.
Я удалил:
uses-permission android:name="android.permission.CAMERA"
и полагался только на:
uses-feature android:name="android.hardware.camera" android:required="true"
в файле манифеста.
Уже немного поздно но я хочу добавить еще одну вещь. всякий раз, когда вы вызываете методы, содержащие функции камеры, используйте их в блоке try catch. в противном случае приложение будет зависать на некоторых устройствах, таких как Moto G4 plus или 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
}
}
}
PS: убедитесь, что не скопировали вставьте переопределенный метод.
Этот мой метод не проверяет только камеру, но все разрешения, требуемые моим приложением во время запуска... У меня есть это в моем файле Helper.java. Также обратите внимание, что для диалога я использую эту библиотеку: https: // github. ком / afollestad / материал-диалоги
///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
};
В своем файле активности я проверяю разрешение следующим образом: класс Helper - это то место, где я хранил метод 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
Вы должны включить разрешение приложения для использования камеры. Я предпочитаю добавить этот метод, добавить команду, активирующую камеру:
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;
}