content: /*
   * Copyright (C) 2006 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package android.content.res;
  
  import android.annotation.UnsupportedAppUsage;
  import android.os.Bundle;
  import android.os.Parcel;
  import android.os.ParcelFileDescriptor;
  import android.os.Parcelable;
  
  import java.io.Closeable;
  import java.io.FileDescriptor;
  import java.io.FileInputStream;
  import java.io.FileOutputStream;
  import java.io.IOException;
  
  /**
   * File descriptor of an entry in the AssetManager.  This provides your own
   * opened FileDescriptor that can be used to read the data, as well as the
   * offset and length of that entry's data in the file.
   */
  public class AssetFileDescriptor implements Parcelable, Closeable {
      /**
       * Length used with {@link #AssetFileDescriptor(ParcelFileDescriptor, long, long)}
       * and {@link #getDeclaredLength} when a length has not been declared.  This means
       * the data extends to the end of the file.
       */
      public static final long UNKNOWN_LENGTH = -1;
      
      @UnsupportedAppUsage
      private final ParcelFileDescriptor mFd;
      @UnsupportedAppUsage
      private final long mStartOffset;
      @UnsupportedAppUsage
      private final long mLength;
      private final Bundle mExtras;
  
      /**
       * Create a new AssetFileDescriptor from the given values.
       *
       * @param fd The underlying file descriptor.
       * @param startOffset The location within the file that the asset starts.
       *            This must be 0 if length is UNKNOWN_LENGTH.
       * @param length The number of bytes of the asset, or
       *            {@link #UNKNOWN_LENGTH} if it extends to the end of the file.
       */
      public AssetFileDescriptor(ParcelFileDescriptor fd, long startOffset,
              long length) {
          this(fd, startOffset, length, null);
      }
  
      /**
       * Create a new AssetFileDescriptor from the given values.
       *
       * @param fd The underlying file descriptor.
       * @param startOffset The location within the file that the asset starts.
       *            This must be 0 if length is UNKNOWN_LENGTH.
       * @param length The number of bytes of the asset, or
       *            {@link #UNKNOWN_LENGTH} if it extends to the end of the file.
       * @param extras additional details that can be used to interpret the
       *            underlying file descriptor. May be null.
       */
      public AssetFileDescriptor(ParcelFileDescriptor fd, long startOffset,
              long length, Bundle extras) {
          if (fd == null) {
              throw new IllegalArgumentException("fd must not be null");
          }
          if (length < 0 && startOffset != 0) {
              throw new IllegalArgumentException(
                      "startOffset must be 0 when using UNKNOWN_LENGTH");
          }
          mFd = fd;
          mStartOffset = startOffset;
          mLength = length;
          mExtras = extras;
      }
  
      /**
       * The AssetFileDescriptor contains its own ParcelFileDescriptor, which
       * in addition to the normal FileDescriptor object also allows you to close
       * the descriptor when you are done with it.
       */
      public ParcelFileDescriptor getParcelFileDescriptor() {
          return mFd;
      }
      
      /**
       * Returns the FileDescriptor that can be used to read the data in the
       * file.
       */
      public FileDescriptor getFileDescriptor() {
          return mFd.getFileDescriptor();
      }
      
      /**
       * Returns the byte offset where this asset entry's data starts.
       */
      public long getStartOffset() {
          return mStartOffset;
      }
  
      /**
       * Returns any additional details that can be used to interpret the
       * underlying file descriptor. May be null.
       */
      public Bundle getExtras() {
          return mExtras;
      }
  
      /**
       * Returns the total number of bytes of this asset entry's data.  May be
       * {@link #UNKNOWN_LENGTH} if the asset extends to the end of the file.
       * If the AssetFileDescriptor was constructed with {@link #UNKNOWN_LENGTH},
       * this will use {@link ParcelFileDescriptor#getStatSize()
       * ParcelFileDescriptor.getStatSize()} to find the total size of the file,
       * returning that number if found or {@link #UNKNOWN_LENGTH} if it could
       * not be determined.
       * 
       * @see #getDeclaredLength()
       */
      public long getLength() {
          if (mLength >= 0) {
              return mLength;
          }
          long len = mFd.getStatSize();
          return len >= 0 ? len : UNKNOWN_LENGTH;
      }
      
      /**
       * Return the actual number of bytes that were declared when the
       * AssetFileDescriptor was constructed.  Will be
       * {@link #UNKNOWN_LENGTH} if the length was not declared, meaning data
       * should be read to the end of the file.
       * 
       * @see #getDeclaredLength()
       */
      public long getDeclaredLength() {
          return mLength;
      }
      
      /**
       * Convenience for calling <code>getParcelFileDescriptor().close()</code>.
       */
      @Override
      public void close() throws IOException {
          mFd.close();
      }
  
      /**
       * Create and return a new auto-close input stream for this asset.  This
       * will either return a full asset {@link AutoCloseInputStream}, or
       * an underlying {@link ParcelFileDescriptor.AutoCloseInputStream
       * ParcelFileDescriptor.AutoCloseInputStream} depending on whether the
       * the object represents a complete file or sub-section of a file.  You
       * should only call this once for a particular asset.
       */
      public FileInputStream createInputStream() throws IOException {
          if (mLength < 0) {
              return new ParcelFileDescriptor.AutoCloseInputStream(mFd);
          }
          return new AutoCloseInputStream(this);
      }
      
      /**
       * Create and return a new auto-close output stream for this asset.  This
       * will either return a full asset {@link AutoCloseOutputStream}, or
       * an underlying {@link ParcelFileDescriptor.AutoCloseOutputStream
       * ParcelFileDescriptor.AutoCloseOutputStream} depending on whether the
       * the object represents a complete file or sub-section of a file.  You
       * should only call this once for a particular asset.
       */
      public FileOutputStream createOutputStream() throws IOException {
          if (mLength < 0) {
              return new ParcelFileDescriptor.AutoCloseOutputStream(mFd);
          }
          return new AutoCloseOutputStream(this);
      }
      
      @Override
      public String toString() {
          return "{AssetFileDescriptor: " + mFd
                  + " start=" + mStartOffset + " len=" + mLength + "}";
      }
      
      /**
       * An InputStream you can create on a ParcelFileDescriptor, which will
       * take care of calling {@link ParcelFileDescriptor#close
       * ParcelFileDescriptor.close()} for you when the stream is closed.
       */
      public static class AutoCloseInputStream
              extends ParcelFileDescriptor.AutoCloseInputStream {
          private long mRemaining;
          
          public AutoCloseInputStream(AssetFileDescriptor fd) throws IOException {
              super(fd.getParcelFileDescriptor());
              super.skip(fd.getStartOffset());
              mRemaining = (int)fd.getLength();
          }
  
          @Override
          public int available() throws IOException {
              return mRemaining >= 0
                      ? (mRemaining < 0x7fffffff ? (int)mRemaining : 0x7fffffff)
                      : super.available();
          }
  
          @Override
          public int read() throws IOException {
              byte[] buffer = new byte[1];
              int result = read(buffer, 0, 1);
              return result == -1 ? -1 : buffer[0] & 0xff;
          }
  
          @Override
          public int read(byte[] buffer, int offset, int count) throws IOException {
              if (mRemaining >= 0) {
                  if (mRemaining == 0) return -1;
                  if (count > mRemaining) count = (int)mRemaining;
                  int res = super.read(buffer, offset, count);
                  if (res >= 0) mRemaining -= res;
                  return res;
              }
              
              return super.read(buffer, offset, count);
          }
  
          @Override
          public int read(byte[] buffer) throws IOException {
              return read(buffer, 0, buffer.length);
          }
  
          @Override
          public long skip(long count) throws IOException {
              if (mRemaining >= 0) {
                  if (mRemaining == 0) return -1;
                  if (count > mRemaining) count = mRemaining;
                  long res = super.skip(count);
                  if (res >= 0) mRemaining -= res;
                  return res;
              }
              
              return super.skip(count);
          }
  
          @Override
          public void mark(int readlimit) {
              if (mRemaining >= 0) {
                  // Not supported.
                  return;
              }
              super.mark(readlimit);
          }
  
          @Override
          public boolean markSupported() {
              if (mRemaining >= 0) {
                  return false;
              }
              return super.markSupported();
          }
  
          @Override
          public synchronized void reset() throws IOException {
              if (mRemaining >= 0) {
                  // Not supported.
                  return;
              }
              super.reset();
          }
      }
  
      /**
       * An OutputStream you can create on a ParcelFileDescriptor, which will
       * take care of calling {@link ParcelFileDescriptor#close
       * ParcelFileDescriptor.close()} for you when the stream is closed.
       */
      public static class AutoCloseOutputStream
              extends ParcelFileDescriptor.AutoCloseOutputStream {
          private long mRemaining;
          
          public AutoCloseOutputStream(AssetFileDescriptor fd) throws IOException {
              super(fd.getParcelFileDescriptor());
              if (fd.getParcelFileDescriptor().seekTo(fd.getStartOffset()) < 0) {
                  throw new IOException("Unable to seek");
              }
              mRemaining = (int)fd.getLength();
          }
  
          @Override
          public void write(byte[] buffer, int offset, int count) throws IOException {
              if (mRemaining >= 0) {
                  if (mRemaining == 0) return;
                  if (count > mRemaining) count = (int)mRemaining;
                  super.write(buffer, offset, count);
                  mRemaining -= count;
                  return;
              }
              
              super.write(buffer, offset, count);
          }
  
          @Override
          public void write(byte[] buffer) throws IOException {
              if (mRemaining >= 0) {
                  if (mRemaining == 0) return;
                  int count = buffer.length;
                  if (count > mRemaining) count = (int)mRemaining;
                  super.write(buffer);
                  mRemaining -= count;
                  return;
              }
              
              super.write(buffer);
          }
  
          @Override
          public void write(int oneByte) throws IOException {
              if (mRemaining >= 0) {
                  if (mRemaining == 0) return;
                  super.write(oneByte);
                  mRemaining--;
                  return;
              }
              
              super.write(oneByte);
          }
      }
  
      /* Parcelable interface */
      @Override
      public int describeContents() {
          return mFd.describeContents();
      }
  
      @Override
      public void writeToParcel(Parcel out, int flags) {
          mFd.writeToParcel(out, flags);
          out.writeLong(mStartOffset);
          out.writeLong(mLength);
          if (mExtras != null) {
              out.writeInt(1);
              out.writeBundle(mExtras);
          } else {
              out.writeInt(0);
          }
      }
  
      AssetFileDescriptor(Parcel src) {
          mFd = ParcelFileDescriptor.CREATOR.createFromParcel(src);
          mStartOffset = src.readLong();
          mLength = src.readLong();
          if (src.readInt() != 0) {
              mExtras = src.readBundle();
          } else {
              mExtras = null;
          }
      }
  
      public static final @android.annotation.NonNull Parcelable.Creator<AssetFileDescriptor> CREATOR
              = new Parcelable.Creator<AssetFileDescriptor>() {
          public AssetFileDescriptor createFromParcel(Parcel in) {
              return new AssetFileDescriptor(in);
          }
          public AssetFileDescriptor[] newArray(int size) {
              return new AssetFileDescriptor[size];
          }
      };
  
  }
  