How to share current app's APK (or of other installed apps) using the new FileProvider?
Asked Answered
V

2

3

Background

In the past, it was easy to share an APK file with any app you wanted, using a simple command:

startActivity(new Intent(Intent.ACTION_SEND,Uri.fromFile(filePath)).setType("*/*"));

The problem

If your app targets Android API 24 (Android Nougat) or above, the above code will cause a crash, caused by FileUriExposedException (written about it here, and an example solution for opening an APK file can be found here) .

This actually worked for me fine, using below code:

    File apkFile = new File(apkFilePathFromSomewhereInExternalStorage);
    Intent intent = new Intent(Intent.ACTION_SEND);
    Uri fileUri = FileProvider.getUriForFile(this, getApplicationContext().getPackageName() + ".provider", apkFile);
    intent.setType("text/plain");
    intent.putExtra(Intent.EXTRA_STREAM, fileUri);
    intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    startActivity(intent);

However, the problem is that I also wish to be able to share the current app's APK (and also other installed apps).

For getting the path of the current app's APK, we use this:

        final PackageInfo packageInfo = getPackageManager().getPackageInfo(getPackageName(), 0);
        File apkFile=new File(packageInfo.applicationInfo.publicSourceDir);
        ...

This APK is accessible to all apps without the need of any permission, and so does the APK of every installed app.

But when I use this file with the above code for sharing using the FileProvider, I get this exception:

IllegalArgumentException: Failed to find configured root that contains ...

The same goes for when I use a symlinked file to the APK, as such:

        File apkFile=new File(packageInfo.applicationInfo.publicSourceDir);
        final String fileName = "symlink.apk";
        File symLinkFile = new File(getFilesDir(), fileName);
        if (!symLinkFile.exists())
            symLinkPath = symLinkFile.getAbsolutePath();
        createSymLink(symLinkPath, apkFile.getAbsolutePath());
        Uri fileUri= FileProvider.getUriForFile(this, getApplicationContext().getPackageName() + ".provider", symLinkFile);
        ...

public static boolean createSymLink(String symLinkFilePath, String originalFilePath) {
    try {
        if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
            Os.symlink(originalFilePath, symLinkFilePath);
            return true;
        }
        final Class<?> libcore = Class.forName("libcore.io.Libcore");
        final java.lang.reflect.Field fOs = libcore.getDeclaredField("os");
        fOs.setAccessible(true);
        final Object os = fOs.get(null);
        final java.lang.reflect.Method method = os.getClass().getMethod("symlink", String.class, String.class);
        method.invoke(os, originalFilePath, symLinkFilePath);
        return true;
    } catch (Exception e) {
        e.printStackTrace();
    }
    return false;
}

What I've tried

I tried to configure the provider_paths.xml file with various combinations of what I thought would help, such as any of those :

<external-path name="external_files" path="."/>
<external-path path="Android/data/lb.com.myapplication/" name="files_root" />
<external-path path="." name="external_storage_root" />
<files-path name="files" path="." />
<files-path name="files" path="" />

I also tried to disable the strictMode that's associated with this mechanism:

    StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
    StrictMode.setVmPolicy(builder.build());

The question

How can I share any APK file, from every possible path that's accessible to my app, including using symlinked files ?

Vanhomrigh answered 2/12, 2016 at 22:18 Comment(0)
I
3

But when I use this file with the above code for sharing using the FileProvider, I get this exception

That is because packageInfo.applicationInfo.publicSourceDir is not underneath one of the possible roots for FileProvider.

The same goes for when I use a symlinked file to the APK

Perhaps symlinks don't work here. One of your <files-path> elements — or one where you leave path off entirely — can serve ordinary files out of getFilesDir().

How can I share any APK file, from every possible path that's accessible to my app, including using symlinked files ?

Since there is no official support for symlinks, I can't help you there.

Otherwise, you have three main options:

  1. Make a copy of the APK file someplace that FileProvider likes

  2. Try using my StreamProvider with some custom extensions to teach it to serve from packageInfo.applicationInfo.publicSourceDir

  3. Write your own ContentProvider that serves from packageInfo.applicationInfo.publicSourceDir

Incubation answered 2/12, 2016 at 22:30 Comment(22)
I don't want to copy the file. Can you please show an example of any of the other solutions? I never used any of them. It's also very odd I can't share a globally available file, that apps can use anyway without any permission...Vanhomrigh
@androiddeveloper: "that apps can use anyway without any permission" -- that's not necessarily the case. Android supports multiple user profiles, and apparently there are some scenarios where an app running in one profile winds up providing Uri values to an app running in another profile. "Can you please show an example of any of the other solutions?" -- there is a demo app and a test suite in the CWAC-Provider repo. This sample app is a bit old but shows rolling your own provider.Incubation
The repo seems to have similar code of FileProvider by Google. What should be changed in the xml file ? Also, I failed to open it (it keeps trying to have minSdkVersion 1 for some reason). About global files, I don't understand: apps can share a file with apps on another user account?!Vanhomrigh
@androiddeveloper: "The repo seems to have similar code of FileProvider by Google" -- correct. I started with FileProvider and extended it. "What should be changed in the xml file ?" -- you would need to extend it to support your desired location, as I do not support it out of the box either. "apps can share a file with apps on another user account?" -- they shouldn't be able to, as each user gets a separate location on internal and external storage. Hence, the ban on using file as a scheme.Incubation
You mean before this, apps could share a file with apps on another user account? If not, I don't see why they changed it to have FileProvider, and if it did work this way, why couldn't they just block this behavior... Also, about the repo, I don't understand what to change there, and I can't import it correctly. Do you think the contentProvider solution should work?Vanhomrigh
@androiddeveloper: "before this, apps could share a file with apps on another user account?" -- no, and apparently this was a cause of a lot of crashes. Hence, the file scheme ban. "Do you think the contentProvider solution should work?" -- yes.Incubation
Yes, the contentProvider solution worked fine, even with symlinked files. Thank you. I still don't get why they changed it. Doesn't seem like an issue that needs fixing.Vanhomrigh
@androiddeveloper: I can see wanting to continue to nudge developers towards away from file Uri values. For example, your app can have access to files on removable storage that another app cannot. Plus, external storage as some sort of dumping ground for content exchange isn't great for security. The FileUriExposedException is rather heavy-handed, though.Incubation
Oh, it allows such apps to access the file, even if they don't have read-external-storage permission, yet with the file uri, they must have it?Vanhomrigh
@androiddeveloper: Correct. Think of a ContentProvider as a Web server: it makes content available (with its own security regime) through a standard protocol in cases where that content otherwise would be unavailable (e.g., in files that the client cannot access, in files that need to be decrypted, in BLOB columns of a database, generated on the fly, etc.). FileProvider is a specific Web server (Apache, nginx, etc.) that knows how to serve files.Incubation
I see. Say, can you please also help me with a similar issue here: https://mcmap.net/q/67127/-how-to-get-orientation-of-image-file-which-was-taken-from-fileprovider-class/878126 ?Vanhomrigh
What would happen if I use ContentProvider, yet the app I'm sharing with worked before using the old API ? If this isn't understandable, here's an image demonstrating the question: postimg.org/image/eyx5kf4zbVanhomrigh
@androiddeveloper: You are missing the third box in the right column, which is "supports both file and content schemes", which is what most receiving apps should do. For ACTION_SEND, there is no filtering on Uri scheme, since the Uri is in an extra. If the receiving app does not support your scheme, the receiving app crashes. For something like ACTION_VIEW, where the Uri is the "data" part of the Intent, scheme filtering via <intent-filter> comes into play. Apps that do not support your scheme simply will not be available options for the user.Incubation
Well, I could add another box there, but if it supports both, I don't have to worry about it. What I asked is about sharing the file (ACTION_SEND), so you tell me I can't know if the receiving app plays nice, so I can't know if what I do will work fine with it?Vanhomrigh
@androiddeveloper: "you tell me I can't know if the receiving app plays nice, so I can't know if what I do will work fine with it?" -- correct. It's one of the flaws in this system.Incubation
I see. Can you please show me a link/tutorial/sample for how to handle this sharing intent using both methods ? Maybe learning more about the receiving app could help me better understand the mechanics of this.Vanhomrigh
@androiddeveloper: Sorry, but all my book examples of ACTION_SEND are just for EXTRA_TEXT.Incubation
@androiddeveloper: I do not have an index of the world's Android sample code. You would need to search the Internet, looking for Android projects that implement ACTION_SEND via an <intent-filter> in their manifest, then look inside those activities to see whether they are handling EXTRA_STREAM or not.Incubation
I've noticed that this solution doesn't work with the Bluetooth app (that's built in on Vanilla) and also on SuperBeam app (third party). This even occurs when I try to share an APK on the external storage. How come?Vanhomrigh
@androiddeveloper: Ask the developers of the Bluetooth app and the developers of SuperBeam.Incubation
Yes I thought so. Sadly I don't see any app that can handle this issue. I thought I've found one (Total Commander , a file manager app), but its targetSdk is 23 ...Vanhomrigh
I've reported an issue about this here: code.google.com/p/android/issues/detail?id=234037Vanhomrigh
O
0

As I recall, the apk file lies in /data/app/your.package.name-1(or -2)/base.apk since SDK 21 (or 23? Correct me if I am wrong.). And /data/app/your.package.name.apk before that. So try this:

<root-path path="data/app/" name="your.name">

My answer here is not applicable in your case. I explained that in the comment section there.

PS: As @CommonsWare said, this feature is undocumented. There is a chance that it would be removed in the future. So there is risk using this.

Openwork answered 4/12, 2016 at 7:15 Comment(8)
Note that <root-path> is undocumented, and support for it could be removed at any time.Incubation
@Incubation Thank you for mentioning this. I was actually wondering why it is not in the document.Openwork
@Incubation Is using the "FileProvider" class actually the same as using a ContentProvider? Is it just a wrapper with its own special rules and API, and yet it does exactly the same as me implementing ContentProvider ?Vanhomrigh
@androiddeveloper: "Is using the "FileProvider" class actually the same as using a ContentProvider?" -- since FileProvider is a subclass of ContentProvider, yes. "Is it just a wrapper with its own special rules and API, and yet it does exactly the same as me implementing ContentProvider ?" -- it is a canned implementation of a ContentProvider, so that for common scenarios, developers do not have to implement their own in order to share content. Yours is not a common scenario.Incubation
@Incubation Well then, I could just create one that extends ContentProvider, and sends the exact same file that I wish to share, without any special configuration (because I know what I share anyway), and that's it... I don't get why we need the FileProvider class, if it's so restricting in rules and configuration. Developers already know what they share...Vanhomrigh
@androiddeveloper: "I could just create one that extends ContentProvider" -- that is covered in point #3 of my answer.Incubation
@Incubation I know. That's what I did. It's just weird that we have this class...Vanhomrigh
@Openwork I tried your code, but it doesn't seem to work with the bluetooth app. Same goes for "SuperBeam" app. Could be those apps' fault, but it worked fine on previous versions of my app, when I used the older API... :(Vanhomrigh

© 2022 - 2024 — McMap. All rights reserved.