0

I have a scenario in Android (SDK 19/KitKat 4.4.2) where my application is to be signed as a system level application (App 1) using android:sharedUserId="android.uid.system" in the Manifest.xml. This means that this application is unable to write or read from SD cards, be they external or built into the device.

If I needed to obtain a large file from the SDCard and read it into my application, what is the best approach to do this?

My goal is simply to obtain image files from the SDCard. However, even images can be relatively big if they're uncompressed bitmaps.

I've tried the following approaches:

  1. Creating a new application that is not signed as the system user (App2). Starting a service that exists in this App2 from App1, then reading in the file from the SD card from here, then obtaining the byte[] of the file, and sending it over via AIDL to App1 in chunks. This works in terms of reading the file from the SDCard and sending it over, however AIDL has a cap of 1mb for each transaction and is also very slow to a point where I should probably limit the size of images allowed to be given to the application to make this feature usable. Not the most ideal in my opinion.

  2. I've tried using FileProvider in App 2 (UID: 10007), however in this scenario I need to not open any graphical interface to select the file I want and a target application. I need to just send it over immediately to App 1 (UID: 10047) or obtain it immediately from App1. I'm not sure if it's possible to use FileProvider without those gui steps. I tried just creating the Uri from App2 then sending the Uri to App1 over AIDL, then giving permissions via context.grantUriPermissions(packageName,uri,READ/WRITE), but always end up with a security error where App1 does not have permission to read the uri App2 is providing.

java.lang.SecurityException: No permission grant found for UID 10047 and Uri content://com.test.sdcard/folder/img.png

Where UID 10047 is App 1 and UID 10007 is App 2.

Any alternative solutions to this problem?

Phantômaxx
  • 37,901
  • 21
  • 84
  • 115
jtompkj
  • 93
  • 2
  • 8

1 Answers1

1

A system app can't read from external storage? That's news to me, but anyway.

You can always just create a pipe and pass the read end back over the IPC mechanism of your choice (ContentProvider.call() for example). The service-side starts a thread and writes the file to the write end, and passes the read end back to the client. Something like this on the service side:

  ParcelFileDescriptor[] fds;
  try {
    fds = ParcelFileDescriptor.createPipe();
  } catch (IOException e) {
    e.printStackTrace();
    return false;
  }

  final ParcelFileDescriptor readFd = fds[0];
  final ParcelFileDescriptor writeFd = fds[1];

  // TODO: start a thread to write file to writeFd

  Bundle result = new Bundle();
  extras.putParcelable(EXTRA_READ_FD, readFd);

  return result; // Return from call() to client

Obviously there is a lot of boilerplate code left as an exercise for the reader.

Jeffrey Blattman
  • 22,176
  • 9
  • 79
  • 134
  • Struggling with this approach. Could you illustrate an example with how you write data into the writeEnd, then read the data back using the readEnd? I was following this https://stackoverflow.com/questions/18212152/transfer-inputstream-to-another-service-across-process-boundaries-with-parcelf which seems similar to what you suggest, but I encountered the error mentioned with java.io.IOException: read failed: EBADF (Bad file number) – jtompkj Nov 08 '18 at 23:18
  • Nevermind, managed to figure it out using the getFileDescriptor(byte[] data) snippet from here. https://stackoverflow.com/questions/51902834/how-to-return-byte-data-with-androids-parcelfiledescriptor-and-documents-provid – jtompkj Nov 09 '18 at 16:53
  • I have a concern regarding the start of the thread on the service side, and the reading of the Descriptor on the client side. In the snippet you provide, it seems that the client can retrieve the readEnd before there is actually any data to be read, given that you spawn a thread to write to the writeEnd. I tried implementing a delay (thread.sleep) in the thread to write on the service side, but it seems that my client is able to read the data fine regardless (albeit delayed) despite that I start my read on the client side as soon as that result in your snippet is returned. Why does this work? – jtompkj Nov 09 '18 at 21:50
  • If nothing is written to the read end (yet), the IO will block. The pipe is still open. Think of it like making a network request and the server hasn't returned any data yet. The network request doesn't fail just because there's no data to read. – Jeffrey Blattman Nov 09 '18 at 21:55
  • This was a valid method that worked for my purposes, thanks. – jtompkj Dec 19 '18 at 00:02