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.Parcel;
20import android.os.ParcelFileDescriptor;
21import android.os.Parcelable;
22
23import java.io.FileDescriptor;
24import java.io.FileInputStream;
25import java.io.FileOutputStream;
26import java.io.IOException;
27
28/**
29 * File descriptor of an entry in the AssetManager.  This provides your own
30 * opened FileDescriptor that can be used to read the data, as well as the
31 * offset and length of that entry's data in the file.
32 */
33public class AssetFileDescriptor implements Parcelable {
34    /**
35     * Length used with {@link #AssetFileDescriptor(ParcelFileDescriptor, long, long)}
36     * and {@link #getDeclaredLength} when a length has not been declared.  This means
37     * the data extends to the end of the file.
38     */
39    public static final long UNKNOWN_LENGTH = -1;
40
41    private final ParcelFileDescriptor mFd;
42    private final long mStartOffset;
43    private final long mLength;
44
45    /**
46     * Create a new AssetFileDescriptor from the given values.
47     * @param fd The underlying file descriptor.
48     * @param startOffset The location within the file that the asset starts.
49     * This must be 0 if length is UNKNOWN_LENGTH.
50     * @param length The number of bytes of the asset, or
51     * {@link #UNKNOWN_LENGTH} if it extends to the end of the file.
52     */
53    public AssetFileDescriptor(ParcelFileDescriptor fd, long startOffset,
54            long length) {
55        if (fd == null) {
56            throw new IllegalArgumentException("fd must not be null");
57        }
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     * Create and return a new auto-close input stream for this asset.  This
131     * will either return a full asset {@link AutoCloseInputStream}, or
132     * an underlying {@link ParcelFileDescriptor.AutoCloseInputStream
133     * ParcelFileDescriptor.AutoCloseInputStream} depending on whether the
134     * the object represents a complete file or sub-section of a file.  You
135     * should only call this once for a particular asset.
136     */
137    public FileInputStream createInputStream() throws IOException {
138        if (mLength < 0) {
139            return new ParcelFileDescriptor.AutoCloseInputStream(mFd);
140        }
141        return new AutoCloseInputStream(this);
142    }
143
144    /**
145     * Create and return a new auto-close output stream for this asset.  This
146     * will either return a full asset {@link AutoCloseOutputStream}, or
147     * an underlying {@link ParcelFileDescriptor.AutoCloseOutputStream
148     * ParcelFileDescriptor.AutoCloseOutputStream} depending on whether the
149     * the object represents a complete file or sub-section of a file.  You
150     * should only call this once for a particular asset.
151     */
152    public FileOutputStream createOutputStream() throws IOException {
153        if (mLength < 0) {
154            return new ParcelFileDescriptor.AutoCloseOutputStream(mFd);
155        }
156        return new AutoCloseOutputStream(this);
157    }
158
159    @Override
160    public String toString() {
161        return "{AssetFileDescriptor: " + mFd
162                + " start=" + mStartOffset + " len=" + mLength + "}";
163    }
164
165    /**
166     * An InputStream you can create on a ParcelFileDescriptor, which will
167     * take care of calling {@link ParcelFileDescriptor#close
168     * ParcelFileDescritor.close()} for you when the stream is closed.
169     */
170    public static class AutoCloseInputStream
171            extends ParcelFileDescriptor.AutoCloseInputStream {
172        private long mRemaining;
173
174        public AutoCloseInputStream(AssetFileDescriptor fd) throws IOException {
175            super(fd.getParcelFileDescriptor());
176            super.skip(fd.getStartOffset());
177            mRemaining = (int)fd.getLength();
178        }
179
180        @Override
181        public int available() throws IOException {
182            return mRemaining >= 0
183                    ? (mRemaining < 0x7fffffff ? (int)mRemaining : 0x7fffffff)
184                    : super.available();
185        }
186
187        @Override
188        public int read() throws IOException {
189            byte[] buffer = new byte[1];
190            int result = read(buffer, 0, 1);
191            return result == -1 ? -1 : buffer[0] & 0xff;
192        }
193
194        @Override
195        public int read(byte[] buffer, int offset, int count) throws IOException {
196            if (mRemaining >= 0) {
197                if (mRemaining == 0) return -1;
198                if (count > mRemaining) count = (int)mRemaining;
199                int res = super.read(buffer, offset, count);
200                if (res >= 0) mRemaining -= res;
201                return res;
202            }
203
204            return super.read(buffer, offset, count);
205        }
206
207        @Override
208        public int read(byte[] buffer) throws IOException {
209            return read(buffer, 0, buffer.length);
210        }
211
212        @Override
213        public long skip(long count) throws IOException {
214            if (mRemaining >= 0) {
215                if (mRemaining == 0) return -1;
216                if (count > mRemaining) count = mRemaining;
217                long res = super.skip(count);
218                if (res >= 0) mRemaining -= res;
219                return res;
220            }
221
222            return super.skip(count);
223        }
224
225        @Override
226        public void mark(int readlimit) {
227            if (mRemaining >= 0) {
228                // Not supported.
229                return;
230            }
231            super.mark(readlimit);
232        }
233
234        @Override
235        public boolean markSupported() {
236            if (mRemaining >= 0) {
237                return false;
238            }
239            return super.markSupported();
240        }
241
242        @Override
243        public synchronized void reset() throws IOException {
244            if (mRemaining >= 0) {
245                // Not supported.
246                return;
247            }
248            super.reset();
249        }
250    }
251
252    /**
253     * An OutputStream you can create on a ParcelFileDescriptor, which will
254     * take care of calling {@link ParcelFileDescriptor#close
255     * ParcelFileDescritor.close()} for you when the stream is closed.
256     */
257    public static class AutoCloseOutputStream
258            extends ParcelFileDescriptor.AutoCloseOutputStream {
259        private long mRemaining;
260
261        public AutoCloseOutputStream(AssetFileDescriptor fd) throws IOException {
262            super(fd.getParcelFileDescriptor());
263            if (fd.getParcelFileDescriptor().seekTo(fd.getStartOffset()) < 0) {
264                throw new IOException("Unable to seek");
265            }
266            mRemaining = (int)fd.getLength();
267        }
268
269        @Override
270        public void write(byte[] buffer, int offset, int count) throws IOException {
271            if (mRemaining >= 0) {
272                if (mRemaining == 0) return;
273                if (count > mRemaining) count = (int)mRemaining;
274                super.write(buffer, offset, count);
275                mRemaining -= count;
276                return;
277            }
278
279            super.write(buffer, offset, count);
280        }
281
282        @Override
283        public void write(byte[] buffer) throws IOException {
284            if (mRemaining >= 0) {
285                if (mRemaining == 0) return;
286                int count = buffer.length;
287                if (count > mRemaining) count = (int)mRemaining;
288                super.write(buffer);
289                mRemaining -= count;
290                return;
291            }
292
293            super.write(buffer);
294        }
295
296        @Override
297        public void write(int oneByte) throws IOException {
298            if (mRemaining >= 0) {
299                if (mRemaining == 0) return;
300                super.write(oneByte);
301                mRemaining--;
302                return;
303            }
304
305            super.write(oneByte);
306        }
307    }
308
309
310    /* Parcelable interface */
311    public int describeContents() {
312        return mFd.describeContents();
313    }
314
315    public void writeToParcel(Parcel out, int flags) {
316        mFd.writeToParcel(out, flags);
317        out.writeLong(mStartOffset);
318        out.writeLong(mLength);
319    }
320
321    AssetFileDescriptor(Parcel src) {
322        mFd = ParcelFileDescriptor.CREATOR.createFromParcel(src);
323        mStartOffset = src.readLong();
324        mLength = src.readLong();
325    }
326
327    public static final Parcelable.Creator<AssetFileDescriptor> CREATOR
328            = new Parcelable.Creator<AssetFileDescriptor>() {
329        public AssetFileDescriptor createFromParcel(Parcel in) {
330            return new AssetFileDescriptor(in);
331        }
332        public AssetFileDescriptor[] newArray(int size) {
333            return new AssetFileDescriptor[size];
334        }
335    };
336
337}
338