Android Question How to get "GetSafeDirDefaultExternal" value without RuntimePermissions lib?

JohnC

Expert
Licensed User
Longtime User
I am developing an app that I want to be as small as possible.

Using the GetSafeDirDefaultExternal location is perfect for my needs, but adding the RuntimesPermission lib (that provides this property) adds 500k to my app size.

Is there some other way to get this value without requiring the RuntimePermissions lib to be added to the project?
 
Last edited:

drgottjr

Expert
Licensed User
Longtime User
JOHNC AND I RELUCTANTLY DETERMINED THAT THERE IS NO SHORTCUT. DON'T WASTE YOUR TIME READING THE REST OF THE THREAD.


basically, safedirexternal has nothing to do with runtime permissions. i don't understand why
it was added to that library or why that library adds 500k to your app. it depends on other
factors which, i believe are already needed even without runtime permissions. whatever.

to answer your question, it is possible to access the so-called safedirexternaldefault folder
directly (in java). i do it in one of my libraries. you would need a create a library or work entirely
with javaobjects to perform the reading and writing of any files stored in that folder. whether this
cuts 500k off your app i can't say. if all you're doing is storing some files there and maybe
reading from them later, i think it would be easy enough to set that up. and, of course, comparing
apk size between one app that uses runtimepermission and another app that doesn't would be
easy. it's just not a great time of year; i'll see what i can do, unless you want to try yourself.
 
Last edited:
Upvote 0

JohnC

Expert
Licensed User
Longtime User
I just need to get the string value for this location and then my app will read and write to that string value (I don't think I need to do any writing and reading to it from java)

I found this page in my research:


And in the 14 vote answer it says:

B4X:
File mediaStorageDir = context.getExternalFilesDir(null);

So, is it possible to simply use a few lines of JavaObject code to get this value?
 
Upvote 0

JohnC

Expert
Licensed User
Longtime User
I know VERY little of Java, but I just lucked out guessing this code:
B4X:
    Dim ctxt As JavaObject
    ctxt.InitializeContext
    Log("SafeExternalDir=" & ctxt.RunMethod("getExternalFilesDir", Array("")))
 
Upvote 0

drgottjr

Expert
Licensed User
Longtime User
that's it. writing would normally require a separate thread, so it balloons from a one-liner. did you save 500k? you probably don't even need the 1-liner to derive where savedirexternal is. you just need to know the app's label (i think). the location is always something like android/data/app_label/files or some such.
 
Upvote 0

JohnC

Expert
Licensed User
Longtime User
Yup! Saved 500k!
 
Upvote 0

drgottjr

Expert
Licensed User
Longtime User
plus you can write to it. i just tested and saw/read the file from my pc

B4X:
Dim safedir As String = jo.RunMethod("getExternalFilesDir",Array(""))
Log(safedir)
File.WriteString( safedir, "test.txt", "just a test!")
 
Upvote 0

JohnC

Expert
Licensed User
Longtime User
Well, I found out why the GetSafeDirDefaultExternal was added to the RuntimePermissions lib - because if that lib is not included in an app that uses FileProvider, the app will crash with no errors at all - even if the lib is not actually used.

So, even though I am using my above code to generate the *same* exact directory location that rp.getsafedirdefaultexternal would return, the app will crash on start if the runtimepermissions library is NOT selected on the library tab in the IDE.
 
Last edited:
Upvote 0

drgottjr

Expert
Licensed User
Longtime User
not here it doesn't. (also with or without file provider. tried both ways)
 

Attachments

  • 1.png
    1.png
    23.8 KB · Views: 91
Last edited:
Upvote 0

JohnC

Expert
Licensed User
Longtime User
OK, attached is an example of the crashing if the RuntimePermission lib is not simply selected on the "Library Manager" tab (even though this project doesn't use it).

The attached sample is based on the pre-b4xpages example and before FileProvider class was an internal lib. Since I am using B4A 9.01, the file provider class is not internal, so it is included in this example. I don't know how easy it would be to modify an internal version of the class, but you need to be able to modofy it in order to remove reference to the runtimepermission lib. So, you might need to disable the internal file provider class on your PC if you can't edit it to do that modification.

The attached sample comments out the use of the RuntimePermision lib in the fileprovider class - so technically it should NOT be needed.

I then substituted my Javaobject code into the class to replace the rp.GetSafeDirDefaultExternal("shared") code - so again, technically the runtimepermission library should NOT be needed.

If you try running the attached project without the runtimepermission lib simply selected on the Library Manager tab in the IDE, the app will crash with the following log:

B4X:
FATAL EXCEPTION: main
Process: b4a.example.fileprovider, PID: 21618
java.lang.RuntimeException: Unable to get provider android.support.v4.content.FileProvider: java.lang.ClassNotFoundException: Didn't find class "android.support.v4.content.FileProvider" on path: DexPathList[[zip file "/data/app/b4a.example.fileprovider-Htg_YuHgyT5GYOMllrB8LQ==/base.apk"],nativeLibraryDirectories=[/data/app/b4a.example.fileprovider-Htg_YuHgyT5GYOMllrB8LQ==/lib/arm64, /system/lib64, /product/lib64]]
    at android.app.ActivityThread.installProvider(ActivityThread.java:6988)
    at android.app.ActivityThread.installContentProviders(ActivityThread.java:6528)
    at android.app.ActivityThread.handleBindApplication(ActivityThread.java:6445)
    at EdHooker_42b8453bf7df105343083f1377c32094be493c49.hook(Unknown Source:120)
    at android.app.ActivityThread.access$1300(ActivityThread.java:219)
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1859)
    at android.os.Handler.dispatchMessage(Handler.java:107)
    at android.os.Looper.loop(Looper.java:214)
    at android.app.ActivityThread.main(ActivityThread.java:7356)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)
Caused by: java.lang.ClassNotFoundException: Didn't find class "android.support.v4.content.FileProvider" on path: DexPathList[[zip file "/data/app/b4a.example.fileprovider-Htg_YuHgyT5GYOMllrB8LQ==/base.apk"],nativeLibraryDirectories=[/data/app/b4a.example.fileprovider-Htg_YuHgyT5GYOMllrB8LQ==/lib/arm64, /system/lib64, /product/lib64]]
    at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:196)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:379)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:312)
    at android.app.AppComponentFactory.instantiateProvider(AppComponentFactory.java:147)
    at android.app.ActivityThread.installProvider(ActivityThread.java:6972)
    ... 11 more
  Force finishing activity b4a.example.fileprovider/.main
add tag=data_app_crash isTagEnabled=true flags=0x2
Showing crash dialog for package b4a.example.fileprovider u0

It looks like the runtimepermission lib contains/resolves some simple reference that is needed for the fileprovider class to work.

I wish there was a way to fix this error so that I truly won't need the runtimepermission lib which doubles the size of my app (my app is only 450k without the runtimepermission lib)
 

Attachments

  • FileProviderCrashingExample.zip
    20.5 KB · Views: 88
Last edited:
Upvote 0

drgottjr

Expert
Licensed User
Longtime User
SEE TOP. THERE IS NO SHORTCUT.

looks to me like the same story as with modified okhttputils2:
tricky part in fileprovider.bas is VERY localized (2 lines!). SharedFolder is public so you could set it
yourself from main after identifying safedirexternal via javaobject. then you could
set fileprovider.sharedfolder by hand and comment out the 2 lines that refer to rp.

or, if you like, move your javaobject routine into fileprovider.bas and do it all
from there. comment out the 2 lines that refer to rp
 
Last edited:
Upvote 0

JohnC

Expert
Licensed User
Longtime User
I don't think the problem is with the "name" (value) of the shared folder.

I think the problem is that this class not only provides a location for files to be shared, but this class also creates a "File Provider Service" that allows the files to be shared using a URI (instead of a simple path), and these lines in the manifest enable that service:

B4X:
AddApplicationText(
  <provider
  android:name="android.support.v4.content.FileProvider"
  android:authorities="$PACKAGE$.provider"
  android:exported="false"
  android:grantUriPermissions="true">
  <meta-data
  android:name="android.support.FILE_PROVIDER_PATHS"
  android:resource="@xml/provider_paths"/>
  </provider>
)
CreateResource(xml, provider_paths,
   <files-path name="name" path="shared" />
)

So maybe the runtimepermission lib contains the support code for this "File Provider Service".
 
Last edited:
Upvote 0

drgottjr

Expert
Licensed User
Longtime User
SEE TOP. THERE IS NO SHORTCUT
i don't think it works that way. the only files an app can share are its
own. it becomes its own fileprovider in the manifest. to share its
files, an app copies them to the shared folder (this is what happens in my apps that
use fileprovider). other apps query the fileprovider to find out where your app's shared
folder is. did you try what i suggested? i can get to it sometime. i hope my using
b4a 12 doesn't cause an issue with what you're using. also the
fileprovider library i would use is the one in fileprovider.b4xlib in the core libraries folder of b4a.
i think once you identify where the shared folder is to the fileprovider class, you won't need
runtimepermissions. fileprovider.bas does that by querying rp.getsafeexternalblahblah("").
you're doing with the javaobject line. same result. just stuff the public sharedfolder variable
with it, and you're good to go (after removing references to rp from fileprovider.bas)
 
Last edited:
Upvote 0

JohnC

Expert
Licensed User
Longtime User
I tried what you said, but the app still crashed without the runtimepermission lib.

Well, I was able to fix the crashing by adding this line:

B4X:
#AdditionalJar: com.android.support:support-v4

Now I can remove the runtimepermission lib without it crashing, but including that additional jar still added 500K.

So now I just need to think of a way to just import the fileprovider support class without having to import the whole set of support-v4 classes.

(I could have sworn I remember Erel showing a way to just load in specific classes and not a whole set...looking for that thread now)
 
Upvote 0

drgottjr

Expert
Licensed User
Longtime User
yeah, i didn't look close enough; he's got it buried in the class module: fp.InitializeStatic("android.support.v4.content.FileProvider")
and he only uses it in 1 line. Return fp.RunMethod("getUriForFile", Array(context, Application.PackageName & ".provider", f))
to get the location of the shared folder. i'm thinking there's gotta be a way to get that without all that luggage...
 
Upvote 0

drgottjr

Expert
Licensed User
Longtime User
DOESN'T WORK. NO SHORTCUT. SEE TOP
also if you already know where the shared folder is, why run "getUriForFile"? obviously, this bypasses everything, but if it works, no harm no foul. i need to see what
getUriForFile returns. i'm smelling fudge somewhere.
 
Last edited:
Upvote 0

JohnC

Expert
Licensed User
Longtime User
When you use the File Provider service, your app doesn't need the WRITE_EXTERNAL_STORAGE permission to share a file - because it uses this special "URI" method to provide another app a secure way to access a file of my app.

I need to share an APK file to the system installer so it can install it, so it needs to be shared using an URI, and it appears it needs the "android.support.v4.content.FileProvider" class to generate a URI for a file to be shared.
 
Upvote 0

drgottjr

Expert
Licensed User
Longtime User
that geturiforfile returns what i think is a plug. look at the attached.
i took erel's fileprovider example and queried the fileprovider for the uri. the fileprovider class treats it as an object (when logged it's a string), but you might be able to use the object instead of having to call geturiforfile. that uri looks pretty simple to mimic, no?

wait, i get what you're saying: google might not let you publish with all the irregular stuff.
 

Attachments

  • 1.png
    1.png
    18.3 KB · Views: 65
Upvote 0

JohnC

Expert
Licensed User
Longtime User
Yes, I can see the string value is "content://b4a.example.provider/name/4ba.png".

But there is no directory called "b4a.example.provider", so its not a real path - it's a special URI that I think the File Provider service knows how to share a file using that URI.

But I did simply try to pass the actual "string" path to the apk file instead of using an URI using this code:

B4X:
    If P.SdkVersion >= 24 Then
        'i.Initialize("android.intent.action.INSTALL_PACKAGE", Starter.Provider.GetFileUri("update.apk"))
        Log("FS: " & "file://" & File.Combine(Starter.Provider.SharedFolder, "update.apk"))
        i.Initialize("android.intent.action.INSTALL_PACKAGE", "file://" & File.Combine(Starter.Provider.SharedFolder, "update.apk"))
        i.Flags = Bit.Or(i.Flags, 1) 'FLAG_GRANT_READ_URI_PERMISSION
    Else
        i.Initialize(i.ACTION_VIEW, "file://" & File.Combine(Starter.Provider.SharedFolder, "update.apk"))
        i.SetType("application/vnd.android.package-archive")
    End If
    StartActivity(i)

But I got a "There was a problem parsing the package" error.
 
Last edited:
Upvote 0

drgottjr

Expert
Licensed User
Longtime User
it's not "file://"; it's "content://". "file" implies something that "content" doesn't. as to whether fileprovider knows something that nobody else knows or does something that nobody else can do is unclear (at least to me). in the fileprovider class discussed earlier, the only reference to com.android.support:support-v4 was the single reference to geturiforfile, which returns the magic location. based on that, i'm suggesting that if you know the magic location beforehand, you don't need support-v4 (at least for that part). whether fileprovider uses support-v4 for other things, i haven't look at android's code.
 
Last edited:
Upvote 0
Top