How can I get read_phone_state permission at runtime to get IMEI number?
if not HasPermission('android.permission.READ_PHONE_STATE') then
begin
//ASK AND GET PERMISSION ?
end;
function TForm1.HasPermission(const Permission: string): Boolean;
begin
//Permissions listed at http://d.android.com/reference/android/Manifest.permission.html
{$IF RTLVersion >= 30}
Result := TAndroidHelper.Context.checkCallingOrSelfPermission(
{$ELSE}
Result := SharedActivityContext.checkCallingOrSelfPermission(
{$ENDIF}
StringToJString(Permission)) =
TJPackageManager.JavaClass.PERMISSION_GRANTED;
end;
EDIT: Sorry I didn't do a little more homework on FireMonkey. This is what I get for sticking my head into topics where it doesn't belong. I've added this content to try to make my answer more deserving of the bounty.
If you can restrict the targetSdk
on the app manifest to 22 (5.1 Lollipop) then the user will have to grant the permission on install so HasPermission
should never return false. (Not sure how that works with FireMonkey).
If you want to use the dynamic permissions capabilities in Marshmallow+, here is some information that I gleaned from this page:
You need to have access to the Activity
callback method onRequestPermissionsResult
. Here's all the hoops you will have to jump through:
- Use the open-source tool Dex2Jar convert the Android
classes.dex
file from Delphi back to Java so you can compile against the FMXNativeActivity
class.
- Code a subclass of
FMXNativeActivity
in Java that defines a native
method (let's call it onRequestPermissionsResultNative
and also overrides the onRequestPermissionsResult
method to call through to the native method.
- run
javac
to get a .class file with your subclass
- run
jar
to put the .class file into a .jar file
- run
dx.bat
to turn your .jar file into an Android .dex file
- run
DexMerger
to merge your .dex file into Delphi's classes.dex file
- Now all that's left to do is to write some tricky Delphi code to define your
onRequestPermissionsResultNative
method and register it with the JNI Environment. Oh, and don't forget to switch to the correct thread in your native method.
The link I referenced shows how to do this with onActivityResult
. You'll have to adapt these steps for the other method.
And I haven't even talked about how to handle the OS pausing your app to ask the user for permission and resuming it after.
Let this be a lesson to all: Don't put your faith in cross-platform tools; you will be disappointed.
I work in Java not Delphi, so you'll have to extrapolate a little bit here.
Like you, I have to get the IMEI number, and the system dialog asks the user something like: "Allow app to make and manage phone calls?" I need to explain to the user that that app is just getting the device ID and isn't going to make or manage phone calls. So
- Check if you have the permission
- If you don't have the permission, check if you should display an explanation
- If you don't need to show an explanation, start the permission request operation
I should mention that shouldShowRequestPermissionRationale
and requestPermissions
are methods on the Activity
class.
private static final int READ_PHONE_STATE_PERMISSIONS_REQUEST = 2;
private boolean mHasReadRationale;
void doPermissionsStuff() {
// version checking code omitted, this block runs for marshmallow and later
if (checkSelfPermission(Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED) {
// do the operation that needs the permission here
} else {
// the flag indicates if the rationale dialog has already been displayed
if (! mHasReadRationale && shouldShowRequestPermissionRationale(Manifest.permission.READ_PHONE_STATE)) {
// pop a dialog that explains what's going on to the user
} else {
requestPermissions(new String[] {Manifest.permission.READ_PHONE_STATE}, READ_PHONE_STATE_PERMISSIONS_REQUEST);
}
}
}
In the positive button of this dialog (i.e. user wants to continue) set the mHasReadRationale
flag to true and call doPermissionsStuff
again. (For Cancel, I send the user back to the previous screen.)
In order to get the result of the requestPermissions
operation you need to override the Activity
's onRequestPermissionsResult
method:
private boolean mPermissionDenied;
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
switch (requestCode) {
case READ_PHONE_STATE_PERMISSIONS_REQUEST:
// I'm only checking for one permission, so I make assumptions here
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// you can do the operation that needs the permission now
} else {
mPermissionDenied = true; // set a flag for checking later
}
}
}
Apparently when the system asks the user for the permission, it stops your app, so you can't show a UI at that point to tell the user you don't have permission. So I set a flag and when the app resumes, then I tell the user that the app doesn't have permission to do the operation.
@Override
protected void onResumeFragments() {
super.onResumeFragments();
if (mPermissionDenied) {
// show dialog to the user that the app can't do the operation because it doesn't have permission
mPermissionDenied = false;
}
}
So here's an example flow:
- User wants a free trial & app needs to get IMEI so they can't keep getting the free trial over and over again, jeez. App calls
doPermissionsStuff()
.
- App calls
checkSelfPermission()
and determines the permission isn't granted yet
- App calls
shouldShowRequestPermissionRationale()
. In my experience, shouldShowRequestPermissionRationale()
only returns true after the user has denied the permission once. So you don't display the rationale UI to the user yet.
- App calls
requestPermissions()
- The system will ask the user "Allow application to make and manage phone calls?"
- User decides this is WAAAAAAY too scary and presses the No button.
onRequestPermissionsResult()
is called with the deny result and the mPermissionDenied
gets set.
onResumeFragments()
gets called and a dialog is displayed to the user that they can't get the free trial because the app doesn't have permission.
- User decides to try again.
doPermissionsStuff()
is called.
- App calls
checkSelfPermission()
and (again) determines the permission isn't granted yet
- App calls
shouldShowRequestPermissionRationale()
. This time it returns true.
- App displays a calming and soothing message to the user that no, we aren't going to take over your phone, we just want the freakin' IMEI number, that's all, and if you don't allow the app to access the IMEI, you don't get a free trial. I have to draw the line somewhere.
- User presses continue, so the
mHasReadRationale
flag is set to true and doPermissionsStuff()
method gets called again.
- App calls
checkSelfPermission()
and — guess what? the permission isn't granted yet
- Since the flag is set, the user doesn't get the rationale UI.
- App calls
requestPermissions()
- The system will ask the user "Allow application to make and manage phone calls?"
- User resigns self to fate and presses Yes.
onRequestPermissionsResult()
is called with the granted result and the free trial registration moves forward.
You should also check out Google's sample code at https://developer.android.com/samples/RuntimePermissions/index.html