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