AssetFileDescriptor.java revision 23fdaf6fb62a9b5154b2508916a21c678462c5d0
1/*
2 * Copyright (C) 2006 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
17package android.content.res;
18
19import android.os.MemoryFile;
20import android.os.Parcel;
21import android.os.ParcelFileDescriptor;
22import android.os.Parcelable;
23
24import java.io.FileDescriptor;
25import java.io.FileInputStream;
26import java.io.FileOutputStream;
27import java.io.IOException;
28import java.io.InputStream;
29import java.nio.channels.FileChannel;
30
31/**
32 * File descriptor of an entry in the AssetManager.  This provides your own
33 * opened FileDescriptor that can be used to read the data, as well as the
34 * offset and length of that entry's data in the file.
35 */
36public class AssetFileDescriptor implements Parcelable {
37    /**
38     * Length used with {@link #AssetFileDescriptor(ParcelFileDescriptor, long, long)}
39     * and {@link #getDeclaredLength} when a length has not been declared.  This means
40     * the data extends to the end of the file.
41     */
42    public static final long UNKNOWN_LENGTH = -1;
43
44    private final ParcelFileDescriptor mFd;
45    private final long mStartOffset;
46    private final long mLength;
47
48    /**
49     * Create a new AssetFileDescriptor from the given values.
50     * @param fd The underlying file descriptor.
51     * @param startOffset The location within the file that the asset starts.
52     * This must be 0 if length is UNKNOWN_LENGTH.
53     * @param length The number of bytes of the asset, or
54     * {@link #UNKNOWN_LENGTH if it extends to the end of the file.
55     */
56    public AssetFileDescriptor(ParcelFileDescriptor fd, long startOffset,
57            long length) {
58        if (length < 0 && startOffset != 0) {
59            throw new IllegalArgumentException(
60                    "startOffset must be 0 when using UNKNOWN_LENGTH");
61        }
62        mFd = fd;
63        mStartOffset = startOffset;
64        mLength = length;
65    }
66
67    /**
68     * The AssetFileDescriptor contains its own ParcelFileDescriptor, which
69     * in addition to the normal FileDescriptor object also allows you to close
70     * the descriptor when you are done with it.
71     */
72    public ParcelFileDescriptor getParcelFileDescriptor() {
73        return mFd;
74    }
75
76    /**
77     * Returns the FileDescriptor that can be used to read the data in the
78     * file.
79     */
80    public FileDescriptor getFileDescriptor() {
81        return mFd.getFileDescriptor();
82    }
83
84    /**
85     * Returns the byte offset where this asset entry's data starts.
86     */
87    public long getStartOffset() {
88        return mStartOffset;
89    }
90
91    /**
92     * Returns the total number of bytes of this asset entry's data.  May be
93     * {@link #UNKNOWN_LENGTH} if the asset extends to the end of the file.
94     * If the AssetFileDescriptor was constructed with {@link #UNKNOWN_LENGTH},
95     * this will use {@link ParcelFileDescriptor#getStatSize()
96     * ParcelFileDescriptor.getStatSize()} to find the total size of the file,
97     * returning that number if found or {@link #UNKNOWN_LENGTH} if it could
98     * not be determined.
99     *
100     * @see #getDeclaredLength()
101     */
102    public long getLength() {
103        if (mLength >= 0) {
104            return mLength;
105        }
106        long len = mFd.getStatSize();
107        return len >= 0 ? len : UNKNOWN_LENGTH;
108    }
109
110    /**
111     * Return the actual number of bytes that were declared when the
112     * AssetFileDescriptor was constructed.  Will be
113     * {@link #UNKNOWN_LENGTH} if the length was not declared, meaning data
114     * should be read to the end of the file.
115     *
116     * @see #getDeclaredLength()
117     */
118    public long getDeclaredLength() {
119        return mLength;
120    }
121
122    /**
123     * Convenience for calling <code>getParcelFileDescriptor().close()</code>.
124     */
125    public void close() throws IOException {
126        mFd.close();
127    }
128
129    /**
130     * Checks whether this file descriptor is for a memory file.
131     */
132    private boolean isMemoryFile() {
133        try {
134            return MemoryFile.isMemoryFile(mFd.getFileDescriptor());
135        } catch (IOException e) {
136            return false;
137        }
138    }
139
140    /**
141     * Create and return a new auto-close input stream for this asset.  This
142     * will either return a full asset {@link AutoCloseInputStream}, or
143     * an underlying {@link ParcelFileDescriptor.AutoCloseInputStream
144     * ParcelFileDescriptor.AutoCloseInputStream} depending on whether the
145     * the object represents a complete file or sub-section of a file.  You
146     * should only call this once for a particular asset.
147     */
148    public FileInputStream createInputStream() throws IOException {
149        if (isMemoryFile()) {
150            if (mLength > Integer.MAX_VALUE) {
151                throw new IOException("File length too large for a memory file: " + mLength);
152            }
153            return new AutoCloseMemoryFileInputStream(mFd, (int)mLength);
154        }
155        if (mLength < 0) {
156            return new ParcelFileDescriptor.AutoCloseInputStream(mFd);
157        }
158        return new AutoCloseInputStream(this);
159    }
160
161    /**
162     * Create and return a new auto-close output stream for this asset.  This
163     * will either return a full asset {@link AutoCloseOutputStream}, or
164     * an underlying {@link ParcelFileDescriptor.AutoCloseOutputStream
165     * ParcelFileDescriptor.AutoCloseOutputStream} depending on whether the
166     * the object represents a complete file or sub-section of a file.  You
167     * should only call this once for a particular asset.
168     */
169    public FileOutputStream createOutputStream() throws IOException {
170        if (mLength < 0) {
171            return new ParcelFileDescriptor.AutoCloseOutputStream(mFd);
172        }
173        return new AutoCloseOutputStream(this);
174    }
175
176    @Override
177    public String toString() {
178        return "{AssetFileDescriptor: " + mFd
179                + " start=" + mStartOffset + " len=" + mLength + "}";
180    }
181
182    /**
183     * An InputStream you can create on a ParcelFileDescriptor, which will
184     * take care of calling {@link ParcelFileDescriptor#close
185     * ParcelFileDescritor.close()} for you when the stream is closed.
186     */
187    public static class AutoCloseInputStream
188            extends ParcelFileDescriptor.AutoCloseInputStream {
189        private long mRemaining;
190
191        public AutoCloseInputStream(AssetFileDescriptor fd) throws IOException {
192            super(fd.getParcelFileDescriptor());
193            super.skip(fd.getStartOffset());
194            mRemaining = (int)fd.getLength();
195        }
196
197        @Override
198        public int available() throws IOException {
199            return mRemaining >= 0
200                    ? (mRemaining < 0x7fffffff ? (int)mRemaining : 0x7fffffff)
201                    : super.available();
202        }
203
204        @Override
205        public int read() throws IOException {
206            if (mRemaining >= 0) {
207                if (mRemaining == 0) return -1;
208                int res = super.read();
209                if (res >= 0) mRemaining--;
210                return res;
211            }
212
213            return super.read();
214        }
215
216        @Override
217        public int read(byte[] buffer, int offset, int count) throws IOException {
218            if (mRemaining >= 0) {
219                if (mRemaining == 0) return -1;
220                if (count > mRemaining) count = (int)mRemaining;
221                int res = super.read(buffer, offset, count);
222                if (res >= 0) mRemaining -= res;
223                return res;
224            }
225
226            return super.read(buffer, offset, count);
227        }
228
229        @Override
230        public int read(byte[] buffer) throws IOException {
231            if (mRemaining >= 0) {
232                if (mRemaining == 0) return -1;
233                int count = buffer.length;
234                if (count > mRemaining) count = (int)mRemaining;
235                int res = super.read(buffer, 0, count);
236                if (res >= 0) mRemaining -= res;
237                return res;
238            }
239
240            return super.read(buffer);
241        }
242
243        @Override
244        public long skip(long count) throws IOException {
245            if (mRemaining >= 0) {
246                if (mRemaining == 0) return -1;
247                if (count > mRemaining) count = mRemaining;
248                long res = super.skip(count);
249                if (res >= 0) mRemaining -= res;
250                return res;
251            }
252
253            // TODO Auto-generated method stub
254            return super.skip(count);
255        }
256
257        @Override
258        public void mark(int readlimit) {
259            if (mRemaining >= 0) {
260                // Not supported.
261                return;
262            }
263            super.mark(readlimit);
264        }
265
266        @Override
267        public boolean markSupported() {
268            if (mRemaining >= 0) {
269                return false;
270            }
271            return super.markSupported();
272        }
273
274        @Override
275        public synchronized void reset() throws IOException {
276            if (mRemaining >= 0) {
277                // Not supported.
278                return;
279            }
280            super.reset();
281        }
282    }
283
284    /**
285     * An input stream that reads from a MemoryFile and closes it when the stream is closed.
286     * This extends FileInputStream just because {@link #createInputStream} returns
287     * a FileInputStream. All the FileInputStream methods are
288     * overridden to use the MemoryFile instead.
289     */
290    private static class AutoCloseMemoryFileInputStream extends FileInputStream {
291        private ParcelFileDescriptor mParcelFd;
292        private MemoryFile mFile;
293        private InputStream mStream;
294
295        public AutoCloseMemoryFileInputStream(ParcelFileDescriptor fd, int length)
296                throws IOException {
297            super(fd.getFileDescriptor());
298            mParcelFd = fd;
299            mFile = new MemoryFile(fd.getFileDescriptor(), length, "r");
300            mStream = mFile.getInputStream();
301        }
302
303        @Override
304        public int available() throws IOException {
305            return mStream.available();
306        }
307
308        @Override
309        public void close() throws IOException {
310            mParcelFd.close();  // must close ParcelFileDescriptor, not just the file descriptor,
311                                // since it could be a subclass of ParcelFileDescriptor.
312                                // E.g. ContentResolver.ParcelFileDescriptorInner.close() releases
313                                // a content provider
314            mFile.close();      // to unmap the memory file from the address space.
315            mStream.close();    // doesn't actually do anything
316        }
317
318        @Override
319        public FileChannel getChannel() {
320            return null;
321        }
322
323        @Override
324        public int read() throws IOException {
325            return mStream.read();
326        }
327
328        @Override
329        public int read(byte[] buffer, int offset, int count) throws IOException {
330            return mStream.read(buffer, offset, count);
331        }
332
333        @Override
334        public int read(byte[] buffer) throws IOException {
335            return mStream.read(buffer);
336        }
337
338        @Override
339        public long skip(long count) throws IOException {
340            return mStream.skip(count);
341        }
342    }
343
344    /**
345     * An OutputStream you can create on a ParcelFileDescriptor, which will
346     * take care of calling {@link ParcelFileDescriptor#close
347     * ParcelFileDescritor.close()} for you when the stream is closed.
348     */
349    public static class AutoCloseOutputStream
350            extends ParcelFileDescriptor.AutoCloseOutputStream {
351        private long mRemaining;
352
353        public AutoCloseOutputStream(AssetFileDescriptor fd) throws IOException {
354            super(fd.getParcelFileDescriptor());
355            if (fd.getParcelFileDescriptor().seekTo(fd.getStartOffset()) < 0) {
356                throw new IOException("Unable to seek");
357            }
358            mRemaining = (int)fd.getLength();
359        }
360
361        @Override
362        public void write(byte[] buffer, int offset, int count) throws IOException {
363            if (mRemaining >= 0) {
364                if (mRemaining == 0) return;
365                if (count > mRemaining) count = (int)mRemaining;
366                super.write(buffer, offset, count);
367                mRemaining -= count;
368                return;
369            }
370
371            super.write(buffer, offset, count);
372        }
373
374        @Override
375        public void write(byte[] buffer) throws IOException {
376            if (mRemaining >= 0) {
377                if (mRemaining == 0) return;
378                int count = buffer.length;
379                if (count > mRemaining) count = (int)mRemaining;
380                super.write(buffer);
381                mRemaining -= count;
382                return;
383            }
384
385            super.write(buffer);
386        }
387
388        @Override
389        public void write(int oneByte) throws IOException {
390            if (mRemaining >= 0) {
391                if (mRemaining == 0) return;
392                super.write(oneByte);
393                mRemaining--;
394                return;
395            }
396
397            super.write(oneByte);
398        }
399    }
400
401
402    /* Parcelable interface */
403    public int describeContents() {
404        return mFd.describeContents();
405    }
406
407    public void writeToParcel(Parcel out, int flags) {
408        mFd.writeToParcel(out, flags);
409        out.writeLong(mStartOffset);
410        out.writeLong(mLength);
411    }
412
413    AssetFileDescriptor(Parcel src) {
414        mFd = ParcelFileDescriptor.CREATOR.createFromParcel(src);
415        mStartOffset = src.readLong();
416        mLength = src.readLong();
417    }
418
419    public static final Parcelable.Creator<AssetFileDescriptor> CREATOR
420            = new Parcelable.Creator<AssetFileDescriptor>() {
421        public AssetFileDescriptor createFromParcel(Parcel in) {
422            return new AssetFileDescriptor(in);
423        }
424        public AssetFileDescriptor[] newArray(int size) {
425            return new AssetFileDescriptor[size];
426        }
427    };
428
429    /**
430     * Creates an AssetFileDescriptor from a memory file.
431     *
432     * @hide
433     */
434    public static AssetFileDescriptor fromMemoryFile(MemoryFile memoryFile)
435            throws IOException {
436        ParcelFileDescriptor fd = memoryFile.getParcelFileDescriptor();
437        return new AssetFileDescriptor(fd, 0, memoryFile.length());
438    }
439
440}
441