2

I am writing apps A and B. I want A to be able to send B a file URI of any given mime type. I found an explanation of how to handle someone making a request for a file, so more like a pull, where as I want to push a URI from A to B, without B requesting. I have also seen a more generic description of how to share data between apps, but that uses ACTION_SEND and, in that case, the user will be presented with a list of apps to choose from. I only want app B specifically to receive the file URI. In addition, I don't want app B to show up as a choice when someone is doing an ACTION_SEND for a different purpose. So, how do I send a file URI from one specific app to another specific app?

Edit 1: Adding some code snippets to the example.

In app A, I am doing the following:

Intent testIntent = new Intent("com.example.appB.TEST");
testIntent.addCategory(Intent.CATEGORY_DEFAULT);
testIntent.setData(Uri.parse("file:///mnt/sdcard/test.txt"));
startActivityForResult(testIntent, TEST);

In app B, I have the following in the manifest:

<activity
    android:name="com.example.appB.mainActivity"
    android:label="@string/app_name" 
    android:launchMode="singleTask" >
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
    <intent-filter>
        <action android:name="com.example.appB.TEST" />
            <category android:name="android.intent.category.DEFAULT"/>
            <data android:scheme="file"/>
            <data android:mimeType="*/*"/>
    </intent-filter>
</activity>

The error I get is ActivityNotFoundException. If I get rid of the setData line, the activity is launched, but obviously I don't have the URI.

You can read about more issues I hit here.

Community
  • 1
  • 1
corbin
  • 1,446
  • 2
  • 27
  • 40
  • What is the user flow? Are you looking to start an activity from B? – CommonsWare Aug 15 '14 at 16:39
  • The user will be using app A. They will click a button causing a particular file to be sent to a particular activity of app B. App B will interpret/process that file and then return a pass/fail result to app A. – corbin Aug 15 '14 at 16:48

5 Answers5

2

Call startActivity() for your activity in B, putting the Uri in the Intent. Assuming that you are following the recipe for sharing files via a FileProvider, all you need to do is include FLAG_ACTIVITY_GRANT_READ_PERMISSION as a flag on the Intent, and B will have temporary access to the file.

Here is a sample project where I share a PDF file via FileProvider, then have my activity start up a PDF viewer to go view that shared file. In this case, my app is A, the PDF viewer app is B.

CommonsWare
  • 986,068
  • 189
  • 2,389
  • 2,491
  • This is also a good solution but has the same issues with knowing the component in B that I mention in my answer. You can avoid that tight coupling using a custom ACTION string at the risk that another activity COULD specify that action string to intercept the intent. – Nick Palmer Aug 15 '14 at 17:13
  • @CommonsWare I have added some code above and the error I get. It is similar to what your app is doing when it calls the intent, but specifies a specific app+activity to launch instead of ACTION_SEND and I forgot to add the FLAG_ACTIVITY_GRANT_READ_PERMISSION, but I think that wouldn't cause the exception seen. What is wrong with what I tried? I will look into how to be more similar to the example. – corbin Aug 15 '14 at 17:38
  • @corbin: You may need to have an `android:scheme` attribute in `` to say what scheme you are supporting. Note that `FLAG_ACTIVITY_GRANT_READ_PERMISSION` is for sharing content via a `ContentProvider`; you are sharing it via a file. Bear in mind that your approach has security issues, though it could be that you are just using that as a stopgap approach for early testing. – CommonsWare Aug 15 '14 at 17:43
  • @CommonsWare Thanks, I understand the security concern. I set the scheme, see new snippet above. Same exception afterwards. – corbin Aug 15 '14 at 17:58
  • @corbin: The value for `android:scheme` would be `file` (or `content`, if you move to a `ContentProvider`), not `fileScheme`. Basically, it is the stuff to the left of the `:` at the beginning of your `Uri`. – CommonsWare Aug 15 '14 at 18:00
  • @CommonsWare Should have read further. The example I saw had "something" as the scheme, so I thought it was arbitrary. Ok. Changed to file, got same exception. – corbin Aug 15 '14 at 18:12
  • 1
    @corbin: Could you post the complete error, not just the exception class name? You might also try matching up the MIME types -- right now you are specifying `*/*` in B but providing nothing in A, and while I'd like to think that it would work, I wouldn't be surprised if you need to use `setDataAndType()` rather than `setData()` and supply a non-`null` MIME type. – CommonsWare Aug 15 '14 at 18:24
  • @CommonsWare I switched to using setDataAndType a couple of minutes ago and that does get the URI over to B. – corbin Aug 15 '14 at 18:26
1

You can do the following:

1.Make App B implement a custom broadcast receiver.

2.The custom broadcast will only be fired(broadcasted) by App A.

3.App B will catch(listen) the broadcast message which will also contain the file URI.

You can add signature level permission for the broadcast so that other apps can intercept(listen) for your broadcast.refer docs

android:permission The name of a permission that broadcasters must have to send a message to the broadcast receiver. If this attribute is not set, the permission set by the element's permission attribute applies to the broadcast receiver. If neither attribute is set, the receiver is not protected by a permission. For more information on permissions, see the Permissions section in the introduction and a separate document, Security and Permissions.

Similarly you can add permission to your file Uri too,to make it more secure.

rupesh jain
  • 3,410
  • 1
  • 14
  • 22
  • This has the down side that other applications COULD intercept the custom broadcast action. This can be avoided using a component at the expense of forcing A to know a component in B as I outline above. – Nick Palmer Aug 15 '14 at 17:25
  • Let me look into this. I added some code snippets. I was trying to do something more simplistic, but maybe that doesn't work? With regards to another app intercepting this, I am not worried about that. This is going to be free (as in speech) code, so specifying a component won't help. – corbin Aug 15 '14 at 17:36
  • @NickPalmer edited the answer so that no other app can listen for our broadcast – rupesh jain Aug 15 '14 at 18:47
  • Adding a permissions certainly helps @rupeshjain. – Nick Palmer Aug 16 '14 at 18:23
1

I would use a custom ACTION known to both applications so that it isn't using the generic ACTION_SEND that other applications know to use. This solves the problem of not wanting it to show up for ACTION_SEND. It also means that only applications which register for your custom action will be able to receive it. The down side is other applications COULD receive that intent if they know what string to use for the ACTION.

If you want to be absolutely sure that only B can receive the intent then you can specify a component. See: https://developer.android.com/reference/android/content/Intent.html#setComponent(android.content.ComponentName) If you do this it means that A has to know a component inside of B to direct the intent to, making B less flexible in terms of changing that component name. But it does mean the intent from A can only go to B.

Note that you still shouldn't trust the intent in B. Another application COULD specify the action string (decompiling your app, or watching logcat to find it) and B's component and send a malicious intent over. So always check the inputs in B.

Nick Palmer
  • 2,589
  • 1
  • 25
  • 34
  • I am using a custom action. See code snippets above. But, if I use setData, I get an ActivityNotFoundException. – corbin Aug 15 '14 at 17:40
0

I think the first three answers provided are all correct ways to make this work. Currently, I am using a small modification of my code. I needed to use setDataAndType and not just setData. Even though app B said it could handle any mimeType, not specifying the mimeType in app A made this not work.

You can read about more issues I hit here.

Community
  • 1
  • 1
corbin
  • 1,446
  • 2
  • 27
  • 40
0

Sorry for answering to an old post.

I can give the below scenario

1)Both apps should have same APPUSERID.

Workflow of APP2 wants to read data from APP1

1)It needs installation information of APP1. (Get the PackageManager)

2)Call ApplicationInfo.getApplicationInfo (Get the META-DATA)

Steps

1)Save the content you want to access in APP2 in a file in APP1.

This is in APP1

FileOutputStream fos = null
File file = null;
file = getFilesDir();
fos = openFileOutput("my.txt", Context.MODE_PRIVATE);
String text = "hello";
fos.write(text.getBytes());

In APP2

String packageName = "your.package.name.of.app1";

2)Then load the file saved in APP1

PackageManager packageManager = getPackageManager();

ApplicationInfo appInfo = packageManager.getApplicationInfo(packageName, PackageManager.GET_META_DATA);

String filePath = appInfo.dataDir + "/files/my.txt";

3) Then read the file using

FileInputStream fis = new FileInputStream (new File (filePath));
// Please write the further code to read the contents of the file 

4)Have the same userID in both APP1 and APP2, i.e. after packag="x.y.z" write the below line in both applications AndroidManifest.xml

android:sharedUserID = "a.b.c"; // Make sure to include this line in both app's manifest file. Here a.b.c and x.y.z are raw data.

P.S : I have not handled the exceptions.

Aniruddha
  • 4,477
  • 2
  • 21
  • 39