AssetFileDescriptor.java revision cf4550c3198d6b3d92cdc52707fe70d7cc0caa9f
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() throws IOException {
133        return MemoryFile.isMemoryFile(mFd.getFileDescriptor());
134    }
135
136    /**
137     * Create and return a new auto-close input stream for this asset.  This
138     * will either return a full asset {@link AutoCloseInputStream}, or
139     * an underlying {@link ParcelFileDescriptor.AutoCloseInputStream
140     * ParcelFileDescriptor.AutoCloseInputStream} depending on whether the
141     * the object represents a complete file or sub-section of a file.  You
142     * should only call this once for a particular asset.
143     */
144    public FileInputStream createInputStream() throws IOException {
145        if (isMemoryFile()) {
146            if (mLength > Integer.MAX_VALUE) {
147                throw new IOException("File length too large for a memory file: " + mLength);
148            }
149            return new AutoCloseMemoryFileInputStream(mFd, (int)mLength);
150        }
151        if (mLength < 0) {
152            return new ParcelFileDescriptor.AutoCloseInputStream(mFd);
153        }
154        return new AutoCloseInputStream(this);
155    }
156
157    /**
158     * Create and return a new auto-close output stream for this asset.  This
159     * will either return a full asset {@link AutoCloseOutputStream}, or
160     * an underlying {@link ParcelFileDescriptor.AutoCloseOutputStream
161     * ParcelFileDescriptor.AutoCloseOutputStream} depending on whether the
162     * the object represents a complete file or sub-section of a file.  You
163     * should only call this once for a particular asset.
164     */
165    public FileOutputStream createOutputStream() throws IOException {
166        if (mLength < 0) {
167            return new ParcelFileDescriptor.AutoCloseOutputStream(mFd);
168        }
169        return new AutoCloseOutputStream(this);
170    }
171
172    @Override
173    public String toString() {
174        return "{AssetFileDescriptor: " + mFd
175                + " start=" + mStartOffset + " len=" + mLength + "}";
176    }
177
178    /**
179     * An InputStream you can create on a ParcelFileDescriptor, which will
180     * take care of calling {@link ParcelFileDescriptor#close
181     * ParcelFileDescritor.close()} for you when the stream is closed.
182     */
183    public static class AutoCloseInputStream
184            extends ParcelFileDescriptor.AutoCloseInputStream {
185        private long mRemaining;
186
187        public AutoCloseInputStream(AssetFileDescriptor fd) throws IOException {
188            super(fd.getParcelFileDescriptor());
189            super.skip(fd.getStartOffset());
190            mRemaining = (int)fd.getLength();
191        }
192
193        @Override
194        public int available() throws IOException {
195            return mRemaining >= 0
196                    ? (mRemaining < 0x7fffffff ? (int)mRemaining : 0x7fffffff)
197                    : super.available();
198        }
199
200        @Override
201        public int read() throws IOException {
202            if (mRemaining >= 0) {
203                if (mRemaining == 0) return -1;
204                int res = super.read();
205                if (res >= 0) mRemaining--;
206                return res;
207            }
208
209            return super.read();
210        }
211
212        @Override
213        public int read(byte[] buffer, int offset, int count) throws IOException {
214            if (mRemaining >= 0) {
215                if (mRemaining == 0) return -1;
216                if (count > mRemaining) count = (int)mRemaining;
217                int res = super.read(buffer, offset, count);
218                if (res >= 0) mRemaining -= res;
219                return res;
220            }
221
222            return super.read(buffer, offset, count);
223        }
224
225        @Override
226        public int read(byte[] buffer) throws IOException {
227            if (mRemaining >= 0) {
228                if (mRemaining == 0) return -1;
229                int count = buffer.length;
230                if (count > mRemaining) count = (int)mRemaining;
231                int res = super.read(buffer, 0, count);
232                if (res >= 0) mRemaining -= res;
233                return res;
234            }
235
236            return super.read(buffer);
237        }
238
239        @Override
240        public long skip(long count) throws IOException {
241            if (mRemaining >= 0) {
242                if (mRemaining == 0) return -1;
243                if (count > mRemaining) count = mRemaining;
244                long res = super.skip(count);
245                if (res >= 0) mRemaining -= res;
246                return res;
247            }
248
249            // TODO Auto-generated method stub
250            return super.skip(count);
251        }
252
253        @Override
254        public void mark(int readlimit) {
255            if (mRemaining >= 0) {
256                // Not supported.
257                return;
258            }
259            super.mark(readlimit);
260        }
261
262        @Override
263        public boolean markSupported() {
264            if (mRemaining >= 0) {
265                return false;
266            }
267            return super.markSupported();
268        }
269
270        @Override
271        public synchronized void reset() throws IOException {
272            if (mRemaining >= 0) {
273                // Not supported.
274                return;
275            }
276            super.reset();
277        }
278    }
279
280    /**
281     * An input stream that reads from a MemoryFile and closes it when the stream is closed.
282     * This extends FileInputStream just because {@link #createInputStream} returns
283     * a FileInputStream. All the FileInputStream methods are
284     * overridden to use the MemoryFile instead.
285     */
286    private static class AutoCloseMemoryFileInputStream extends FileInputStream {
287        private ParcelFileDescriptor mParcelFd;
288        private MemoryFile mFile;
289        private InputStream mStream;
290
291        public AutoCloseMemoryFileInputStream(ParcelFileDescriptor fd, int length)
292                throws IOException {
293            super(fd.getFileDescriptor());
294            mParcelFd = fd;
295            mFile = new MemoryFile(fd.getFileDescriptor(), length, "r");
296            mStream = mFile.getInputStream();
297        }
298
299        @Override
300        public int available() throws IOException {
301            return mStream.available();
302        }
303
304        @Override
305        public void close() throws IOException {
306            mParcelFd.close();  // must close ParcelFileDescriptor, not just the file descriptor,
307                                // since it could be a subclass of ParcelFileDescriptor.
308                                // E.g. ContentResolver.ParcelFileDescriptorInner.close() releases
309                                // a content provider
310            mFile.close();      // to unmap the memory file from the address space.
311            mStream.close();    // doesn't actually do anything
312        }
313
314        @Override
315        public FileChannel getChannel() {
316            return null;
317        }
318
319        @Override
320        public int read() throws IOException {
321            return mStream.read();
322        }
323
324        @Override
325        public int read(byte[] buffer, int offset, int count) throws IOException {
326            return mStream.read(buffer, offset, count);
327        }
328
329        @Override
330        public int read(byte[] buffer) throws IOException {
331            return mStream.read(buffer);
332        }
333
334        @Override
335        public long skip(long count) throws IOException {
336            return mStream.skip(count);
337        }
338    }
339
340    /**
341     * An OutputStream you can create on a ParcelFileDescriptor, which will
342     * take care of calling {@link ParcelFileDescriptor#close
343     * ParcelFileDescritor.close()} for you when the stream is closed.
344     */
345    public static class AutoCloseOutputStream
346            extends ParcelFileDescriptor.AutoCloseOutputStream {
347        private long mRemaining;
348
349        public AutoCloseOutputStream(AssetFileDescriptor fd) throws IOException {
350            super(fd.getParcelFileDescriptor());
351            if (fd.getParcelFileDescriptor().seekTo(fd.getStartOffset()) < 0) {
352                throw new IOException("Unable to seek");
353            }
354            mRemaining = (int)fd.getLength();
355        }
356
357        @Override
358        public void write(byte[] buffer, int offset, int count) throws IOException {
359            if (mRemaining >= 0) {
360                if (mRemaining == 0) return;
361                if (count > mRemaining) count = (int)mRemaining;
362                super.write(buffer, offset, count);
363                mRemaining -= count;
364                return;
365            }
366
367            super.write(buffer, offset, count);
368        }
369
370        @Override
371        public void write(byte[] buffer) throws IOException {
372            if (mRemaining >= 0) {
373                if (mRemaining == 0) return;
374                int count = buffer.length;
375                if (count > mRemaining) count = (int)mRemaining;
376                super.write(buffer);
377                mRemaining -= count;
378                return;
379            }
380
381            super.write(buffer);
382        }
383
384        @Override
385        public void write(int oneByte) throws IOException {
386            if (mRemaining >= 0) {
387                if (mRemaining == 0) return;
388                super.write(oneByte);
389                mRemaining--;
390                return;
391            }
392
393            super.write(oneByte);
394        }
395    }
396
397
398    /* Parcelable interface */
399    public int describeContents() {
400        return mFd.describeContents();
401    }
402
403    public void writeToParcel(Parcel out, int flags) {
404        mFd.writeToParcel(out, flags);
405        out.writeLong(mStartOffset);
406        out.writeLong(mLength);
407    }
408
409    AssetFileDescriptor(Parcel src) {
410        mFd = ParcelFileDescriptor.CREATOR.createFromParcel(src);
411        mStartOffset = src.readLong();
412        mLength = src.readLong();
413    }
414
415    public static final Parcelable.Creator<AssetFileDescriptor> CREATOR
416            = new Parcelable.Creator<AssetFileDescriptor>() {
417        public AssetFileDescriptor createFromParcel(Parcel in) {
418            return new AssetFileDescriptor(in);
419        }
420        public AssetFileDescriptor[] newArray(int size) {
421            return new AssetFileDescriptor[size];
422        }
423    };
424
425    /**
426     * Creates an AssetFileDescriptor from a memory file.
427     *
428     * @hide
429     */
430    public static AssetFileDescriptor fromMemoryFile(MemoryFile memoryFile)
431            throws IOException {
432        ParcelFileDescriptor fd = memoryFile.getParcelFileDescriptor();
433        return new AssetFileDescriptor(fd, 0, memoryFile.length());
434    }
435
436}
437