/, Patches, Project tweaks, Using APIs/Targeting Android 8 and higher, continued

Targeting Android 8 and higher, continued

In my last article, I discussed the new requirements for Android apps on Google Play to target Android 8 (API level 26) and higher, and described one possible way of handling permission requests at runtime, as required when targeting Android 6 (API level 23) and higher. In this article, I describe an alternative, and also cover the requirement for apps targeting Android 7.0 (API level 24) and higher when accessing “external” URIs.

As per the last article, this article and demo was produced using Delphi Tokyo 10.2.3. The code may or may not work with earlier versions.

UPDATE (July 20th, 2018): The demo has been modified to allow for a workaround for local notifications when targeting API 26 or greater. See below for details.

TL;DR: The demo code for this article can be found in the KastriFree project, here.

Targeting an API level

There has been some confusion over what “targeting” a particular API level means. It does not mean which Android SDK you have configured in the SDK Manager in the Delphi IDE. It means what value is set for the targetSdkVersion value in the application manifest:

This is the API level at which Android will act in “compatibility mode” for. By default, Delphi sets this value to 14 (Android 4.0). The device can still have a later version of Android installed, and your application can still use API calls that apply for the installed version, however it will act as if it were at the target level. For example, an app targeting API level 14 on a device running Android 6 will not require requesting permissions at runtime.

At present, to target a particular API level, you need to manually modify the AndroidManifest.template.xml file, as described in the previous article.

A change in the method for responding to runtime permission requests

In the previous article, I devised a method of requesting permissions at runtime that required “overriding” FMXNativeActivity in the manifest. Unfortunately, that meant having to launch the application from the device manually in order to debug via the IDE. Thanks to a suggestion from Stephane Vanderclock, author of the Alcinoe library, I’ve devised a different method that removes the need to “override” the activity.

Through necessity (which will become clearer later), I have created a new class called TPermissionsRequester, which more accurately reflects what it does anyway. This replaces the TSystemHelper class, which I recommend you stop using altogether, if you choose to change to using this new solution.

When a runtime permissions request is made, the focus moves away from the application, and once the user has granted or denied permission, the application receives focus again, and the BecameActive event fires. TPermissionsRequester handles this event, and if a permissions request has been made, checks whether such permissions were granted:

procedure TPlatformPermissionsRequester.ApplicationEventMessageHandler(const Sender: TObject; const AMsg: TMessage);
begin
  case TApplicationEventMessage(AMsg).Value.Event of
    TApplicationEvent.BecameActive:
      CheckPermissionsResults;
  end;
end;

procedure TPlatformPermissionsRequester.CheckPermissionsResults;
var
  LResults: TPermissionResults;
  LIndex, I: Integer;
begin
  if FRequestCode = -1 then
    Exit;
  SetLength(LResults, Length(FPermissions));
  for I := Low(FPermissions) to High(FPermissions) do
  begin
    LIndex := I - Low(FPermissions);
    LResults[LIndex].Permission := FPermissions[I];
    LResults[LIndex].Granted := TOSDevice.CheckPermission(FPermissions[I]);
  end;
  TOpenPermissionsRequester(PermissionsRequester).DoPermissionsResult(FRequestCode, LResults);
  FRequestCode := -1;
end;

So all the developer needs to be concerned with is requesting the actual permissions, and handling the OnPermissionsResult event:

constructor TForm1.Create(AOwner: TComponent);
begin
  inherited;
  FRequester := TPermissionsRequester.Create;
  FRequester.OnPermissionsResult := PermissionsResultHandler;
end;

procedure TForm1.TakePhotoButtonClick(Sender: TObject);
begin
  FRequester.RequestPermissions([cPermissionReadExternalStorage, cPermissionWriteExternalStorage, cPermissionCamera], cPermissionsCodeExternalStorage);
end;

As TPlatformPermissionsRequester (in DW.PermissionsRequester.Android) uses TApplicationEventMessage which comes from the FMX.Platform unit, it cannot be used in a service (at least at present). That’s why TPermissonsRequester was created, and CheckPermission method has been moved to TOSDevice, so that it can be used in a service.

Note that in this demo, permissions are requested only when absolutely required (i.e. when the user wants to take a photo). This makes the application a little more user friendly than if the permissions were requested as soon as the application starts.

In the code, I am passing the string representation of the permission, e.g.

cPermissionCamera = android.permission.CAMERA;

The format for all of the “dangerous” permissions is the same, i.e. it follows the pattern: android.permission.XXXX, where XXXX is one of the “Permissions” values in this list, so you can define your own values to be passed to the RequestPermissions method in the same manner.

Accessing “external” URIs

If you have tried to target API 26, and attempted to use the TTakePhotoFromCameraAction, you would have noticed that it fails, with the error:

android.os.FileUriExposedException: file:///storage/emulated/0/test.txt exposed beyond app through Intent.getData()

From Android 7.0 (API level 24), when accessing “external” URIs, it is necessary to use the FileProvider class. Unfortunately, the code that causes the problem for TTakePhotoFromCameraAction is actually within the Java binaries (FMX.jar) that Delphi uses, so rather than expect developers to patch and recompile FMX.jar, I’ve come up with an alternative solution.

TMediaLibrary is an alternative for (at least for the present), TTakePhotoFromCameraAction. I may or may not expand on it, depending on how useful it becomes, and how Embarcadero handle the required changes. It has a single method: TakePhoto, and has event handlers for when the image is captured successfully, or when the user canceled.

  TMediaLibrary = class(TObject)
  private
    FPlatformMediaLibrary: TCustomPlatformMediaLibrary;
    FOnCanceled: TNotifyEvent;
    FOnReceivedImage: TReceivedImageEvent;
  protected
    procedure DoCanceled;
    procedure DoReceivedImage(const AImagePath: string; const ABitmap: TBitmap);
  public
    constructor Create;
    destructor Destroy; override;
    procedure TakePhoto;
    property OnCanceled: TNotifyEvent read FOnCanceled write FOnCanceled;
    property OnReceivedImage: TReceivedImageEvent read FOnReceivedImage write FOnReceivedImage;
  end;

Also it is currently useful only on Android, as there are no implementations for the other platforms.

TMediaLibrary makes use of a function from the newly created unit DW.Android.Helpers, called UriFromFile. This method checks what the targetSdkVersion value is, and if >= 24 it uses the FileProvider class to create a Uri from the file reference, passing in the “authority” which needs to be specified in the manifest. This is done by adding a Provider section, like this:

When using the UriFromFile function, the value specified in the manifest for android:authorities will need to match the application’s package name. This value appears in the Project Options under Version Info.

Note the entry: <meta-data android:name=”android.support.FILE_PROVIDER_PATHS” android:resource=”@xml/provider_paths”/>. This is a reference to a file that needs to be deployed with the application, called (in this case): provider_paths.xml. This is what the file looks like:

This file needs to be added to the application’s deployment using Deployment Manager, and the Remote Path value set to: res\xml\

Remember that when targeting API level 24 or greater, your app will need to request the appropriate permissions at runtime (as per the demo), before attempting to use the TakePhoto method. I had considered making TMediaLibrary handle all this automatically; perhaps in the future 🙂 You should also remember that there are a number of other “dangerous” permissions that may be used throughout Delphi (e.g. by TLocationSensor). Be sure to request permissions at runtime for any of those that require it, before you attempt to use them.

Remember also that the UriFromFile function can be used in your own code if your app needs to access “external” files. You will certainly know this is the case if your application throws the dreaded FileUriExposedException.

Taking care of the status bar

In the previous article, it had slipped past me that when changing the API target, the status bar was no longer visible!

In this demo, the workaround was to change the Fill property of the form, setting the Color to Null, and the Kind to Solid. In addition, a Rectangle is added to the form, the Align property set to Contents, and the Color property of Fill set to Whitesmoke. Now the status bar is visible again, however remember that this is just a workaround; hopefully an official solution will present itself in the next update.

Getting notified..

The demo has been updated (July 20th, 2018) to allow for a workaround for local notifications. In order for them to work however, you will need to patch the System.Android.Notification unit from the Delphi source. Details on how to do this are in the readme. Also, you will need to use new files added to KastriFree, namely:

DW.Androidapi.JNI.App
DW.Androidapi.JNI.Support
DW.NotificationReceiver
support-compat-26.1.0.jar

Note also in the demo that the original android-support-v4.dex.jar is disabled. This is because the demo uses a newer version of the Android support libraries:

Are there other issues that need to be accounted for when targeting later API levels?

I’m glad you asked! At present, I’m unaware of any other issues, however my plan is to expand the demo in the future if any others come to light. If you are aware of any that are not related to what I’ve already covered, please let me know in the comments.

The demo code for this article can be found in the KastriFree project, here.

By |2018-09-17T15:06:32+00:00June 30, 2018 7:00 pm|General tips, Patches, Project tweaks, Using APIs|28 Comments

About the Author:

28 Comments

  1. Stiaan Pretorius July 3, 2018 at 4:25 pm - Reply

    Thank you for the article Dave. It will surely help allot. Have you posted you solution on the EMBT quality portal? I see there some issues logged there, and the guys are getting nervous. I don’t see any useful communication from EMBT regarding this issue.

    • Dave July 3, 2018 at 5:00 pm - Reply

      Can you let me know which issues in the quality portal that are affected that you know of, where I haven’t already commented about my workarounds? Thanks!

  2. JuanM July 8, 2018 at 4:07 am - Reply

    I’m sure Embarcadero read your posts. I Hope they support or sponsorize your work because I read a solution (with some problems of course) in your website several weeks before embt said any word.

    We are nervous because the countdown continues and we have no roadmap to solve this issue.

    Thank you very much Dave,and sorry, my English is not good

  3. MG July 20, 2018 at 9:56 am - Reply

    What NDK version and API version combo is able to be used? Will API 28 work ok with NDK r9c (Standard in Tokyo?)

    • Dave July 20, 2018 at 10:15 am - Reply

      API 27 (Android 8.1) works OK with it. I don’t have a device with API 28, so I could not say for sure. Perhaps someone else can check?

      • Matt July 20, 2018 at 10:53 am - Reply

        just installed NDK r17c and getting segfaults in app at TAndroidApplicationGlue.OnCreate at startup so guessing something is not compatible with Delphi with newer NDK’s, No EMB documentation on this at all. Read somewhere that someone was using NDK 14 or 15.

      • Matt July 20, 2018 at 12:30 pm - Reply

        Installing the 64 bit version of NDK seems to have worked, odd as r9c doesent look to be 64bit,
        So have API 28 and NDK r17b running our app.

        • Stefan W. August 3, 2018 at 10:54 pm - Reply

          Hello Matt,
          just a question regarding the SDK / NDK.
          Do you use SDK and NDK 17b 64 Bit downloaded from https://developer.android.com/ndk/downloads/ with Delphi Tokyo 10.2.3 and does this work?
          For some external libraries I have to update the NDK from 9 to somewhat higher but when I did I get crashes when my apps start.

  4. Kit Bayun July 25, 2018 at 5:09 am - Reply

    Android 5.0.2 errors: Invoke error: method not found:
    Project AndroidDangerousPermissions.apk raised exception class EJNIFatal with message ‘Invoke error: method not found’.

    line 288: raise EJNIFatal.CreateRes(@SJNIUnknownMethod); in unit unit Androidapi.JNIMarshal
    Delphi Tokyo

  5. […] Head over and check out the full solution for targeting Android 8 (SDK 26) with Delphi 10.2.3 Tokyo. […]

  6. […] Targeting Android 8 and higher, continued […]

  7. […] In light of this requirement, Embarcadero Technologies has announced that they plan to deliver support for Android API level 26 in their next major release of RAD Studio, 10.3. In the meantime, they suggest that their customers on Update Subscription join the upcoming 10.3 Beta. They also recommend that you take a look at Embarcadero MVP Dave Nottage’s blog post on how to target Android API level 26 with Delphi, C++Builder and RAD Studio 10.2.3 Tokyo (read it here).  […]

  8. Lucas Magalhães August 20, 2018 at 10:20 pm - Reply

    Do you know if for Delphi Berlin Aniversario version contemplates this your correction for the new permissions method?

    • Dave August 20, 2018 at 10:22 pm - Reply

      I don’t know what the Aniversario version is, however I do not use Berlin any more. Perhaps someone who does use it can check?

  9. Dêividy Alcãntara September 2, 2018 at 11:05 pm - Reply

    Hello Delphi Words, would you help me please. I’m having trouble getting “Gettubg notified”. I can not complete the last step.
    “3. Open a command prompt, change to the project folder, and execute the following command:”
    Could you tell me if you are having any problems with the procedure?

    1. Install the Patch tool from http://gnuwin32.sourceforge.net/packages/patch.htm
    Location of installation: C:\Program Files (x86)\GnuWin32\

    2. Copy System.Android.Notification.pas from the Delphi source (described above) to the demo project folder
    Copied to: D:\Embacadeiro\Projetos\KastriFree-master\KastriFree-master\Demos\AndroidAPI26\System.Android.Notification.pas

    3. Copy System.Android.Notification.10.2.3.API26.patch from the Workarounds folder in the KastriFree library (described above) to the demo project folder
    Copied to: D:\Embacadeiro\Projetos\KastriFree-master\KastriFree-master\Demos\AndroidAPI26\FMX.VirtualKeyboard.Android.10.2.3.patch

    3. Open a command prompt, change to the project folder, and execute the following command:
    1 Open CMD as administrator!
    2 I went in the project folder
    CMD – d:
    CMD – cd D:\Embacadeiro\Projetos\KastriFree-master\KastriFree-master\Demos\AndroidAPI26\

    3 \Patch System.Android.Notification.pas < System.Android.Notification.10.2.3.API26.patch
    Command executed: C:\Program Files (x86)\GnuWin32\Patch System.Android.Notification.pas < System.Android.Notification.10.2.3.API26.patch

  10. Sam September 15, 2018 at 1:24 am - Reply

    Hi Dave, brilliant solution for 10.2.3. However, have you thought of a solution concerning earlier versions of Delphi. It appears that some of the AndroidAPI units such as Androidapi.JNI.GraphicsContentViewText have changed for 10.2.3 when compared with 10.1 🙁

    • Dave September 15, 2018 at 8:13 am - Reply

      Hi Sam,

      Aside from the copyright notice, Androidapi.JNI.GraphicsContentViewText is identical between 10.1 and 10.2.3. Perhaps you mean some other unit(s)? If so, please describe exactly what is different, and what problems the differences cause.

  11. silvia September 19, 2018 at 3:24 am - Reply

    I’m lost ! I don know how o make this work. What I need is API26 requirement for Android (read external storage, push notifications).. even I dont know if i have to patch some tokio files and wich files

    1- Downloaded patch from http://gnuwin32.sourceforge.net/packages/patch.htm.. doesn´t work in w10 or at least I only see a command box.. if i write in it .. System.Android.Notification.pas < System.Android.Notification.10.2.3.API26.patch do NOTHING

    I followed all other steps (manifest,etc)

    What I missing? all… i cant go forward

    • Dave September 19, 2018 at 8:22 am - Reply

      Patch is a command-line program, i.e. you should open a command-line window to run it. As per the instructions, make a copy of System.Android.Notification.pas in the *same* folder as the demo project. It makes things easier if you copy the .patch file to the same folder, then change directory to the project folder and use this command:

      C:\Utils\patch System.Android.Notification.pas < System.Android.Notification.10.2.3.API26.patch Replacing C:\Utils with whatever folder you installed Patch in. This is the only source file that needs to be patched, and it is for Local Notifications to work in API 26+

      • silvia September 19, 2018 at 11:11 pm - Reply

        Thank you Dave. It works fine… the reason was because I was not running CMD as administrator

  12. Tom Keith September 21, 2018 at 4:27 pm - Reply

    Hi Dave, I have used the TMediaLibrary for the Camera Action fix, but it seems to rotate an image (90 deg Anticlockwise) when picture is taken in portrait on Android device, it’s OK if taken in landscape. I tried my same project on an old device (Android 4.4) where the my code will then use TTakePhotoFromCameraAction instead and the image comes in correctly in portrait (no rotation). Do you have any ideas what I can do to fix that?
    Thanks
    Tom

    • Dave September 23, 2018 at 1:25 pm - Reply

      Hi Tom,

      Not sure why it would be doing that (it works ok on both my devices), however there’s some code in this demo:

      https://github.com/DelphiWorlds/KastriFree/tree/master/Demos/ObtainPhotoInfoDemo

      That shows how to obtain information about an image using the ExifInterface Java class, specifically in the GetEXIF method of TForm1 in Unit1.pas. You could modify the code in TPlatformMediaLibrary.MessageResultNotificationHandler in the DW.MediaLibrary.Android unit to make a call like this:

      LEXIF.getAttributeInt(TJExifInterface.JavaClass.TAG_ORIENTATION, -1)

      which retrieves the orientation, and compare it to TJExifInterface.JavaClass.ORIENTATION_NORMAL, TJExifInterface.JavaClass.ORIENTATION_ROTATE_180 etc. You could then use the Rotate method of the LBitmap to correct the orientation.

      I’m unable to give a complete solution right now as I am travelling at the moment and my time is a bit limited. If you get stuck, let me know and I’ll see what I can do.

  13. Tom Keith September 24, 2018 at 7:08 am - Reply

    Thanks a lot for the reply I will look into that today. Another thing we discovered is the image is very large (probably original size) compared to the old Camera Action method, any suggestions on that would be appreciated.

    • Tom Keith September 24, 2018 at 1:11 pm - Reply

      Hi Dave, using your suggestion I got the Orientation corrected if it happens to be out, and have found a way to resize the image
      Thanks

Leave a Reply

Show Buttons
Hide Buttons