6

FileDescriptor API in Android says:

Instances of the file descriptor class serve as an opaque handle to the underlying machine-specific structure representing an open file, an open socket, or another source or sink of bytes.

I want to create a FileDescriptor object using ByteArrayOutputStream and ByteArrayInputStream

Also, FileDescriptor is a final class and cannot be overridden. The only constructor it has says -

Constructs an (invalid) FileDescriptor object.

Any idea how to use FileDescriptor in Android?

EDIT

I want to use it in MediaMuxer. Instead of writing to a file, I want to have the media data in-memory and copy it to a TCP socket for live streaming. So my FileDescriptor should be a "sink of bytes."

PC.
  • 6,870
  • 5
  • 36
  • 71
  • 1
    where do you want to return that `FileDescriptor` from? from a `ContentProvider`? – pskink Oct 24 '17 at 18:25
  • Use `ParcelFileDescriptor.createPipe()` and kin, though I'm not sure it's going to do what you want it to. – CommonsWare Oct 24 '17 at 18:27
  • I want to use it in `MediaMuxer (FileDescriptor fd, int format)` and so that I can write the bytes to output stream of a TCP socket without writing the data to a file. – PC. Oct 24 '17 at 18:34
  • then use `ParcelFileDescriptor#fromSocket()` method – pskink Oct 24 '17 at 18:36
  • 1
    #fromSocket() did not work for me. `E/MPEG4Writer: cannot seek mFd: Illegal seek (29) 47` OutputStream of TCP socket cannot be seeked. So looking for another solution that involves ByteArrayOutputStream with FileDescriptor. – PC. Oct 24 '17 at 19:23
  • so try what @CommonsWare said: create a pipe, but i think you will get the same `"cannot seek mFd"` error - i believe that only physical file is "seekable" – pskink Oct 24 '17 at 19:36
  • @PC. Did you find a solution? – mh taqia Jun 14 '18 at 10:54
  • @mh taquia No. I didn't. – PC. Jun 14 '18 at 11:26
  • @PC. How did you solve it? – Abu Noman Jun 25 '20 at 16:29

2 Answers2

1

Use a LocalSocket, but be careful because there are security implications.

LocalSocket is the Android API for working with Unix Domain Sockets. Domain Sockets are similar to TCP/IP sockets except that they only exist on the device, they cannot be used for communication across a network.

A simple, but insecure solution to your problem is as follows:

// Create a unique name for the socket.
String name = "your.package.name-" + UUID.randomUUID();

// Bind a server to the socket.
final LocalServerSocket server = new LocalServerSocket(name);

// Connect a client to the socket.
LocalSocket client = new LocalSocket(LocalSocket.SOCKET_STREAM);
client.connect(new LocalSocketAddress(name, LocalSocketAddress.Namespace.ABSTRACT));

// Start a thread to read from the server socket.
new Thread(new Runnable {
    @Override
    public void run() {
        LocalSocket socket = server.accept();

        // To read data sent by the client, read from socket.getInputStream().
        // To send data to the client, write to socket.getOutputStream().

        // After you are done you will need to call socket.close().
    }
}).start();

// Get the FileDescriptor associated with the client.
// You can use this FileDescriptor to write data to and/or read data
// sent from the server.
FileDescriptor fileDescriptor = client.getFileDescriptor();

// After you are done you will need to call server.close() and client.close().

This creates a socket in the abstract socket namespace. This is insecure because domain sockets are system-wide and sockets in the abstract namespace are not restricted by any permissions system. The only requirement to connect or bind to a name in the abstract namespace is to know the name, and the socket name used by your app can be easily discovered by an attacker through decompilation. There is also a possibility that another app may use the same socket name by accident. So another app could intercept your data, or send unexpected data through the socket, and it is difficult to guard against this.

A better, but more complicated solution is to create a socket in the filesystem namespace. The API to do this is super weird, but it can be achieved as follows:

// Create a unique name for the socket in your app's private data area.
// Note this example creates a file named like socket-xxxx in the root of
// your app's private data area. You might want to put it in a subdirectory.
String name = context
    .getFileStreamPath("socket-" + UUID.randomUUID())
    .getAbsolutePath();
LocalSocketAddress address = new LocalSocketAddress(name, LocalSocketAddress.Namespace.FILESYSTEM);

// Bind a server to the socket.
LocalSocket server = new LocalSocket(LocalSocket.SOCKET_STREAM);
server.bind(address);
final LocalServerSocket serverWrapper = new LocalServerSocket(server.getFileDescriptor());

// Connect a client to the socket.
LocalSocket client = new LocalSocket(LocalSocket.SOCKET_STREAM);
client.connect(address);

// Start a thread to read from the server socket.
new Thread(new Runnable {
    @Override
    public void run() {
        LocalSocket socket = serverWrapper.accept();

        // To read data sent by the client, read from socket.getInputStream().
        // To send data to the client, write to socket.getOutputStream().

        // After you are done you will need to call socket.close() and
        // serverWrapper.close().
    }
}).start();

// Get the FileDescriptor associated with the client.
// You can use this FileDescriptor to write data to and/or read data
// sent from the server.
FileDescriptor fileDescriptor = client.getFileDescriptor();

// After you are done you will need to call server.close() and client.close().

This does create a file on the filesystem, but none of the data that passes through the socket is ever written to disk, it is entirely in-memory. The file is just a name that represents the socket, similar to the files in /dev that represent devices. Because the socket is accessed through the filesystem, it is subject to the usual filesystem permissions, so it is easy to restrict access to the socket by placing the socket in your app's private data area.

Since this technique creates a file on the filesystem, it would be a good idea to delete the file after you're done, and also perhaps to check for and clean up old sockets every so often, in case your app crashes and leaves old files laying around.

Daniel Cassidy
  • 24,676
  • 5
  • 41
  • 54
-1

You can redirect System.out to ByteArrayOutputStream and use FileDescriptor.out as filedescriptor

ByteArrayOutputStream buff = new ByteArrayOutputStream();
PrintStream old = System.out;
System.setOut(new PrintStream(buff));

... use FileDescriptor.out ...

System.setOut(old);
System.out.println(new String(buff.toByteArray()));