Recently I have encountered a very strange issue while working with a React-Native app. Here's my environement:
Rect-Native 0.42.3
Samsung Galaxy Note 5
Android 6.0
As you may know, the Android permission model has changed since Marshmallow, but some days ago, I got his exception:
java.lang.RuntimeException:
at android.app.ActivityThread.performResumeActivity(ActivityThread.java:4156)
at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:4250)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1839)
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:0)
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.ArrayIndexOutOfBoundsException:
at com.facebook.react.modules.permissions.PermissionsModule$1.invoke(PermissionsModule.java:119)
at com.facebook.react.modules.permissions.PermissionsModule.onRequestPermissionsResult(PermissionsModule.java:207)
at com.facebook.react.ReactActivityDelegate$1.invoke(ReactActivityDelegate.java:211)
at com.facebook.react.ReactActivityDelegate.onResume(ReactActivityDelegate.java:131)
at com.facebook.react.ReactActivity.onResume(ReactActivity.java:66)
at android.app.Instrumentation.callActivityOnResume(Instrumentation.java:1286)
at android.app.Activity.performResume(Activity.java:6987)
at android.app.ActivityThread.performResumeActivity(ActivityThread.java:4145)
First, I thought I was missing a check in the code, as explained here but this is not the case as otherwise I would have a message like this instead:
java.lang.SecurityException: Permission Denial: opening provider com.android.providers.contacts.ContactsProvider2 from ProcessRecord{fac49ea 4674:com.gospacesmobile/u0a65} (pid=4674, uid=10065) requires android.permission.READ_CONTACTS or android.permission.WRITE_CONTACTS
I looked at React-Native's code and found this:
// com.facebook.react.modules.permissions.PermissionsModule.java
int[] results = (int[]) args[0];
if (results[0] == PackageManager.PERMISSION_GRANTED) { // <- the exception happens when results is empty
promise.resolve(GRANTED);
} else {
PermissionAwareActivity activity = (PermissionAwareActivity) args[1];
if (activity.shouldShowRequestPermissionRationale(permission)) {
promise.resolve(DENIED);
} else {
promise.resolve(NEVER_ASK_AGAIN);
}
}
According the documentation, the grantResults variable may be empty whenever the permission request has been canceled:
#Override
public void onRequestPermissionsResult(int requestCode,
String permissions[], int[] grantResults) {
switch (requestCode) {
case MY_PERMISSIONS_REQUEST_READ_CONTACTS: {
// If request is cancelled, the result arrays are empty.
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// permission was granted, yay! Do the
// contacts-related task you need to do.
} else {
// permission denied, boo! Disable the
// functionality that depends on this permission.
}
return;
}
// other 'case' lines to check for other
// permissions this app might request
}
}
Unfortunately, I do not have the client code (as the crash report in the google console dev does not mention it). In any case, shouldn't React-Native check for empty grantResults? Is this a bug? If yes, how could this be reproduced? I don't see any way this could be done, except by canceling the promise responsible for ensuring if a permission has been enabled or not (which is impossible considering react-native permissions API).
Any thought is very welcome. Thank you in advance.
So I finally found the problem. Whenever the permission request dialog appear, if the user minimizes the app, and then goes back to it by clicking on the app's icon in from the home menu, the results is empty which makes the app crashing.
This seems to be quiet a simple issue, and I've decided to monkey patch React-Native in order to prevent an unstable upgrade (Now I check if the results array is empty or not, and if it is, then deny the request). I still don't know if this is a common issue or something that comes from my app.
Related
I have an app that I published, and it has something like this:
requestStoragePermission();
and the function:
private void requestStoragePermission(){
ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},STORAGE_PERMISSION_CODE);
}
#Override //user response
public void onRequestPermissionsResult(int requestCode, #NonNull String[] permissions, #NonNull int[] grantResults) {
if(requestCode == STORAGE_PERMISSION_CODE){
//If permission is granted
if(grantResults.length >0 && grantResults[0] == PackageManager.PERMISSION_GRANTED){
//Displaying a toast
String string = getString(R.string.permissionOk);
Toast.makeText(this,string,Toast.LENGTH_LONG).show();
}
else{
//Displaying another toast if permission is not granted
String string = getString(R.string.permissionError);
Toast.makeText(this,string,Toast.LENGTH_LONG).show();
}
}
}
It works ok on android 6... but how will it be interpretation on android 4, for example, which does not need permissions? is it a bad practise?
On API levels where it is not required for the user to grant a dangerous permission, requesting that permission does not pop up a dialog for the user and the permission is automatically granted. This still adheres with Google's security policy because for those API levels the user has to grant the permissions during the installation process instead.
Some will tell you to check the API level in your code and only request the permission if it is below 23. This is just an extra layer on top of what the code already does internally and thus increases the complexity of your code. You don't have to do that.
There is no way to request persmission in android 4.
You simply have to add the permissions in android-manifest file and everything will be taken care of.
However, make sure your request permission code is not executed in these versions. For that use something like:
if(Build.VERSION.SDK_INT<23){
//DoNOtRequestPersmissions
}
In my projects, to request permissions, I usually have a function that I call arePermissionsGranted(). In that function, if the API level is lower than 23, it automatically says 'the permissions are granted', since you do not need to request them. If API level is 23 or higher, it checks if the permissions have been granted and if not, requests them.
I'm developing an app that require some system permissions, however these are no longer granted automatically at installation time on Android Marshmallow.
I would like to request these permissions at runtime and run some kind of automation to grant them without needing a user to tap the 'allow' button when the System permissions Dialog appears.
How can I achieve this? Is there any way to do so in Marshmallow and later versions?
For Marshmallow or later permissions are not granted at install time and must be requested when required at runtime (if not granted previously.)
To do this, you need to run ActivityCompat.requestPermissions() to pop up the systems permissions dialog in your Activity, at the time when the user is undertaking an action that requires additional system permissions.
An example of this for the WRITE_EXTERNAL_STORAGE permission would be:
ActivityCompat.requestPermissions(
this,
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
WRITE_EXTERNAL_STORAGE_REQUEST_CODE
);
Note: WRITE_EXTERNAL_STORAGE_REQUEST_CODE is an arbitrary integer constant you should define elsewhere.
The permissions you request should also be declared in your AndroidManifest.xml. In this example the declaration would be:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
In order to handle the system permissions dialog response you will also need to implement onRequestPermissionsResult() in your Activity. For this example the code would be similar to
#Override
public void onRequestPermissionsResult(int requestCode, #NonNull String permissions[], #NonNull int[] grantResults) {
if (grantResults.length == 0 || grantResults[0] == PackageManager.PERMISSION_DENIED) {
return; //permission not granted, could also optionally log an error
}
if (requestCode == WRITE_EXTERNAL_STORAGE_REQUEST_CODE) {
//Do whatever you needed the write permissions for
}
}
If you are automating your app through Espresso, UIAutomator and/or some other UI testing framework you will need to anticipate and click the system dialog during your test, which can be accomplished with the following test code:
private void allowPermissionsIfNeeded() {
if (Build.VERSION.SDK_INT >= 23) {
UiObject allowPermissions = mDevice.findObject(new UiSelector().text("Allow"));
if (allowPermissions.exists()) {
try {
allowPermissions.click();
} catch (UiObjectNotFoundException e) {
Timber.e(e, "There is no permissions dialog to interact with ");
}
}
}
}
A more comprehensive explanation of testing System UI Permissions is available here.
I found out that an simpler way to automate the permission acceptance without using UIAutomator or espresso in a CI scenario is to simply, pre-installing the apk via adb using:
adb install -g {my_apk_file}
The -g flag automatically grants all manifest permissions to the app. Afterwards if you launch your espresso test suite, the ui won't ask you again to grant them.
I'm trying to get the user to enable the location group permission for my app with Cordova's hasPermission/requestPermission methods, but the results are confusing...
When I call hasPermission with ACCESS_FINE_LOCATION, it always returns true. Calling this on Manifest.permission_group.LOCATION seems to return true/false appropriately.
Calling requestPermission with Manifest.permission_group.LOCATION doesn't present a system dialog, so I'm calling this with ACCESS_FINE_LOCATION to get the dialog.
The dialog Allow button turns on the Location group permission for my app and calls onRequestPermissionResult with PackageManager.PERMISSION_GRANTED, but the Deny button also returns ...GRANTED, leaving the Location group permission off.
For illustration, here's my current code:
private void checkPermissions() {
if (!cordova.hasPermission(Manifest.permission_group.LOCATION)) {
cordova.requestPermission(this, PERMISSION_RUNTIME_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION);
}
}
#Override
public void onRequestPermissionResult(int requestCode, String[] permissions, int[] grantResults) throws JSONException {
if (permissions.length != 1 || grantResults.length != 1 || !Manifest.permission.ACCESS_FINE_LOCATION.equals(permissions[0])) {
throw new RuntimeException("Unexpected permission results " + Arrays.toString(permissions) + ", " + Arrays.toString(grantResults));
}
int result = grantResults[0];
String action = null;
switch (result) {
case PackageManager.PERMISSION_DENIED:
action = Constants.ACTION_RUNTIME_PERMISSION_DENIED;
break;
case PackageManager.PERMISSION_GRANTED:
action = Constants.ACTION_RUNTIME_PERMISSION_GRANTED;
break;
default:
throw new RuntimeException("Unexpected permission result int " + result);
}
Intent i = new Intent(action);
i.putExtra("permission", Constants.EXTRA_RUNTIME_PERMISSION_NOTIFICATION_ID);
getContext().sendBroadcast(i);
}
What's the right way to handle this? Sometimes using an individual permission and sometimes using a group with these methods doesn't seem right - I would expect this to be consistent. My guess is that the PERMISSION_GRANTED after the DENY button is pushed on the dialog is because I'm requesting an individual permission, which is on even though the group is off; is there a way to detect that the user denied the request?
I have a lot of questions there which basically boil down to "how do I either get the user to enable the Location group permission when it's off or know when they decline"?
If it helps, my android-targetSdkVersion is set to 22, and I'm using Cordova 6.1.1.
If it helps, my android-targetSdkVersion is set to 22, and I'm using Cordova 6.1.1.
Android run-time permissions were only introduced in API 23, so if your android-targetSdkVersion is set to 22, run-time permissions code will always return GRANTED for any permission, since permissions are granted at installation time via the manifest.
However, if your app is displaying runtime permissions dialogs, I'm guessing that you must be building against API 23 and using cordova-android#5+ for the Android platform.
Regarding permission groups vs individual permissions, you should read the Android documentation regarding runtime permissions:
The dialog box shown by the system describes the permission group your app needs access to; it does not list the specific permission. For example, if you request the READ_CONTACTS permission, the system dialog box just says your app needs access to the device's contacts. The user only needs to grant permission once for each permission group. If your app requests any other permissions in that group (that are listed in your app manifest), the system automatically grants them. When you request the permission, the system calls your onRequestPermissionsResult() callback method and passes PERMISSION_GRANTED, the same way it would if the user had explicitly granted your request through the system dialog box.
So in your case, requesting ACCESS_FINE_LOCATION grants access to all permissions in the LOCATION group (you can find the full list of groups and permissions here).
the Deny button also returns ...GRANTED, leaving the Location group permission off.
This should not return GRANTED. If the Deny button is pressed, access will be DENIED to that entire permission group (including the requested permission). The logic in your code snippet looks OK to handle this, so I would use the step-through debugger in Android Studio to see exactly what is happening in your code here.
I'm trying to use the new Android camera2 api. I started with source from this tutorial : http://jylee-world.blogspot.com/2014/12/a-tutorial-of-androidhardwarecamera2.html . When I try to usb-debug-deploy it to any phone, I get a SecurityException from CameraManager.openCamera(...).
My AndroidManifest looks like this:
<uses-feature android:name="com.android.hardware.camera2.full"/>
<uses-permission android:name="android.permission.CAMERA"/>
This seems to be what every tutorial I've been able to find does. I'm able to get permission for other actions; for example, I can make the camera vibrate just fine. I'm also able to enumerate cameras with CameraManager.getCameraIdLists() just fine, but I'm not sure if that actually requires permission. But I can't openCamera.
Are there some additional permissions I need? Am I doing something wrong?
Thanks for the help!
This is my full stack trace:
SecurityException
java.lang.SecurityException: Lacking privileges to access camera serviceat android.hardware.camera2.utils.CameraBinderDecorator.throwOnError(CameraBinderDecorator.java:108)
at android.hardware.camera2.legacy.CameraDeviceUserShim.connectBinderShim(CameraDeviceUserShim.java:336)
at android.hardware.camera2.CameraManager.openCameraDeviceUserAsync(CameraManager.java:327)
at android.hardware.camera2.CameraManager.openCamera(CameraManager.java:457)
at com.example.quinnfreedman.camera2test.MainActivity$1.onSurfaceTextureAvailable(MainActivity.java:74)
at android.view.TextureView.getHardwareLayer(TextureView.java:368)
at android.view.View.updateDisplayListIfDirty(View.java:15167)
at android.view.View.draw(View.java:15964)
at android.view.ViewGroup.drawChild(ViewGroup.java:3612)
at android.view.ViewGroup.dispatchDraw(ViewGroup.java:3402)
at android.view.View.updateDisplayListIfDirty(View.java:15185)
at android.view.View.draw(View.java:15964)
at android.view.ViewGroup.drawChild(ViewGroup.java:3612)
at android.view.ViewGroup.dispatchDraw(ViewGroup.java:3402)
at android.view.View.updateDisplayListIfDirty(View.java:15185)
at android.view.View.draw(View.java:15964)
at android.view.ViewGroup.drawChild(ViewGroup.java:3612)
at android.view.ViewGroup.dispatchDraw(ViewGroup.java:3402)
at android.view.View.updateDisplayListIfDirty(View.java:15185)
at android.view.View.draw(View.java:15964)
at android.view.ViewGroup.drawChild(ViewGroup.java:3612)
at android.view.ViewGroup.dispatchDraw(ViewGroup.java:3402)
at android.view.View.draw(View.java:16197)
at com.android.internal.policy.PhoneWindow$DecorView.draw(PhoneWindow.java:2690)
at android.view.View.updateDisplayListIfDirty(View.java:15190)
at android.view.ThreadedRenderer.updateViewTreeDisplayList(ThreadedRenderer.java:281)
at android.view.ThreadedRenderer.updateRootDisplayList(ThreadedRenderer.java:287)
at android.view.ThreadedRenderer.draw(ThreadedRenderer.java:322)
at android.view.ViewRootImpl.draw(ViewRootImpl.java:2627)
at android.view.ViewRootImpl.performDraw(ViewRootImpl.java:2446)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2079)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1119)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:6060)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:858)
at android.view.Choreographer.doCallbacks(Choreographer.java:670)
at android.view.Choreographer.doFrame(Choreographer.java:606)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:844)
at android.os.Handler.handleCallback(Handler.java:746)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:148)
at android.app.ActivityThread.main(ActivityThread.java:5443)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:728)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:618)
In Android M, run time permission check is required for dangerous permission. You can see dangerous permission here.
Check for permission :
// Assume thisActivity is the current activity
int permissionCheck = ContextCompat.checkSelfPermission(thisActivity,
Manifest.permission.CAMERA);
If the app has the permission, the method returns PackageManager.PERMISSION_GRANTED, and the app can proceed with the operation. If the app does not have the permission, the method returns PERMISSION_DENIED, and the app has to explicitly ask the user for permission.
For details: https://developer.android.com/training/permissions/requesting.html#perm-request
Just close your camera device in onSurfaceTextureDestroyed function
onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture){cameraDevice.close();cameraDevice = null;}
Security exception will get fix
When you are running your application on android 6+, you need to give the famous runtime permissions.
https://developer.android.com/training/permissions/requesting.html
The permission you are trying to give is considered a dangerous permissions android.
https://developer.android.com/guide/topics/security/permissions.html#normal-dangerous
Different of other responses about runtime permission, I suggest you to use this https://github.com/Karumi/Dexter
This lib makes the permission handling easily
Struggled with this off and on for weeks, thinking several times I'd solved the problem. In the end, none of the "fixes" I'd read about here worked. Then, after putting in ~100 Log.v statements in my Java, I realized it was a threading issue that might, or might not, kick off this error depending on events on the camera. Basically, I think, the main program was running on the main thread, but there was an extra thread kicked off by the following statement:
//this code seems to be the culprit ... commenting it out solve my problem
private void showToast(final String text) {
final Activity activity = MyStupidProgram.this;
if (activity != null) {
activity.runOnUiThread(new Runnable() {
#Override
public void run() {
Toast.makeText(activity, text, Toast.LENGTH_SHORT).show();
}
});
}
}
So while there's nothing in this statement calling the camera, perhaps because of thread safety, Android 5.x and 6.x were throwing security errors when I called showToast('some crap');
Commenting that out and just using a Toast.makeText('blah blah'); statement, I was able to get rid of the security error.
Additionally, I added this to the code on the page's onCreate(); statement, to catch any issues on the main thread:
Thread.setDefaultUncaughtExceptionHandler(
new Thread.UncaughtExceptionHandler() {
#Override
public void uncaughtException(
Thread paramThread,
Throwable paramThrowable
) {
//Do your own error handling here
if (exceptionHandler != null)
exceptionHandler.uncaughtException(
paramThread,
paramThrowable
); //Delegates to Android's error handling
else
System.exit(2); //Prevents the service/app from freezing
}
});
this is regarding the new model of runtime permissions introduced in Android Marshmallow when requesting Manifest.permission.WRITE_EXTERNAL_STORAGE permission.
In short, what I am experiencing is that if I request (and the user allows) Manifest.permission.WRITE_EXTERNAL_STORAGE permission, the app won't be able to read and write from the external storage directory until I destroy and restart the app.
This is what I am doing/experiencing:
My app starts from a state where:
ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED
This is, I don't have permissions to to access external storage.
Then, I request permission to Manifest.permission.WRITE_EXTERNAL_STORAGE just as Google explains
private void requestWriteExternalStoragePermission() {
// Should we show an explanation?
if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
new AlertDialog.Builder(this)
.setTitle("Inform and request")
.setMessage("You need to enable permissions, bla bla bla")
.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
ActivityCompat.requestPermissions(MendeleyActivity.this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, RC_PERMISSION_WRITE_EXTERNAL_STORAGE);
}
})
.show();
} else {
ActivityCompat.requestPermissions(MendeleyActivity.this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, RC_PERMISSION_WRITE_EXTERNAL_STORAGE);
}
}
Once the user allows the permission, onRequestPermissionsResult gets invoked.
#Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
switch (requestCode) {
case RC_PERMISSION_WRITE_EXTERNAL_STORAGE: {
// If request is cancelled, the result arrays are empty.
if (grantResults.length > 0 && PackageManager.PERMISSION_GRANTED
// allowed
} else {
// denied
}
break;
}
}
}
The allowed block is executed, confirming the user has granted permissions.
Immediately after this, if I don't destroy and open the app again, I still have no access permission to external storage. More specifically:
hasWriteExternalStoragePermission(); // returns true
Environment.getExternalStorageDirectory().canRead(); // RETURNS FALSE!!
Environment.getExternalStorageDirectory().canWrite(); // RETURNS FALSE!!
So, it seems the Android runtime thinks I have permissions, but the file system doesn't...
Indeed, trying to access Environment.getExternalStorageDirectory() throws the exception:
android.system.ErrnoException: open failed: EACCES (Permission denied)
at libcore.io.Posix.open(Native Method)
at libcore.io.BlockGuardOs.open(BlockGuardOs.java:186)
at libcore.io.IoBridge.open(IoBridge.java:438)
at java.io.FileOutputStream.<init>(FileOutputStream.java:87)
at java.io.FileOutputStream.<init>(FileOutputStream.java:72)
If I now destroy the app and open it again, the behaviour becomes as it should, being able to read and write in the external storage folder.
Is anyone experiencing this?
I am using one official emulator with:
Latest Android 6.0 (API 23) API 23, Rev 1.
Emulator running Intel x86 Atom System Image, API 23, Rev 1.
I build the app with:
android {
compileSdkVersion 23
buildToolsVersion "22.0.1"
defaultConfig {
minSdkVersion 16
targetSdkVersion 23
}
...
}
If someone confirms this and I am not the only one I guess we'll need to open a bug, but I hope I am doing something wrong, as I think such a core feature is unlikely to be buggy in the SDK.
http://developer.android.com/reference/android/Manifest.permission.html#WRITE_EXTERNAL_STORAGE:
Starting in API level 19, this permission is not required to read/write files in your application-specific directories returned by getExternalFilesDir(String) and getExternalCacheDir().
Runtime permissions start at API level 23, obviously above 19, so the permission is not required anymore unless you're accessing the data outside of the folder pointed by getExternalFilesDir(). Therefore, I believe this is a bug only present when testing on an emulator.
On targets below level 19, which don't support requesting permission at runtime, just claim the permission in manifest and it will work.
I had the same problem. Turns out this seems to be a bigger issue. Changing the permission to write to the external storage changes the GID for this process (on the linux side).
In order to change the ID the process has to be restarted. Next time you open the app, the new groupID is set and the permission is granted.
Long story short, I'm afraid this is not a bug in the emulator but in fact a bigger issue with Linux and Android.
I "solved" this by asking for permission the first time the app is executed and restarting it when the permission is given like this:
PackageManager packageManager = getPackageManager();
Intent intent = packageManager.getLaunchIntentForPackage(getPackageName());
ComponentName componentName = intent.getComponent();
Intent mainIntent = IntentCompat.makeRestartActivityTask(componentName);
startActivity(mainIntent);
System.exit(0);
You may try to create a service running in the background (having another process id) and giving it the permission. That way you would only need to restart the service and not the complete app. On the down side this might make more work for you.
Hope this helps.
--- EDIT ---
The user M66B (https://stackoverflow.com/a/32473449/1565635) found a list of the related gids. Further information can be found here: https://android.googlesource.com/platform/frameworks/base/+/master/data/etc/platform.xml
For Android 10+
In my case, it was:
If targeting Android 10 (API level 29) or higher, set the value of requestLegacyExternalStorage to true in your app's AndroidManifest file.
Just add this below code in your Application block.
<application
android:requestLegacyExternalStorage="true"
... >