Firemonkey android read_phone_state runtime permis

2019-04-24 10:12发布

问题:

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;

回答1:

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