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 ?

Community
  • 1
  • 1
android developer
  • 114,585
  • 152
  • 739
  • 1,270

2 Answers2

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

CommonsWare
  • 986,068
  • 189
  • 2,389
  • 2,491
  • 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... – android developer Dec 02 '16 at 23:23
  • @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](https://github.com/commonsguy/cw-omnibus/tree/master/ContentProvider/GrantUriPermissions) is a bit old but shows rolling your own provider. – CommonsWare Dec 02 '16 at 23:31
  • 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?! – android developer Dec 03 '16 at 00:46
  • @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](https://github.com/commonsguy/cwac-provider/blob/master/docs/EXTENDING.markdown) 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. – CommonsWare Dec 03 '16 at 00:49
  • 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? – android developer Dec 03 '16 at 13:53
  • @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. – CommonsWare Dec 03 '16 at 14:05
  • 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. – android developer Dec 03 '16 at 22:25
  • @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. – CommonsWare Dec 03 '16 at 22:36
  • 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? – android developer Dec 11 '16 at 07:52
  • @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. – CommonsWare Dec 11 '16 at 12:17
  • I see. Say, can you please also help me with a similar issue here: http://stackoverflow.com/q/41085950/878126 ? – android developer Dec 11 '16 at 12:19
  • 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: https://postimg.org/image/eyx5kf4zb/ – android developer Dec 16 '16 at 10:21
  • @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 `` comes into play. Apps that do not support your scheme simply will not be available options for the user. – CommonsWare Dec 16 '16 at 12:33
  • 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? – android developer Dec 16 '16 at 15:10
  • @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. – CommonsWare Dec 16 '16 at 15:15
  • 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. – android developer Dec 16 '16 at 21:22
  • @androiddeveloper: Sorry, but all my book examples of `ACTION_SEND` are just for `EXTRA_TEXT`. – CommonsWare Dec 16 '16 at 21:29
  • @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 `` in their manifest, then look inside those activities to see whether they are handling `EXTRA_STREAM` or not. – CommonsWare Dec 16 '16 at 21:36
  • 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? – android developer Feb 13 '17 at 15:32
  • @androiddeveloper: Ask the developers of the Bluetooth app and the developers of SuperBeam. – CommonsWare Feb 13 '17 at 15:37
  • 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 ... – android developer Feb 13 '17 at 15:49
  • I've reported an issue about this here: https://code.google.com/p/android/issues/detail?id=234037 – android developer Feb 13 '17 at 15:59
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.

Community
  • 1
  • 1
hqzxzwb
  • 4,661
  • 2
  • 14
  • 15
  • Note that `` is undocumented, and support for it could be removed at any time. – CommonsWare Dec 04 '16 at 12:29
  • @CommonsWare Thank you for mentioning this. I was actually wondering why it is not in the document. – hqzxzwb Dec 04 '16 at 14:56
  • @CommonsWare 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 ? – android developer Dec 05 '16 at 08:23
  • @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. – CommonsWare Dec 05 '16 at 12:16
  • @CommonsWare 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... – android developer Dec 05 '16 at 18:57
  • @androiddeveloper: "I could just create one that extends ContentProvider" -- that is covered in point #3 of my answer. – CommonsWare Dec 05 '16 at 19:02
  • @CommonsWare I know. That's what I did. It's just weird that we have this class... – android developer Dec 05 '16 at 22:24
  • @hqzxzwb 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... :( – android developer Feb 13 '17 at 15:30