content: /*
   * Copyright 2018 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.media;
  
  import android.annotation.NonNull;
  import android.annotation.Nullable;
  import android.net.Uri;
  import android.os.ParcelFileDescriptor;
  
  import java.net.CookieHandler;
  import java.net.CookieManager;
  import java.net.HttpCookie;
  import java.util.ArrayList;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  
  /**
   * Data source descriptor.
   *
   * Used by {@link MediaPlayer2#setDataSource}, {@link MediaPlayer2#setNextDataSource} and
   * {@link MediaPlayer2#setNextDataSources} to set data source for playback.
   *
   * @hide
   */
  public class DataSourceDesc {
      // intentionally less than long.MAX_VALUE
      static final long LONG_MAX = 0x7ffffffffffffffL;
  
      // keep consistent with native code
      public static final long LONG_MAX_TIME_MS = LONG_MAX / 1000;
      /**
       * @hide
       */
      public static final long LONG_MAX_TIME_US = LONG_MAX_TIME_MS * 1000;
  
      public static final long POSITION_UNKNOWN = LONG_MAX_TIME_MS;
  
      private String mMediaId;
      private long mStartPositionMs = 0;
      private long mEndPositionMs = POSITION_UNKNOWN;
  
      DataSourceDesc(String mediaId, long startPositionMs, long endPositionMs) {
          mMediaId = mediaId;
          mStartPositionMs = startPositionMs;
          mEndPositionMs = endPositionMs;
      }
  
      /**
       * Releases the resources held by this {@code DataSourceDesc} object.
       */
      void close() {
      }
  
      // Have to declare protected for finalize() since it is protected
      // in the base class Object.
      @Override
      protected void finalize() throws Throwable {
          close();
      }
  
      /**
       * Return the media Id of data source.
       * @return the media Id of data source
       */
      public @Nullable String getMediaId() {
          return mMediaId;
      }
  
      /**
       * Return the position in milliseconds at which the playback will start.
       * @return the position in milliseconds at which the playback will start
       */
      public long getStartPosition() {
          return mStartPositionMs;
      }
  
      /**
       * Return the position in milliseconds at which the playback will end.
       * {@link #POSITION_UNKNOWN} means ending at the end of source content.
       * @return the position in milliseconds at which the playback will end
       */
      public long getEndPosition() {
          return mEndPositionMs;
      }
  
      @Override
      public String toString() {
          final StringBuilder sb = new StringBuilder("DataSourceDesc{");
          sb.append("mMediaId=").append(mMediaId);
          sb.append(", mStartPositionMs=").append(mStartPositionMs);
          sb.append(", mEndPositionMs=").append(mEndPositionMs);
          sb.append('}');
          return sb.toString();
      }
  
      /**
       * Builder for {@link DataSourceDesc}.
       * <p>
       * Here is an example where <code>Builder</code> is used to define the
       * {@link DataSourceDesc} to be used by a {@link MediaPlayer2} instance:
       *
       * <pre class="prettyprint">
       * DataSourceDesc newDSD = new DataSourceDesc.Builder()
       *         .setDataSource(context, uri, headers, cookies)
       *         .setStartPosition(1000)
       *         .setEndPosition(15000)
       *         .build();
       * mediaplayer2.setDataSourceDesc(newDSD);
       * </pre>
       */
      public static final class Builder {
          private static final int SOURCE_TYPE_UNKNOWN = 0;
          private static final int SOURCE_TYPE_URI = 1;
          private static final int SOURCE_TYPE_FILE = 2;
  
          private int mSourceType = SOURCE_TYPE_UNKNOWN;
          private String mMediaId;
          private long mStartPositionMs = 0;
          private long mEndPositionMs = POSITION_UNKNOWN;
  
          // For UriDataSourceDesc
          private Uri mUri;
          private Map<String, String> mHeader;
          private List<HttpCookie> mCookies;
  
          // For FileDataSourceDesc
          private ParcelFileDescriptor mPFD;
          private long mOffset = 0;
          private long mLength = FileDataSourceDesc.FD_LENGTH_UNKNOWN;
  
          /**
           * Constructs a new BuilderBase with the defaults.
           */
          public Builder() {
          }
  
          /**
           * Constructs a new BuilderBase from a given {@link DataSourceDesc} instance
           * @param dsd the {@link DataSourceDesc} object whose data will be reused
           * in the new BuilderBase.
           */
          public Builder(@Nullable DataSourceDesc dsd) {
              if (dsd == null) {
                  return;
              }
              mMediaId = dsd.mMediaId;
              mStartPositionMs = dsd.mStartPositionMs;
              mEndPositionMs = dsd.mEndPositionMs;
              if (dsd instanceof FileDataSourceDesc) {
                  mSourceType = SOURCE_TYPE_FILE;
                  mPFD = ((FileDataSourceDesc) dsd).getParcelFileDescriptor();
                  mOffset = ((FileDataSourceDesc) dsd).getOffset();
                  mLength = ((FileDataSourceDesc) dsd).getLength();
              } else if (dsd instanceof UriDataSourceDesc) {
                  mSourceType = SOURCE_TYPE_URI;
                  mUri = ((UriDataSourceDesc) dsd).getUri();
                  mHeader = ((UriDataSourceDesc) dsd).getHeaders();
                  mCookies = ((UriDataSourceDesc) dsd).getCookies();
              } else {
                  throw new IllegalStateException("Unknown source type:" + mSourceType);
              }
          }
  
          /**
           * Sets all fields that have been set in the {@link DataSourceDesc} object.
           * <code>IllegalStateException</code> will be thrown if there is conflict between fields.
           *
           * @return {@link DataSourceDesc}
           */
          @NonNull
          public DataSourceDesc build() {
              if (mSourceType == SOURCE_TYPE_UNKNOWN) {
                  throw new IllegalStateException("Source is not set.");
              }
              if (mStartPositionMs > mEndPositionMs) {
                  throw new IllegalStateException("Illegal start/end position: "
                      + mStartPositionMs + " : " + mEndPositionMs);
              }
  
              DataSourceDesc desc;
              if (mSourceType == SOURCE_TYPE_FILE) {
                  desc = new FileDataSourceDesc(
                          mMediaId, mStartPositionMs, mEndPositionMs, mPFD, mOffset, mLength);
              } else if (mSourceType == SOURCE_TYPE_URI) {
                  desc = new UriDataSourceDesc(
                          mMediaId, mStartPositionMs, mEndPositionMs, mUri, mHeader, mCookies);
              } else {
                  throw new IllegalStateException("Unknown source type:" + mSourceType);
              }
              return desc;
          }
  
          /**
           * Sets the media Id of this data source.
           *
           * @param mediaId the media Id of this data source
           * @return the same Builder instance.
           */
          @NonNull
          public Builder setMediaId(@Nullable String mediaId) {
              mMediaId = mediaId;
              return this;
          }
  
          /**
           * Sets the start position in milliseconds at which the playback will start.
           * Any negative number is treated as 0.
           *
           * @param position the start position in milliseconds at which the playback will start
           * @return the same Builder instance.
           *
           */
          @NonNull
          public Builder setStartPosition(long position) {
              if (position < 0) {
                  position = 0;
              }
              mStartPositionMs = position;
              return this;
          }
  
          /**
           * Sets the end position in milliseconds at which the playback will end.
           * Any negative number is treated as maximum duration {@link #LONG_MAX_TIME_MS}
           * of the data source
           *
           * @param position the end position in milliseconds at which the playback will end
           * @return the same Builder instance.
           */
          @NonNull
          public Builder setEndPosition(long position) {
              if (position < 0) {
                  position = LONG_MAX_TIME_MS;
              }
              mEndPositionMs = position;
              return this;
          }
  
          /**
           * Sets the data source as a content Uri.
           *
           * @param uri the Content URI of the data you want to play
           * @return the same Builder instance.
           * @throws NullPointerException if context or uri is null.
           */
          @NonNull
          public Builder setDataSource(@NonNull Uri uri) {
              setSourceType(SOURCE_TYPE_URI);
              Media2Utils.checkArgument(uri != null, "uri cannot be null");
              mUri = uri;
              return this;
          }
  
          /**
           * Sets the data source as a content Uri.
           *
           * To provide cookies for the subsequent HTTP requests, you can install your own default
           * cookie handler and use other variants of setDataSource APIs instead. Alternatively, you
           * can use this API to pass the cookies as a list of HttpCookie. If the app has not
           * installed a CookieHandler already, {@link MediaPlayer2} will create a CookieManager
           * and populates its CookieStore with the provided cookies when this data source is passed
           * to {@link MediaPlayer2}. If the app has installed its own handler already, the handler
           * is required to be of CookieManager type such that {@link MediaPlayer2} can update the
           * manager’s CookieStore.
           *
           *  <p><strong>Note</strong> that the cross domain redirection is allowed by default,
           * but that can be changed with key/value pairs through the headers parameter with
           * "android-allow-cross-domain-redirect" as the key and "0" or "1" as the value to
           * disallow or allow cross domain redirection.
           *
           * @param uri the Content URI of the data you want to play
           * @param headers the headers to be sent together with the request for the data
           *                The headers must not include cookies. Instead, use the cookies param.
           * @param cookies the cookies to be sent together with the request
           * @return the same Builder instance.
           * @throws NullPointerException if context or uri is null.
           * @throws IllegalArgumentException if the cookie handler is not of CookieManager type
           *                                  when cookies are provided.
           */
          @NonNull
          public Builder setDataSource(@NonNull Uri uri, @Nullable Map<String, String> headers,
                  @Nullable List<HttpCookie> cookies) {
              setSourceType(SOURCE_TYPE_URI);
              Media2Utils.checkArgument(uri != null, "uri cannot be null");
              if (cookies != null) {
                  CookieHandler cookieHandler = CookieHandler.getDefault();
                  if (cookieHandler != null && !(cookieHandler instanceof CookieManager)) {
                      throw new IllegalArgumentException(
                              "The cookie handler has to be of CookieManager type "
                                      + "when cookies are provided.");
                  }
              }
  
              mUri = uri;
              if (headers != null) {
                  mHeader = new HashMap<String, String>(headers);
              }
              if (cookies != null) {
                  mCookies = new ArrayList<HttpCookie>(cookies);
              }
              return this;
          }
  
          /**
           * Sets the data source (ParcelFileDescriptor) to use. The ParcelFileDescriptor must be
           * seekable (N.B. a LocalSocket is not seekable). When the {@link DataSourceDesc}
           * created by this builder is passed to {@link MediaPlayer2} via
           * {@link MediaPlayer2#setDataSource},
           * {@link MediaPlayer2#setNextDataSource} or
           * {@link MediaPlayer2#setNextDataSources}, MediaPlayer2 will
           * close the ParcelFileDescriptor.
           *
           * @param pfd the ParcelFileDescriptor for the file to play
           * @return the same Builder instance.
           * @throws NullPointerException if pfd is null.
           */
          @NonNull
          public Builder setDataSource(@NonNull ParcelFileDescriptor pfd) {
              setSourceType(SOURCE_TYPE_FILE);
              Media2Utils.checkArgument(pfd != null, "pfd cannot be null.");
              mPFD = pfd;
              return this;
          }
  
          /**
           * Sets the data source (ParcelFileDescriptor) to use. The ParcelFileDescriptor must be
           * seekable (N.B. a LocalSocket is not seekable). When the {@link DataSourceDesc}
           * created by this builder is passed to {@link MediaPlayer2} via
           * {@link MediaPlayer2#setDataSource},
           * {@link MediaPlayer2#setNextDataSource} or
           * {@link MediaPlayer2#setNextDataSources}, MediaPlayer2 will
           * close the ParcelFileDescriptor.
           *
           * Any negative number for offset is treated as 0.
           * Any negative number for length is treated as maximum length of the data source.
           *
           * @param pfd the ParcelFileDescriptor for the file to play
           * @param offset the offset into the file where the data to be played starts, in bytes
           * @param length the length in bytes of the data to be played
           * @return the same Builder instance.
           * @throws NullPointerException if pfd is null.
           */
          @NonNull
          public Builder setDataSource(
                  @NonNull ParcelFileDescriptor pfd, long offset, long length) {
              setSourceType(SOURCE_TYPE_FILE);
              if (pfd == null) {
                  throw new NullPointerException("pfd cannot be null.");
              }
              if (offset < 0) {
                  offset = 0;
              }
              if (length < 0) {
                  length = FileDataSourceDesc.FD_LENGTH_UNKNOWN;
              }
              mPFD = pfd;
              mOffset = offset;
              mLength = length;
              return this;
          }
  
          private void setSourceType(int type) {
              if (mSourceType != SOURCE_TYPE_UNKNOWN) {
                  throw new IllegalStateException("Source is already set. type=" + mSourceType);
              }
              mSourceType = type;
          }
      }
  }
  