content: /*
   * Copyright (C) 2008 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.IntDef;
  import android.annotation.NonNull;
  import android.annotation.Nullable;
  import android.annotation.UnsupportedAppUsage;
  import android.content.ContentResolver;
  import android.content.Context;
  import android.content.res.AssetFileDescriptor;
  import android.graphics.Bitmap;
  import android.net.Uri;
  import android.os.Build;
  import android.os.IBinder;
  
  import java.io.FileDescriptor;
  import java.io.FileInputStream;
  import java.io.FileNotFoundException;
  import java.io.IOException;
  import java.lang.annotation.Retention;
  import java.lang.annotation.RetentionPolicy;
  import java.util.List;
  import java.util.Map;
  
  /**
   * MediaMetadataRetriever class provides a unified interface for retrieving
   * frame and meta data from an input media file.
   */
  public class MediaMetadataRetriever implements AutoCloseable {
      static {
          System.loadLibrary("media_jni");
          native_init();
      }
  
      // The field below is accessed by native methods
      @SuppressWarnings("unused")
      private long mNativeContext;
  
      private static final int EMBEDDED_PICTURE_TYPE_ANY = 0xFFFF;
  
      public MediaMetadataRetriever() {
          native_setup();
      }
  
      /**
       * Sets the data source (file pathname) to use. Call this
       * method before the rest of the methods in this class. This method may be
       * time-consuming.
       *
       * @param path The path of the input media file.
       * @throws IllegalArgumentException If the path is invalid.
       */
      public void setDataSource(String path) throws IllegalArgumentException {
          if (path == null) {
              throw new IllegalArgumentException();
          }
  
          try (FileInputStream is = new FileInputStream(path)) {
              FileDescriptor fd = is.getFD();
              setDataSource(fd, 0, 0x7ffffffffffffffL);
          } catch (FileNotFoundException fileEx) {
              throw new IllegalArgumentException();
          } catch (IOException ioEx) {
              throw new IllegalArgumentException();
          }
      }
  
      /**
       * Sets the data source (URI) to use. Call this
       * method before the rest of the methods in this class. This method may be
       * time-consuming.
       *
       * @param uri The URI of the input media.
       * @param headers the headers to be sent together with the request for the data
       * @throws IllegalArgumentException If the URI is invalid.
       */
      public void setDataSource(String uri,  Map<String, String> headers)
              throws IllegalArgumentException {
          int i = 0;
          String[] keys = new String[headers.size()];
          String[] values = new String[headers.size()];
          for (Map.Entry<String, String> entry: headers.entrySet()) {
              keys[i] = entry.getKey();
              values[i] = entry.getValue();
              ++i;
          }
  
          _setDataSource(
                  MediaHTTPService.createHttpServiceBinderIfNecessary(uri),
                  uri,
                  keys,
                  values);
      }
  
      private native void _setDataSource(
          IBinder httpServiceBinder, String uri, String[] keys, String[] values)
          throws IllegalArgumentException;
  
      /**
       * Sets the data source (FileDescriptor) to use.  It is the caller's
       * responsibility to close the file descriptor. It is safe to do so as soon
       * as this call returns. Call this method before the rest of the methods in
       * this class. This method may be time-consuming.
       *
       * @param fd the FileDescriptor for the file you want to play
       * @param offset the offset into the file where the data to be played starts,
       * in bytes. It must be non-negative
       * @param length the length in bytes of the data to be played. It must be
       * non-negative.
       * @throws IllegalArgumentException if the arguments are invalid
       */
      public native void setDataSource(FileDescriptor fd, long offset, long length)
              throws IllegalArgumentException;
  
      /**
       * Sets the data source (FileDescriptor) to use. It is the caller's
       * responsibility to close the file descriptor. It is safe to do so as soon
       * as this call returns. Call this method before the rest of the methods in
       * this class. This method may be time-consuming.
       *
       * @param fd the FileDescriptor for the file you want to play
       * @throws IllegalArgumentException if the FileDescriptor is invalid
       */
      public void setDataSource(FileDescriptor fd)
              throws IllegalArgumentException {
          // intentionally less than LONG_MAX
          setDataSource(fd, 0, 0x7ffffffffffffffL);
      }
  
      /**
       * Sets the data source as a content Uri. Call this method before
       * the rest of the methods in this class. This method may be time-consuming.
       *
       * @param context the Context to use when resolving the Uri
       * @param uri the Content URI of the data you want to play
       * @throws IllegalArgumentException if the Uri is invalid
       * @throws SecurityException if the Uri cannot be used due to lack of
       * permission.
       */
      public void setDataSource(Context context, Uri uri)
          throws IllegalArgumentException, SecurityException {
          if (uri == null) {
              throw new IllegalArgumentException();
          }
  
          String scheme = uri.getScheme();
          if(scheme == null || scheme.equals("file")) {
              setDataSource(uri.getPath());
              return;
          }
  
          AssetFileDescriptor fd = null;
          try {
              ContentResolver resolver = context.getContentResolver();
              try {
                  fd = resolver.openAssetFileDescriptor(uri, "r");
              } catch(FileNotFoundException e) {
                  throw new IllegalArgumentException();
              }
              if (fd == null) {
                  throw new IllegalArgumentException();
              }
              FileDescriptor descriptor = fd.getFileDescriptor();
              if (!descriptor.valid()) {
                  throw new IllegalArgumentException();
              }
              // Note: using getDeclaredLength so that our behavior is the same
              // as previous versions when the content provider is returning
              // a full file.
              if (fd.getDeclaredLength() < 0) {
                  setDataSource(descriptor);
              } else {
                  setDataSource(descriptor, fd.getStartOffset(), fd.getDeclaredLength());
              }
              return;
          } catch (SecurityException ex) {
          } finally {
              try {
                  if (fd != null) {
                      fd.close();
                  }
              } catch(IOException ioEx) {
              }
          }
          setDataSource(uri.toString());
      }
  
      /**
       * Sets the data source (MediaDataSource) to use.
       *
       * @param dataSource the MediaDataSource for the media you want to play
       */
      public void setDataSource(MediaDataSource dataSource)
              throws IllegalArgumentException {
          _setDataSource(dataSource);
      }
  
      private native void _setDataSource(MediaDataSource dataSource)
            throws IllegalArgumentException;
  
      /**
       * Call this method after setDataSource(). This method retrieves the
       * meta data value associated with the keyCode.
       *
       * The keyCode currently supported is listed below as METADATA_XXX
       * constants. With any other value, it returns a null pointer.
       *
       * @param keyCode One of the constants listed below at the end of the class.
       * @return The meta data value associate with the given keyCode on success;
       * null on failure.
       */
      public native String extractMetadata(int keyCode);
  
      /**
       * Call this method after setDataSource(). This method finds a
       * representative frame close to the given time position by considering
       * the given option if possible, and returns it as a bitmap.
       *
       * <p>If you don't need a full-resolution
       * frame (for example, because you need a thumbnail image), use
       * {@link #getScaledFrameAtTime getScaledFrameAtTime()} instead of this
       * method.</p>
       *
       * @param timeUs The time position where the frame will be retrieved.
       * When retrieving the frame at the given time position, there is no
       * guarantee that the data source has a frame located at the position.
       * When this happens, a frame nearby will be returned. If timeUs is
       * negative, time position and option will ignored, and any frame
       * that the implementation considers as representative may be returned.
       *
       * @param option a hint on how the frame is found. Use
       * {@link #OPTION_PREVIOUS_SYNC} if one wants to retrieve a sync frame
       * that has a timestamp earlier than or the same as timeUs. Use
       * {@link #OPTION_NEXT_SYNC} if one wants to retrieve a sync frame
       * that has a timestamp later than or the same as timeUs. Use
       * {@link #OPTION_CLOSEST_SYNC} if one wants to retrieve a sync frame
       * that has a timestamp closest to or the same as timeUs. Use
       * {@link #OPTION_CLOSEST} if one wants to retrieve a frame that may
       * or may not be a sync frame but is closest to or the same as timeUs.
       * {@link #OPTION_CLOSEST} often has larger performance overhead compared
       * to the other options if there is no sync frame located at timeUs.
       *
       * @return A Bitmap containing a representative video frame, which
       *         can be null, if such a frame cannot be retrieved.
       */
      public Bitmap getFrameAtTime(long timeUs, @Option int option) {
          if (option < OPTION_PREVIOUS_SYNC ||
              option > OPTION_CLOSEST) {
              throw new IllegalArgumentException("Unsupported option: " + option);
          }
  
          return _getFrameAtTime(timeUs, option, -1 /*dst_width*/, -1 /*dst_height*/);
      }
  
      /**
       * Retrieve a video frame near a given timestamp scaled to a desired size.
       * Call this method after setDataSource(). This method finds a representative
       * frame close to the given time position by considering the given option
       * if possible, and returns it as a bitmap with same aspect ratio as the source
       * while scaling it so that it fits into the desired size of dst_width by dst_height.
       * This is useful for generating a thumbnail for an input data source or just to
       * obtain a scaled frame at the given time position.
       *
       * @param timeUs The time position in microseconds where the frame will be retrieved.
       * When retrieving the frame at the given time position, there is no
       * guarantee that the data source has a frame located at the position.
       * When this happens, a frame nearby will be returned. If timeUs is
       * negative, time position and option will ignored, and any frame
       * that the implementation considers as representative may be returned.
       *
       * @param option a hint on how the frame is found. Use
       * {@link #OPTION_PREVIOUS_SYNC} if one wants to retrieve a sync frame
       * that has a timestamp earlier than or the same as timeUs. Use
       * {@link #OPTION_NEXT_SYNC} if one wants to retrieve a sync frame
       * that has a timestamp later than or the same as timeUs. Use
       * {@link #OPTION_CLOSEST_SYNC} if one wants to retrieve a sync frame
       * that has a timestamp closest to or the same as timeUs. Use
       * {@link #OPTION_CLOSEST} if one wants to retrieve a frame that may
       * or may not be a sync frame but is closest to or the same as timeUs.
       * {@link #OPTION_CLOSEST} often has larger performance overhead compared
       * to the other options if there is no sync frame located at timeUs.
       *
       * @param dstWidth expected output bitmap width
       * @param dstHeight expected output bitmap height
       * @return A Bitmap of size not larger than dstWidth by dstHeight containing a
       *         scaled video frame, which can be null, if such a frame cannot be retrieved.
       * @throws IllegalArgumentException if passed in invalid option or width by height
       *         is less than or equal to 0.
       */
      public Bitmap getScaledFrameAtTime(
              long timeUs, @Option int option, int dstWidth, int dstHeight) {
          if (option < OPTION_PREVIOUS_SYNC ||
              option > OPTION_CLOSEST) {
              throw new IllegalArgumentException("Unsupported option: " + option);
          }
          if (dstWidth <= 0) {
              throw new IllegalArgumentException("Invalid width: " + dstWidth);
          }
          if (dstHeight <= 0) {
              throw new IllegalArgumentException("Invalid height: " + dstHeight);
          }
  
          return _getFrameAtTime(timeUs, option, dstWidth, dstHeight);
      }
  
      /**
       * Call this method after setDataSource(). This method finds a
       * representative frame close to the given time position if possible,
       * and returns it as a bitmap. Call this method if one does not care
       * how the frame is found as long as it is close to the given time;
       * otherwise, please call {@link #getFrameAtTime(long, int)}.
       *
       * <p>If you don't need a full-resolution
       * frame (for example, because you need a thumbnail image), use
       * {@link #getScaledFrameAtTime getScaledFrameAtTime()} instead of this
       * method.</p>
       *
       * @param timeUs The time position where the frame will be retrieved.
       * When retrieving the frame at the given time position, there is no
       * guarentee that the data source has a frame located at the position.
       * When this happens, a frame nearby will be returned. If timeUs is
       * negative, time position and option will ignored, and any frame
       * that the implementation considers as representative may be returned.
       *
       * @return A Bitmap of size dst_widthxdst_height containing a representative
       *         video frame, which can be null, if such a frame cannot be retrieved.
       *
       * @see #getFrameAtTime(long, int)
       */
      public Bitmap getFrameAtTime(long timeUs) {
          return getFrameAtTime(timeUs, OPTION_CLOSEST_SYNC);
      }
  
      /**
       * Call this method after setDataSource(). This method finds a
       * representative frame at any time position if possible,
       * and returns it as a bitmap. Call this method if one does not
       * care about where the frame is located; otherwise, please call
       * {@link #getFrameAtTime(long)} or {@link #getFrameAtTime(long, int)}
       *
       * <p>If you don't need a full-resolution
       * frame (for example, because you need a thumbnail image), use
       * {@link #getScaledFrameAtTime getScaledFrameAtTime()} instead of this
       * method.</p>
       *
       * @return A Bitmap containing a representative video frame, which
       *         can be null, if such a frame cannot be retrieved.
       *
       * @see #getFrameAtTime(long)
       * @see #getFrameAtTime(long, int)
       */
      public Bitmap getFrameAtTime() {
          return _getFrameAtTime(-1, OPTION_CLOSEST_SYNC, -1 /*dst_width*/, -1 /*dst_height*/);
      }
  
      private native Bitmap _getFrameAtTime(long timeUs, int option, int width, int height);
  
      public static final class BitmapParams {
          private Bitmap.Config inPreferredConfig = Bitmap.Config.ARGB_8888;
          private Bitmap.Config outActualConfig = Bitmap.Config.ARGB_8888;
  
          /**
           * Create a default BitmapParams object. By default, it uses {@link Bitmap.Config#ARGB_8888}
           * as the preferred bitmap config.
           */
          public BitmapParams() {}
  
          /**
           * Set the preferred bitmap config for the decoder to decode into.
           *
           * If not set, or the request cannot be met, the decoder will output
           * in {@link Bitmap.Config#ARGB_8888} config by default.
           *
           * After decode, the actual config used can be retrieved by {@link #getActualConfig()}.
           *
           * @param config the preferred bitmap config to use.
           */
          public void setPreferredConfig(@NonNull Bitmap.Config config) {
              if (config == null) {
                  throw new IllegalArgumentException("preferred config can't be null");
              }
              inPreferredConfig = config;
          }
  
          /**
           * Retrieve the preferred bitmap config in the params.
           *
           * @return the preferred bitmap config.
           */
          public @NonNull Bitmap.Config getPreferredConfig() {
              return inPreferredConfig;
          }
  
          /**
           * Get the actual bitmap config used to decode the bitmap after the decoding.
           *
           * @return the actual bitmap config used.
           */
          public @NonNull Bitmap.Config getActualConfig() {
              return outActualConfig;
          }
      }
  
      /**
       * This method retrieves a video frame by its index. It should only be called
       * after {@link #setDataSource}.
       *
       * After the bitmap is returned, you can query the actual parameters that were
       * used to create the bitmap from the {@code BitmapParams} argument, for instance
       * to query the bitmap config used for the bitmap with {@link BitmapParams#getActualConfig}.
       *
       * @param frameIndex 0-based index of the video frame. The frame index must be that of
       *        a valid frame. The total number of frames available for retrieval can be queried
       *        via the {@link #METADATA_KEY_VIDEO_FRAME_COUNT} key.
       * @param params BitmapParams that controls the returned bitmap config (such as pixel formats).
       *
       * @throws IllegalStateException if the container doesn't contain video or image sequences.
       * @throws IllegalArgumentException if the requested frame index does not exist.
       *
       * @return A Bitmap containing the requested video frame, or null if the retrieval fails.
       *
       * @see #getFrameAtIndex(int)
       * @see #getFramesAtIndex(int, int, BitmapParams)
       * @see #getFramesAtIndex(int, int)
       */
      public Bitmap getFrameAtIndex(int frameIndex, @NonNull BitmapParams params) {
          List<Bitmap> bitmaps = getFramesAtIndex(frameIndex, 1, params);
          return bitmaps.get(0);
      }
  
      /**
       * This method is similar to {@link #getFrameAtIndex(int, BitmapParams)} except that
       * the default for {@link BitmapParams} will be used.
       *
       * @param frameIndex 0-based index of the video frame. The frame index must be that of
       *        a valid frame. The total number of frames available for retrieval can be queried
       *        via the {@link #METADATA_KEY_VIDEO_FRAME_COUNT} key.
       *
       * @throws IllegalStateException if the container doesn't contain video or image sequences.
       * @throws IllegalArgumentException if the requested frame index does not exist.
       *
       * @return A Bitmap containing the requested video frame, or null if the retrieval fails.
       *
       * @see #getFrameAtIndex(int, BitmapParams)
       * @see #getFramesAtIndex(int, int, BitmapParams)
       * @see #getFramesAtIndex(int, int)
       */
      public Bitmap getFrameAtIndex(int frameIndex) {
          List<Bitmap> bitmaps = getFramesAtIndex(frameIndex, 1);
          return bitmaps.get(0);
      }
  
      /**
       * This method retrieves a consecutive set of video frames starting at the
       * specified index. It should only be called after {@link #setDataSource}.
       *
       * If the caller intends to retrieve more than one consecutive video frames,
       * this method is preferred over {@link #getFrameAtIndex(int, BitmapParams)} for efficiency.
       *
       * After the bitmaps are returned, you can query the actual parameters that were
       * used to create the bitmaps from the {@code BitmapParams} argument, for instance
       * to query the bitmap config used for the bitmaps with {@link BitmapParams#getActualConfig}.
       *
       * @param frameIndex 0-based index of the first video frame to retrieve. The frame index
       *        must be that of a valid frame. The total number of frames available for retrieval
       *        can be queried via the {@link #METADATA_KEY_VIDEO_FRAME_COUNT} key.
       * @param numFrames number of consecutive video frames to retrieve. Must be a positive
       *        value. The stream must contain at least numFrames frames starting at frameIndex.
       * @param params BitmapParams that controls the returned bitmap config (such as pixel formats).
       *
       * @throws IllegalStateException if the container doesn't contain video or image sequences.
       * @throws IllegalArgumentException if the frameIndex or numFrames is invalid, or the
       *         stream doesn't contain at least numFrames starting at frameIndex.
  
       * @return An list of Bitmaps containing the requested video frames. The returned
       *         array could contain less frames than requested if the retrieval fails.
       *
       * @see #getFrameAtIndex(int, BitmapParams)
       * @see #getFrameAtIndex(int)
       * @see #getFramesAtIndex(int, int)
       */
      public @NonNull List<Bitmap> getFramesAtIndex(
              int frameIndex, int numFrames, @NonNull BitmapParams params) {
          return getFramesAtIndexInternal(frameIndex, numFrames, params);
      }
  
      /**
       * This method is similar to {@link #getFramesAtIndex(int, int, BitmapParams)} except that
       * the default for {@link BitmapParams} will be used.
       *
       * @param frameIndex 0-based index of the first video frame to retrieve. The frame index
       *        must be that of a valid frame. The total number of frames available for retrieval
       *        can be queried via the {@link #METADATA_KEY_VIDEO_FRAME_COUNT} key.
       * @param numFrames number of consecutive video frames to retrieve. Must be a positive
       *        value. The stream must contain at least numFrames frames starting at frameIndex.
       *
       * @throws IllegalStateException if the container doesn't contain video or image sequences.
       * @throws IllegalArgumentException if the frameIndex or numFrames is invalid, or the
       *         stream doesn't contain at least numFrames starting at frameIndex.
  
       * @return An list of Bitmaps containing the requested video frames. The returned
       *         array could contain less frames than requested if the retrieval fails.
       *
       * @see #getFrameAtIndex(int, BitmapParams)
       * @see #getFrameAtIndex(int)
       * @see #getFramesAtIndex(int, int, BitmapParams)
       */
      public @NonNull List<Bitmap> getFramesAtIndex(int frameIndex, int numFrames) {
          return getFramesAtIndexInternal(frameIndex, numFrames, null);
      }
  
      private @NonNull List<Bitmap> getFramesAtIndexInternal(
              int frameIndex, int numFrames, @Nullable BitmapParams params) {
          if (!"yes".equals(extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_VIDEO))) {
              throw new IllegalStateException("Does not contail video or image sequences");
          }
          int frameCount = Integer.parseInt(
                  extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_FRAME_COUNT));
          if (frameIndex < 0 || numFrames < 1
                  || frameIndex >= frameCount
                  || frameIndex > frameCount - numFrames) {
              throw new IllegalArgumentException("Invalid frameIndex or numFrames: "
                  + frameIndex + ", " + numFrames);
          }
          return _getFrameAtIndex(frameIndex, numFrames, params);
      }
  
      private native @NonNull List<Bitmap> _getFrameAtIndex(
              int frameIndex, int numFrames, @Nullable BitmapParams params);
  
      /**
       * This method retrieves a still image by its index. It should only be called
       * after {@link #setDataSource}.
       *
       * After the bitmap is returned, you can query the actual parameters that were
       * used to create the bitmap from the {@code BitmapParams} argument, for instance
       * to query the bitmap config used for the bitmap with {@link BitmapParams#getActualConfig}.
       *
       * @param imageIndex 0-based index of the image.
       * @param params BitmapParams that controls the returned bitmap config (such as pixel formats).
       *
       * @throws IllegalStateException if the container doesn't contain still images.
       * @throws IllegalArgumentException if the requested image does not exist.
       *
       * @return the requested still image, or null if the image cannot be retrieved.
       *
       * @see #getImageAtIndex(int)
       * @see #getPrimaryImage(BitmapParams)
       * @see #getPrimaryImage()
       */
      public Bitmap getImageAtIndex(int imageIndex, @NonNull BitmapParams params) {
          return getImageAtIndexInternal(imageIndex, params);
      }
  
      /**
       * @hide
       *
       * This method retrieves the thumbnail image for a still image if it's available.
       * It should only be called after {@link #setDataSource}.
       *
       * @param imageIndex 0-based index of the image, negative value indicates primary image.
       * @param params BitmapParams that controls the returned bitmap config (such as pixel formats).
       * @param targetSize intended size of one edge (wdith or height) of the thumbnail,
       *                   this is a heuristic for the framework to decide whether the embedded
       *                   thumbnail should be used.
       * @param maxPixels maximum pixels of thumbnail, this is a heuristic for the frameowrk to
       *                  decide whehther the embedded thumnbail (or a downscaled version of it)
       *                  should be used.
       * @return the retrieved thumbnail, or null if no suitable thumbnail is available.
       */
      public native @Nullable Bitmap getThumbnailImageAtIndex(
              int imageIndex, @NonNull BitmapParams params, int targetSize, int maxPixels);
  
      /**
       * This method is similar to {@link #getImageAtIndex(int, BitmapParams)} except that
       * the default for {@link BitmapParams} will be used.
       *
       * @param imageIndex 0-based index of the image.
       *
       * @throws IllegalStateException if the container doesn't contain still images.
       * @throws IllegalArgumentException if the requested image does not exist.
       *
       * @return the requested still image, or null if the image cannot be retrieved.
       *
       * @see #getImageAtIndex(int, BitmapParams)
       * @see #getPrimaryImage(BitmapParams)
       * @see #getPrimaryImage()
       */
      public Bitmap getImageAtIndex(int imageIndex) {
          return getImageAtIndexInternal(imageIndex, null);
      }
  
      /**
       * This method retrieves the primary image of the media content. It should only
       * be called after {@link #setDataSource}.
       *
       * After the bitmap is returned, you can query the actual parameters that were
       * used to create the bitmap from the {@code BitmapParams} argument, for instance
       * to query the bitmap config used for the bitmap with {@link BitmapParams#getActualConfig}.
       *
       * @param params BitmapParams that controls the returned bitmap config (such as pixel formats).
       *
       * @return the primary image, or null if it cannot be retrieved.
       *
       * @throws IllegalStateException if the container doesn't contain still images.
       *
       * @see #getImageAtIndex(int, BitmapParams)
       * @see #getImageAtIndex(int)
       * @see #getPrimaryImage()
       */
      public Bitmap getPrimaryImage(@NonNull BitmapParams params) {
          return getImageAtIndexInternal(-1, params);
      }
  
      /**
       * This method is similar to {@link #getPrimaryImage(BitmapParams)} except that
       * the default for {@link BitmapParams} will be used.
       *
       * @return the primary image, or null if it cannot be retrieved.
       *
       * @throws IllegalStateException if the container doesn't contain still images.
       *
       * @see #getImageAtIndex(int, BitmapParams)
       * @see #getImageAtIndex(int)
       * @see #getPrimaryImage(BitmapParams)
       */
      public Bitmap getPrimaryImage() {
          return getImageAtIndexInternal(-1, null);
      }
  
      private Bitmap getImageAtIndexInternal(int imageIndex, @Nullable BitmapParams params) {
          if (!"yes".equals(extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_IMAGE))) {
              throw new IllegalStateException("Does not contail still images");
          }
  
          String imageCount = extractMetadata(MediaMetadataRetriever.METADATA_KEY_IMAGE_COUNT);
          if (imageIndex >= Integer.parseInt(imageCount)) {
              throw new IllegalArgumentException("Invalid image index: " + imageCount);
          }
  
          return _getImageAtIndex(imageIndex, params);
      }
  
      private native Bitmap _getImageAtIndex(int imageIndex, @Nullable BitmapParams params);
  
      /**
       * Call this method after setDataSource(). This method finds the optional
       * graphic or album/cover art associated associated with the data source. If
       * there are more than one pictures, (any) one of them is returned.
       *
       * @return null if no such graphic is found.
       */
      public byte[] getEmbeddedPicture() {
          return getEmbeddedPicture(EMBEDDED_PICTURE_TYPE_ANY);
      }
  
      @UnsupportedAppUsage
      private native byte[] getEmbeddedPicture(int pictureType);
  
      @Override
      public void close() {
          release();
      }
  
      /**
       * Call it when one is done with the object. This method releases the memory
       * allocated internally.
       */
      public native void release();
      @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
      private native void native_setup();
      @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
      private static native void native_init();
  
      @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
      private native final void native_finalize();
  
      @Override
      protected void finalize() throws Throwable {
          try {
              native_finalize();
          } finally {
              super.finalize();
          }
      }
  
      /**
       * Option used in method {@link #getFrameAtTime(long, int)} to get a
       * frame at a specified location.
       *
       * @see #getFrameAtTime(long, int)
       */
      /* Do not change these option values without updating their counterparts
       * in include/media/MediaSource.h!
       */
      /**
       * This option is used with {@link #getFrameAtTime(long, int)} to retrieve
       * a sync (or key) frame associated with a data source that is located
       * right before or at the given time.
       *
       * @see #getFrameAtTime(long, int)
       */
      public static final int OPTION_PREVIOUS_SYNC    = 0x00;
      /**
       * This option is used with {@link #getFrameAtTime(long, int)} to retrieve
       * a sync (or key) frame associated with a data source that is located
       * right after or at the given time.
       *
       * @see #getFrameAtTime(long, int)
       */
      public static final int OPTION_NEXT_SYNC        = 0x01;
      /**
       * This option is used with {@link #getFrameAtTime(long, int)} to retrieve
       * a sync (or key) frame associated with a data source that is located
       * closest to (in time) or at the given time.
       *
       * @see #getFrameAtTime(long, int)
       */
      public static final int OPTION_CLOSEST_SYNC     = 0x02;
      /**
       * This option is used with {@link #getFrameAtTime(long, int)} to retrieve
       * a frame (not necessarily a key frame) associated with a data source that
       * is located closest to or at the given time.
       *
       * @see #getFrameAtTime(long, int)
       */
      public static final int OPTION_CLOSEST          = 0x03;
  
      /** @hide */
      @IntDef(flag = true, prefix = { "OPTION_" }, value = {
              OPTION_PREVIOUS_SYNC,
              OPTION_NEXT_SYNC,
              OPTION_CLOSEST_SYNC,
              OPTION_CLOSEST,
      })
      @Retention(RetentionPolicy.SOURCE)
      public @interface Option {}
  
      /*
       * Do not change these metadata key values without updating their
       * counterparts in include/media/mediametadataretriever.h!
       */
      /**
       * The metadata key to retrieve the numeric string describing the
       * order of the audio data source on its original recording.
       */
      public static final int METADATA_KEY_CD_TRACK_NUMBER = 0;
      /**
       * The metadata key to retrieve the information about the album title
       * of the data source.
       */
      public static final int METADATA_KEY_ALBUM           = 1;
      /**
       * The metadata key to retrieve the information about the artist of
       * the data source.
       */
      public static final int METADATA_KEY_ARTIST          = 2;
      /**
       * The metadata key to retrieve the information about the author of
       * the data source.
       */
      public static final int METADATA_KEY_AUTHOR          = 3;
      /**
       * The metadata key to retrieve the information about the composer of
       * the data source.
       */
      public static final int METADATA_KEY_COMPOSER        = 4;
      /**
       * The metadata key to retrieve the date when the data source was created
       * or modified.
       */
      public static final int METADATA_KEY_DATE            = 5;
      /**
       * The metadata key to retrieve the content type or genre of the data
       * source.
       */
      public static final int METADATA_KEY_GENRE           = 6;
      /**
       * The metadata key to retrieve the data source title.
       */
      public static final int METADATA_KEY_TITLE           = 7;
      /**
       * The metadata key to retrieve the year when the data source was created
       * or modified.
       */
      public static final int METADATA_KEY_YEAR            = 8;
      /**
       * The metadata key to retrieve the playback duration of the data source.
       */
      public static final int METADATA_KEY_DURATION        = 9;
      /**
       * The metadata key to retrieve the number of tracks, such as audio, video,
       * text, in the data source, such as a mp4 or 3gpp file.
       */
      public static final int METADATA_KEY_NUM_TRACKS      = 10;
      /**
       * The metadata key to retrieve the information of the writer (such as
       * lyricist) of the data source.
       */
      public static final int METADATA_KEY_WRITER          = 11;
      /**
       * The metadata key to retrieve the mime type of the data source. Some
       * example mime types include: "video/mp4", "audio/mp4", "audio/amr-wb",
       * etc.
       */
      public static final int METADATA_KEY_MIMETYPE        = 12;
      /**
       * The metadata key to retrieve the information about the performers or
       * artist associated with the data source.
       */
      public static final int METADATA_KEY_ALBUMARTIST     = 13;
      /**
       * The metadata key to retrieve the numberic string that describes which
       * part of a set the audio data source comes from.
       */
      public static final int METADATA_KEY_DISC_NUMBER     = 14;
      /**
       * The metadata key to retrieve the music album compilation status.
       */
      public static final int METADATA_KEY_COMPILATION     = 15;
      /**
       * If this key exists the media contains audio content.
       */
      public static final int METADATA_KEY_HAS_AUDIO       = 16;
      /**
       * If this key exists the media contains video content.
       */
      public static final int METADATA_KEY_HAS_VIDEO       = 17;
      /**
       * If the media contains video, this key retrieves its width.
       */
      public static final int METADATA_KEY_VIDEO_WIDTH     = 18;
      /**
       * If the media contains video, this key retrieves its height.
       */
      public static final int METADATA_KEY_VIDEO_HEIGHT    = 19;
      /**
       * This key retrieves the average bitrate (in bits/sec), if available.
       */
      public static final int METADATA_KEY_BITRATE         = 20;
      /**
       * This key retrieves the language code of text tracks, if available.
       * If multiple text tracks present, the return value will look like:
       * "eng:chi"
       * @hide
       */
      public static final int METADATA_KEY_TIMED_TEXT_LANGUAGES      = 21;
      /**
       * If this key exists the media is drm-protected.
       * @hide
       */
      public static final int METADATA_KEY_IS_DRM          = 22;
      /**
       * This key retrieves the location information, if available.
       * The location should be specified according to ISO-6709 standard, under
       * a mp4/3gp box "@xyz". Location with longitude of -90 degrees and latitude
       * of 180 degrees will be retrieved as "+180.0000-90.0000/", for instance.
       */
      public static final int METADATA_KEY_LOCATION        = 23;
      /**
       * This key retrieves the video rotation angle in degrees, if available.
       * The video rotation angle may be 0, 90, 180, or 270 degrees.
       */
      public static final int METADATA_KEY_VIDEO_ROTATION = 24;
      /**
       * This key retrieves the original capture framerate, if it's
       * available. The capture framerate will be a floating point
       * number.
       */
      public static final int METADATA_KEY_CAPTURE_FRAMERATE = 25;
      /**
       * If this key exists the media contains still image content.
       */
      public static final int METADATA_KEY_HAS_IMAGE       = 26;
      /**
       * If the media contains still images, this key retrieves the number
       * of still images.
       */
      public static final int METADATA_KEY_IMAGE_COUNT     = 27;
      /**
       * If the media contains still images, this key retrieves the image
       * index of the primary image.
       */
      public static final int METADATA_KEY_IMAGE_PRIMARY   = 28;
      /**
       * If the media contains still images, this key retrieves the width
       * of the primary image.
       */
      public static final int METADATA_KEY_IMAGE_WIDTH     = 29;
      /**
       * If the media contains still images, this key retrieves the height
       * of the primary image.
       */
      public static final int METADATA_KEY_IMAGE_HEIGHT    = 30;
      /**
       * If the media contains still images, this key retrieves the rotation
       * angle (in degrees clockwise) of the primary image. The image rotation
       * angle must be one of 0, 90, 180, or 270 degrees.
       */
      public static final int METADATA_KEY_IMAGE_ROTATION  = 31;
      /**
       * If the media contains video and this key exists, it retrieves the
       * total number of frames in the video sequence.
       */
      public static final int METADATA_KEY_VIDEO_FRAME_COUNT = 32;
  
      /**
       * If the media contains EXIF data, this key retrieves the offset value
       * of the data.
       */
      public static final int METADATA_KEY_EXIF_OFFSET = 33;
  
      /**
       * If the media contains EXIF data, this key retrieves the length of the
       * data.
       */
      public static final int METADATA_KEY_EXIF_LENGTH = 34;
  
      /**
       * This key retrieves the color standard, if available.
       *
       * @see MediaFormat#COLOR_STANDARD_BT709
       * @see MediaFormat#COLOR_STANDARD_BT601_PAL
       * @see MediaFormat#COLOR_STANDARD_BT601_NTSC
       * @see MediaFormat#COLOR_STANDARD_BT2020
       *
       * @hide
       */
      public static final int METADATA_KEY_COLOR_STANDARD = 35;
  
      /**
       * This key retrieves the color transfer, if available.
       *
       * @see MediaFormat#COLOR_TRANSFER_LINEAR
       * @see MediaFormat#COLOR_TRANSFER_SDR_VIDEO
       * @see MediaFormat#COLOR_TRANSFER_ST2084
       * @see MediaFormat#COLOR_TRANSFER_HLG
       *
       * @hide
       */
      public static final int METADATA_KEY_COLOR_TRANSFER = 36;
  
      /**
       * This key retrieves the color range, if available.
       *
       * @see MediaFormat#COLOR_RANGE_LIMITED
       * @see MediaFormat#COLOR_RANGE_FULL
       *
       * @hide
       */
      public static final int METADATA_KEY_COLOR_RANGE    = 37;
      // Add more here...
  
      /**
       * This key retrieves the sample rate, if available.
       * @hide
       */
      public static final int METADATA_KEY_SAMPLERATE      = 38;
  
      /**
       * This key retrieves the bits per sample, if available.
       * @hide
       */
      public static final int METADATA_KEY_BITS_PER_SAMPLE = 39;
  }
  