transcoding: Address API council comments Part1

1. Create a base class TranscodingRequest for VideoTranscodingRequest
and upcoming ImageTranscodingRequest in Androd T. Also have builder in each type
with VideoTranscodingRequest.builder inherit TranscodingRequest.builder.
2. Hiding the PRIORITY_REALTIME and setPrority api for Android S.
3. Hiding TRANSCODING_TYPE_VIDEO as it is only for internal bookkeeping
4. Move TRANSCODING_TYPE_* and PRIORITY_* to inside TranscodingRequest.
5. Remove the setSrcUri, setDstUri, setVideoTrackFormat and set them in constructor.

Bug: 181551684
Test: atest  CtsMediaTranscodingTestCases:MediaTranscodeManagerTest
Change-Id: Ib6fead5e9fcb48bfd1db35f9f35402d6fa1ed73d
diff --git a/apex/media/framework/Android.bp b/apex/media/framework/Android.bp
index 3d129d8..20ce133 100644
--- a/apex/media/framework/Android.bp
+++ b/apex/media/framework/Android.bp
@@ -135,6 +135,10 @@
         ":updatable-media-srcs",
     ],
 
+    api_lint: {
+        enabled: false,
+    },
+
     libs: [
         "framework_media_annotation",
     ],
diff --git a/apex/media/framework/api/system-current.txt b/apex/media/framework/api/system-current.txt
index 6158e2e..1d912eb 100644
--- a/apex/media/framework/api/system-current.txt
+++ b/apex/media/framework/api/system-current.txt
@@ -3,38 +3,19 @@
 
   public final class MediaTranscodeManager {
     method @Nullable public android.media.MediaTranscodeManager.TranscodingSession enqueueRequest(@NonNull android.media.MediaTranscodeManager.TranscodingRequest, @NonNull java.util.concurrent.Executor, @NonNull android.media.MediaTranscodeManager.OnTranscodingFinishedListener);
-    field public static final int PRIORITY_REALTIME = 1; // 0x1
-    field public static final int TRANSCODING_TYPE_VIDEO = 1; // 0x1
   }
 
   @java.lang.FunctionalInterface public static interface MediaTranscodeManager.OnTranscodingFinishedListener {
     method public void onTranscodingFinished(@NonNull android.media.MediaTranscodeManager.TranscodingSession);
   }
 
-  public static final class MediaTranscodeManager.TranscodingRequest {
+  public abstract static class MediaTranscodeManager.TranscodingRequest {
     method public int getClientPid();
     method public int getClientUid();
     method @Nullable public android.os.ParcelFileDescriptor getDestinationFileDescriptor();
     method @NonNull public android.net.Uri getDestinationUri();
-    method public int getPriority();
     method @Nullable public android.os.ParcelFileDescriptor getSourceFileDescriptor();
     method @NonNull public android.net.Uri getSourceUri();
-    method public int getType();
-    method @Nullable public android.media.MediaFormat getVideoTrackFormat();
-  }
-
-  public static final class MediaTranscodeManager.TranscodingRequest.Builder {
-    ctor public MediaTranscodeManager.TranscodingRequest.Builder();
-    method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest build();
-    method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest.Builder setClientPid(int);
-    method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest.Builder setClientUid(int);
-    method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest.Builder setDestinationFileDescriptor(@NonNull android.os.ParcelFileDescriptor);
-    method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest.Builder setDestinationUri(@NonNull android.net.Uri);
-    method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest.Builder setPriority(int);
-    method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest.Builder setSourceFileDescriptor(@NonNull android.os.ParcelFileDescriptor);
-    method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest.Builder setSourceUri(@NonNull android.net.Uri);
-    method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest.Builder setType(int);
-    method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest.Builder setVideoTrackFormat(@NonNull android.media.MediaFormat);
   }
 
   public static class MediaTranscodeManager.TranscodingRequest.MediaFormatResolver {
@@ -71,5 +52,18 @@
     method public void onProgressUpdate(@NonNull android.media.MediaTranscodeManager.TranscodingSession, @IntRange(from=0, to=100) int);
   }
 
+  public static final class MediaTranscodeManager.VideoTranscodingRequest extends android.media.MediaTranscodeManager.TranscodingRequest {
+    method @NonNull public android.media.MediaFormat getVideoTrackFormat();
+  }
+
+  public static final class MediaTranscodeManager.VideoTranscodingRequest.Builder {
+    ctor public MediaTranscodeManager.VideoTranscodingRequest.Builder(@NonNull android.net.Uri, @NonNull android.net.Uri, @NonNull android.media.MediaFormat);
+    method @NonNull public android.media.MediaTranscodeManager.VideoTranscodingRequest build();
+    method @NonNull public android.media.MediaTranscodeManager.VideoTranscodingRequest.Builder setClientPid(int);
+    method @NonNull public android.media.MediaTranscodeManager.VideoTranscodingRequest.Builder setClientUid(int);
+    method @NonNull public android.media.MediaTranscodeManager.VideoTranscodingRequest.Builder setDestinationFileDescriptor(android.os.ParcelFileDescriptor);
+    method @NonNull public android.media.MediaTranscodeManager.VideoTranscodingRequest.Builder setSourceFileDescriptor(android.os.ParcelFileDescriptor);
+  }
+
 }
 
diff --git a/apex/media/framework/java/android/media/MediaTranscodeManager.java b/apex/media/framework/java/android/media/MediaTranscodeManager.java
index 30d1896..af2aa6b 100644
--- a/apex/media/framework/java/android/media/MediaTranscodeManager.java
+++ b/apex/media/framework/java/android/media/MediaTranscodeManager.java
@@ -48,27 +48,23 @@
 import java.util.concurrent.Executors;
 
 /**
- MediaTranscodeManager provides an interface to the system's media transcoding service and can be
- used to transcode media files, e.g. transcoding a video from HEVC to AVC.
+ Android 12 introduces Compatible media transcoding feature.  See
+ <a href="https://developer.android.com/about/versions/12/features#compatible_media_transcoding">
+ Compatible media transcoding</a>. MediaTranscodeManager provides an interface to the system's media
+ transcoding service and can be used to transcode media files, e.g. transcoding a video from HEVC to
+ AVC.
 
  <h3>Transcoding Types</h3>
  <h4>Video Transcoding</h4>
- When transcoding a video file, the video file could be of any of the following types:
- <ul>
- <li> Video file with single video track. </li>
- <li> Video file with multiple video track. </li>
- <li> Video file with multiple video tracks and audio tracks. </li>
- <li> Video file with video/audio tracks and metadata track. Note that metadata track will be passed
- through only if it could be recognized by {@link MediaExtractor}.
- TODO(hkuang): Finalize the metadata track behavior. </li>
- </ul>
+ When transcoding a video file, the video track will be transcoded based on the desired track format
+ and the audio track will be pass through without any modification.
  <p class=note>
- Note that currently only support transcoding video file in mp4 format.
+ Note that currently only support transcoding video file in mp4 format and with single video track.
 
  <h3>Transcoding Request</h3>
  <p>
  To transcode a media file, first create a {@link TranscodingRequest} through its builder class
- {@link TranscodingRequest.Builder}. Transcode requests are then enqueue to the manager through
+ {@link VideoTranscodingRequest.Builder}. Transcode requests are then enqueue to the manager through
  {@link MediaTranscodeManager#enqueueRequest(
          TranscodingRequest, Executor, OnTranscodingFinishedListener)}
  TranscodeRequest are processed based on client process's priority and request priority. When a
@@ -80,23 +76,9 @@
  Here is an example where <code>Builder</code> is used to specify all parameters
 
  <pre class=prettyprint>
- TranscodingRequest request =
-    new TranscodingRequest.Builder()
-        .setSourceUri(srcUri)
-        .setDestinationUri(dstUri)
-        .setType(MediaTranscodeManager.TRANSCODING_TYPE_VIDEO)
-        .setPriority(REALTIME)
-        .setVideoTrackFormat(videoFormat)
-        .build();
+ VideoTranscodingRequest request =
+    new VideoTranscodingRequest.Builder(srcUri, dstUri, videoFormat).build();
  }</pre>
-
- TODO(hkuang): Add architecture diagram showing the transcoding service and api.
- TODO(hkuang): Add sample code when API is settled.
- TODO(hkuang): Clarify whether multiple video tracks is supported or not.
- TODO(hkuang): Clarify whether image/audio transcoding is supported or not.
- TODO(hkuang): Clarify what will happen if there is unrecognized track in the source.
- TODO(hkuang): Clarify whether supports scaling.
- TODO(hkuang): Clarify whether supports framerate conversion.
  @hide
  */
 @SystemApi
@@ -113,68 +95,6 @@
     private static final float BPP = 0.25f;
 
     /**
-     * Default transcoding type.
-     * @hide
-     */
-    public static final int TRANSCODING_TYPE_UNKNOWN = 0;
-
-    /**
-     * TRANSCODING_TYPE_VIDEO indicates that client wants to perform transcoding on a video file.
-     * <p>Note that currently only support transcoding video file in mp4 format.
-     */
-    public static final int TRANSCODING_TYPE_VIDEO = 1;
-
-    /**
-     * TRANSCODING_TYPE_IMAGE indicates that client wants to perform transcoding on an image file.
-     * @hide
-     */
-    public static final int TRANSCODING_TYPE_IMAGE = 2;
-
-    /** @hide */
-    @IntDef(prefix = {"TRANSCODING_TYPE_"}, value = {
-            TRANSCODING_TYPE_UNKNOWN,
-            TRANSCODING_TYPE_VIDEO,
-            TRANSCODING_TYPE_IMAGE,
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface TranscodingType {}
-
-    /**
-     * Default value.
-     * @hide
-     */
-    public static final int PRIORITY_UNKNOWN = 0;
-    /**
-     * PRIORITY_REALTIME indicates that the transcoding request is time-critical and that the
-     * client wants the transcoding result as soon as possible.
-     * <p> Set PRIORITY_REALTIME only if the transcoding is time-critical as it will involve
-     * performance penalty due to resource reallocation to prioritize the sessions with higher
-     * priority.
-     * TODO(hkuang): Add more description of this when priority is finalized.
-     */
-    public static final int PRIORITY_REALTIME = 1;
-
-    /**
-     * PRIORITY_OFFLINE indicates the transcoding is not time-critical and the client does not need
-     * the transcoding result as soon as possible.
-     * <p>Sessions with PRIORITY_OFFLINE will be scheduled behind PRIORITY_REALTIME. Always set to
-     * PRIORITY_OFFLINE if client does not need the result as soon as possible and could accept
-     * delay of the transcoding result.
-     * @hide
-     * TODO(hkuang): Add more description of this when priority is finalized.
-     */
-    public static final int PRIORITY_OFFLINE = 2;
-
-    /** @hide */
-    @IntDef(prefix = {"PRIORITY_"}, value = {
-            PRIORITY_UNKNOWN,
-            PRIORITY_REALTIME,
-            PRIORITY_OFFLINE,
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface TranscodingPriority {}
-
-    /**
      * Listener that gets notified when a transcoding operation has finished.
      * This listener gets notified regardless of how the operation finished. It is up to the
      * listener implementation to check the result and take appropriate action.
@@ -500,7 +420,79 @@
         }
     }
 
-    public static final class TranscodingRequest {
+    /**
+     * Abstract base class for all the TranscodingRequest.
+     * <p> TranscodingRequest encapsulates the desired configuration for the transcoding.
+     */
+    public abstract static class TranscodingRequest {
+        /**
+         *
+         * Default transcoding type.
+         * @hide
+         */
+        public static final int TRANSCODING_TYPE_UNKNOWN = 0;
+
+        /**
+         * TRANSCODING_TYPE_VIDEO indicates that client wants to perform transcoding on a video.
+         * <p>Note that currently only support transcoding video file in mp4 format.
+         * @hide
+         */
+        public static final int TRANSCODING_TYPE_VIDEO = 1;
+
+        /**
+         * TRANSCODING_TYPE_IMAGE indicates that client wants to perform transcoding on an image.
+         * @hide
+         */
+        public static final int TRANSCODING_TYPE_IMAGE = 2;
+
+        /** @hide */
+        @IntDef(prefix = {"TRANSCODING_TYPE_"}, value = {
+                TRANSCODING_TYPE_UNKNOWN,
+                TRANSCODING_TYPE_VIDEO,
+                TRANSCODING_TYPE_IMAGE,
+        })
+        @Retention(RetentionPolicy.SOURCE)
+        public @interface TranscodingType {}
+
+        /**
+         * Default value.
+         *
+         * @hide
+         */
+        public static final int PRIORITY_UNKNOWN = 0;
+        /**
+         * PRIORITY_REALTIME indicates that the transcoding request is time-critical and that the
+         * client wants the transcoding result as soon as possible.
+         * <p> Set PRIORITY_REALTIME only if the transcoding is time-critical as it will involve
+         * performance penalty due to resource reallocation to prioritize the sessions with higher
+         * priority.
+         *
+         * @hide
+         */
+        public static final int PRIORITY_REALTIME = 1;
+
+        /**
+         * PRIORITY_OFFLINE indicates the transcoding is not time-critical and the client does not
+         * need the transcoding result as soon as possible.
+         * <p>Sessions with PRIORITY_OFFLINE will be scheduled behind PRIORITY_REALTIME. Always set
+         * to
+         * PRIORITY_OFFLINE if client does not need the result as soon as possible and could accept
+         * delay of the transcoding result.
+         *
+         * @hide
+         *
+         */
+        public static final int PRIORITY_OFFLINE = 2;
+
+        /** @hide */
+        @IntDef(prefix = {"PRIORITY_"}, value = {
+                PRIORITY_UNKNOWN,
+                PRIORITY_REALTIME,
+                PRIORITY_OFFLINE,
+        })
+        @Retention(RetentionPolicy.SOURCE)
+        public @interface TranscodingPriority {}
+
         /** Uri of the source media file. */
         private @NonNull Uri mSourceUri;
 
@@ -534,22 +526,6 @@
         private @TranscodingPriority int mPriority = PRIORITY_UNKNOWN;
 
         /**
-         * Desired output video format of the destination file.
-         * <p> If this is null, source file's video track will be passed through and copied to the
-         * destination file.
-         * <p>
-         */
-        private @Nullable MediaFormat mVideoTrackFormat = null;
-
-        /**
-         * Desired output audio format of the destination file.
-         * <p> If this is null, source file's audio track will be passed through and copied to the
-         * destination file.
-         * @hide
-         */
-        private @Nullable MediaFormat mAudioTrackFormat = null;
-
-        /**
          * Desired image format for the destination file.
          * <p> If this is null, source file's image track will be passed through and copied to the
          * destination file.
@@ -560,6 +536,12 @@
         @VisibleForTesting
         private TranscodingTestConfig mTestConfig = null;
 
+        /**
+         * Prevent public constructor access.
+         */
+        /* package private */ TranscodingRequest() {
+        }
+
         private TranscodingRequest(Builder b) {
             mSourceUri = b.mSourceUri;
             mSourceFileDescriptor = b.mSourceFileDescriptor;
@@ -569,13 +551,13 @@
             mClientPid = b.mClientPid;
             mPriority = b.mPriority;
             mType = b.mType;
-            mVideoTrackFormat = b.mVideoTrackFormat;
-            mAudioTrackFormat = b.mAudioTrackFormat;
-            mImageFormat = b.mImageFormat;
             mTestConfig = b.mTestConfig;
         }
 
-        /** Return the type of the transcoding. */
+        /**
+         * Return the type of the transcoding.
+         * @hide
+         */
         @TranscodingType
         public int getType() {
             return mType;
@@ -621,22 +603,16 @@
             return mDestinationFileDescriptor;
         }
 
-        /** Return priority of the transcoding. */
+        /**
+         * Return priority of the transcoding.
+         * @hide
+         */
         @TranscodingPriority
         public int getPriority() {
             return mPriority;
         }
 
         /**
-         * Return the video track format of the transcoding.
-         * This will be null is the transcoding is not for video transcoding.
-         */
-        @Nullable
-        public MediaFormat getVideoTrackFormat() {
-            return mVideoTrackFormat;
-        }
-
-        /**
          * Return TestConfig of the transcoding.
          * @hide
          */
@@ -645,6 +621,8 @@
             return mTestConfig;
         }
 
+        abstract void writeFormatToParcel(TranscodingRequestParcel parcel);
+
         /* Writes the TranscodingRequest to a parcel. */
         private TranscodingRequestParcel writeToParcel(@NonNull Context context) {
             TranscodingRequestParcel parcel = new TranscodingRequestParcel();
@@ -668,7 +646,7 @@
                 }
                 parcel.clientPackageName = packageName;
             }
-            parcel.requestedVideoTrackFormat = convertToVideoTrackFormat(mVideoTrackFormat);
+            writeFormatToParcel(parcel);
             if (mTestConfig != null) {
                 parcel.isForTesting = true;
                 parcel.testConfig = mTestConfig;
@@ -676,71 +654,12 @@
             return parcel;
         }
 
-        /* Converts the MediaFormat to TranscodingVideoTrackFormat. */
-        private static TranscodingVideoTrackFormat convertToVideoTrackFormat(MediaFormat format) {
-            if (format == null) {
-                throw new IllegalArgumentException("Invalid MediaFormat");
-            }
-
-            TranscodingVideoTrackFormat trackFormat = new TranscodingVideoTrackFormat();
-
-            if (format.containsKey(MediaFormat.KEY_MIME)) {
-                String mime = format.getString(MediaFormat.KEY_MIME);
-                if (MediaFormat.MIMETYPE_VIDEO_AVC.equals(mime)) {
-                    trackFormat.codecType = TranscodingVideoCodecType.kAvc;
-                } else if (MediaFormat.MIMETYPE_VIDEO_HEVC.equals(mime)) {
-                    trackFormat.codecType = TranscodingVideoCodecType.kHevc;
-                } else {
-                    throw new UnsupportedOperationException("Only support transcode to avc/hevc");
-                }
-            }
-
-            if (format.containsKey(MediaFormat.KEY_BIT_RATE)) {
-                int bitrateBps = format.getInteger(MediaFormat.KEY_BIT_RATE);
-                if (bitrateBps <= 0) {
-                    throw new IllegalArgumentException("Bitrate must be larger than 0");
-                }
-                trackFormat.bitrateBps = bitrateBps;
-            }
-
-            if (format.containsKey(MediaFormat.KEY_WIDTH) && format.containsKey(
-                    MediaFormat.KEY_HEIGHT)) {
-                int width = format.getInteger(MediaFormat.KEY_WIDTH);
-                int height = format.getInteger(MediaFormat.KEY_HEIGHT);
-                if (width <= 0 || height <= 0) {
-                    throw new IllegalArgumentException("Width and height must be larger than 0");
-                }
-                // TODO(hkuang): Validate the aspect ratio after adding scaling.
-                trackFormat.width = width;
-                trackFormat.height = height;
-            }
-
-            if (format.containsKey(MediaFormat.KEY_PROFILE)) {
-                int profile = format.getInteger(MediaFormat.KEY_PROFILE);
-                if (profile <= 0) {
-                    throw new IllegalArgumentException("Invalid codec profile");
-                }
-                // TODO(hkuang): Validate the profile according to codec type.
-                trackFormat.profile = profile;
-            }
-
-            if (format.containsKey(MediaFormat.KEY_LEVEL)) {
-                int level = format.getInteger(MediaFormat.KEY_LEVEL);
-                if (level <= 0) {
-                    throw new IllegalArgumentException("Invalid codec level");
-                }
-                // TODO(hkuang): Validate the level according to codec type.
-                trackFormat.level = level;
-            }
-
-            return trackFormat;
-        }
-
         /**
-         * Builder class for {@link TranscodingRequest} objects.
-         * Use this class to configure and create a <code>TranscodingRequest</code> instance.
+         * Builder to build a {@link TranscodingRequest} object.
+         *
+         * @param <T> The subclass to be built.
          */
-        public static final class Builder {
+        abstract static class Builder<T extends Builder<T>> {
             private @NonNull Uri mSourceUri;
             private @NonNull Uri mDestinationUri;
             private @Nullable ParcelFileDescriptor mSourceFileDescriptor = null;
@@ -749,80 +668,68 @@
             private int mClientPid = -1;
             private @TranscodingType int mType = TRANSCODING_TYPE_UNKNOWN;
             private @TranscodingPriority int mPriority = PRIORITY_UNKNOWN;
-            private @Nullable MediaFormat mVideoTrackFormat;
-            private @Nullable MediaFormat mAudioTrackFormat;
-            private @Nullable MediaFormat mImageFormat;
             private TranscodingTestConfig mTestConfig;
 
+            abstract T self();
+
             /**
-             * Specifies the uri of source media file.
+             * Creates a builder for building {@link TranscodingRequest}s.
              *
              * Client must set the source Uri. If client also provides the source fileDescriptor
              * through is provided by {@link #setSourceFileDescriptor(ParcelFileDescriptor)},
              * TranscodingSession will use the fd instead of calling back to the client to open the
              * sourceUri.
+             *
+             *
+             * @param type The transcoding type.
              * @param sourceUri Content uri for the source media file.
-             * @return The same builder instance.
-             * @throws IllegalArgumentException if Uri is null or empty.
+             * @param destinationUri Content uri for the destination media file.
+             *
              */
-            @NonNull
-            public Builder setSourceUri(@NonNull Uri sourceUri) {
+            private Builder(@TranscodingType int type, @NonNull Uri sourceUri,
+                    @NonNull Uri destinationUri) {
+                mType = type;
+
                 if (sourceUri == null || Uri.EMPTY.equals(sourceUri)) {
                     throw new IllegalArgumentException(
                             "You must specify a non-empty source Uri.");
                 }
                 mSourceUri = sourceUri;
-                return this;
+
+                if (destinationUri == null || Uri.EMPTY.equals(destinationUri)) {
+                    throw new IllegalArgumentException(
+                            "You must specify a non-empty destination Uri.");
+                }
+                mDestinationUri = destinationUri;
             }
 
             /**
              * Specifies the fileDescriptor opened from the source media file.
              *
              * This call is optional. If the source fileDescriptor is provided, TranscodingSession
-             * will use it directly instead of opening the uri from {@link #setSourceUri(Uri)}. It
-             * is client's responsibility to make sure the fileDescriptor is opened from the source
-             * uri.
+             * will use it directly instead of opening the uri from {@link #Builder(int, Uri, Uri)}.
+             * It is client's responsibility to make sure the fileDescriptor is opened from the
+             * source uri.
              * @param fileDescriptor a {@link ParcelFileDescriptor} opened from source media file.
              * @return The same builder instance.
              * @throws IllegalArgumentException if fileDescriptor is invalid.
              */
             @NonNull
-            public Builder setSourceFileDescriptor(@NonNull ParcelFileDescriptor fileDescriptor) {
+            public T setSourceFileDescriptor(@NonNull ParcelFileDescriptor fileDescriptor) {
                 if (fileDescriptor == null || fileDescriptor.getFd() < 0) {
                     throw new IllegalArgumentException(
                             "Invalid source descriptor.");
                 }
                 mSourceFileDescriptor = fileDescriptor;
-                return this;
-            }
-
-            /**
-             * Specifies the uri of the destination media file.
-             *
-             * Client must set the destination Uri. If client also provides the destination
-             * fileDescriptor through {@link #setDestinationFileDescriptor(ParcelFileDescriptor)},
-             * TranscodingSession will use the fd instead of calling back to the client to open the
-             * destinationUri.
-             * @param destinationUri Content uri for the destination media file.
-             * @return The same builder instance.
-             * @throws IllegalArgumentException if Uri is null or empty.
-             */
-            @NonNull
-            public Builder setDestinationUri(@NonNull Uri destinationUri) {
-                if (destinationUri == null || Uri.EMPTY.equals(destinationUri)) {
-                    throw new IllegalArgumentException(
-                            "You must specify a non-empty destination Uri.");
-                }
-                mDestinationUri = destinationUri;
-                return this;
+                return self();
             }
 
             /**
              * Specifies the fileDescriptor opened from the destination media file.
              *
              * This call is optional. If the destination fileDescriptor is provided,
-             * TranscodingSession will use it directly instead of opening the uri from
-             * {@link #setDestinationUri(Uri)} upon transcoding starts. It is client's
+             * TranscodingSession will use it directly instead of opening the source uri from
+             * {@link #Builder(int, Uri, Uri)} upon transcoding starts. It is client's
              * responsibility to make sure the fileDescriptor is opened from the destination uri.
              * @param fileDescriptor a {@link ParcelFileDescriptor} opened from destination media
              *                       file.
@@ -830,46 +737,54 @@
              * @throws IllegalArgumentException if fileDescriptor is invalid.
              */
             @NonNull
-            public Builder setDestinationFileDescriptor(
+            public T setDestinationFileDescriptor(
                     @NonNull ParcelFileDescriptor fileDescriptor) {
                 if (fileDescriptor == null || fileDescriptor.getFd() < 0) {
                     throw new IllegalArgumentException(
                             "Invalid destination descriptor.");
                 }
                 mDestinationFileDescriptor = fileDescriptor;
-                return this;
+                return self();
             }
 
             /**
              * Specify the UID of the client that this request is for.
+             * <p>
+             * Only privilege caller with android.permission.WRITE_MEDIA_STORAGE could forward the
+             * pid. Note that the permission check happens on the service side upon starting the
+             * transcoding. If the client does not have the permission, the transcoding will fail.
+             *
              * @param uid client Uid.
              * @return The same builder instance.
              * @throws IllegalArgumentException if uid is invalid.
-             * TODO(hkuang): Check the permission if it is allowed.
              */
             @NonNull
-            public Builder setClientUid(int uid) {
+            public T setClientUid(int uid) {
                 if (uid < 0) {
                     throw new IllegalArgumentException("Invalid Uid");
                 }
                 mClientUid = uid;
-                return this;
+                return self();
             }
 
             /**
-             * Specify the PID of the client that this request is for.
+             * Specify the pid of the client that this request is for.
+             * <p>
+             * Only privilege caller with android.permission.WRITE_MEDIA_STORAGE could forward the
+             * pid. Note that the permission check happens on the service side upon starting the
+             * transcoding. If the client does not have the permission, the transcoding will fail.
+             *
              * @param pid client Pid.
              * @return The same builder instance.
              * @throws IllegalArgumentException if pid is invalid.
-             * TODO(hkuang): Check the permission if it is allowed.
              */
             @NonNull
-            public Builder setClientPid(int pid) {
+            public T setClientPid(int pid) {
                 if (pid < 0) {
                     throw new IllegalArgumentException("Invalid pid");
                 }
                 mClientPid = pid;
-                return this;
+                return self();
             }
 
             /**
@@ -878,64 +793,15 @@
              * @param priority Must be one of the {@code PRIORITY_*}
              * @return The same builder instance.
              * @throws IllegalArgumentException if flags is invalid.
+             * @hide
              */
             @NonNull
-            public Builder setPriority(@TranscodingPriority int priority) {
+            public T setPriority(@TranscodingPriority int priority) {
                 if (priority != PRIORITY_OFFLINE && priority != PRIORITY_REALTIME) {
                     throw new IllegalArgumentException("Invalid priority: " + priority);
                 }
                 mPriority = priority;
-                return this;
-            }
-
-            /**
-             * Specifies the type of transcoding.
-             * <p> Clients must provide the source and destination that corresponds to the
-             * transcoding type.
-             *
-             * @param type Must be one of the {@code TRANSCODING_TYPE_*}
-             * @return The same builder instance.
-             * @throws IllegalArgumentException if flags is invalid.
-             */
-            @NonNull
-            public Builder setType(@TranscodingType int type) {
-                if (type != TRANSCODING_TYPE_VIDEO && type != TRANSCODING_TYPE_IMAGE) {
-                    throw new IllegalArgumentException("Invalid transcoding type");
-                }
-                mType = type;
-                return this;
-            }
-
-            /**
-             * Specifies the desired video track format in the destination media file.
-             * <p>Client could only specify the settings that matters to them, e.g. codec format or
-             * bitrate. And by default, transcoding will preserve the original video's
-             * settings(bitrate, framerate, resolution) if not provided.
-             * <p>Note that some settings may silently fail to apply if the device does not
-             * support them.
-             * TODO(hkuang): Add MediaTranscodeUtil to help client generate transcoding setting.
-             * TODO(hkuang): Add MediaTranscodeUtil to check if the setting is valid.
-             *
-             * @param videoFormat MediaFormat containing the settings that client wants override in
-             *                    the original video's video track.
-             * @return The same builder instance.
-             * @throws IllegalArgumentException if videoFormat is invalid.
-             */
-            @NonNull
-            public Builder setVideoTrackFormat(@NonNull MediaFormat videoFormat) {
-                if (videoFormat == null) {
-                    throw new IllegalArgumentException("videoFormat must not be null");
-                }
-
-                // Check if the MediaFormat is for video by looking at the MIME type.
-                String mime = videoFormat.containsKey(MediaFormat.KEY_MIME)
-                        ? videoFormat.getString(MediaFormat.KEY_MIME) : null;
-                if (mime == null || !mime.startsWith("video/")) {
-                    throw new IllegalArgumentException("Invalid video format: wrong mime type");
-                }
-
-                mVideoTrackFormat = videoFormat;
-                return this;
+                return self();
             }
 
             /**
@@ -946,44 +812,9 @@
              */
             @VisibleForTesting
             @NonNull
-            public Builder setTestConfig(@NonNull TranscodingTestConfig config) {
+            public T setTestConfig(@NonNull TranscodingTestConfig config) {
                 mTestConfig = config;
-                return this;
-            }
-
-            /**
-             * @return a new {@link TranscodingRequest} instance successfully initialized with all
-             *     the parameters set on this <code>Builder</code>.
-             * @throws UnsupportedOperationException if the parameters set on the
-             *         <code>Builder</code> were incompatible, or if they are not supported by the
-             *         device.
-             */
-            @NonNull
-            public TranscodingRequest build() {
-                if (mSourceUri == null) {
-                    throw new UnsupportedOperationException("Source URI must not be null");
-                }
-
-                if (mDestinationUri == null) {
-                    throw new UnsupportedOperationException("Destination URI must not be null");
-                }
-
-                if (mPriority == PRIORITY_UNKNOWN) {
-                    throw new UnsupportedOperationException("Must specify transcoding priority");
-                }
-
-                // Only support video transcoding now.
-                if (mType != TRANSCODING_TYPE_VIDEO) {
-                    throw new UnsupportedOperationException("Only supports video transcoding now");
-                }
-
-                // Must provide video track format for video transcoding.
-                if (mType == TRANSCODING_TYPE_VIDEO && mVideoTrackFormat == null) {
-                    throw new UnsupportedOperationException(
-                            "Must provide video track format for video transcoding");
-                }
-
-                return new TranscodingRequest(this);
+                return self();
             }
         }
 
@@ -1198,6 +1029,206 @@
     }
 
     /**
+     * VideoTranscodingRequest encapsulates the configuration for transcoding a video.
+     */
+    public static final class VideoTranscodingRequest extends TranscodingRequest {
+        /**
+         * Desired output video format of the destination file.
+         * <p> If this is null, source file's video track will be passed through and copied to the
+         * destination file.
+         */
+        private @Nullable MediaFormat mVideoTrackFormat = null;
+
+        /**
+         * Desired output audio format of the destination file.
+         * <p> If this is null, source file's audio track will be passed through and copied to the
+         * destination file.
+         */
+        private @Nullable MediaFormat mAudioTrackFormat = null;
+
+        private VideoTranscodingRequest(VideoTranscodingRequest.Builder builder) {
+            super(builder);
+            mVideoTrackFormat = builder.mVideoTrackFormat;
+            mAudioTrackFormat = builder.mAudioTrackFormat;
+        }
+
+        /**
+         * Return the video track format of the transcoding.
+         * This will be null if client has not specified the video track format.
+         */
+        @NonNull
+        public MediaFormat getVideoTrackFormat() {
+            return mVideoTrackFormat;
+        }
+
+        @Override
+        void writeFormatToParcel(TranscodingRequestParcel parcel) {
+            parcel.requestedVideoTrackFormat = convertToVideoTrackFormat(mVideoTrackFormat);
+        }
+
+        /* Converts the MediaFormat to TranscodingVideoTrackFormat. */
+        private static TranscodingVideoTrackFormat convertToVideoTrackFormat(MediaFormat format) {
+            if (format == null) {
+                throw new IllegalArgumentException("Invalid MediaFormat");
+            }
+
+            TranscodingVideoTrackFormat trackFormat = new TranscodingVideoTrackFormat();
+
+            if (format.containsKey(MediaFormat.KEY_MIME)) {
+                String mime = format.getString(MediaFormat.KEY_MIME);
+                if (MediaFormat.MIMETYPE_VIDEO_AVC.equals(mime)) {
+                    trackFormat.codecType = TranscodingVideoCodecType.kAvc;
+                } else if (MediaFormat.MIMETYPE_VIDEO_HEVC.equals(mime)) {
+                    trackFormat.codecType = TranscodingVideoCodecType.kHevc;
+                } else {
+                    throw new UnsupportedOperationException("Only support transcode to avc/hevc");
+                }
+            }
+
+            if (format.containsKey(MediaFormat.KEY_BIT_RATE)) {
+                int bitrateBps = format.getInteger(MediaFormat.KEY_BIT_RATE);
+                if (bitrateBps <= 0) {
+                    throw new IllegalArgumentException("Bitrate must be larger than 0");
+                }
+                trackFormat.bitrateBps = bitrateBps;
+            }
+
+            if (format.containsKey(MediaFormat.KEY_WIDTH) && format.containsKey(
+                    MediaFormat.KEY_HEIGHT)) {
+                int width = format.getInteger(MediaFormat.KEY_WIDTH);
+                int height = format.getInteger(MediaFormat.KEY_HEIGHT);
+                if (width <= 0 || height <= 0) {
+                    throw new IllegalArgumentException("Width and height must be larger than 0");
+                }
+                // TODO: Validate the aspect ratio after adding scaling.
+                trackFormat.width = width;
+                trackFormat.height = height;
+            }
+
+            if (format.containsKey(MediaFormat.KEY_PROFILE)) {
+                int profile = format.getInteger(MediaFormat.KEY_PROFILE);
+                if (profile <= 0) {
+                    throw new IllegalArgumentException("Invalid codec profile");
+                }
+                // TODO: Validate the profile according to codec type.
+                trackFormat.profile = profile;
+            }
+
+            if (format.containsKey(MediaFormat.KEY_LEVEL)) {
+                int level = format.getInteger(MediaFormat.KEY_LEVEL);
+                if (level <= 0) {
+                    throw new IllegalArgumentException("Invalid codec level");
+                }
+                // TODO: Validate the level according to codec type.
+                trackFormat.level = level;
+            }
+
+            return trackFormat;
+        }
+
+        /**
+         * Builder class for {@link VideoTranscodingRequest}.
+         */
+        public static final class Builder extends
+                TranscodingRequest.Builder<VideoTranscodingRequest.Builder> {
+            /**
+             * Desired output video format of the destination file.
+             * <p> If this is null, source file's video track will be passed through and
+             * copied to the destination file.
+             */
+            private @Nullable MediaFormat mVideoTrackFormat = null;
+
+            /**
+             * Desired output audio format of the destination file.
+             * <p> If this is null, source file's audio track will be passed through and copied
+             * to the destination file.
+             */
+            private @Nullable MediaFormat mAudioTrackFormat = null;
+
+            /**
+             * Creates a builder for building {@link VideoTranscodingRequest}s.
+             *
+             * <p> Client could only specify the settings that matters to them, e.g. codec format or
+             * bitrate. And by default, transcoding will preserve the original video's settings
+             * (bitrate, framerate, resolution) if not provided.
+             * <p>Note that some settings may silently fail to apply if the device does not support
+             * them.
+             * @param sourceUri Content uri for the source media file.
+             * @param destinationUri Content uri for the destination media file.
+             * @param videoFormat MediaFormat containing the settings that client wants override in
+             *                    the original video's video track.
+             * @throws IllegalArgumentException if videoFormat is invalid.
+             */
+            public Builder(@NonNull Uri sourceUri, @NonNull Uri destinationUri,
+                    @NonNull MediaFormat videoFormat) {
+                super(TRANSCODING_TYPE_VIDEO, sourceUri, destinationUri);
+                setVideoTrackFormat(videoFormat);
+            }
+
+            @Override
+            @NonNull
+            public Builder setClientUid(int uid) {
+                super.setClientUid(uid);
+                return self();
+            }
+
+            @Override
+            @NonNull
+            public Builder setClientPid(int pid) {
+                super.setClientPid(pid);
+                return self();
+            }
+
+            @Override
+            @NonNull
+            public Builder setSourceFileDescriptor(ParcelFileDescriptor fd) {
+                super.setSourceFileDescriptor(fd);
+                return self();
+            }
+
+            @Override
+            @NonNull
+            public Builder setDestinationFileDescriptor(ParcelFileDescriptor fd) {
+                super.setDestinationFileDescriptor(fd);
+                return self();
+            }
+
+            private void setVideoTrackFormat(@NonNull MediaFormat videoFormat) {
+                if (videoFormat == null) {
+                    throw new IllegalArgumentException("videoFormat must not be null");
+                }
+
+                // Check if the MediaFormat is for video by looking at the MIME type.
+                String mime = videoFormat.containsKey(MediaFormat.KEY_MIME)
+                        ? videoFormat.getString(MediaFormat.KEY_MIME) : null;
+                if (mime == null || !mime.startsWith("video/")) {
+                    throw new IllegalArgumentException("Invalid video format: wrong mime type");
+                }
+
+                mVideoTrackFormat = videoFormat;
+            }
+
+            /**
+             * @return a new {@link TranscodingRequest} instance successfully initialized
+             * with all the parameters set on this <code>Builder</code>.
+             * @throws UnsupportedOperationException if the parameters set on the
+             *                                       <code>Builder</code> were incompatible, or
+             *                                       if they are not supported by the
+             *                                       device.
+             */
+            @NonNull
+            public VideoTranscodingRequest build() {
+                return new VideoTranscodingRequest(this);
+            }
+
+            @Override
+            VideoTranscodingRequest.Builder self() {
+                return this;
+            }
+        }
+    }
+
+    /**
      * Handle to an enqueued transcoding operation. An instance of this class represents a single
      * enqueued transcoding operation. The caller can use that instance to query the status or
      * progress, and to get the result once the operation has completed.