1 /*
2  * Copyright (C) 2009 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.os.cts;
18 
19 import static org.junit.Assert.assertArrayEquals;
20 import static org.junit.Assert.assertEquals;
21 import static org.junit.Assert.assertNotNull;
22 import static org.junit.Assert.assertNull;
23 import static org.junit.Assert.assertSame;
24 import static org.junit.Assert.assertThrows;
25 import static org.junit.Assert.assertTrue;
26 import static org.junit.Assert.fail;
27 
28 import android.os.Handler;
29 import android.os.Looper;
30 import android.os.MessageQueue;
31 import android.os.Parcel;
32 import android.os.ParcelFileDescriptor;
33 import android.os.ParcelFileDescriptor.AutoCloseInputStream;
34 import android.os.Parcelable;
35 import android.platform.test.annotations.AppModeFull;
36 import android.platform.test.annotations.AppModeSdkSandbox;
37 import android.platform.test.annotations.IgnoreUnderRavenwood;
38 import android.platform.test.ravenwood.RavenwoodRule;
39 import android.system.ErrnoException;
40 import android.system.Os;
41 import android.system.OsConstants;
42 
43 import androidx.test.runner.AndroidJUnit4;
44 
45 import com.google.common.util.concurrent.AbstractFuture;
46 
47 import junit.framework.ComparisonFailure;
48 
49 import org.junit.Rule;
50 import org.junit.Test;
51 import org.junit.runner.RunWith;
52 
53 import java.io.File;
54 import java.io.FileDescriptor;
55 import java.io.FileInputStream;
56 import java.io.FileOutputStream;
57 import java.io.IOException;
58 import java.io.InputStream;
59 import java.io.OutputStream;
60 import java.net.InetAddress;
61 import java.net.ServerSocket;
62 import java.net.Socket;
63 import java.nio.file.Files;
64 import java.util.concurrent.ExecutionException;
65 import java.util.concurrent.TimeUnit;
66 import java.util.concurrent.TimeoutException;
67 
68 @AppModeSdkSandbox(reason = "Allow test in the SDK sandbox (does not prevent other modes).")
69 @RunWith(AndroidJUnit4.class)
70 public class ParcelFileDescriptorTest {
71     @Rule
72     public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
73             .setProvideMainThread(true).build();
74 
75     private static final long DURATION = 100l;
76 
77     @Test
testConstructorAndOpen()78     public void testConstructorAndOpen() throws Exception {
79         ParcelFileDescriptor tempFile = makeParcelFileDescriptor();
80 
81         ParcelFileDescriptor pfd = new ParcelFileDescriptor(tempFile);
82         AutoCloseInputStream in = new AutoCloseInputStream(pfd);
83         try {
84             // read the data that was wrote previously
85             assertEquals(0, in.read());
86             assertEquals(1, in.read());
87             assertEquals(2, in.read());
88             assertEquals(3, in.read());
89         } finally {
90             in.close();
91         }
92     }
93 
94     @Test
testDetachAdopt()95     public void testDetachAdopt() throws Exception {
96         ParcelFileDescriptor tempFile = makeParcelFileDescriptor();
97 
98         ParcelFileDescriptor before = new ParcelFileDescriptor(tempFile);
99         ParcelFileDescriptor after = ParcelFileDescriptor.adoptFd(before.detachFd());
100 
101         // Verify reading from post-adopted FD works
102         try (AutoCloseInputStream in = new AutoCloseInputStream(after)) {
103             assertEquals(0, in.read());
104             assertEquals(1, in.read());
105             assertEquals(2, in.read());
106             assertEquals(3, in.read());
107         }
108 
109         // Verify trying to detach a second time fails
110         assertThrows(IllegalStateException.class, () -> {
111             before.detachFd();
112         });
113     }
114 
115     @Test
testReadOnly()116     public void testReadOnly() throws Exception {
117         final File file = File.createTempFile("testReadOnly", "bin");
118         file.createNewFile();
119         try (ParcelFileDescriptor pfd = ParcelFileDescriptor.open(file,
120                 ParcelFileDescriptor.MODE_READ_ONLY);
121              ParcelFileDescriptor.AutoCloseOutputStream out =
122                      new ParcelFileDescriptor.AutoCloseOutputStream(pfd)) {
123             assertThrows(IOException.class, () -> {
124                 out.write(42);
125             });
126         }
127     }
128 
129     @Test
testNormal()130     public void testNormal() throws Exception {
131         final File file = File.createTempFile("testNormal", "bin");
132         doMultiWrite(file, ParcelFileDescriptor.MODE_READ_WRITE
133                 | ParcelFileDescriptor.MODE_CREATE);
134         assertArrayEquals(new byte[]{21, 42}, Files.readAllBytes(file.toPath()));
135     }
136 
137     @Test
testAppend()138     public void testAppend() throws Exception {
139         final File file = File.createTempFile("testAppend", "bin");
140         doMultiWrite(file, ParcelFileDescriptor.MODE_READ_WRITE
141                 | ParcelFileDescriptor.MODE_CREATE
142                 | ParcelFileDescriptor.MODE_APPEND);
143         assertArrayEquals(new byte[]{42, 42, 21}, Files.readAllBytes(file.toPath()));
144     }
145 
146     @Test
testTruncate()147     public void testTruncate() throws Exception {
148         final File file = File.createTempFile("testTruncate", "bin");
149         doMultiWrite(file, ParcelFileDescriptor.MODE_READ_WRITE
150                 | ParcelFileDescriptor.MODE_CREATE
151                 | ParcelFileDescriptor.MODE_TRUNCATE);
152         assertArrayEquals(new byte[]{21}, Files.readAllBytes(file.toPath()));
153     }
154 
doMultiWrite(File file, int flags)155     private void doMultiWrite(File file, int flags) throws Exception {
156         try (ParcelFileDescriptor pfd = ParcelFileDescriptor.open(file, flags);
157              ParcelFileDescriptor.AutoCloseOutputStream out =
158                      new ParcelFileDescriptor.AutoCloseOutputStream(pfd)) {
159             out.write(42);
160             out.write(42);
161         }
162         try (ParcelFileDescriptor pfd = ParcelFileDescriptor.open(file, flags);
163              ParcelFileDescriptor.AutoCloseOutputStream out =
164                      new ParcelFileDescriptor.AutoCloseOutputStream(pfd)) {
165             out.write(21);
166         }
167     }
168 
169     private static class DoneSignal extends AbstractFuture<Void> {
set()170         public boolean set() {
171             return super.set(null);
172         }
173 
174         @Override
setException(Throwable t)175         public boolean setException(Throwable t) {
176             return super.setException(t);
177         }
178     }
179 
180     @Test
181     @AppModeFull // opening a listening socket not permitted for instant apps
182     @IgnoreUnderRavenwood(blockedBy = ParcelFileDescriptor.class)
testFromSocket()183     public void testFromSocket() throws Throwable {
184         final int PORT = 12222;
185         final int DATA = 1;
186 
187         final DoneSignal done = new DoneSignal();
188 
189         final Thread t = new Thread(new Runnable() {
190             @Override
191             public void run() {
192                 try {
193                     ServerSocket ss;
194                     ss = new ServerSocket(PORT);
195                     Socket sSocket = ss.accept();
196                     OutputStream out = sSocket.getOutputStream();
197                     out.write(DATA);
198                     Thread.sleep(DURATION);
199                     out.close();
200                     done.set();
201                 } catch (Exception e) {
202                     done.setException(e);
203                 }
204             }
205         });
206         t.start();
207 
208         Thread.sleep(DURATION);
209         Socket socket;
210         socket = new Socket(InetAddress.getLocalHost(), PORT);
211         ParcelFileDescriptor pfd = ParcelFileDescriptor.fromSocket(socket);
212         AutoCloseInputStream in = new AutoCloseInputStream(pfd);
213         assertEquals(DATA, in.read());
214         in.close();
215         socket.close();
216         pfd.close();
217 
218         done.get(5, TimeUnit.SECONDS);
219     }
220 
assertFileDescriptorContent(byte[] expected, ParcelFileDescriptor fd)221     private static void assertFileDescriptorContent(byte[] expected, ParcelFileDescriptor fd)
222         throws IOException {
223         assertInputStreamContent(expected, new ParcelFileDescriptor.AutoCloseInputStream(fd));
224     }
225 
assertInputStreamContent(byte[] expected, InputStream is)226     private static void assertInputStreamContent(byte[] expected, InputStream is)
227             throws IOException {
228         try {
229             byte[] observed = new byte[expected.length];
230             int count = is.read(observed);
231             assertEquals(expected.length, count);
232             assertEquals(-1, is.read());
233             assertArrayEquals(expected, observed);
234         } finally {
235             is.close();
236         }
237     }
238 
239     @Test
testToString()240     public void testToString() throws Exception {
241         ParcelFileDescriptor pfd = makeParcelFileDescriptor();
242         assertNotNull(pfd.toString());
243     }
244 
245     @Test
246     @IgnoreUnderRavenwood(blockedBy = Parcel.class)
testWriteToParcel()247     public void testWriteToParcel() throws Exception {
248         ParcelFileDescriptor pf = makeParcelFileDescriptor();
249 
250         Parcel pl = Parcel.obtain();
251         pf.writeToParcel(pl, ParcelFileDescriptor.PARCELABLE_WRITE_RETURN_VALUE);
252         pl.setDataPosition(0);
253         ParcelFileDescriptor pfd = ParcelFileDescriptor.CREATOR.createFromParcel(pl);
254         AutoCloseInputStream in = new AutoCloseInputStream(pfd);
255         try {
256             // read the data that was wrote previously
257             assertEquals(0, in.read());
258             assertEquals(1, in.read());
259             assertEquals(2, in.read());
260             assertEquals(3, in.read());
261         } finally {
262             in.close();
263         }
264     }
265 
266     @Test
testClose()267     public void testClose() throws Exception {
268         ParcelFileDescriptor pf = makeParcelFileDescriptor();
269         AutoCloseInputStream in1 = new AutoCloseInputStream(pf);
270         try {
271             assertEquals(0, in1.read());
272         } finally {
273             in1.close();
274         }
275 
276         pf.close();
277 
278         AutoCloseInputStream in2 = new AutoCloseInputStream(pf);
279         try {
280             assertEquals(0, in2.read());
281             fail("Failed to throw exception.");
282         } catch (Exception e) {
283             // expected
284         } finally {
285             in2.close();
286         }
287     }
288 
289     @Test
290     @IgnoreUnderRavenwood(blockedBy = ParcelFileDescriptor.class)
testGetStatSize()291     public void testGetStatSize() throws Exception {
292         ParcelFileDescriptor pf = makeParcelFileDescriptor();
293         assertTrue(pf.getStatSize() >= 0);
294     }
295 
296     @Test
testGetFileDescriptor()297     public void testGetFileDescriptor() throws Exception {
298         ParcelFileDescriptor pfd = makeParcelFileDescriptor();
299         assertNotNull(pfd.getFileDescriptor());
300 
301         ParcelFileDescriptor p = new ParcelFileDescriptor(pfd);
302         assertSame(pfd.getFileDescriptor(), p.getFileDescriptor());
303     }
304 
305     @Test
testDescribeContents()306     public void testDescribeContents() throws Exception{
307         ParcelFileDescriptor pfd = makeParcelFileDescriptor();
308         assertTrue((Parcelable.CONTENTS_FILE_DESCRIPTOR & pfd.describeContents()) != 0);
309     }
310 
assertContains(String expected, String actual)311     private static void assertContains(String expected, String actual) {
312         if (actual.contains(expected)) return;
313         throw new ComparisonFailure("", expected, actual);
314     }
315 
write(ParcelFileDescriptor pfd, int oneByte)316     private static void write(ParcelFileDescriptor pfd, int oneByte)  throws IOException{
317         new FileOutputStream(pfd.getFileDescriptor()).write(oneByte);
318     }
319 
320     // This method is unlikely to be used by clients, as clients use ContentResolver,
321     // which builds AutoCloseInputStream under the hood rather than FileInputStream
322     // built from a raw FD.
323     //
324     // Using new FileInputStream(PFD.getFileDescriptor()) is discouraged, as error
325     // propagation is lost, and read() will never throw IOException in such case.
read(ParcelFileDescriptor pfd)326     private static int read(ParcelFileDescriptor pfd) throws IOException {
327         return new FileInputStream(pfd.getFileDescriptor()).read();
328     }
329 
330     @Test
testBasicPipeNormal()331     public void testBasicPipeNormal() throws Exception {
332         final ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
333         final ParcelFileDescriptor red = pipe[0];
334         final ParcelFileDescriptor blue = pipe[1];
335 
336         write(blue, 1);
337         assertEquals(1, read(red));
338 
339         blue.close();
340         assertEquals(-1, read(red));
341         red.checkError();
342     }
343 
344     @Test
345     @IgnoreUnderRavenwood(blockedBy = ParcelFileDescriptor.class)
testPipeNormal()346     public void testPipeNormal() throws Exception {
347         final ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createReliablePipe();
348         final ParcelFileDescriptor red = pipe[0];
349         final ParcelFileDescriptor blue = pipe[1];
350 
351         write(blue, 1);
352         assertEquals(1, read(red));
353 
354         blue.close();
355         assertEquals(-1, read(red));
356         red.checkError();
357     }
358 
359     // Reading should be done via AutoCloseInputStream if possible, rather than
360     // recreating a FileInputStream from a raw FD, what's done in read(PFD).
361     @Test
362     @IgnoreUnderRavenwood(blockedBy = ParcelFileDescriptor.class)
testPipeError_Discouraged()363     public void testPipeError_Discouraged() throws Exception {
364         final ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createReliablePipe();
365         final ParcelFileDescriptor red = pipe[0];
366         final ParcelFileDescriptor blue = pipe[1];
367 
368         write(blue, 2);
369         blue.closeWithError("OMG MUFFINS");
370 
371         // Even though closed we should still drain pipe.
372         assertEquals(2, read(red));
373         assertEquals(-1, read(red));
374         try {
375             red.checkError();
376             fail("expected throw!");
377         } catch (IOException e) {
378             assertContains("OMG MUFFINS", e.getMessage());
379         }
380     }
381 
382     @Test
383     @IgnoreUnderRavenwood(blockedBy = ParcelFileDescriptor.class)
testPipeError()384     public void testPipeError() throws Exception {
385         final ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createReliablePipe();
386         final ParcelFileDescriptor red = pipe[0];
387         final ParcelFileDescriptor blue = pipe[1];
388 
389         write(blue, 2);
390         blue.closeWithError("OMG MUFFINS");
391 
392         try (AutoCloseInputStream is = new AutoCloseInputStream(red)) {
393             is.read();
394             is.read();  // Checks errors on EOF.
395             fail("expected throw!");
396         } catch (IOException e) {
397             assertContains("OMG MUFFINS", e.getMessage());
398         }
399     }
400 
401     @Test
402     @IgnoreUnderRavenwood(blockedBy = MessageQueue.class)
testFileNormal()403     public void testFileNormal() throws Exception {
404         final Handler handler = new Handler(Looper.getMainLooper());
405         final FutureCloseListener listener = new FutureCloseListener();
406         final ParcelFileDescriptor file = ParcelFileDescriptor.open(
407                 File.createTempFile("pfd", "bbq"), ParcelFileDescriptor.MODE_READ_WRITE, handler,
408                 listener);
409 
410         write(file, 7);
411         file.close();
412 
413         // make sure we were notified
414         assertEquals(null, listener.get());
415     }
416 
417     @Test
418     @IgnoreUnderRavenwood(blockedBy = MessageQueue.class)
testFileError()419     public void testFileError() throws Exception {
420         final Handler handler = new Handler(Looper.getMainLooper());
421         final FutureCloseListener listener = new FutureCloseListener();
422         final ParcelFileDescriptor file = ParcelFileDescriptor.open(
423                 File.createTempFile("pfd", "bbq"), ParcelFileDescriptor.MODE_READ_WRITE, handler,
424                 listener);
425 
426         write(file, 8);
427         file.closeWithError("OMG BANANAS");
428 
429         // make sure error came through
430         assertContains("OMG BANANAS", listener.get().getMessage());
431     }
432 
433     @Test
434     @IgnoreUnderRavenwood(blockedBy = MessageQueue.class)
testFileDetach()435     public void testFileDetach() throws Exception {
436         final Handler handler = new Handler(Looper.getMainLooper());
437         final FutureCloseListener listener = new FutureCloseListener();
438         final ParcelFileDescriptor file = ParcelFileDescriptor.open(
439                 File.createTempFile("pfd", "bbq"), ParcelFileDescriptor.MODE_READ_WRITE, handler,
440                 listener);
441 
442         file.detachFd();
443 
444         // make sure detach came through
445         assertContains("DETACHED", listener.get().getMessage());
446     }
447 
448     @Test
449     @IgnoreUnderRavenwood(blockedBy = MessageQueue.class)
testFileWrapped()450     public void testFileWrapped() throws Exception {
451         final Handler handler1 = new Handler(Looper.getMainLooper());
452         final Handler handler2 = new Handler(Looper.getMainLooper());
453         final FutureCloseListener listener1 = new FutureCloseListener();
454         final FutureCloseListener listener2 = new FutureCloseListener();
455         final ParcelFileDescriptor file1 = ParcelFileDescriptor.open(
456                 File.createTempFile("pfd", "bbq"), ParcelFileDescriptor.MODE_READ_WRITE, handler1,
457                 listener1);
458         final ParcelFileDescriptor file2 = ParcelFileDescriptor.wrap(file1, handler2, listener2);
459 
460         write(file2, 7);
461         file2.close();
462 
463         // make sure we were notified
464         assertEquals(null, listener2.get());
465     }
466 
467     @Test
468     @IgnoreUnderRavenwood(blockedBy = ParcelFileDescriptor.class)
testSocketErrorAfterClose()469     public void testSocketErrorAfterClose() throws Exception {
470         final ParcelFileDescriptor[] pair = ParcelFileDescriptor.createReliableSocketPair();
471         final ParcelFileDescriptor red = pair[0];
472         final ParcelFileDescriptor blue = pair[1];
473 
474         // both sides throw their hands in the air
475         blue.closeWithError("BLUE RAWR");
476         red.closeWithError("RED RAWR");
477 
478         // red noticed the blue error, but after that the comm pipe was dead so
479         // blue had no way of seeing the red error.
480         try {
481             red.checkError();
482             fail("expected throw!");
483         } catch (IOException e) {
484             assertContains("BLUE RAWR", e.getMessage());
485         }
486 
487         // expected to not throw; no error
488         blue.checkError();
489     }
490 
491     @Test
492     @IgnoreUnderRavenwood(blockedBy = ParcelFileDescriptor.class)
testSocketMultipleCheck()493     public void testSocketMultipleCheck() throws Exception {
494         final ParcelFileDescriptor[] pair = ParcelFileDescriptor.createReliableSocketPair();
495         final ParcelFileDescriptor red = pair[0];
496         final ParcelFileDescriptor blue = pair[1];
497 
498         // allow checking before closed; they should all pass
499         blue.checkError();
500         blue.checkError();
501         blue.checkError();
502 
503         // and verify we actually see it
504         red.closeWithError("RAWR RED");
505         try {
506             blue.checkError();
507             fail("expected throw!");
508         } catch (IOException e) {
509             assertContains("RAWR RED", e.getMessage());
510         }
511     }
512 
513     // http://b/21578056
514     @Test
testFileNamesWithNonBmpChars()515     public void testFileNamesWithNonBmpChars() throws Exception {
516         final File file = File.createTempFile("treble_clef_\ud834\udd1e", ".tmp");
517         final ParcelFileDescriptor pfd = ParcelFileDescriptor.open(file,
518                 ParcelFileDescriptor.MODE_READ_ONLY);
519         assertNotNull(pfd);
520         pfd.close();
521     }
522 
523     @Test
524     @IgnoreUnderRavenwood(blockedBy = Os.class)
testCheckFinalizerBehavior()525     public void testCheckFinalizerBehavior() throws Exception {
526         final Runtime runtime = Runtime.getRuntime();
527         ParcelFileDescriptor pfd = makeParcelFileDescriptor();
528         assertTrue(checkIsValid(pfd.getFileDescriptor()));
529 
530         ParcelFileDescriptor wrappedPfd = new ParcelFileDescriptor(pfd);
531         assertTrue(checkIsValid(wrappedPfd.getFileDescriptor()));
532 
533         FileDescriptor fd = pfd.getFileDescriptor();
534         int rawFd = pfd.getFd();
535         pfd = null;
536         assertNull(pfd); // To keep tools happy - yes we are using the write to null
537         runtime.gc(); runtime.runFinalization();
538         assertTrue("Wrapped PFD failed to hold reference",
539                 checkIsValid(wrappedPfd.getFileDescriptor()));
540         assertTrue("FileDescriptor failed to hold reference", checkIsValid(fd));
541 
542         wrappedPfd = null;
543         assertNull(wrappedPfd); // To keep tools happy - yes we are using the write to null
544         runtime.gc(); runtime.runFinalization();
545         // TODO: Enable this once b/65027998 is fixed
546         //assertTrue("FileDescriptor failed to hold reference", checkIsValid(fd));
547 
548         fd = null;
549         assertNull(fd); // To keep tools happy - yes we are using the write to null
550         runtime.gc(); runtime.runFinalization();
551 
552         try {
553             ParcelFileDescriptor.fromFd(rawFd);
554             fail("FD leaked");
555         } catch (IOException ex) {
556             // Success
557         }
558     }
559 
560     @Test
testFromFd()561     public void testFromFd() throws Exception {
562         try (var pfd1 = makeParcelFileDescriptor()) {
563             try (var pfd2 = ParcelFileDescriptor.fromFd(pfd1.getFd())) {
564                 checkSameFd(pfd1, pfd2);
565             }
566         }
567     }
568 
569     @Test
testDup()570     public void testDup() throws Exception {
571         try (var pfd1 = makeParcelFileDescriptor()) {
572             try (var pfd2 = pfd1.dup()) {
573                 checkSameFd(pfd1, pfd2);
574             }
575         }
576     }
577 
578     @Test
testDupStatic()579     public void testDupStatic() throws Exception {
580         try (var pfd1 = makeParcelFileDescriptor()) {
581             try (var pfd2 = ParcelFileDescriptor.dup(pfd1.getFileDescriptor())) {
582                 checkSameFd(pfd1, pfd2);
583             }
584         }
585     }
586 
checkSameFd(ParcelFileDescriptor pfd1, ParcelFileDescriptor pfd2)587     void checkSameFd(ParcelFileDescriptor pfd1, ParcelFileDescriptor pfd2) throws Exception {
588         // Make sure dup'ed FDs share the same position.
589         seekTo(pfd1, 0);
590         seekTo(pfd2, 0);
591 
592         assertEquals(0, tell(pfd1));
593         assertEquals(0, tell(pfd2));
594 
595         seekTo(pfd1, 2);
596 
597         assertEquals(2, tell(pfd1));
598         assertEquals(2, tell(pfd2));
599     }
600 
601     /** Change PFD's current position. */
seekTo(ParcelFileDescriptor pfd, int pos)602     long seekTo(ParcelFileDescriptor pfd, int pos) throws Exception {
603         return Os.lseek(pfd.getFileDescriptor(), pos, OsConstants.SEEK_SET);
604     }
605 
606     /** Returns the PFD's current position */
tell(ParcelFileDescriptor pfd)607     long tell(ParcelFileDescriptor pfd) throws Exception {
608         return Os.lseek(pfd.getFileDescriptor(), 0, OsConstants.SEEK_CUR);
609     }
610 
checkIsValid(FileDescriptor fd)611     boolean checkIsValid(FileDescriptor fd) {
612         try {
613             Os.fstat(fd);
614             return true;
615         } catch (ErrnoException e) {
616             if (e.errno == OsConstants.EBADF) {
617                 return false;
618             } else {
619                 fail(e.getMessage());
620                 // not reached
621                 return false;
622             }
623         }
624     }
625 
makeParcelFileDescriptor()626     static ParcelFileDescriptor makeParcelFileDescriptor() throws Exception {
627         File file = File.createTempFile("testParcelFileDescriptor", "bin");
628         try (FileOutputStream fout = new FileOutputStream(file)) {
629             fout.write(new byte[] { 0x0, 0x1, 0x2, 0x3 });
630         }
631 
632         ParcelFileDescriptor pf = null;
633         pf = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_WRITE);
634         return pf;
635     }
636 
637     public static class FutureCloseListener extends AbstractFuture<IOException>
638             implements ParcelFileDescriptor.OnCloseListener {
639         @Override
onClose(IOException e)640         public void onClose(IOException e) {
641             if (e instanceof ParcelFileDescriptor.FileDescriptorDetachedException) {
642                 set(new IOException("DETACHED"));
643             } else {
644                 set(e);
645             }
646         }
647 
648         @Override
get()649         public IOException get() throws InterruptedException, ExecutionException {
650             try {
651                 return get(5, TimeUnit.SECONDS);
652             } catch (TimeoutException e) {
653                 throw new RuntimeException(e);
654             }
655         }
656     }
657 }
658