0

I would like to open a PDF that is allocated locally in my application (ANDROID only), but I am not getting it at all.

I already tried this link: Getting exception while opening PDF file for Android 26 using Firemonkey/Delphi but not work in Delphi RIO.

So I kept looking and got this code:

procedure TF_NovaART.PRO_MapeaClick(Sender: TObject);
var
  fName       : String;
  LIntent: JIntent;
  LAuthority: JString;
  LUri: Jnet_Uri;
begin
    fName := TPath.GetDocumentsPath + PathDelim + 'PRO_Topo.pdf';

    {$IFDEF ANDROID}
      LAuthority := StringToJString(JStringToString(TAndroidHelper.Context.getApplicationContext.getPackageName) + '.fileprovider');
      LUri := TJFileProvider.JavaClass.getUriForFile(TAndroidHelper.Context, LAuthority, TJFile.JavaClass.init(StringToJString(fName)));
      LIntent := TJIntent.JavaClass.init(TJIntent.JavaClass.ACTION_VIEW);
      LIntent.setDataAndType(LUri, StringToJString('application/pdf'));
      LIntent.setFlags(TJIntent.JavaClass.FLAG_GRANT_READ_URI_PERMISSION);
      TAndroidHelper.Activity.startActivity(LIntent);

    {$ENDIF}

end;

Which was found on this link: Getting exception while opening PDF file for Android 26 using Firemonkey/Delphi

I did exactly as it is written, but returns me the following error:

java;lang;NullPointerException: Attempt to invoke virtual method 'android.content.res.XmlResourceParser android.content.pm.Package ItemInfo.loadXmlMetaData(android.content.pm.PackageManager, java.lang;String)' on a null object reference

now it's giving :

java.kabg.illegalArgumentExcpetion: Failed to find configurated root that contains /data/data/com.embarcadero.ART_Dinamica/files/PRO_Topo.pdf.

PS: I put my pdf (PRO_Topo.pdf) in the deployment and the remote patch is ".\assets\internal"

I think I need to put in the "AndroidManifest", but I've already researched it and I'm not sure how to set it. (This bug has been stalking me for over a month. I skipped this part of the code to do other things, but again I'm unable to continue because of it)

The GIT link I got from the File Provider drive is this: https://github.com/DelphiWorlds/KastriFree/blob/master/API/DW.Androidapi.JNI.FileProvider.pas

If anyone can help me I will be sooo grateful.

Below I will leave my AndroidManifest.template.xml

<?xml version="1.0" encoding="utf-8"?>
<!-- BEGIN_INCLUDE(manifest) -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="%package%"
        android:versionCode="%versionCode%"
        android:versionName="%versionName%"
        android:installLocation="%installLocation%">

    <uses-sdk android:minSdkVersion="%minSdkVersion%"        
    android:targetSdkVersion="%targetSdkVersion%" />
    <%uses-permission%>
    <uses-feature android:glEsVersion="0x00020000" android:required="True"/>
    <application android:persistent="%persistent%" 
        android:restoreAnyVersion="%restoreAnyVersion%" 
        android:label="%label%" 
        android:debuggable="%debuggable%" 
        android:largeHeap="%largeHeap%"
        android:icon="%icon%"
        android:theme="%theme%"
        android:hardwareAccelerated="%hardwareAccelerated%"
        android:resizeableActivity="false">

        <%provider%>
        <%application-meta-data%>
        <%services%>
        <!-- Our activity is a subclass of the built-in NativeActivity framework class.
             This will take care of integrating with our NDK code. -->
        <activity android:name="com.embarcadero.firemonkey.FMXNativeActivity"
                android:label="%activityLabel%"
                android:configChanges="orientation|keyboard|keyboardHidden|screenSize"
                android:launchMode="singleTask">
            <!-- Tell NativeActivity the name of our .so -->
            <meta-data android:name="android.app.lib_name"
                android:value="%libNameValue%" />
            <intent-filter>  
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter> 
        </activity>
        <%activity%>
        <%receivers%>
    </application>
</manifest>
<!-- END_INCLUDE(manifest) -->

Now my AndroidManifest is:

<uses-sdk android:minSdkVersion="19" android:targetSdkVersion="26" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.CALL_PHONE" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

<uses-feature android:glEsVersion="0x00020000" android:required="True"/>
<application android:persistent="False" 
    android:restoreAnyVersion="False" 
    android:label="ART_Dinamica" 
    android:debuggable="True" 
    android:largeHeap="False"
    android:icon="@drawable/ic_launcher"
    android:theme="@style/AppTheme"
    android:hardwareAccelerated="true"
    android:resizeableActivity="false">

    <provider
        android:name="android.support.v4.content.FileProvider"
        android:authorities="com.embarcadero.ART_Dinamica.fileprovider"
        android:exported="false"
        android:grantUriPermissions="true">
        <meta-data android:name="android.support.FILE_PROVIDER_PATHS"

android:resource="@xml/provider_paths"/>

    <!-- Our activity is a subclass of the built-in NativeActivity framework class.
         This will take care of integrating with our NDK code. -->
    <activity android:name="com.embarcadero.firemonkey.FMXNativeActivity"
            android:label="ART_Dinamica"
            android:configChanges="orientation|keyboard|keyboardHidden|screenSize"
            android:launchMode="singleTask">
        <!-- Tell NativeActivity the name of our .so -->
        <meta-data android:name="android.app.lib_name"
            android:value="ART_Dinamica" />
        <intent-filter>  
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter> 
    </activity>

    <receiver android:name="com.embarcadero.rtl.notifications.NotificationAlarm" />

</application> </manifest> <!-- END_INCLUDE(manifest) -->

My provider_paths.xml:

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
  <external-path name="external_files" path="."/>
</paths>

Deployment image

Deployment

user3602803
  • 99
  • 5
  • 12
  • Have you checked the checkbox for the Secure File Sharing option in the Entitlements section of the Project Options? – Dave Nottage Nov 20 '19 at 06:04
  • Yes ... =\ marked or unmarked from the same ... – user3602803 Nov 20 '19 at 07:19
  • I updated my post, there is a new error. If you can read please ... – user3602803 Nov 20 '19 at 07:52
  • Can you check the Deployment Manager to make sure that the provider_paths.xml file is being deployed? – Dave Nottage Nov 20 '19 at 08:18
  • This you were asking for? I put a picture in the post – user3602803 Nov 20 '19 at 08:29
  • Uncheck the first one in the list – Dave Nottage Nov 20 '19 at 08:30
  • Tested here, same error =\ – user3602803 Nov 20 '19 at 08:44
  • I unchecked what is like "type" file. I put my provider_paths.xml so you can make sure everything is correct – user3602803 Nov 20 '19 at 08:45
  • Just to clarify, I meant uncheck the first provider_paths.xml. You may need to uninstall the app, do a Clean and Build – Dave Nottage Nov 20 '19 at 08:50
  • I updated the deployment print in the post. About re-installation, I formatted my notebook a few weeks ago and had to re-install delphi. Is it really necessary to reinstall it? Since it wasn't that long ago that I installed it (maximum 2 weeks) – user3602803 Nov 20 '19 at 08:55
  • I meant uninstall the Android app from the device – Dave Nottage Nov 20 '19 at 08:55
  • Oh yes, every time I make a new version of the app I uninstall and install it again. So this should not be the problem. =\ – user3602803 Nov 20 '19 at 08:57
  • A question that may be silly: Doesn't provider_paths have to be "interal-path"? Since I am trying to insert the PDF inside the APK? Another thing: If this doesn't work at all, do you know any other way I can open a PDF? Even if it's via an online link ... (because I need to deliver this Thursday) – user3602803 Nov 20 '19 at 09:02
  • [This SE question](https://stackoverflow.com/questions/49200962/delphi-10-2-how-can-i-open-a-url-in-androids-web-browser-from-my-application) has answer that you can follow to use intents to open a web page. It will work with pdf as well - e.g. `http://somesite.com/your.pdf`. His solution put it in Function form but you can do it however. – relayman357 Nov 20 '19 at 18:02
  • @user3602803 [This helped](https://en.delphipraxis.net/topic/1361-android-how-to-call-a-tjintent/) me figure out how to open a local pdf. The key was down at the bottom about using `LUri := TAndroidHelper.JFileToJURI(TJFile.JavaClass.init(StringToJString(AFileName)));` instead of the `file://` approach... – relayman357 Nov 20 '19 at 21:17

1 Answers1

0

The problem comes down to a missing entry in the provider_paths.xml file that is generated by Delphi. In order for your scenario to work, the file should look like this:

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-path name="external_files" path="." />
    <files-path name="files" path="." />
</paths>

One solution is to modify the original provider_paths.xml file so that it contains the missing entry, add it to the projects deployment ensuring that the remote path is res\xml\, and uncheck the deployment for the original.

Given that the PDF being deployed is called sample.pdf, and is deployed to .\assets\internal, this code will open the PDF if a viewer is installed:

uses
  System.IOUtils,
  Androidapi.JNI.GraphicsContentViewText, Androidapi.Helpers, Androidapi.JNI.Net, Androidapi.JNI.JavaTypes;

procedure TForm1.Button1Click(Sender: TObject);
var
  LIntent: JIntent;
  LFile: JFile;
  LFileName: string;
begin
  // NOTE: You will need a PDF viewer installed on your device in order for this to work
  LFileName := TPath.Combine(TPath.GetDocumentsPath, 'sample.pdf');
  LFile := TJFile.JavaClass.init(StringToJString(LFileName));
  LIntent := TJIntent.JavaClass.init(TJIntent.JavaClass.ACTION_VIEW);
  LIntent.setDataAndType(TAndroidHelper.JFileToJURI(LFile), StringToJString('application/pdf'));
  LIntent.setFlags(TJIntent.JavaClass.FLAG_GRANT_READ_URI_PERMISSION);
  TAndroidHelper.Activity.startActivity(LIntent);
end;

I've put together a test app that does what I've described, here:

https://github.com/DelphiWorlds/MiscStuff/blob/master/Test/OpenPDFTest.zip

Dave Nottage
  • 3,411
  • 1
  • 20
  • 57