新しい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()
を呼び出します。アプリにカメラの権限がない場合は問題なく動作しますが、追加すると上記の例外が発生します。
@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();
}
}
私は同じ問題を抱えていて、Googleからこのドキュメントを見つけました: https://developer.Android.com/reference/Android/provider/MediaStore.html#ACTION_IMAGE_CAPTURE
「注:アプリがM以上をターゲットとし、付与されていないCAMERA権限を使用すると宣言した場合、このアクションを使用しようとするとSecurityExceptionが発生します。」
これは本当に奇妙です。まったく意味がありません。アプリは、IMAGE_CAPTUREアクションでインテントを使用してCameraExceptionを宣言し、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;
}
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カメラでカメラ権限が有効になっていないようです。結局、これは実際には後方互換性のように見えるかもしれません。
Googleカメラ(またはACTIONインテントを処理する他のアプリケーション)に特定の許可が必要であると仮定しましょう。
アプリにCAMERA権限がない場合は、古い権限モデルを使用してGoogleカメラに許可するだけです。
ただし、マニフェストでCAMERA権限が宣言されていると、Android M権限モデルを持たないGoogleカメラ内のCAMERA権限もAndroid M権限モデルを使用するように強制されます(おもう。)
つまり、上記の方法を使用するということは、実行時にアプリの許可を与える必要があります。つまり、子タスク(この場合はGoogleカメラ)にもその許可が与えられます。
「これはMの既知の問題ですか?」 Googleの開発者が、この問題をバグとして報告した人に応答しました。
Googleの男からの言葉は次のとおりです。「これは、ユーザーがアプリからカメラの許可を取り消し、アプリが意図を介して写真を撮ることができるというユーザーのフラストレーションを避けるための動作です。ユーザーは、許可の取り消し後に撮影された写真が異なるメカニズムを介して発生することを認識していないため、許可モデルの正確性に疑問を抱きます。これは、MediaStore.ACTION_IMAGE_CAPTURE、MediaStore.ACTION_VIDEO_CAPTURE、およびIntent.ACTION_CALLに適用されます。これらのドキュメントでは、Mを対象とするアプリの動作の変更が文書化されています。
Googleはユーザーからカメラを使用する仕組みを抽象化することを気にしないため、カメラ許可の最初のリクエストを戦略的にトリガーし、リクエストの理由としてカメラを使用するアクティビティの機能を参照することもできます。ユーザーが単に写真を撮ろうとしているときにアプリが最初にこの許可リクエストを行うことを許可した場合、写真を撮るには通常許可を与える必要がないため、ユーザーはアプリの動作がおかしいと思うかもしれません。
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;
}
}
}
意図した動作は、ユーザーが誤って「二度と尋ねない」をチェックした場合、アプリがスタックし(リクエストダイアログが表示されない)、ユーザーがイライラする可能性があることです。このように、メッセージは彼にこの許可が必要であることを伝えます。
少し遅れています。しかし、もう1つ追加します。カメラ機能を含むメソッドを呼び出すときは常に、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
}
}
}
追伸:オーバーライドされたメソッドをコピーして貼り付けないようにしてください。
私は削除しました:
uses-permission Android:name="Android.permission.CAMERA"
のみに依存:
uses-feature Android:name="Android.hardware.camera" Android:required="true"
マニフェストファイル内。
カメラを使用するには、アプリの許可を有効にする必要があります。カメラをアクティブにするこのメソッドのaddコマンドを追加することを好みます:
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;
}
私のこの方法は、カメラだけをチェックするのではなく、起動時にアプリが必要とするすべての権限を確認します... 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