Merge "Fixed a flash of the preview image when going to the dialer" into lmp-dev
diff --git a/api/current.txt b/api/current.txt
index 6fbb5ce..2ff971b 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -14526,7 +14526,6 @@
     method public abstract long getTimestamp();
     method public abstract int getWidth();
     method public void setCropRect(android.graphics.Rect);
-    field protected android.graphics.Rect mCropRect;
   }
 
   public static abstract class Image.Plane {
@@ -14658,7 +14657,6 @@
   }
 
   public static final class MediaCodec.CodecException extends java.lang.IllegalStateException {
-    ctor public MediaCodec.CodecException(int, int, java.lang.String);
     method public int getErrorCode();
     method public boolean isRecoverable();
     method public boolean isTransient();
@@ -14690,14 +14688,22 @@
     method public final boolean isEncoder();
   }
 
+  public static final class MediaCodecInfo.AudioCapabilities {
+    method public android.util.Range<java.lang.Integer> getBitrateRange();
+    method public int getMaxInputChannelCount();
+    method public android.util.Range<java.lang.Integer>[] getSupportedSampleRateRanges();
+    method public int[] getSupportedSampleRates();
+    method public boolean isSampleRateSupported(int);
+  }
+
   public static final class MediaCodecInfo.CodecCapabilities {
     ctor public MediaCodecInfo.CodecCapabilities();
-    method public static final android.media.MediaCodecInfo.CodecCapabilities CreateFromProfileLevel(java.lang.String, int, int);
-    method public final android.media.MediaCodecInfo.CodecCapabilities.AudioCapabilities getAudioCapabilities();
-    method public final android.media.MediaFormat getDefaultFormat();
-    method public final android.media.MediaCodecInfo.CodecCapabilities.EncoderCapabilities getEncoderCapabilities();
-    method public final java.lang.String getMime();
-    method public final android.media.MediaCodecInfo.CodecCapabilities.VideoCapabilities getVideoCapabilities();
+    method public static android.media.MediaCodecInfo.CodecCapabilities CreateFromProfileLevel(java.lang.String, int, int);
+    method public android.media.MediaCodecInfo.AudioCapabilities getAudioCapabilities();
+    method public android.media.MediaFormat getDefaultFormat();
+    method public android.media.MediaCodecInfo.EncoderCapabilities getEncoderCapabilities();
+    method public java.lang.String getMimeType();
+    method public android.media.MediaCodecInfo.VideoCapabilities getVideoCapabilities();
     method public final boolean isFeatureRequired(java.lang.String);
     method public final boolean isFeatureSupported(java.lang.String);
     method public final boolean isFormatSupported(android.media.MediaFormat);
@@ -14755,39 +14761,6 @@
     field public android.media.MediaCodecInfo.CodecProfileLevel[] profileLevels;
   }
 
-  public static final class MediaCodecInfo.CodecCapabilities.AudioCapabilities extends android.media.MediaCodecInfo.CodecCapabilities.BaseCapabilities {
-    method public final int getMaxInputChannelCount();
-    method public final android.util.Range<java.lang.Integer>[] getSupportedSampleRateRanges();
-    method public final int[] getSupportedSampleRates();
-    method public final boolean isSampleRateSupported(int);
-  }
-
-  public static class MediaCodecInfo.CodecCapabilities.BaseCapabilities {
-    method public final android.util.Range<java.lang.Integer> getBitrateRange();
-  }
-
-  public static final class MediaCodecInfo.CodecCapabilities.EncoderCapabilities {
-    method public final android.util.Range<java.lang.Integer> getComplexityRange();
-    method public final android.util.Range<java.lang.Integer> getQualityRange();
-    method public final boolean isBitrateModeSupported(int);
-    field public static final int BITRATE_MODE_CBR = 2; // 0x2
-    field public static final int BITRATE_MODE_CQ = 0; // 0x0
-    field public static final int BITRATE_MODE_VBR = 1; // 0x1
-  }
-
-  public static final class MediaCodecInfo.CodecCapabilities.VideoCapabilities extends android.media.MediaCodecInfo.CodecCapabilities.BaseCapabilities {
-    method public final boolean areSizeAndRateSupported(int, int, double);
-    method public final int getHeightAlignment();
-    method public final android.util.Range<java.lang.Integer> getSupportedFrameRates();
-    method public final android.util.Range<java.lang.Double> getSupportedFrameRatesFor(int, int);
-    method public final android.util.Range<java.lang.Integer> getSupportedHeights();
-    method public final android.util.Range<java.lang.Integer> getSupportedHeightsFor(int);
-    method public final android.util.Range<java.lang.Integer> getSupportedWidths();
-    method public final android.util.Range<java.lang.Integer> getSupportedWidthsFor(int);
-    method public final int getWidthAlignment();
-    method public final boolean isSizeSupported(int, int);
-  }
-
   public static final class MediaCodecInfo.CodecProfileLevel {
     ctor public MediaCodecInfo.CodecProfileLevel();
     field public static final int AACObjectELD = 39; // 0x27
@@ -14902,12 +14875,34 @@
     field public int profile;
   }
 
+  public static final class MediaCodecInfo.EncoderCapabilities {
+    method public android.util.Range<java.lang.Integer> getComplexityRange();
+    method public boolean isBitrateModeSupported(int);
+    field public static final int BITRATE_MODE_CBR = 2; // 0x2
+    field public static final int BITRATE_MODE_CQ = 0; // 0x0
+    field public static final int BITRATE_MODE_VBR = 1; // 0x1
+  }
+
+  public static final class MediaCodecInfo.VideoCapabilities {
+    method public boolean areSizeAndRateSupported(int, int, double);
+    method public android.util.Range<java.lang.Integer> getBitrateRange();
+    method public int getHeightAlignment();
+    method public android.util.Range<java.lang.Integer> getSupportedFrameRates();
+    method public android.util.Range<java.lang.Double> getSupportedFrameRatesFor(int, int);
+    method public android.util.Range<java.lang.Integer> getSupportedHeights();
+    method public android.util.Range<java.lang.Integer> getSupportedHeightsFor(int);
+    method public android.util.Range<java.lang.Integer> getSupportedWidths();
+    method public android.util.Range<java.lang.Integer> getSupportedWidthsFor(int);
+    method public int getWidthAlignment();
+    method public boolean isSizeSupported(int, int);
+  }
+
   public final class MediaCodecList {
     ctor public MediaCodecList(int);
     method public final java.lang.String findDecoderForFormat(android.media.MediaFormat);
     method public final java.lang.String findEncoderForFormat(android.media.MediaFormat);
-    method public static final int getCodecCount();
-    method public static final android.media.MediaCodecInfo getCodecInfoAt(int);
+    method public static final deprecated int getCodecCount();
+    method public static final deprecated android.media.MediaCodecInfo getCodecInfoAt(int);
     method public final android.media.MediaCodecInfo[] getCodecInfos();
     field public static final int ALL_CODECS = 1; // 0x1
     field public static final int REGULAR_CODECS = 0; // 0x0
@@ -15026,11 +15021,13 @@
     method public static final android.media.MediaFormat createSubtitleFormat(java.lang.String, java.lang.String);
     method public static final android.media.MediaFormat createVideoFormat(java.lang.String, int, int);
     method public final java.nio.ByteBuffer getByteBuffer(java.lang.String);
+    method public boolean getFeatureEnabled(java.lang.String);
     method public final float getFloat(java.lang.String);
     method public final int getInteger(java.lang.String);
     method public final long getLong(java.lang.String);
     method public final java.lang.String getString(java.lang.String);
     method public final void setByteBuffer(java.lang.String, java.nio.ByteBuffer);
+    method public void setFeatureEnabled(java.lang.String, boolean);
     method public final void setFloat(java.lang.String, float);
     method public final void setInteger(java.lang.String, int);
     method public final void setLong(java.lang.String, long);
@@ -15052,7 +15049,6 @@
     field public static final java.lang.String KEY_COLOR_FORMAT = "color-format";
     field public static final java.lang.String KEY_COMPLEXITY = "complexity";
     field public static final java.lang.String KEY_DURATION = "durationUs";
-    field public static final java.lang.String KEY_FEATURE_ = "feature-";
     field public static final java.lang.String KEY_FLAC_COMPRESSION_LEVEL = "flac-compression-level";
     field public static final java.lang.String KEY_FRAME_RATE = "frame-rate";
     field public static final java.lang.String KEY_HEIGHT = "height";
@@ -15068,7 +15064,6 @@
     field public static final java.lang.String KEY_MIME = "mime";
     field public static final java.lang.String KEY_PROFILE = "profile";
     field public static final java.lang.String KEY_PUSH_BLANK_BUFFERS_ON_STOP = "push-blank-buffers-on-shutdown";
-    field public static final java.lang.String KEY_QUALITY = "quality";
     field public static final java.lang.String KEY_REPEAT_PREVIOUS_FRAME_AFTER = "repeat-previous-frame-after";
     field public static final java.lang.String KEY_SAMPLE_RATE = "sample-rate";
     field public static final java.lang.String KEY_TEMPORAL_LAYERING = "ts-schema";
diff --git a/media/java/android/media/Image.java b/media/java/android/media/Image.java
index 2856edb..522e45d 100644
--- a/media/java/android/media/Image.java
+++ b/media/java/android/media/Image.java
@@ -123,7 +123,7 @@
      */
     public abstract long getTimestamp();
 
-    protected Rect mCropRect;
+    private Rect mCropRect;
 
     /**
      * Get the crop rectangle associated with this frame.
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index 96e6ab9..032f07f 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -665,7 +665,7 @@
      * Thrown when an internal codec error occurs.
      */
     public final static class CodecException extends IllegalStateException {
-        public CodecException(int errorCode, int actionCode, String detailMessage) {
+        CodecException(int errorCode, int actionCode, String detailMessage) {
             super(detailMessage);
             mErrorCode = errorCode;
             mActionCode = actionCode;
@@ -1729,7 +1729,7 @@
             if (cropRect != null) {
                 cropRect.offset(-xOffset, -yOffset);
             }
-            mCropRect = cropRect;
+            setCropRect(cropRect);
 
             // save offsets and info
             mXOffset = xOffset;
diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java
index acb2186..323a3e3 100644
--- a/media/java/android/media/MediaCodecInfo.java
+++ b/media/java/android/media/MediaCodecInfo.java
@@ -70,7 +70,7 @@
         mIsEncoder = isEncoder;
         mCaps = new HashMap<String, CodecCapabilities>();
         for (CodecCapabilities c: caps) {
-            mCaps.put(c.getMime(), c);
+            mCaps.put(c.getMimeType(), c);
         }
     }
 
@@ -98,6 +98,43 @@
         return types;
     }
 
+    private static int checkPowerOfTwo(int value, String message) {
+        if ((value & (value - 1)) != 0) {
+            throw new IllegalArgumentException(message);
+        }
+        return value;
+    }
+
+    private static class Feature {
+        public String mName;
+        public int mValue;
+        public boolean mDefault;
+        public Feature(String name, int value, boolean def) {
+            mName = name;
+            mValue = value;
+            mDefault = def;
+        }
+    }
+
+    // COMMON CONSTANTS
+    private static final Range<Integer> POSITIVE_INTEGERS =
+        Range.create(1, Integer.MAX_VALUE);
+    private static final Range<Long> POSITIVE_LONGS =
+        Range.create(1l, Long.MAX_VALUE);
+    private static final Range<Rational> POSITIVE_RATIONALS =
+        Range.create(new Rational(1, Integer.MAX_VALUE),
+                     new Rational(Integer.MAX_VALUE, 1));
+    private static final Range<Integer> SIZE_RANGE = Range.create(1, 32768);
+    private static final Range<Integer> FRAME_RATE_RANGE = Range.create(0, 960);
+
+    // found stuff that is not supported by framework (=> this should not happen)
+    private static final int ERROR_UNRECOGNIZED   = (1 << 0);
+    // found profile/level for which we don't have capability estimates
+    private static final int ERROR_UNSUPPORTED    = (1 << 1);
+    // have not found any profile/level for which we don't have capability estimate
+    private static final int ERROR_NONE_SUPPORTED = (1 << 2);
+
+
     /**
      * Encapsulates the capabilities of a given codec component.
      * For example, what profile/level combinations it supports and what colorspaces
@@ -222,17 +259,6 @@
             return checkFeature(name, mFlagsRequired);
         }
 
-        private static class Feature {
-            public String mName;
-            public int mValue;
-            public boolean mDefault;
-            public Feature(String name, int value, boolean def) {
-                mName = name;
-                mValue = value;
-                mDefault = def;
-            }
-        }
-
         private static final Feature[] decoderFeatures = {
             new Feature(FEATURE_AdaptivePlayback, (1 << 0), true),
             new Feature(FEATURE_SecurePlayback,   (1 << 1), false),
@@ -312,1311 +338,54 @@
             return true;
         }
 
-        // errors while reading profile levels
-        private int mError;
-        // found stuff that is not supported by framework (=> this should not happen)
-        private static final int ERROR_UNRECOGNIZED   = (1 << 0);
-        // found profile/level for which we don't have capability estimates
-        private static final int ERROR_UNSUPPORTED    = (1 << 1);
-        // have not found any profile/level for which we don't have capability estimate
-        private static final int ERROR_NONE_SUPPORTED = (1 << 2);
-
-
-        // UTILITY METHODS
-        private static final Range<Integer> POSITIVE_INTEGERS =
-            Range.create(1, Integer.MAX_VALUE);
-        private static final Range<Long> POSITIVE_LONGS =
-            Range.create(1l, Long.MAX_VALUE);
-        private static final Range<Rational> POSITIVE_RATIONALS =
-            Range.create(new Rational(1, Integer.MAX_VALUE),
-                         new Rational(Integer.MAX_VALUE, 1));
-        private static final Range<Integer> SIZE_RANGE = Range.create(1, 32768);
-        private static final Range<Integer> FRAME_RATE_RANGE = Range.create(0, 960);
+        // errors while reading profile levels - accessed from sister capabilities
+        int mError;
 
         private static final String TAG = "CodecCapabilities";
 
         // NEW-STYLE CAPABILITIES
+        private AudioCapabilities mAudioCaps;
+        private VideoCapabilities mVideoCaps;
+        private EncoderCapabilities mEncoderCaps;
+        private MediaFormat mDefaultFormat;
 
         /**
          * Returns a MediaFormat object with default values for configurations that have
          * defaults.
          */
-        public final MediaFormat getDefaultFormat() {
+        public MediaFormat getDefaultFormat() {
             return mDefaultFormat;
         }
-        private MediaFormat mDefaultFormat;
 
         /**
          * Returns the mime type for which this codec-capability object was created.
          */
-        public final String getMime() {
+        public String getMimeType() {
             return mMime;
         }
 
-        /**
-         * Returns the encoding capabilities or {@code null} if this is not an encoder.
-         */
-        public final EncoderCapabilities getEncoderCapabilities() {
-            return mEncoderCaps;
+        private boolean isAudio() {
+            return mAudioCaps != null;
         }
-        private EncoderCapabilities mEncoderCaps;
+
+        /**
+         * Returns the audio capabilities or {@code null} if this is not an audio codec.
+         */
+        public AudioCapabilities getAudioCapabilities() {
+            return mAudioCaps;
+        }
 
         private boolean isEncoder() {
             return mEncoderCaps != null;
         }
 
         /**
-         * A class that supports querying the encoding capabilities of a codec.
+         * Returns the encoding capabilities or {@code null} if this is not an encoder.
          */
-        public static final class EncoderCapabilities {
-            /**
-             * Returns the supported range of quality values.
-             */
-            public final Range<Integer> getQualityRange() {
-                return mQualityRange;
-            }
-
-            /**
-             * Returns the supported range of encoder complexity values.
-             * <p>
-             * Some codecs may support multiple complexity levels, where higher
-             * complexity values use more encoder tools (e.g. perform more
-             * intensive calculations) to improve the quality or the compression
-             * ratio.  Use a lower value to save power and/or time.
-             */
-            public final Range<Integer> getComplexityRange() {
-                return mComplexityRange;
-            }
-
-            /** Constant quality mode */
-            public static final int BITRATE_MODE_CQ = 0;
-            /** Variable bitrate mode */
-            public static final int BITRATE_MODE_VBR = 1;
-            /** Constant bitrate mode */
-            public static final int BITRATE_MODE_CBR = 2;
-
-            private static final Feature[] bitrates = new Feature[] {
-                new Feature("VBR", BITRATE_MODE_VBR, true),
-                new Feature("CBR", BITRATE_MODE_CBR, false),
-                new Feature("CQ",  BITRATE_MODE_CQ,  false)
-            };
-
-            private static int parseBitrateMode(String mode) {
-                for (Feature feat: bitrates) {
-                    if (feat.mName.equalsIgnoreCase(mode)) {
-                        return feat.mValue;
-                    }
-                }
-                return 0;
-            }
-
-            /**
-             * Query whether a bitrate mode is supported.
-             */
-            public final boolean isBitrateModeSupported(int mode) {
-                for (Feature feat: bitrates) {
-                    if (mode == feat.mValue) {
-                        return (mBitControl & (1 << mode)) != 0;
-                    }
-                }
-                return false;
-            }
-
-            private Range<Integer> mQualityRange;
-            private Range<Integer> mComplexityRange;
-            private CodecCapabilities mParent;
-
-            /* no public constructor */
-            private EncoderCapabilities() { }
-
-            /** @hide */
-            public static EncoderCapabilities create(
-                    MediaFormat info, CodecCapabilities parent) {
-                EncoderCapabilities caps = new EncoderCapabilities();
-                caps.init(info, parent);
-                return caps;
-            }
-
-            /** @hide */
-            public void init(MediaFormat info, CodecCapabilities parent) {
-                // no support for complexity or quality yet
-                mParent = parent;
-                mComplexityRange = Range.create(0, 0);
-                mQualityRange = Range.create(0, 0);
-                mBitControl = (1 << BITRATE_MODE_VBR);
-
-                applyLevelLimits();
-                parseFromInfo(info);
-            }
-
-            private void applyLevelLimits() {
-                String mime = mParent.getMime();
-                if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_FLAC)) {
-                    mComplexityRange = Range.create(0, 8);
-                    mBitControl = (1 << BITRATE_MODE_CQ);
-                } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AMR_NB)
-                        || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AMR_WB)
-                        || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_G711_ALAW)
-                        || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_G711_MLAW)
-                        || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_MSGSM)) {
-                    mBitControl = (1 << BITRATE_MODE_CBR);
-                }
-            }
-
-            private int mBitControl;
-            private Integer mDefaultComplexity;
-            private Integer mDefaultQuality;
-            private String mQualityScale;
-
-            private void parseFromInfo(MediaFormat info) {
-                Map<String, Object> map = info.getMap();
-
-                if (info.containsKey("complexity-range")) {
-                    mComplexityRange = Utils
-                            .parseIntRange(info.getString("complexity-range"), mComplexityRange);
-                    // TODO should we limit this to level limits?
-                }
-                if (info.containsKey("quality-range")) {
-                    mQualityRange = Utils
-                            .parseIntRange(info.getString("quality-range"), mQualityRange);
-                }
-                if (info.containsKey("feature-bitrate-control")) {
-                    for (String mode: info.getString("feature-bitrate-control").split(",")) {
-                        mBitControl |= parseBitrateMode(mode);
-                    }
-                }
-
-                try {
-                    mDefaultComplexity = Integer.parseInt((String)map.get("complexity-default"));
-                } catch (NumberFormatException e) { }
-
-                try {
-                    mDefaultQuality = Integer.parseInt((String)map.get("quality-default"));
-                } catch (NumberFormatException e) { }
-
-                mQualityScale = (String)map.get("quality-scale");
-            }
-
-            private boolean supports(
-                    Integer complexity, Integer quality, Integer profile) {
-                boolean ok = true;
-                if (ok && complexity != null) {
-                    ok = mComplexityRange.contains(complexity);
-                }
-                if (ok && quality != null) {
-                    ok = mQualityRange.contains(quality);
-                }
-                if (ok && profile != null) {
-                    for (CodecProfileLevel pl: mParent.profileLevels) {
-                        if (pl.profile == profile) {
-                            profile = null;
-                            break;
-                        }
-                    }
-                    ok = profile == null;
-                }
-                return ok;
-            }
-
-            /** @hide */
-            public void setDefaultFormat(MediaFormat format) {
-                // don't list trivial quality/complexity as default for now
-                if (!mQualityRange.getUpper().equals(mQualityRange.getLower())
-                        && mDefaultQuality != null) {
-                    format.setInteger(MediaFormat.KEY_QUALITY, mDefaultQuality);
-                }
-                if (!mComplexityRange.getUpper().equals(mComplexityRange.getLower())
-                        && mDefaultComplexity != null) {
-                    format.setInteger(MediaFormat.KEY_COMPLEXITY, mDefaultComplexity);
-                }
-                // bitrates are listed in order of preference
-                for (Feature feat: bitrates) {
-                    if ((mBitControl & (1 << feat.mValue)) != 0) {
-                        format.setInteger(MediaFormat.KEY_BITRATE_MODE, feat.mValue);
-                        break;
-                    }
-                }
-            }
-
-            /** @hide */
-            public boolean supportsFormat(MediaFormat format) {
-                final Map<String, Object> map = format.getMap();
-                final String mime = mParent.getMime();
-
-                Integer mode = (Integer)map.get(MediaFormat.KEY_BITRATE_MODE);
-                if (mode != null && !isBitrateModeSupported(mode)) {
-                    return false;
-                }
-
-                Integer complexity = (Integer)map.get(MediaFormat.KEY_COMPLEXITY);
-                if (MediaFormat.MIMETYPE_AUDIO_FLAC.equalsIgnoreCase(mime)) {
-                    Integer flacComplexity =
-                        (Integer)map.get(MediaFormat.KEY_FLAC_COMPRESSION_LEVEL);
-                    if (complexity == null) {
-                        complexity = flacComplexity;
-                    } else if (flacComplexity != null && complexity != flacComplexity) {
-                        throw new IllegalArgumentException(
-                                "conflicting values for complexity and " +
-                                "flac-compression-level");
-                    }
-                }
-
-                // other audio parameters
-                Integer profile = (Integer)map.get(MediaFormat.KEY_PROFILE);
-                if (MediaFormat.MIMETYPE_AUDIO_AAC.equalsIgnoreCase(mime)) {
-                    Integer aacProfile = (Integer)map.get(MediaFormat.KEY_AAC_PROFILE);
-                    if (profile == null) {
-                        profile = aacProfile;
-                    } else if (aacProfile != null && aacProfile != profile) {
-                        throw new IllegalArgumentException(
-                                "conflicting values for profile and aac-profile");
-                    }
-                }
-
-                Integer quality = (Integer)map.get(MediaFormat.KEY_QUALITY);
-
-                return supports(complexity, quality, profile);
-            }
-        };
-
-        /**
-         * A class that supports querying basic capabilities of a codec.
-         */
-        public static class BaseCapabilities {
-            /**
-             * Returns the range of supported bitrates in bits/second.
-             */
-            public final Range<Integer> getBitrateRange() {
-                return mBitrateRange;
-            }
-
-            /** @hide */
-            protected Range<Integer> mBitrateRange;
-
-            /** @hide */
-            protected CodecCapabilities mParent;
-
-            /** @hide */
-            protected BaseCapabilities() {
-            }
-
-            /** @hide */
-            protected void init(MediaFormat info, CodecCapabilities parent) {
-                mParent = parent;
-                mBitrateRange = Range.create(0, Integer.MAX_VALUE);
-            }
+        public EncoderCapabilities getEncoderCapabilities() {
+            return mEncoderCaps;
         }
 
-        /**
-         * A class that supports querying the video capabilities of a codec.
-         */
-        public static final class VideoCapabilities extends BaseCapabilities {
-            private static final String TAG = "VideoCapabilities";
-            private Range<Integer> mHeightRange;
-            private Range<Integer> mWidthRange;
-            private Range<Integer> mBlockCountRange;
-            private Range<Integer> mHorizontalBlockRange;
-            private Range<Integer> mVerticalBlockRange;
-            private Range<Rational> mAspectRatioRange;
-            private Range<Rational> mBlockAspectRatioRange;
-            private Range<Long> mBlocksPerSecondRange;
-            private Range<Integer> mFrameRateRange;
-
-            private int mBlockWidth;
-            private int mBlockHeight;
-            private int mWidthAlignment;
-            private int mHeightAlignment;
-            private int mSmallerDimensionUpperLimit;
-
-            /**
-             * Returns the range of supported video widths.
-             */
-            public final Range<Integer> getSupportedWidths() {
-                return mWidthRange;
-            }
-
-            /**
-             * Returns the range of supported video heights.
-             */
-            public final Range<Integer> getSupportedHeights() {
-                return mHeightRange;
-            }
-
-            /**
-             * Returns the alignment requirement for video width.
-             */
-            public final int getWidthAlignment() {
-                return mWidthAlignment;
-            }
-
-            /**
-             * Returns the alignment requirement for video height.
-             */
-            public final int getHeightAlignment() {
-                return mHeightAlignment;
-            }
-
-            /**
-             * Return the upper limit on the smaller dimension of width or height.
-             * <p></p>
-             * Some codecs have a limit on the smaller dimension, whether it be
-             * the width or the height.  E.g. a codec may only be able to handle
-             * up to 1920x1080 both in landscape and portrait mode (1080x1920).
-             * In this case the maximum width and height are both 1920, but the
-             * smaller dimension limit will be 1080. For other codecs, this is
-             * {@code Math.min(getSupportedWidths().getUpper(),
-             * getSupportedHeights().getUpper())}.
-             *
-             * @hide
-             */
-            public int getSmallerDimensionUpperLimit() {
-                return mSmallerDimensionUpperLimit;
-            }
-
-            /**
-             * Returns the range of supported frame rates.
-             * <p>
-             * This is not a performance indicator.  Rather, it expresses the
-             * limits specified in the coding standard, based on the complexities
-             * of encoding material for later playback at a certain frame rate,
-             * or the decoding of such material in non-realtime.
-             */
-            public final Range<Integer> getSupportedFrameRates() {
-                return mFrameRateRange;
-            }
-
-            /**
-             * Returns the range of supported video widths for a video height.
-             * @param height the height of the video
-             */
-            public final Range<Integer> getSupportedWidthsFor(int height) {
-                try {
-                    Range<Integer> range = mWidthRange;
-                    if (!mHeightRange.contains(height)
-                            || (height % mHeightAlignment) != 0) {
-                        throw new IllegalArgumentException("unsupported height");
-                    }
-                    final int heightInBlocks = Utils.divUp(height, mBlockHeight);
-
-                    // constrain by block count and by block aspect ratio
-                    final int minWidthInBlocks = Math.max(
-                            Utils.divUp(mBlockCountRange.getLower(), heightInBlocks),
-                            (int)Math.ceil(mBlockAspectRatioRange.getLower().doubleValue()
-                                    * heightInBlocks));
-                    final int maxWidthInBlocks = Math.min(
-                            mBlockCountRange.getUpper() / heightInBlocks,
-                            (int)(mBlockAspectRatioRange.getUpper().doubleValue()
-                                    * heightInBlocks));
-                    range = range.intersect(
-                            (minWidthInBlocks - 1) * mBlockWidth + mWidthAlignment,
-                            maxWidthInBlocks * mBlockWidth);
-
-                    // constrain by smaller dimension limit
-                    if (height > mSmallerDimensionUpperLimit) {
-                        range = range.intersect(1, mSmallerDimensionUpperLimit);
-                    }
-
-                    // constrain by aspect ratio
-                    range = range.intersect(
-                            (int)Math.ceil(mAspectRatioRange.getLower().doubleValue()
-                                    * height),
-                            (int)(mAspectRatioRange.getUpper().doubleValue() * height));
-                    return range;
-                } catch (IllegalArgumentException e) {
-                    // should not be here
-                    Log.w(TAG, "could not get supported widths for " + height , e);
-                    throw new IllegalArgumentException("unsupported height");
-                }
-            }
-
-            /**
-             * Returns the range of supported video heights for a video width
-             * @param width the width of the video
-             */
-            public final Range<Integer> getSupportedHeightsFor(int width) {
-                try {
-                    Range<Integer> range = mHeightRange;
-                    if (!mWidthRange.contains(width)
-                            || (width % mWidthAlignment) != 0) {
-                        throw new IllegalArgumentException("unsupported width");
-                    }
-                    final int widthInBlocks = Utils.divUp(width, mBlockWidth);
-
-                    // constrain by block count and by block aspect ratio
-                    final int minHeightInBlocks = Math.max(
-                            Utils.divUp(mBlockCountRange.getLower(), widthInBlocks),
-                            (int)Math.ceil(widthInBlocks /
-                                    mBlockAspectRatioRange.getUpper().doubleValue()));
-                    final int maxHeightInBlocks = Math.min(
-                            mBlockCountRange.getUpper() / widthInBlocks,
-                            (int)(widthInBlocks /
-                                    mBlockAspectRatioRange.getLower().doubleValue()));
-                    range = range.intersect(
-                            (minHeightInBlocks - 1) * mBlockHeight + mHeightAlignment,
-                            maxHeightInBlocks * mBlockHeight);
-
-                    // constrain by smaller dimension limit
-                    if (width > mSmallerDimensionUpperLimit) {
-                        range = range.intersect(1, mSmallerDimensionUpperLimit);
-                    }
-
-                    // constrain by aspect ratio
-                    range = range.intersect(
-                            (int)Math.ceil(width /
-                                    mAspectRatioRange.getUpper().doubleValue()),
-                            (int)(width / mAspectRatioRange.getLower().doubleValue()));
-                    return range;
-                } catch (IllegalArgumentException e) {
-                    // should not be here
-                    Log.w(TAG, "could not get supported heights for " + width , e);
-                    throw new IllegalArgumentException("unsupported width");
-                }
-            }
-
-            /**
-             * Returns the range of supported video frame rates for a video size.
-             * <p>
-             * This is not a performance indicator.  Rather, it expresses the limits specified in
-             * the coding standard, based on the complexities of encoding material of a given
-             * size for later playback at a certain frame rate, or the decoding of such material
-             * in non-realtime.
-
-             * @param width the width of the video
-             * @param height the height of the video
-             */
-            public final Range<Double> getSupportedFrameRatesFor(int width, int height) {
-                Range<Integer> range = mHeightRange;
-                if (!supports(width, height, null)) {
-                    throw new IllegalArgumentException("unsupported size");
-                }
-                final int blockCount =
-                    Utils.divUp(width, mBlockWidth) * Utils.divUp(height, mBlockHeight);
-
-                return Range.create(
-                        Math.max(mBlocksPerSecondRange.getLower() / (double) blockCount,
-                                (double) mFrameRateRange.getLower()),
-                        Math.min(mBlocksPerSecondRange.getUpper() / (double) blockCount,
-                                (double) mFrameRateRange.getUpper()));
-            }
-
-            /**
-             * Returns whether a given video size ({@code width} and
-             * {@code height}) and {@code frameRate} combination is supported.
-             */
-            public final boolean areSizeAndRateSupported(
-                    int width, int height, double frameRate) {
-                return supports(width, height, frameRate);
-            }
-
-            /**
-             * Returns whether a given video size ({@code width} and
-             * {@code height}) is supported.
-             */
-            public final boolean isSizeSupported(int width, int height) {
-                return supports(width, height, null);
-            }
-
-            private final boolean supports(
-                    Integer width, Integer height, Double rate) {
-                boolean ok = true;
-
-                if (ok && width != null) {
-                    ok = mWidthRange.contains(width)
-                            && (width % mWidthAlignment == 0);
-                }
-                if (ok && height != null) {
-                    ok = mHeightRange.contains(height)
-                            && (height % mHeightAlignment == 0);
-                }
-                if (ok && rate != null) {
-                    ok = mFrameRateRange.contains(Utils.intRangeFor(rate));
-                }
-                if (ok && height != null && width != null) {
-                    ok = Math.min(height, width) <= mSmallerDimensionUpperLimit;
-
-                    final int widthInBlocks = Utils.divUp(width, mBlockWidth);
-                    final int heightInBlocks = Utils.divUp(height, mBlockHeight);
-                    final int blockCount = widthInBlocks * heightInBlocks;
-                    ok = ok && mBlockCountRange.contains(blockCount)
-                            && mBlockAspectRatioRange.contains(
-                                    new Rational(widthInBlocks, heightInBlocks))
-                            && mAspectRatioRange.contains(new Rational(width, height));
-                    if (ok && rate != null) {
-                        double blocksPerSec = blockCount * rate;
-                        ok = mBlocksPerSecondRange.contains(
-                                Utils.longRangeFor(blocksPerSec));
-                    }
-                }
-                return ok;
-            }
-
-            /**
-             * @hide
-             * @throws java.lang.ClassCastException */
-            public boolean supportsFormat(MediaFormat format) {
-                final Map<String, Object> map = format.getMap();
-                Integer width = (Integer)map.get(MediaFormat.KEY_WIDTH);
-                Integer height = (Integer)map.get(MediaFormat.KEY_HEIGHT);
-                Double rate = (Double)map.get(MediaFormat.KEY_FRAME_RATE);
-
-                // we ignore color-format for now as it is not reliably reported by codec
-
-                return supports(width, height, rate);
-            }
-
-            /* no public constructor */
-            private VideoCapabilities() { }
-
-            /** @hide */
-            public static VideoCapabilities create(
-                    MediaFormat info, CodecCapabilities parent) {
-                VideoCapabilities caps = new VideoCapabilities();
-                caps.init(info, parent);
-                return caps;
-            }
-
-            /** @hide */
-            public void init(MediaFormat info, CodecCapabilities parent) {
-                super.init(info, parent);
-                initWithPlatformLimits();
-                applyLevelLimits();
-                parseFromInfo(info);
-                updateLimits();
-            }
-
-            /** @hide */
-            public Size getBlockSize() {
-                return new Size(mBlockWidth, mBlockHeight);
-            }
-
-            /** @hide */
-            public Range<Integer> getBlockCountRange() {
-                return mBlockCountRange;
-            }
-
-            /** @hide */
-            public Range<Long> getBlocksPerSecondRange() {
-                return mBlocksPerSecondRange;
-            }
-
-            /** @hide */
-            public Range<Rational> getAspectRatioRange(boolean blocks) {
-                return blocks ? mBlockAspectRatioRange : mAspectRatioRange;
-            }
-
-            private void initWithPlatformLimits() {
-                mWidthRange  = SIZE_RANGE;
-                mHeightRange = SIZE_RANGE;
-                mFrameRateRange = FRAME_RATE_RANGE;
-
-                mHorizontalBlockRange = SIZE_RANGE;
-                mVerticalBlockRange   = SIZE_RANGE;
-
-                // full positive ranges are supported as these get calculated
-                mBlockCountRange      = POSITIVE_INTEGERS;
-                mBlocksPerSecondRange = POSITIVE_LONGS;
-
-                mBlockAspectRatioRange = POSITIVE_RATIONALS;
-                mAspectRatioRange      = POSITIVE_RATIONALS;
-
-                // YUV 4:2:0 requires 2:2 alignment
-                mWidthAlignment = 2;
-                mHeightAlignment = 2;
-                mBlockWidth = 2;
-                mBlockHeight = 2;
-                mSmallerDimensionUpperLimit = SIZE_RANGE.getUpper();
-            }
-
-            private void parseFromInfo(MediaFormat info) {
-                final Map<String, Object> map = info.getMap();
-                Size blockSize = new Size(mBlockWidth, mBlockHeight);
-                Size alignment = new Size(mWidthAlignment, mHeightAlignment);
-                Range<Integer> counts = null, widths = null, heights = null;
-                Range<Integer> frameRates = null;
-                Range<Long> blockRates = null;
-                Range<Rational> ratios = null, blockRatios = null;
-
-                blockSize = Utils.parseSize(map.get("block-size"), blockSize);
-                alignment = Utils.parseSize(map.get("alignment"), alignment);
-                counts = Utils.parseIntRange(map.get("block-count-range"), null);
-                blockRates =
-                    Utils.parseLongRange(map.get("blocks-per-second-range"), null);
-                {
-                    Object o = map.get("size-range");
-                    Pair<Size, Size> sizeRange = Utils.parseSizeRange(o);
-                    if (sizeRange != null) {
-                        try {
-                            widths = Range.create(
-                                    sizeRange.first.getWidth(),
-                                    sizeRange.second.getWidth());
-                            heights = Range.create(
-                                    sizeRange.first.getHeight(),
-                                    sizeRange.second.getHeight());
-                        } catch (IllegalArgumentException e) {
-                            Log.w(TAG, "could not parse size range '" + o + "'");
-                            widths = null;
-                            heights = null;
-                        }
-                    }
-                }
-                // for now this just means using the smaller max size as 2nd
-                // upper limit.
-                // for now we are keeping the profile specific "width/height
-                // in macroblocks" limits.
-                if (Integer.valueOf(1).equals(map.get("feature-can-swap-width-height"))) {
-                    if (widths != null) {
-                        mSmallerDimensionUpperLimit =
-                            Math.min(widths.getUpper(), heights.getUpper());
-                        widths = heights = widths.extend(heights);
-                    } else {
-                        Log.w(TAG, "feature can-swap-width-height is best used with size-range");
-                        mSmallerDimensionUpperLimit =
-                            Math.min(mWidthRange.getUpper(), mHeightRange.getUpper());
-                        mWidthRange = mHeightRange = mWidthRange.extend(mHeightRange);
-                    }
-                }
-
-                ratios = Utils.parseRationalRange(
-                        map.get("block-aspect-ratio-range"), null);
-                blockRatios = Utils.parseRationalRange(
-                        map.get("pixel-aspect-ratio-range"), null);
-                frameRates = Utils.parseIntRange(map.get("frame-rate-range"), null);
-                if (frameRates != null) {
-                    try {
-                        frameRates = frameRates.intersect(FRAME_RATE_RANGE);
-                    } catch (IllegalArgumentException e) {
-                        Log.w(TAG, "frame rate range (" + frameRates
-                                + ") is out of limits: " + FRAME_RATE_RANGE);
-                        frameRates = null;
-                    }
-                }
-
-                checkPowerOfTwo(
-                        blockSize.getWidth(), "block-size width must be power of two");
-                checkPowerOfTwo(
-                        blockSize.getHeight(), "block-size height must be power of two");
-
-                checkPowerOfTwo(
-                        alignment.getWidth(), "alignment width must be power of two");
-                checkPowerOfTwo(
-                        alignment.getHeight(), "alignment height must be power of two");
-
-                // update block-size and alignment
-                applyMacroBlockLimits(
-                        Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE,
-                        Long.MAX_VALUE, blockSize.getWidth(), blockSize.getHeight(),
-                        alignment.getWidth(), alignment.getHeight());
-
-                if ((mParent.mError & ERROR_UNSUPPORTED) != 0) {
-                    // codec supports profiles that we don't know.
-                    // Use supplied values clipped to platform limits
-                    if (widths != null) {
-                        mWidthRange = SIZE_RANGE.intersect(widths);
-                    }
-                    if (heights != null) {
-                        mHeightRange = SIZE_RANGE.intersect(heights);
-                    }
-                    if (counts != null) {
-                        mBlockCountRange = POSITIVE_INTEGERS.intersect(
-                                Utils.factorRange(counts, mBlockWidth * mBlockHeight
-                                        / blockSize.getWidth() / blockSize.getHeight()));
-                    }
-                    if (blockRates != null) {
-                        mBlocksPerSecondRange = POSITIVE_LONGS.intersect(
-                                Utils.factorRange(blockRates, mBlockWidth * mBlockHeight
-                                        / blockSize.getWidth() / blockSize.getHeight()));
-                    }
-                    if (blockRatios != null) {
-                        mBlockAspectRatioRange = POSITIVE_RATIONALS.intersect(
-                                Utils.scaleRange(blockRatios,
-                                        mBlockHeight / blockSize.getHeight(),
-                                        mBlockWidth / blockSize.getWidth()));
-                    }
-                    if (ratios != null) {
-                        mAspectRatioRange = POSITIVE_RATIONALS.intersect(ratios);
-                    }
-                    if (frameRates != null) {
-                        mFrameRateRange = FRAME_RATE_RANGE.intersect(frameRates);
-                    }
-                } else {
-                    // no unsupported profile/levels, so restrict values to known limits
-                    if (widths != null) {
-                        mWidthRange = mWidthRange.intersect(widths);
-                    }
-                    if (heights != null) {
-                        mHeightRange = mHeightRange.intersect(heights);
-                    }
-                    if (counts != null) {
-                        mBlockCountRange = mBlockCountRange.intersect(
-                                Utils.factorRange(counts, mBlockWidth * mBlockHeight
-                                        / blockSize.getWidth() / blockSize.getHeight()));
-                    }
-                    if (blockRates != null) {
-                        mBlocksPerSecondRange = mBlocksPerSecondRange.intersect(
-                                Utils.factorRange(blockRates, mBlockWidth * mBlockHeight
-                                        / blockSize.getWidth() / blockSize.getHeight()));
-                    }
-                    if (blockRatios != null) {
-                        mBlockAspectRatioRange = mBlockAspectRatioRange.intersect(
-                                Utils.scaleRange(blockRatios,
-                                        mBlockHeight / blockSize.getHeight(),
-                                        mBlockWidth / blockSize.getWidth()));
-                    }
-                    if (ratios != null) {
-                        mAspectRatioRange = mAspectRatioRange.intersect(ratios);
-                    }
-                    if (frameRates != null) {
-                        mFrameRateRange = mFrameRateRange.intersect(frameRates);
-                    }
-                }
-                updateLimits();
-            }
-
-            private int checkPowerOfTwo(int value, String message) {
-                if ((value & (value - 1)) != 0) {
-                    throw new IllegalArgumentException(message);
-                }
-                return value;
-            }
-
-            private void applyBlockLimits(
-                    int blockWidth, int blockHeight,
-                    Range<Integer> counts, Range<Long> rates, Range<Rational> ratios) {
-                checkPowerOfTwo(blockWidth, "blockWidth must be a power of two");
-                checkPowerOfTwo(blockHeight, "blockHeight must be a power of two");
-
-                final int newBlockWidth = Math.max(blockWidth, mBlockWidth);
-                final int newBlockHeight = Math.max(blockHeight, mBlockHeight);
-
-                // factor will always be a power-of-2
-                int factor =
-                    newBlockWidth * newBlockHeight / mBlockWidth / mBlockHeight;
-                if (factor != 1) {
-                    mBlockCountRange = Utils.factorRange(mBlockCountRange, factor);
-                    mBlocksPerSecondRange = Utils.factorRange(
-                            mBlocksPerSecondRange, factor);
-                    mBlockAspectRatioRange = Utils.scaleRange(
-                            mBlockAspectRatioRange,
-                            newBlockHeight / mBlockHeight,
-                            newBlockWidth / mBlockWidth);
-                    mHorizontalBlockRange = Utils.factorRange(
-                            mHorizontalBlockRange, newBlockWidth / mBlockWidth);
-                    mVerticalBlockRange = Utils.factorRange(
-                            mVerticalBlockRange, newBlockHeight / mBlockHeight);
-                }
-                factor = newBlockWidth * newBlockHeight / blockWidth / blockHeight;
-                if (factor != 1) {
-                    counts = Utils.factorRange(counts, factor);
-                    rates = Utils.factorRange(rates, factor);
-                    ratios = Utils.scaleRange(
-                            ratios, newBlockHeight / blockHeight,
-                            newBlockWidth / blockWidth);
-                }
-                mBlockCountRange = mBlockCountRange.intersect(counts);
-                mBlocksPerSecondRange = mBlocksPerSecondRange.intersect(rates);
-                mBlockAspectRatioRange = mBlockAspectRatioRange.intersect(ratios);
-                mBlockWidth = newBlockWidth;
-                mBlockHeight = newBlockHeight;
-            }
-
-            private void applyAlignment(int widthAlignment, int heightAlignment) {
-                checkPowerOfTwo(widthAlignment, "widthAlignment must be a power of two");
-                checkPowerOfTwo(heightAlignment, "heightAlignment must be a power of two");
-
-                if (widthAlignment > mBlockWidth || heightAlignment > mBlockHeight) {
-                    // maintain assumption that 0 < alignment <= block-size
-                    applyBlockLimits(
-                            Math.max(widthAlignment, mBlockWidth),
-                            Math.max(heightAlignment, mBlockHeight),
-                            POSITIVE_INTEGERS, POSITIVE_LONGS, POSITIVE_RATIONALS);
-                }
-
-                mWidthAlignment = Math.max(widthAlignment, mWidthAlignment);
-                mHeightAlignment = Math.max(heightAlignment, mHeightAlignment);
-
-                mWidthRange = Utils.alignRange(mWidthRange, mWidthAlignment);
-                mHeightRange = Utils.alignRange(mHeightRange, mHeightAlignment);
-            }
-
-            private void updateLimits() {
-                // pixels -> blocks <- counts
-                mHorizontalBlockRange = mHorizontalBlockRange.intersect(
-                        Utils.factorRange(mWidthRange, mBlockWidth));
-                mHorizontalBlockRange = mHorizontalBlockRange.intersect(
-                        Range.create(
-                                mBlockCountRange.getLower() / mVerticalBlockRange.getUpper(),
-                                mBlockCountRange.getUpper() / mVerticalBlockRange.getLower()));
-                mVerticalBlockRange = mVerticalBlockRange.intersect(
-                        Utils.factorRange(mHeightRange, mBlockHeight));
-                mVerticalBlockRange = mVerticalBlockRange.intersect(
-                        Range.create(
-                                mBlockCountRange.getLower() / mHorizontalBlockRange.getUpper(),
-                                mBlockCountRange.getUpper() / mHorizontalBlockRange.getLower()));
-                mBlockCountRange = mBlockCountRange.intersect(
-                        Range.create(
-                                mHorizontalBlockRange.getLower()
-                                        * mVerticalBlockRange.getLower(),
-                                mHorizontalBlockRange.getUpper()
-                                        * mVerticalBlockRange.getUpper()));
-                mBlockAspectRatioRange = mBlockAspectRatioRange.intersect(
-                        new Rational(
-                                mHorizontalBlockRange.getLower(), mVerticalBlockRange.getUpper()),
-                        new Rational(
-                                mHorizontalBlockRange.getUpper(), mVerticalBlockRange.getLower()));
-
-                // blocks -> pixels
-                mWidthRange = mWidthRange.intersect(
-                        (mHorizontalBlockRange.getLower() - 1) * mBlockWidth + mWidthAlignment,
-                        mHorizontalBlockRange.getUpper() * mBlockWidth);
-                mHeightRange = mHeightRange.intersect(
-                        (mVerticalBlockRange.getLower() - 1) * mBlockHeight + mHeightAlignment,
-                        mVerticalBlockRange.getUpper() * mBlockHeight);
-                mAspectRatioRange = mAspectRatioRange.intersect(
-                        new Rational(mWidthRange.getLower(), mHeightRange.getUpper()),
-                        new Rational(mWidthRange.getUpper(), mHeightRange.getLower()));
-
-                mSmallerDimensionUpperLimit = Math.min(
-                        mSmallerDimensionUpperLimit,
-                        Math.min(mWidthRange.getUpper(), mHeightRange.getUpper()));
-
-                // blocks -> rate
-                mBlocksPerSecondRange = mBlocksPerSecondRange.intersect(
-                        mBlockCountRange.getLower() * (long)mFrameRateRange.getLower(),
-                        mBlockCountRange.getUpper() * (long)mFrameRateRange.getUpper());
-                mFrameRateRange = mFrameRateRange.intersect(
-                        (int)(mBlocksPerSecondRange.getLower()
-                                / mBlockCountRange.getUpper()),
-                        (int)(mBlocksPerSecondRange.getUpper()
-                                / (double)mBlockCountRange.getLower()));
-            }
-
-            private void applyMacroBlockLimits(
-                    int maxHorizontalBlocks, int maxVerticalBlocks,
-                    int maxBlocks, long maxBlocksPerSecond,
-                    int blockWidth, int blockHeight,
-                    int widthAlignment, int heightAlignment) {
-                applyAlignment(widthAlignment, heightAlignment);
-                applyBlockLimits(
-                        blockWidth, blockHeight, Range.create(1, maxBlocks),
-                        Range.create(1L, maxBlocksPerSecond),
-                        Range.create(
-                                new Rational(1, maxVerticalBlocks),
-                                new Rational(maxHorizontalBlocks, 1)));
-                mHorizontalBlockRange =
-                        mHorizontalBlockRange.intersect(
-                                1, maxHorizontalBlocks / (mBlockWidth / blockWidth));
-                mVerticalBlockRange =
-                        mVerticalBlockRange.intersect(
-                                1, maxVerticalBlocks / (mBlockHeight / blockHeight));
-            }
-
-            private void applyLevelLimits() {
-                int maxBlocksPerSecond = 0;
-                int maxBlocks = 0;
-                int maxBps = 0;
-                int maxDPBBlocks = 0;
-
-                int errors = ERROR_NONE_SUPPORTED;
-                CodecProfileLevel[] profileLevels = mParent.profileLevels;
-                String mime = mParent.getMime();
-
-                if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_AVC)) {
-                    maxBlocks = 99;
-                    maxBlocksPerSecond = 1485;
-                    maxBps = 64000;
-                    maxDPBBlocks = 396;
-                    for (CodecProfileLevel profileLevel: profileLevels) {
-                        int MBPS = 0, FS = 0, BR = 0, DPB = 0;
-                        boolean supported = true;
-                        switch (profileLevel.level) {
-                            case CodecProfileLevel.AVCLevel1:
-                                MBPS =    1485; FS =    99; BR =     64; DPB =    396; break;
-                            case CodecProfileLevel.AVCLevel1b:
-                                MBPS =    1485; FS =    99; BR =    128; DPB =    396; break;
-                            case CodecProfileLevel.AVCLevel11:
-                                MBPS =    3000; FS =   396; BR =    192; DPB =    900; break;
-                            case CodecProfileLevel.AVCLevel12:
-                                MBPS =    6000; FS =   396; BR =    384; DPB =   2376; break;
-                            case CodecProfileLevel.AVCLevel13:
-                                MBPS =   11880; FS =   396; BR =    768; DPB =   2376; break;
-                            case CodecProfileLevel.AVCLevel2:
-                                MBPS =   11880; FS =   396; BR =   2000; DPB =   2376; break;
-                            case CodecProfileLevel.AVCLevel21:
-                                MBPS =   19800; FS =   792; BR =   4000; DPB =   4752; break;
-                            case CodecProfileLevel.AVCLevel22:
-                                MBPS =   20250; FS =  1620; BR =   4000; DPB =   8100; break;
-                            case CodecProfileLevel.AVCLevel3:
-                                MBPS =   40500; FS =  1620; BR =  10000; DPB =   8100; break;
-                            case CodecProfileLevel.AVCLevel31:
-                                MBPS =  108000; FS =  3600; BR =  14000; DPB =  18000; break;
-                            case CodecProfileLevel.AVCLevel32:
-                                MBPS =  216000; FS =  5120; BR =  20000; DPB =  20480; break;
-                            case CodecProfileLevel.AVCLevel4:
-                                MBPS =  245760; FS =  8192; BR =  20000; DPB =  32768; break;
-                            case CodecProfileLevel.AVCLevel41:
-                                MBPS =  245760; FS =  8192; BR =  50000; DPB =  32768; break;
-                            case CodecProfileLevel.AVCLevel42:
-                                MBPS =  522240; FS =  8704; BR =  50000; DPB =  34816; break;
-                            case CodecProfileLevel.AVCLevel5:
-                                MBPS =  589824; FS = 22080; BR = 135000; DPB = 110400; break;
-                            case CodecProfileLevel.AVCLevel51:
-                                MBPS =  983040; FS = 36864; BR = 240000; DPB = 184320; break;
-                            case CodecProfileLevel.AVCLevel52:
-                                MBPS = 2073600; FS = 36864; BR = 240000; DPB = 184320; break;
-                            default:
-                                Log.w(TAG, "Unrecognized level "
-                                        + profileLevel.level + " for " + mime);
-                                errors |= ERROR_UNRECOGNIZED;
-                        }
-                        switch (profileLevel.profile) {
-                            case CodecProfileLevel.AVCProfileHigh:
-                                BR *= 1250; break;
-                            case CodecProfileLevel.AVCProfileHigh10:
-                                BR *= 3000; break;
-                            case CodecProfileLevel.AVCProfileExtended:
-                            case CodecProfileLevel.AVCProfileHigh422:
-                            case CodecProfileLevel.AVCProfileHigh444:
-                                Log.w(TAG, "Unsupported profile "
-                                        + profileLevel.profile + " for " + mime);
-                                errors |= ERROR_UNSUPPORTED;
-                                supported = false;
-                                // fall through - treat as base profile
-                            case CodecProfileLevel.AVCProfileBaseline:
-                            case CodecProfileLevel.AVCProfileMain:
-                                BR *= 1000; break;
-                            default:
-                                Log.w(TAG, "Unrecognized profile "
-                                        + profileLevel.profile + " for " + mime);
-                                errors |= ERROR_UNRECOGNIZED;
-                                BR *= 1000;
-                        }
-                        if (supported) {
-                            errors &= ~ERROR_NONE_SUPPORTED;
-                        }
-                        maxBlocksPerSecond = Math.max(MBPS, maxBlocksPerSecond);
-                        maxBlocks = Math.max(FS, maxBlocks);
-                        maxBps = Math.max(BR, maxBps);
-                        maxDPBBlocks = Math.max(maxDPBBlocks, DPB);
-                    }
-
-                    int maxLengthInBlocks = (int)(Math.sqrt(maxBlocks * 8));
-                    applyMacroBlockLimits(
-                            maxLengthInBlocks, maxLengthInBlocks,
-                            maxBlocks, maxBlocksPerSecond,
-                            16 /* blockWidth */, 16 /* blockHeight */,
-                            1 /* widthAlignment */, 1 /* heightAlignment */);
-                } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_MPEG4)) {
-                    int maxWidth = 11, maxHeight = 9, maxRate = 15;
-                    maxBlocks = 99;
-                    maxBlocksPerSecond = 1485;
-                    maxBps = 64000;
-                    for (CodecProfileLevel profileLevel: profileLevels) {
-                        int MBPS = 0, FS = 0, BR = 0, FR = 0, W = 0, H = 0;
-                        boolean supported = true;
-                        switch (profileLevel.profile) {
-                            case CodecProfileLevel.MPEG4ProfileSimple:
-                                switch (profileLevel.level) {
-                                    case CodecProfileLevel.MPEG4Level0:
-                                        FR = 15; W = 11; H =  9; MBPS =  1485; FS =  99; BR =  64; break;
-                                    case CodecProfileLevel.MPEG4Level1:
-                                        FR = 30; W = 11; H =  9; MBPS =  1485; FS =  99; BR =  64; break;
-                                    case CodecProfileLevel.MPEG4Level0b:
-                                        FR = 30; W = 11; H =  9; MBPS =  1485; FS =  99; BR = 128; break;
-                                    case CodecProfileLevel.MPEG4Level2:
-                                        FR = 30; W = 22; H = 18; MBPS =  5940; FS = 396; BR = 128; break;
-                                    case CodecProfileLevel.MPEG4Level3:
-                                        FR = 30; W = 22; H = 18; MBPS = 11880; FS = 396; BR = 384; break;
-                                    case CodecProfileLevel.MPEG4Level4:
-                                    case CodecProfileLevel.MPEG4Level4a:
-                                    case CodecProfileLevel.MPEG4Level5:
-                                        // While MPEG4 SP does not have level 4 or 5, some vendors
-                                        // report it. Use the same limits as level 3, but mark as
-                                        // unsupported.
-                                        FR = 30; W = 22; H = 18; MBPS = 11880; FS = 396; BR = 384;
-                                        supported = false;
-                                        break;
-                                    default:
-                                        Log.w(TAG, "Unrecognized profile/level "
-                                                + profileLevel.profile + "/"
-                                                + profileLevel.level + " for " + mime);
-                                        errors |= ERROR_UNRECOGNIZED;
-                                }
-                                break;
-                            case CodecProfileLevel.MPEG4ProfileAdvancedSimple:
-                                switch (profileLevel.level) {
-                                    case CodecProfileLevel.MPEG4Level0:
-                                    case CodecProfileLevel.MPEG4Level1:
-                                        FR = 30; W = 11; H =  9; MBPS =  2970; FS =   99; BR =  128; break;
-                                    case CodecProfileLevel.MPEG4Level2:
-                                        FR = 30; W = 22; H = 18; MBPS =  5940; FS =  396; BR =  384; break;
-                                    case CodecProfileLevel.MPEG4Level3:
-                                        FR = 30; W = 22; H = 18; MBPS = 11880; FS =  396; BR =  768; break;
-                                    // case CodecProfileLevel.MPEG4Level3b:
-                                    // TODO: MPEG4 level 3b is not defined in OMX
-                                    //  MBPS = 11880; FS =  396; BR = 1500; break;
-                                    case CodecProfileLevel.MPEG4Level4:
-                                    case CodecProfileLevel.MPEG4Level4a:
-                                        // TODO: MPEG4 level 4a is not defined in spec
-                                        FR = 30; W = 44; H = 36; MBPS = 23760; FS =  792; BR = 3000; break;
-                                    case CodecProfileLevel.MPEG4Level5:
-                                        FR = 30; W = 45; H = 36; MBPS = 48600; FS = 1620; BR = 8000; break;
-                                    default:
-                                        Log.w(TAG, "Unrecognized profile/level "
-                                                + profileLevel.profile + "/"
-                                                + profileLevel.level + " for " + mime);
-                                        errors |= ERROR_UNRECOGNIZED;
-                                }
-                                break;
-                            case CodecProfileLevel.MPEG4ProfileMain:             // 2-4
-                            case CodecProfileLevel.MPEG4ProfileNbit:             // 2
-                            case CodecProfileLevel.MPEG4ProfileAdvancedRealTime: // 1-4
-                            case CodecProfileLevel.MPEG4ProfileCoreScalable:     // 1-3
-                            case CodecProfileLevel.MPEG4ProfileAdvancedCoding:   // 1-4
-                            case CodecProfileLevel.MPEG4ProfileCore:             // 1-2
-                            case CodecProfileLevel.MPEG4ProfileAdvancedCore:     // 1-4
-                            case CodecProfileLevel.MPEG4ProfileSimpleScalable:   // 0-2
-                            case CodecProfileLevel.MPEG4ProfileAdvancedScalable: // 1-3
-                            case CodecProfileLevel.MPEG4ProfileHybrid:           // 1-2
-                            case CodecProfileLevel.MPEG4ProfileBasicAnimated:    // 1-2
-                            case CodecProfileLevel.MPEG4ProfileScalableTexture:  // 1
-                            case CodecProfileLevel.MPEG4ProfileSimpleFace:       // 1-2
-                            case CodecProfileLevel.MPEG4ProfileSimpleFBA:        // 1-2
-                                Log.i(TAG, "Unsupported profile "
-                                        + profileLevel.profile + " for " + mime);
-                                errors |= ERROR_UNSUPPORTED;
-                                supported = false;
-                                break;
-                            default:
-                                Log.w(TAG, "Unrecognized profile "
-                                        + profileLevel.profile + " for " + mime);
-                                errors |= ERROR_UNRECOGNIZED;
-                        }
-                        if (supported) {
-                            errors &= ~ERROR_NONE_SUPPORTED;
-                        }
-                        maxBlocksPerSecond = Math.max(MBPS, maxBlocksPerSecond);
-                        maxBlocks = Math.max(FS, maxBlocks);
-                        maxBps = Math.max(BR * 1000, maxBps);
-                        maxWidth = Math.max(W, maxWidth);
-                        maxHeight = Math.max(H, maxHeight);
-                        maxRate = Math.max(FR, maxRate);
-                    }
-                    applyMacroBlockLimits(maxWidth, maxHeight,
-                            maxBlocks, maxBlocksPerSecond,
-                            16 /* blockWidth */, 16 /* blockHeight */,
-                            1 /* widthAlignment */, 1 /* heightAlignment */);
-                    mFrameRateRange = mFrameRateRange.intersect(12, maxRate);
-                } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_H263)) {
-                    int maxWidth = 11, maxHeight = 9, maxRate = 15;
-                    maxBlocks = 99;
-                    maxBlocksPerSecond = 1485;
-                    maxBps = 64000;
-                    for (CodecProfileLevel profileLevel: profileLevels) {
-                        int MBPS = 0, BR = 0, FR = 0, W = 0, H = 0;
-                        switch (profileLevel.level) {
-                            case CodecProfileLevel.H263Level10:
-                                FR = 15; W = 11; H =  9; BR =   1; MBPS =  W * H * FR; break;
-                            case CodecProfileLevel.H263Level20:
-                                // only supports CIF, 0..QCIF
-                                FR = 30; W = 22; H = 18; BR =   2; MBPS =  W * H * FR; break;
-                            case CodecProfileLevel.H263Level30:
-                                // only supports CIF, 0..QCIF
-                                FR = 30; W = 22; H = 18; BR =   6; MBPS =  W * H * FR; break;
-                            case CodecProfileLevel.H263Level40:
-                                // only supports CIF, 0..QCIF
-                                FR = 30; W = 22; H = 18; BR =  32; MBPS =  W * H * FR; break;
-                            case CodecProfileLevel.H263Level45:
-                                // only implies level 10 support
-                                FR = 30; W = 11; H =  9; BR =   2; MBPS =  W * H * FR; break;
-                            case CodecProfileLevel.H263Level50:
-                                // only supports 50fps for H > 15
-                                FR = 60; W = 22; H = 18; BR =  64; MBPS =  W * H * 50; break;
-                            case CodecProfileLevel.H263Level60:
-                                // only supports 50fps for H > 15
-                                FR = 60; W = 45; H = 18; BR = 128; MBPS =  W * H * 50; break;
-                            case CodecProfileLevel.H263Level70:
-                                // only supports 50fps for H > 30
-                                FR = 60; W = 45; H = 36; BR = 256; MBPS =  W * H * 50; break;
-                            default:
-                                Log.w(TAG, "Unrecognized profile/level " + profileLevel.profile
-                                        + "/" + profileLevel.level + " for " + mime);
-                                errors |= ERROR_UNRECOGNIZED;
-                        }
-                        switch (profileLevel.profile) {
-                            case CodecProfileLevel.H263ProfileBackwardCompatible:
-                            case CodecProfileLevel.H263ProfileBaseline:
-                            case CodecProfileLevel.H263ProfileH320Coding:
-                            case CodecProfileLevel.H263ProfileHighCompression:
-                            case CodecProfileLevel.H263ProfileHighLatency:
-                            case CodecProfileLevel.H263ProfileInterlace:
-                            case CodecProfileLevel.H263ProfileInternet:
-                            case CodecProfileLevel.H263ProfileISWV2:
-                            case CodecProfileLevel.H263ProfileISWV3:
-                                break;
-                            default:
-                                Log.w(TAG, "Unrecognized profile "
-                                        + profileLevel.profile + " for " + mime);
-                                errors |= ERROR_UNRECOGNIZED;
-                        }
-                        errors &= ~ERROR_NONE_SUPPORTED;
-                        maxBlocksPerSecond = Math.max(MBPS, maxBlocksPerSecond);
-                        maxBlocks = Math.max(W * H, maxBlocks);
-                        maxBps = Math.max(BR * 64000, maxBps);
-                        maxWidth = Math.max(W, maxWidth);
-                        maxHeight = Math.max(H, maxHeight);
-                        maxRate = Math.max(FR, maxRate);
-                    }
-                    applyMacroBlockLimits(maxWidth, maxHeight,
-                            maxBlocks, maxBlocksPerSecond,
-                            16 /* blockWidth */, 16 /* blockHeight */,
-                            1 /* widthAlignment */, 1 /* heightAlignment */);
-                    mFrameRateRange = Range.create(1, maxRate);
-                } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_VP8) ||
-                        mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_VP9)) {
-                    maxBlocks = maxBlocksPerSecond = Integer.MAX_VALUE;
-
-                    // TODO: set to 100Mbps for now, need a number for VPX
-                    maxBps = 100000000;
-
-                    // profile levels are not indicative for VPx, but verify
-                    // them nonetheless
-                    for (CodecProfileLevel profileLevel: profileLevels) {
-                        switch (profileLevel.level) {
-                            case CodecProfileLevel.VP8Level_Version0:
-                            case CodecProfileLevel.VP8Level_Version1:
-                            case CodecProfileLevel.VP8Level_Version2:
-                            case CodecProfileLevel.VP8Level_Version3:
-                                break;
-                            default:
-                                Log.w(TAG, "Unrecognized level "
-                                        + profileLevel.level + " for " + mime);
-                                errors |= ERROR_UNRECOGNIZED;
-                        }
-                        switch (profileLevel.profile) {
-                            case CodecProfileLevel.VP8ProfileMain:
-                                break;
-                            default:
-                                Log.w(TAG, "Unrecognized profile "
-                                        + profileLevel.profile + " for " + mime);
-                                errors |= ERROR_UNRECOGNIZED;
-                        }
-                        errors &= ~ERROR_NONE_SUPPORTED;
-                    }
-
-                    final int blockSize =
-                        mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_VP8) ? 16 : 8;
-                    applyMacroBlockLimits(Short.MAX_VALUE, Short.MAX_VALUE,
-                            maxBlocks, maxBlocksPerSecond, blockSize, blockSize,
-                            1 /* widthAlignment */, 1 /* heightAlignment */);
-                } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_HEVC)) {
-                    maxBlocks = 36864;
-                    maxBlocksPerSecond = maxBlocks * 15;
-                    maxBps = 128000;
-                    for (CodecProfileLevel profileLevel: profileLevels) {
-                        double FR = 0;
-                        int FS = 0;
-                        int BR = 0;
-                        switch (profileLevel.level) {
-                            case CodecProfileLevel.HEVCMainTierLevel1:
-                            case CodecProfileLevel.HEVCHighTierLevel1:
-                                FR =    15; FS =    36864; BR =    128; break;
-                            case CodecProfileLevel.HEVCMainTierLevel2:
-                            case CodecProfileLevel.HEVCHighTierLevel2:
-                                FR =    30; FS =   122880; BR =   1500; break;
-                            case CodecProfileLevel.HEVCMainTierLevel21:
-                            case CodecProfileLevel.HEVCHighTierLevel21:
-                                FR =    30; FS =   245760; BR =   3000; break;
-                            case CodecProfileLevel.HEVCMainTierLevel3:
-                            case CodecProfileLevel.HEVCHighTierLevel3:
-                                FR =    30; FS =   552960; BR =   6000; break;
-                            case CodecProfileLevel.HEVCMainTierLevel31:
-                            case CodecProfileLevel.HEVCHighTierLevel31:
-                                FR = 33.75; FS =   983040; BR =  10000; break;
-                            case CodecProfileLevel.HEVCMainTierLevel4:
-                                FR =    30; FS =  2228224; BR =  12000; break;
-                            case CodecProfileLevel.HEVCHighTierLevel4:
-                                FR =    30; FS =  2228224; BR =  30000; break;
-                            case CodecProfileLevel.HEVCMainTierLevel41:
-                                FR =    60; FS =  2228224; BR =  20000; break;
-                            case CodecProfileLevel.HEVCHighTierLevel41:
-                                FR =    60; FS =  2228224; BR =  50000; break;
-                            case CodecProfileLevel.HEVCMainTierLevel5:
-                                FR =    30; FS =  8912896; BR =  25000; break;
-                            case CodecProfileLevel.HEVCHighTierLevel5:
-                                FR =    30; FS =  8912896; BR = 100000; break;
-                            case CodecProfileLevel.HEVCMainTierLevel51:
-                                FR =    60; FS =  8912896; BR =  40000; break;
-                            case CodecProfileLevel.HEVCHighTierLevel51:
-                                FR =    60; FS =  8912896; BR = 160000; break;
-                            case CodecProfileLevel.HEVCMainTierLevel52:
-                                FR =   120; FS =  8912896; BR =  60000; break;
-                            case CodecProfileLevel.HEVCHighTierLevel52:
-                                FR =   120; FS =  8912896; BR = 240000; break;
-                            case CodecProfileLevel.HEVCMainTierLevel6:
-                                FR =    30; FS = 35651584; BR =  60000; break;
-                            case CodecProfileLevel.HEVCHighTierLevel6:
-                                FR =    30; FS = 35651584; BR = 240000; break;
-                            case CodecProfileLevel.HEVCMainTierLevel61:
-                                FR =    60; FS = 35651584; BR = 120000; break;
-                            case CodecProfileLevel.HEVCHighTierLevel61:
-                                FR =    60; FS = 35651584; BR = 480000; break;
-                            case CodecProfileLevel.HEVCMainTierLevel62:
-                                FR =   120; FS = 35651584; BR = 240000; break;
-                            case CodecProfileLevel.HEVCHighTierLevel62:
-                                FR =   120; FS = 35651584; BR = 800000; break;
-                            default:
-                                Log.w(TAG, "Unrecognized level "
-                                        + profileLevel.level + " for " + mime);
-                                errors |= ERROR_UNRECOGNIZED;
-                        }
-                        switch (profileLevel.profile) {
-                            case CodecProfileLevel.HEVCProfileMain:
-                            case CodecProfileLevel.HEVCProfileMain10:
-                                break;
-                            default:
-                                Log.w(TAG, "Unrecognized profile "
-                                        + profileLevel.profile + " for " + mime);
-                                errors |= ERROR_UNRECOGNIZED;
-                        }
-
-                        /* DPB logic:
-                        if      (width * height <= FS / 4)    DPB = 16;
-                        else if (width * height <= FS / 2)    DPB = 12;
-                        else if (width * height <= FS * 0.75) DPB = 8;
-                        else                                  DPB = 6;
-                        */
-
-                        errors &= ~ERROR_NONE_SUPPORTED;
-                        maxBlocksPerSecond = Math.max((int)(FR * FS), maxBlocksPerSecond);
-                        maxBlocks = Math.max(FS, maxBlocks);
-                        maxBps = Math.max(BR * 1000, maxBps);
-                    }
-
-                    int maxLengthInBlocks = (int)(Math.sqrt(maxBlocks * 8));
-                    // CTBs are at least 8x8
-                    maxBlocks = Utils.divUp(maxBlocks, 8 * 8);
-                    maxBlocksPerSecond = Utils.divUp(maxBlocksPerSecond, 8 * 8);
-                    maxLengthInBlocks = Utils.divUp(maxLengthInBlocks, 8);
-
-                    applyMacroBlockLimits(
-                            maxLengthInBlocks, maxLengthInBlocks,
-                            maxBlocks, maxBlocksPerSecond,
-                            8 /* blockWidth */, 8 /* blockHeight */,
-                            1 /* widthAlignment */, 1 /* heightAlignment */);
-                } else {
-                    Log.w(TAG, "Unsupported mime " + mime);
-                    // using minimal bitrate here.  should be overriden by
-                    // info from media_codecs.xml
-                    maxBps = 64000;
-                    errors |= ERROR_UNSUPPORTED;
-                }
-                mBitrateRange = Range.create(1, maxBps);
-                mParent.mError |= errors;
-            }
-        };
-
-        VideoCapabilities mVideoCaps;
-
         private boolean isVideo() {
             return mVideoCaps != null;
         }
@@ -1624,16 +393,28 @@
         /**
          * Returns the video capabilities or {@code null} if this is not a video codec.
          */
-        public final VideoCapabilities getVideoCapabilities() {
+        public VideoCapabilities getVideoCapabilities() {
             return mVideoCaps;
         }
 
+        /** @hide */
+        public CodecCapabilities dup() {
+            return new CodecCapabilities(
+                // clone writable arrays
+                Arrays.copyOf(profileLevels, profileLevels.length),
+                Arrays.copyOf(colorFormats, colorFormats.length),
+                isEncoder(),
+                mFlagsVerified,
+                mDefaultFormat,
+                mCapabilitiesInfo);
+        }
+
         /**
          * Retrieve the codec capabilities for a certain {@code mime type}, {@code
          * profile} and {@code level}.  If the type, or profile-level combination
          * is not understood by the framework, it returns null.
          */
-        public static final CodecCapabilities CreateFromProfileLevel(
+        public static CodecCapabilities CreateFromProfileLevel(
                 String mime, int profile, int level) {
             CodecProfileLevel pl = new CodecProfileLevel();
             pl.profile = profile;
@@ -1699,282 +480,1503 @@
                 // TODO restrict features by mFlagsVerified once all codecs reliably verify them
             }
         }
+    }
+
+    /**
+     * A class that supports querying the audio capabilities of a codec.
+     */
+    public static final class AudioCapabilities {
+        private static final String TAG = "AudioCapabilities";
+        private CodecCapabilities mParent;
+        private Range<Integer> mBitrateRange;
+
+        private int[] mSampleRates;
+        private Range<Integer>[] mSampleRateRanges;
+        private int mMaxInputChannelCount;
+
+        private static final int MAX_INPUT_CHANNEL_COUNT = 30;
 
         /**
-         * A class that supports querying the audio capabilities of a codec.
+         * Returns the range of supported bitrates in bits/second.
          */
-        public static final class AudioCapabilities extends BaseCapabilities {
-            private static final String TAG = "AudioCapabilities";
-
-            private int[] mSampleRates;
-            private Range<Integer>[] mSampleRateRanges;
-            private int mMaxInputChannelCount;
-
-            private static final int MAX_INPUT_CHANNEL_COUNT = 30;
-
-            /**
-             * Returns the array of supported sample rates if the codec
-             * supports only discrete values.  Otherwise, it returns
-             * {@code null}.  The array is sorted in ascending order.
-             */
-            public final int[] getSupportedSampleRates() {
-                return Arrays.copyOf(mSampleRates, mSampleRates.length);
-            }
-
-            /**
-             * Returns the array of supported sample rate ranges.  The
-             * array is sorted in ascending order, and the ranges are
-             * distinct.
-             */
-            public final Range<Integer>[] getSupportedSampleRateRanges() {
-                return Arrays.copyOf(mSampleRateRanges, mSampleRateRanges.length);
-            }
-
-            /**
-             * Returns the maximum number of input channels supported.  The codec
-             * supports any number of channels between 1 and this maximum value.
-             */
-            public final int getMaxInputChannelCount() {
-                return mMaxInputChannelCount;
-            }
-
-            /* no public constructor */
-            private AudioCapabilities() { }
-
-            /** @hide */
-            public static AudioCapabilities create(
-                    MediaFormat info, CodecCapabilities parent) {
-                AudioCapabilities caps = new AudioCapabilities();
-                caps.init(info, parent);
-                return caps;
-            }
-
-            /** @hide */
-            public void init(MediaFormat info, CodecCapabilities parent) {
-                super.init(info, parent);
-                initWithPlatformLimits();
-                applyLevelLimits();
-                parseFromInfo(info);
-            }
-
-            private void initWithPlatformLimits() {
-                mMaxInputChannelCount = MAX_INPUT_CHANNEL_COUNT;
-                // mBitrateRange = Range.create(1, 320000);
-                mSampleRateRanges = new Range[] { Range.create(8000, 96000) };
-                mSampleRates = null;
-            }
-
-            private boolean supports(Integer sampleRate, Integer inputChannels) {
-                // channels and sample rates are checked orthogonally
-                if (inputChannels != null &&
-                        (inputChannels < 1 || inputChannels > mMaxInputChannelCount)) {
-                    return false;
-                }
-                if (sampleRate != null) {
-                    int ix = Utils.binarySearchDistinctRanges(
-                            mSampleRateRanges, sampleRate);
-                    if (ix < 0) {
-                        return false;
-                    }
-                }
-                return true;
-            }
-
-            /**
-             * Query whether the sample rate is supported by the codec.
-             */
-            public final boolean isSampleRateSupported(int sampleRate) {
-                return supports(sampleRate, null);
-            }
-
-            /** modifies rates */
-            private void limitSampleRates(int[] rates) {
-                Arrays.sort(rates);
-                ArrayList<Range<Integer>> ranges = new ArrayList<Range<Integer>>();
-                for (int rate: rates) {
-                    if (supports(rate, null /* channels */)) {
-                        ranges.add(Range.create(rate, rate));
-                    }
-                }
-                mSampleRateRanges = ranges.toArray(new Range[ranges.size()]);
-                createDiscreteSampleRates();
-            }
-
-            private void createDiscreteSampleRates() {
-                mSampleRates = new int[mSampleRateRanges.length];
-                for (int i = 0; i < mSampleRateRanges.length; i++) {
-                    mSampleRates[i] = mSampleRateRanges[i].getLower();
-                }
-            }
-
-            /** modifies rateRanges */
-            private void limitSampleRates(Range<Integer>[] rateRanges) {
-                sortDistinctRanges(rateRanges);
-                mSampleRateRanges = intersectSortedDistinctRanges(mSampleRateRanges, rateRanges);
-
-                // check if all values are discrete
-                for (Range<Integer> range: mSampleRateRanges) {
-                    if (!range.getLower().equals(range.getUpper())) {
-                        mSampleRates = null;
-                        return;
-                    }
-                }
-                createDiscreteSampleRates();
-            }
-
-            private void applyLevelLimits() {
-                int[] sampleRates = null;
-                Range<Integer> sampleRateRange = null, bitRates = null;
-                int maxChannels = 0;
-                String mime = mParent.getMime();
-
-                if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_MPEG)) {
-                    sampleRates = new int[] {
-                            8000, 11025, 12000,
-                            16000, 22050, 24000,
-                            32000, 44100, 48000 };
-                    bitRates = Range.create(8000, 320000);
-                    maxChannels = 2;
-                } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AMR_NB)) {
-                    sampleRates = new int[] { 8000 };
-                    bitRates = Range.create(4750, 12200);
-                    maxChannels = 1;
-                } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AMR_WB)) {
-                    sampleRates = new int[] { 16000 };
-                    bitRates = Range.create(6600, 23850);
-                    maxChannels = 1;
-                } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AAC)) {
-                    sampleRates = new int[] {
-                            7350, 8000,
-                            11025, 12000, 16000,
-                            22050, 24000, 32000,
-                            44100, 48000, 64000,
-                            88200, 96000 };
-                    bitRates = Range.create(8000, 510000);
-                    maxChannels = 48;
-                } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_VORBIS)) {
-                    bitRates = Range.create(32000, 500000);
-                    sampleRates = new int[] { 8000, 12000, 16000, 24000, 48000, 192000 };
-                    maxChannels = 255;
-                } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_OPUS)) {
-                    bitRates = Range.create(6000, 510000);
-                    sampleRates = new int[] { 8000, 12000, 16000, 24000, 48000 };
-                    maxChannels = 255;
-                } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_RAW)) {
-                    sampleRateRange = Range.create(1, 96000);
-                    bitRates = Range.create(1, 10000000);
-                    maxChannels = 8;
-                } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_FLAC)) {
-                    sampleRateRange = Range.create(1, 655350);
-                    // lossless codec, so bitrate is ignored
-                    maxChannels = 255;
-                } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_G711_ALAW)
-                        || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_G711_MLAW)) {
-                    sampleRates = new int[] { 8000 };
-                    bitRates = Range.create(64000, 64000);
-                    // platform allows multiple channels for this format
-                } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_MSGSM)) {
-                    sampleRates = new int[] { 8000 };
-                    bitRates = Range.create(13000, 13000);
-                    maxChannels = 1;
-                } else {
-                    Log.w(TAG, "Unsupported mime " + mime);
-                    mParent.mError |= ERROR_UNSUPPORTED;
-                }
-
-                // restrict ranges
-                if (sampleRates != null) {
-                    limitSampleRates(sampleRates);
-                } else if (sampleRateRange != null) {
-                    limitSampleRates(new Range[] { sampleRateRange });
-                }
-                applyLimits(maxChannels, bitRates);
-            }
-
-            private void applyLimits(int maxInputChannels, Range<Integer> bitRates) {
-                mMaxInputChannelCount = Range.create(1, mMaxInputChannelCount)
-                        .clamp(maxInputChannels);
-                if (bitRates != null) {
-                    mBitrateRange = mBitrateRange.intersect(bitRates);
-                }
-            }
-
-            private void parseFromInfo(MediaFormat info) {
-                int maxInputChannels = MAX_INPUT_CHANNEL_COUNT;
-                Range<Integer> bitRates = POSITIVE_INTEGERS;
-
-                if (info.containsKey("sample-rate-ranges")) {
-                    String[] rateStrings = info.getString("sample-rate-ranges").split(",");
-                    Range<Integer>[] rateRanges = new Range[rateStrings.length];
-                    for (int i = 0; i < rateStrings.length; i++) {
-                        rateRanges[i] = Utils.parseIntRange(rateStrings[i], null);
-                    }
-                    limitSampleRates(rateRanges);
-                }
-                if (info.containsKey("max-channel-count")) {
-                    maxInputChannels = Utils.parseIntSafely(
-                            info.getString("max-channel-count"), maxInputChannels);
-                }
-                if (info.containsKey("bitrate-range")) {
-                    bitRates = bitRates.intersect(
-                            Utils.parseIntRange(info.getString("bitrate"), bitRates));
-                }
-                applyLimits(maxInputChannels, bitRates);
-            }
-
-            /** @hide */
-            public void setDefaultFormat(MediaFormat format) {
-                // report settings that have only a single choice
-                if (mBitrateRange.getLower().equals(mBitrateRange.getUpper())) {
-                    format.setInteger(MediaFormat.KEY_BIT_RATE, mBitrateRange.getLower());
-                }
-                if (mMaxInputChannelCount == 1) {
-                    // mono-only format
-                    format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1);
-                }
-                if (mSampleRates != null && mSampleRates.length == 1) {
-                    format.setInteger(MediaFormat.KEY_SAMPLE_RATE, mSampleRates[0]);
-                }
-            }
-
-            /** @hide */
-            public boolean supportsFormat(MediaFormat format) {
-                Map<String, Object> map = format.getMap();
-                Integer sampleRate = (Integer)map.get(MediaFormat.KEY_SAMPLE_RATE);
-                Integer channels = (Integer)map.get(MediaFormat.KEY_CHANNEL_COUNT);
-                if (!supports(sampleRate, channels)) {
-                    return false;
-                }
-
-                // nothing to do for:
-                // KEY_CHANNEL_MASK: codecs don't get this
-                // KEY_IS_ADTS:      required feature for all AAC decoders
-                return true;
-            }
-        };
-
-        AudioCapabilities mAudioCaps;
-        private boolean isAudio() {
-            return mAudioCaps != null;
+        public Range<Integer> getBitrateRange() {
+            return mBitrateRange;
         }
 
         /**
-         * Returns the audio capabilities or {@code null} if this is not an audio codec.
+         * Returns the array of supported sample rates if the codec
+         * supports only discrete values.  Otherwise, it returns
+         * {@code null}.  The array is sorted in ascending order.
          */
-        public final AudioCapabilities getAudioCapabilities() {
-            return mAudioCaps;
+        public int[] getSupportedSampleRates() {
+            return Arrays.copyOf(mSampleRates, mSampleRates.length);
+        }
+
+        /**
+         * Returns the array of supported sample rate ranges.  The
+         * array is sorted in ascending order, and the ranges are
+         * distinct.
+         */
+        public Range<Integer>[] getSupportedSampleRateRanges() {
+            return Arrays.copyOf(mSampleRateRanges, mSampleRateRanges.length);
+        }
+
+        /**
+         * Returns the maximum number of input channels supported.  The codec
+         * supports any number of channels between 1 and this maximum value.
+         */
+        public int getMaxInputChannelCount() {
+            return mMaxInputChannelCount;
+        }
+
+        /* no public constructor */
+        private AudioCapabilities() { }
+
+        /** @hide */
+        public static AudioCapabilities create(
+                MediaFormat info, CodecCapabilities parent) {
+            AudioCapabilities caps = new AudioCapabilities();
+            caps.init(info, parent);
+            return caps;
         }
 
         /** @hide */
-        public CodecCapabilities dup() {
-            return new CodecCapabilities(
-                // clone writable arrays
-                Arrays.copyOf(profileLevels, profileLevels.length),
-                Arrays.copyOf(colorFormats, colorFormats.length),
-                isEncoder(),
-                mFlagsVerified,
-                mDefaultFormat,
-                mCapabilitiesInfo);
+        public void init(MediaFormat info, CodecCapabilities parent) {
+            mParent = parent;
+            initWithPlatformLimits();
+            applyLevelLimits();
+            parseFromInfo(info);
+        }
+
+        private void initWithPlatformLimits() {
+            mBitrateRange = Range.create(0, Integer.MAX_VALUE);
+            mMaxInputChannelCount = MAX_INPUT_CHANNEL_COUNT;
+            // mBitrateRange = Range.create(1, 320000);
+            mSampleRateRanges = new Range[] { Range.create(8000, 96000) };
+            mSampleRates = null;
+        }
+
+        private boolean supports(Integer sampleRate, Integer inputChannels) {
+            // channels and sample rates are checked orthogonally
+            if (inputChannels != null &&
+                    (inputChannels < 1 || inputChannels > mMaxInputChannelCount)) {
+                return false;
+            }
+            if (sampleRate != null) {
+                int ix = Utils.binarySearchDistinctRanges(
+                        mSampleRateRanges, sampleRate);
+                if (ix < 0) {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        /**
+         * Query whether the sample rate is supported by the codec.
+         */
+        public boolean isSampleRateSupported(int sampleRate) {
+            return supports(sampleRate, null);
+        }
+
+        /** modifies rates */
+        private void limitSampleRates(int[] rates) {
+            Arrays.sort(rates);
+            ArrayList<Range<Integer>> ranges = new ArrayList<Range<Integer>>();
+            for (int rate: rates) {
+                if (supports(rate, null /* channels */)) {
+                    ranges.add(Range.create(rate, rate));
+                }
+            }
+            mSampleRateRanges = ranges.toArray(new Range[ranges.size()]);
+            createDiscreteSampleRates();
+        }
+
+        private void createDiscreteSampleRates() {
+            mSampleRates = new int[mSampleRateRanges.length];
+            for (int i = 0; i < mSampleRateRanges.length; i++) {
+                mSampleRates[i] = mSampleRateRanges[i].getLower();
+            }
+        }
+
+        /** modifies rateRanges */
+        private void limitSampleRates(Range<Integer>[] rateRanges) {
+            sortDistinctRanges(rateRanges);
+            mSampleRateRanges = intersectSortedDistinctRanges(mSampleRateRanges, rateRanges);
+
+            // check if all values are discrete
+            for (Range<Integer> range: mSampleRateRanges) {
+                if (!range.getLower().equals(range.getUpper())) {
+                    mSampleRates = null;
+                    return;
+                }
+            }
+            createDiscreteSampleRates();
+        }
+
+        private void applyLevelLimits() {
+            int[] sampleRates = null;
+            Range<Integer> sampleRateRange = null, bitRates = null;
+            int maxChannels = 0;
+            String mime = mParent.getMimeType();
+
+            if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_MPEG)) {
+                sampleRates = new int[] {
+                        8000, 11025, 12000,
+                        16000, 22050, 24000,
+                        32000, 44100, 48000 };
+                bitRates = Range.create(8000, 320000);
+                maxChannels = 2;
+            } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AMR_NB)) {
+                sampleRates = new int[] { 8000 };
+                bitRates = Range.create(4750, 12200);
+                maxChannels = 1;
+            } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AMR_WB)) {
+                sampleRates = new int[] { 16000 };
+                bitRates = Range.create(6600, 23850);
+                maxChannels = 1;
+            } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AAC)) {
+                sampleRates = new int[] {
+                        7350, 8000,
+                        11025, 12000, 16000,
+                        22050, 24000, 32000,
+                        44100, 48000, 64000,
+                        88200, 96000 };
+                bitRates = Range.create(8000, 510000);
+                maxChannels = 48;
+            } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_VORBIS)) {
+                bitRates = Range.create(32000, 500000);
+                sampleRates = new int[] { 8000, 12000, 16000, 24000, 48000, 192000 };
+                maxChannels = 255;
+            } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_OPUS)) {
+                bitRates = Range.create(6000, 510000);
+                sampleRates = new int[] { 8000, 12000, 16000, 24000, 48000 };
+                maxChannels = 255;
+            } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_RAW)) {
+                sampleRateRange = Range.create(1, 96000);
+                bitRates = Range.create(1, 10000000);
+                maxChannels = 8;
+            } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_FLAC)) {
+                sampleRateRange = Range.create(1, 655350);
+                // lossless codec, so bitrate is ignored
+                maxChannels = 255;
+            } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_G711_ALAW)
+                    || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_G711_MLAW)) {
+                sampleRates = new int[] { 8000 };
+                bitRates = Range.create(64000, 64000);
+                // platform allows multiple channels for this format
+            } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_MSGSM)) {
+                sampleRates = new int[] { 8000 };
+                bitRates = Range.create(13000, 13000);
+                maxChannels = 1;
+            } else {
+                Log.w(TAG, "Unsupported mime " + mime);
+                mParent.mError |= ERROR_UNSUPPORTED;
+            }
+
+            // restrict ranges
+            if (sampleRates != null) {
+                limitSampleRates(sampleRates);
+            } else if (sampleRateRange != null) {
+                limitSampleRates(new Range[] { sampleRateRange });
+            }
+            applyLimits(maxChannels, bitRates);
+        }
+
+        private void applyLimits(int maxInputChannels, Range<Integer> bitRates) {
+            mMaxInputChannelCount = Range.create(1, mMaxInputChannelCount)
+                    .clamp(maxInputChannels);
+            if (bitRates != null) {
+                mBitrateRange = mBitrateRange.intersect(bitRates);
+            }
+        }
+
+        private void parseFromInfo(MediaFormat info) {
+            int maxInputChannels = MAX_INPUT_CHANNEL_COUNT;
+            Range<Integer> bitRates = POSITIVE_INTEGERS;
+
+            if (info.containsKey("sample-rate-ranges")) {
+                String[] rateStrings = info.getString("sample-rate-ranges").split(",");
+                Range<Integer>[] rateRanges = new Range[rateStrings.length];
+                for (int i = 0; i < rateStrings.length; i++) {
+                    rateRanges[i] = Utils.parseIntRange(rateStrings[i], null);
+                }
+                limitSampleRates(rateRanges);
+            }
+            if (info.containsKey("max-channel-count")) {
+                maxInputChannels = Utils.parseIntSafely(
+                        info.getString("max-channel-count"), maxInputChannels);
+            }
+            if (info.containsKey("bitrate-range")) {
+                bitRates = bitRates.intersect(
+                        Utils.parseIntRange(info.getString("bitrate"), bitRates));
+            }
+            applyLimits(maxInputChannels, bitRates);
+        }
+
+        /** @hide */
+        public void setDefaultFormat(MediaFormat format) {
+            // report settings that have only a single choice
+            if (mBitrateRange.getLower().equals(mBitrateRange.getUpper())) {
+                format.setInteger(MediaFormat.KEY_BIT_RATE, mBitrateRange.getLower());
+            }
+            if (mMaxInputChannelCount == 1) {
+                // mono-only format
+                format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1);
+            }
+            if (mSampleRates != null && mSampleRates.length == 1) {
+                format.setInteger(MediaFormat.KEY_SAMPLE_RATE, mSampleRates[0]);
+            }
+        }
+
+        /** @hide */
+        public boolean supportsFormat(MediaFormat format) {
+            Map<String, Object> map = format.getMap();
+            Integer sampleRate = (Integer)map.get(MediaFormat.KEY_SAMPLE_RATE);
+            Integer channels = (Integer)map.get(MediaFormat.KEY_CHANNEL_COUNT);
+            if (!supports(sampleRate, channels)) {
+                return false;
+            }
+
+            // nothing to do for:
+            // KEY_CHANNEL_MASK: codecs don't get this
+            // KEY_IS_ADTS:      required feature for all AAC decoders
+            return true;
+        }
+    }
+
+    /**
+     * A class that supports querying the video capabilities of a codec.
+     */
+    public static final class VideoCapabilities {
+        private static final String TAG = "VideoCapabilities";
+        private CodecCapabilities mParent;
+        private Range<Integer> mBitrateRange;
+
+        private Range<Integer> mHeightRange;
+        private Range<Integer> mWidthRange;
+        private Range<Integer> mBlockCountRange;
+        private Range<Integer> mHorizontalBlockRange;
+        private Range<Integer> mVerticalBlockRange;
+        private Range<Rational> mAspectRatioRange;
+        private Range<Rational> mBlockAspectRatioRange;
+        private Range<Long> mBlocksPerSecondRange;
+        private Range<Integer> mFrameRateRange;
+
+        private int mBlockWidth;
+        private int mBlockHeight;
+        private int mWidthAlignment;
+        private int mHeightAlignment;
+        private int mSmallerDimensionUpperLimit;
+
+        /**
+         * Returns the range of supported bitrates in bits/second.
+         */
+        public Range<Integer> getBitrateRange() {
+            return mBitrateRange;
+        }
+
+        /**
+         * Returns the range of supported video widths.
+         */
+        public Range<Integer> getSupportedWidths() {
+            return mWidthRange;
+        }
+
+        /**
+         * Returns the range of supported video heights.
+         */
+        public Range<Integer> getSupportedHeights() {
+            return mHeightRange;
+        }
+
+        /**
+         * Returns the alignment requirement for video width (in pixels).
+         *
+         * This is a power-of-2 value that video width must be a
+         * multiple of.
+         */
+        public int getWidthAlignment() {
+            return mWidthAlignment;
+        }
+
+        /**
+         * Returns the alignment requirement for video height (in pixels).
+         *
+         * This is a power-of-2 value that video height must be a
+         * multiple of.
+         */
+        public int getHeightAlignment() {
+            return mHeightAlignment;
+        }
+
+        /**
+         * Return the upper limit on the smaller dimension of width or height.
+         * <p></p>
+         * Some codecs have a limit on the smaller dimension, whether it be
+         * the width or the height.  E.g. a codec may only be able to handle
+         * up to 1920x1080 both in landscape and portrait mode (1080x1920).
+         * In this case the maximum width and height are both 1920, but the
+         * smaller dimension limit will be 1080. For other codecs, this is
+         * {@code Math.min(getSupportedWidths().getUpper(),
+         * getSupportedHeights().getUpper())}.
+         *
+         * @hide
+         */
+        public int getSmallerDimensionUpperLimit() {
+            return mSmallerDimensionUpperLimit;
+        }
+
+        /**
+         * Returns the range of supported frame rates.
+         * <p>
+         * This is not a performance indicator.  Rather, it expresses the
+         * limits specified in the coding standard, based on the complexities
+         * of encoding material for later playback at a certain frame rate,
+         * or the decoding of such material in non-realtime.
+         */
+        public Range<Integer> getSupportedFrameRates() {
+            return mFrameRateRange;
+        }
+
+        /**
+         * Returns the range of supported video widths for a video height.
+         * @param height the height of the video
+         */
+        public Range<Integer> getSupportedWidthsFor(int height) {
+            try {
+                Range<Integer> range = mWidthRange;
+                if (!mHeightRange.contains(height)
+                        || (height % mHeightAlignment) != 0) {
+                    throw new IllegalArgumentException("unsupported height");
+                }
+                final int heightInBlocks = Utils.divUp(height, mBlockHeight);
+
+                // constrain by block count and by block aspect ratio
+                final int minWidthInBlocks = Math.max(
+                        Utils.divUp(mBlockCountRange.getLower(), heightInBlocks),
+                        (int)Math.ceil(mBlockAspectRatioRange.getLower().doubleValue()
+                                * heightInBlocks));
+                final int maxWidthInBlocks = Math.min(
+                        mBlockCountRange.getUpper() / heightInBlocks,
+                        (int)(mBlockAspectRatioRange.getUpper().doubleValue()
+                                * heightInBlocks));
+                range = range.intersect(
+                        (minWidthInBlocks - 1) * mBlockWidth + mWidthAlignment,
+                        maxWidthInBlocks * mBlockWidth);
+
+                // constrain by smaller dimension limit
+                if (height > mSmallerDimensionUpperLimit) {
+                    range = range.intersect(1, mSmallerDimensionUpperLimit);
+                }
+
+                // constrain by aspect ratio
+                range = range.intersect(
+                        (int)Math.ceil(mAspectRatioRange.getLower().doubleValue()
+                                * height),
+                        (int)(mAspectRatioRange.getUpper().doubleValue() * height));
+                return range;
+            } catch (IllegalArgumentException e) {
+                // should not be here
+                Log.w(TAG, "could not get supported widths for " + height , e);
+                throw new IllegalArgumentException("unsupported height");
+            }
+        }
+
+        /**
+         * Returns the range of supported video heights for a video width
+         * @param width the width of the video
+         */
+        public Range<Integer> getSupportedHeightsFor(int width) {
+            try {
+                Range<Integer> range = mHeightRange;
+                if (!mWidthRange.contains(width)
+                        || (width % mWidthAlignment) != 0) {
+                    throw new IllegalArgumentException("unsupported width");
+                }
+                final int widthInBlocks = Utils.divUp(width, mBlockWidth);
+
+                // constrain by block count and by block aspect ratio
+                final int minHeightInBlocks = Math.max(
+                        Utils.divUp(mBlockCountRange.getLower(), widthInBlocks),
+                        (int)Math.ceil(widthInBlocks /
+                                mBlockAspectRatioRange.getUpper().doubleValue()));
+                final int maxHeightInBlocks = Math.min(
+                        mBlockCountRange.getUpper() / widthInBlocks,
+                        (int)(widthInBlocks /
+                                mBlockAspectRatioRange.getLower().doubleValue()));
+                range = range.intersect(
+                        (minHeightInBlocks - 1) * mBlockHeight + mHeightAlignment,
+                        maxHeightInBlocks * mBlockHeight);
+
+                // constrain by smaller dimension limit
+                if (width > mSmallerDimensionUpperLimit) {
+                    range = range.intersect(1, mSmallerDimensionUpperLimit);
+                }
+
+                // constrain by aspect ratio
+                range = range.intersect(
+                        (int)Math.ceil(width /
+                                mAspectRatioRange.getUpper().doubleValue()),
+                        (int)(width / mAspectRatioRange.getLower().doubleValue()));
+                return range;
+            } catch (IllegalArgumentException e) {
+                // should not be here
+                Log.w(TAG, "could not get supported heights for " + width , e);
+                throw new IllegalArgumentException("unsupported width");
+            }
+        }
+
+        /**
+         * Returns the range of supported video frame rates for a video size.
+         * <p>
+         * This is not a performance indicator.  Rather, it expresses the limits specified in
+         * the coding standard, based on the complexities of encoding material of a given
+         * size for later playback at a certain frame rate, or the decoding of such material
+         * in non-realtime.
+
+         * @param width the width of the video
+         * @param height the height of the video
+         */
+        public Range<Double> getSupportedFrameRatesFor(int width, int height) {
+            Range<Integer> range = mHeightRange;
+            if (!supports(width, height, null)) {
+                throw new IllegalArgumentException("unsupported size");
+            }
+            final int blockCount =
+                Utils.divUp(width, mBlockWidth) * Utils.divUp(height, mBlockHeight);
+
+            return Range.create(
+                    Math.max(mBlocksPerSecondRange.getLower() / (double) blockCount,
+                            (double) mFrameRateRange.getLower()),
+                    Math.min(mBlocksPerSecondRange.getUpper() / (double) blockCount,
+                            (double) mFrameRateRange.getUpper()));
+        }
+
+        /**
+         * Returns whether a given video size ({@code width} and
+         * {@code height}) and {@code frameRate} combination is supported.
+         */
+        public boolean areSizeAndRateSupported(
+                int width, int height, double frameRate) {
+            return supports(width, height, frameRate);
+        }
+
+        /**
+         * Returns whether a given video size ({@code width} and
+         * {@code height}) is supported.
+         */
+        public boolean isSizeSupported(int width, int height) {
+            return supports(width, height, null);
+        }
+
+        private boolean supports(
+                Integer width, Integer height, Double rate) {
+            boolean ok = true;
+
+            if (ok && width != null) {
+                ok = mWidthRange.contains(width)
+                        && (width % mWidthAlignment == 0);
+            }
+            if (ok && height != null) {
+                ok = mHeightRange.contains(height)
+                        && (height % mHeightAlignment == 0);
+            }
+            if (ok && rate != null) {
+                ok = mFrameRateRange.contains(Utils.intRangeFor(rate));
+            }
+            if (ok && height != null && width != null) {
+                ok = Math.min(height, width) <= mSmallerDimensionUpperLimit;
+
+                final int widthInBlocks = Utils.divUp(width, mBlockWidth);
+                final int heightInBlocks = Utils.divUp(height, mBlockHeight);
+                final int blockCount = widthInBlocks * heightInBlocks;
+                ok = ok && mBlockCountRange.contains(blockCount)
+                        && mBlockAspectRatioRange.contains(
+                                new Rational(widthInBlocks, heightInBlocks))
+                        && mAspectRatioRange.contains(new Rational(width, height));
+                if (ok && rate != null) {
+                    double blocksPerSec = blockCount * rate;
+                    ok = mBlocksPerSecondRange.contains(
+                            Utils.longRangeFor(blocksPerSec));
+                }
+            }
+            return ok;
+        }
+
+        /**
+         * @hide
+         * @throws java.lang.ClassCastException */
+        public boolean supportsFormat(MediaFormat format) {
+            final Map<String, Object> map = format.getMap();
+            Integer width = (Integer)map.get(MediaFormat.KEY_WIDTH);
+            Integer height = (Integer)map.get(MediaFormat.KEY_HEIGHT);
+            Double rate = (Double)map.get(MediaFormat.KEY_FRAME_RATE);
+
+            // we ignore color-format for now as it is not reliably reported by codec
+
+            return supports(width, height, rate);
+        }
+
+        /* no public constructor */
+        private VideoCapabilities() { }
+
+        /** @hide */
+        public static VideoCapabilities create(
+                MediaFormat info, CodecCapabilities parent) {
+            VideoCapabilities caps = new VideoCapabilities();
+            caps.init(info, parent);
+            return caps;
+        }
+
+        /** @hide */
+        public void init(MediaFormat info, CodecCapabilities parent) {
+            mParent = parent;
+            initWithPlatformLimits();
+            applyLevelLimits();
+            parseFromInfo(info);
+            updateLimits();
+        }
+
+        /** @hide */
+        public Size getBlockSize() {
+            return new Size(mBlockWidth, mBlockHeight);
+        }
+
+        /** @hide */
+        public Range<Integer> getBlockCountRange() {
+            return mBlockCountRange;
+        }
+
+        /** @hide */
+        public Range<Long> getBlocksPerSecondRange() {
+            return mBlocksPerSecondRange;
+        }
+
+        /** @hide */
+        public Range<Rational> getAspectRatioRange(boolean blocks) {
+            return blocks ? mBlockAspectRatioRange : mAspectRatioRange;
+        }
+
+        private void initWithPlatformLimits() {
+            mBitrateRange = Range.create(0, Integer.MAX_VALUE);
+
+            mWidthRange  = SIZE_RANGE;
+            mHeightRange = SIZE_RANGE;
+            mFrameRateRange = FRAME_RATE_RANGE;
+
+            mHorizontalBlockRange = SIZE_RANGE;
+            mVerticalBlockRange   = SIZE_RANGE;
+
+            // full positive ranges are supported as these get calculated
+            mBlockCountRange      = POSITIVE_INTEGERS;
+            mBlocksPerSecondRange = POSITIVE_LONGS;
+
+            mBlockAspectRatioRange = POSITIVE_RATIONALS;
+            mAspectRatioRange      = POSITIVE_RATIONALS;
+
+            // YUV 4:2:0 requires 2:2 alignment
+            mWidthAlignment = 2;
+            mHeightAlignment = 2;
+            mBlockWidth = 2;
+            mBlockHeight = 2;
+            mSmallerDimensionUpperLimit = SIZE_RANGE.getUpper();
+        }
+
+        private void parseFromInfo(MediaFormat info) {
+            final Map<String, Object> map = info.getMap();
+            Size blockSize = new Size(mBlockWidth, mBlockHeight);
+            Size alignment = new Size(mWidthAlignment, mHeightAlignment);
+            Range<Integer> counts = null, widths = null, heights = null;
+            Range<Integer> frameRates = null;
+            Range<Long> blockRates = null;
+            Range<Rational> ratios = null, blockRatios = null;
+
+            blockSize = Utils.parseSize(map.get("block-size"), blockSize);
+            alignment = Utils.parseSize(map.get("alignment"), alignment);
+            counts = Utils.parseIntRange(map.get("block-count-range"), null);
+            blockRates =
+                Utils.parseLongRange(map.get("blocks-per-second-range"), null);
+            {
+                Object o = map.get("size-range");
+                Pair<Size, Size> sizeRange = Utils.parseSizeRange(o);
+                if (sizeRange != null) {
+                    try {
+                        widths = Range.create(
+                                sizeRange.first.getWidth(),
+                                sizeRange.second.getWidth());
+                        heights = Range.create(
+                                sizeRange.first.getHeight(),
+                                sizeRange.second.getHeight());
+                    } catch (IllegalArgumentException e) {
+                        Log.w(TAG, "could not parse size range '" + o + "'");
+                        widths = null;
+                        heights = null;
+                    }
+                }
+            }
+            // for now this just means using the smaller max size as 2nd
+            // upper limit.
+            // for now we are keeping the profile specific "width/height
+            // in macroblocks" limits.
+            if (Integer.valueOf(1).equals(map.get("feature-can-swap-width-height"))) {
+                if (widths != null) {
+                    mSmallerDimensionUpperLimit =
+                        Math.min(widths.getUpper(), heights.getUpper());
+                    widths = heights = widths.extend(heights);
+                } else {
+                    Log.w(TAG, "feature can-swap-width-height is best used with size-range");
+                    mSmallerDimensionUpperLimit =
+                        Math.min(mWidthRange.getUpper(), mHeightRange.getUpper());
+                    mWidthRange = mHeightRange = mWidthRange.extend(mHeightRange);
+                }
+            }
+
+            ratios = Utils.parseRationalRange(
+                    map.get("block-aspect-ratio-range"), null);
+            blockRatios = Utils.parseRationalRange(
+                    map.get("pixel-aspect-ratio-range"), null);
+            frameRates = Utils.parseIntRange(map.get("frame-rate-range"), null);
+            if (frameRates != null) {
+                try {
+                    frameRates = frameRates.intersect(FRAME_RATE_RANGE);
+                } catch (IllegalArgumentException e) {
+                    Log.w(TAG, "frame rate range (" + frameRates
+                            + ") is out of limits: " + FRAME_RATE_RANGE);
+                    frameRates = null;
+                }
+            }
+
+            checkPowerOfTwo(
+                    blockSize.getWidth(), "block-size width must be power of two");
+            checkPowerOfTwo(
+                    blockSize.getHeight(), "block-size height must be power of two");
+
+            checkPowerOfTwo(
+                    alignment.getWidth(), "alignment width must be power of two");
+            checkPowerOfTwo(
+                    alignment.getHeight(), "alignment height must be power of two");
+
+            // update block-size and alignment
+            applyMacroBlockLimits(
+                    Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE,
+                    Long.MAX_VALUE, blockSize.getWidth(), blockSize.getHeight(),
+                    alignment.getWidth(), alignment.getHeight());
+
+            if ((mParent.mError & ERROR_UNSUPPORTED) != 0) {
+                // codec supports profiles that we don't know.
+                // Use supplied values clipped to platform limits
+                if (widths != null) {
+                    mWidthRange = SIZE_RANGE.intersect(widths);
+                }
+                if (heights != null) {
+                    mHeightRange = SIZE_RANGE.intersect(heights);
+                }
+                if (counts != null) {
+                    mBlockCountRange = POSITIVE_INTEGERS.intersect(
+                            Utils.factorRange(counts, mBlockWidth * mBlockHeight
+                                    / blockSize.getWidth() / blockSize.getHeight()));
+                }
+                if (blockRates != null) {
+                    mBlocksPerSecondRange = POSITIVE_LONGS.intersect(
+                            Utils.factorRange(blockRates, mBlockWidth * mBlockHeight
+                                    / blockSize.getWidth() / blockSize.getHeight()));
+                }
+                if (blockRatios != null) {
+                    mBlockAspectRatioRange = POSITIVE_RATIONALS.intersect(
+                            Utils.scaleRange(blockRatios,
+                                    mBlockHeight / blockSize.getHeight(),
+                                    mBlockWidth / blockSize.getWidth()));
+                }
+                if (ratios != null) {
+                    mAspectRatioRange = POSITIVE_RATIONALS.intersect(ratios);
+                }
+                if (frameRates != null) {
+                    mFrameRateRange = FRAME_RATE_RANGE.intersect(frameRates);
+                }
+            } else {
+                // no unsupported profile/levels, so restrict values to known limits
+                if (widths != null) {
+                    mWidthRange = mWidthRange.intersect(widths);
+                }
+                if (heights != null) {
+                    mHeightRange = mHeightRange.intersect(heights);
+                }
+                if (counts != null) {
+                    mBlockCountRange = mBlockCountRange.intersect(
+                            Utils.factorRange(counts, mBlockWidth * mBlockHeight
+                                    / blockSize.getWidth() / blockSize.getHeight()));
+                }
+                if (blockRates != null) {
+                    mBlocksPerSecondRange = mBlocksPerSecondRange.intersect(
+                            Utils.factorRange(blockRates, mBlockWidth * mBlockHeight
+                                    / blockSize.getWidth() / blockSize.getHeight()));
+                }
+                if (blockRatios != null) {
+                    mBlockAspectRatioRange = mBlockAspectRatioRange.intersect(
+                            Utils.scaleRange(blockRatios,
+                                    mBlockHeight / blockSize.getHeight(),
+                                    mBlockWidth / blockSize.getWidth()));
+                }
+                if (ratios != null) {
+                    mAspectRatioRange = mAspectRatioRange.intersect(ratios);
+                }
+                if (frameRates != null) {
+                    mFrameRateRange = mFrameRateRange.intersect(frameRates);
+                }
+            }
+            updateLimits();
+        }
+
+        private void applyBlockLimits(
+                int blockWidth, int blockHeight,
+                Range<Integer> counts, Range<Long> rates, Range<Rational> ratios) {
+            checkPowerOfTwo(blockWidth, "blockWidth must be a power of two");
+            checkPowerOfTwo(blockHeight, "blockHeight must be a power of two");
+
+            final int newBlockWidth = Math.max(blockWidth, mBlockWidth);
+            final int newBlockHeight = Math.max(blockHeight, mBlockHeight);
+
+            // factor will always be a power-of-2
+            int factor =
+                newBlockWidth * newBlockHeight / mBlockWidth / mBlockHeight;
+            if (factor != 1) {
+                mBlockCountRange = Utils.factorRange(mBlockCountRange, factor);
+                mBlocksPerSecondRange = Utils.factorRange(
+                        mBlocksPerSecondRange, factor);
+                mBlockAspectRatioRange = Utils.scaleRange(
+                        mBlockAspectRatioRange,
+                        newBlockHeight / mBlockHeight,
+                        newBlockWidth / mBlockWidth);
+                mHorizontalBlockRange = Utils.factorRange(
+                        mHorizontalBlockRange, newBlockWidth / mBlockWidth);
+                mVerticalBlockRange = Utils.factorRange(
+                        mVerticalBlockRange, newBlockHeight / mBlockHeight);
+            }
+            factor = newBlockWidth * newBlockHeight / blockWidth / blockHeight;
+            if (factor != 1) {
+                counts = Utils.factorRange(counts, factor);
+                rates = Utils.factorRange(rates, factor);
+                ratios = Utils.scaleRange(
+                        ratios, newBlockHeight / blockHeight,
+                        newBlockWidth / blockWidth);
+            }
+            mBlockCountRange = mBlockCountRange.intersect(counts);
+            mBlocksPerSecondRange = mBlocksPerSecondRange.intersect(rates);
+            mBlockAspectRatioRange = mBlockAspectRatioRange.intersect(ratios);
+            mBlockWidth = newBlockWidth;
+            mBlockHeight = newBlockHeight;
+        }
+
+        private void applyAlignment(int widthAlignment, int heightAlignment) {
+            checkPowerOfTwo(widthAlignment, "widthAlignment must be a power of two");
+            checkPowerOfTwo(heightAlignment, "heightAlignment must be a power of two");
+
+            if (widthAlignment > mBlockWidth || heightAlignment > mBlockHeight) {
+                // maintain assumption that 0 < alignment <= block-size
+                applyBlockLimits(
+                        Math.max(widthAlignment, mBlockWidth),
+                        Math.max(heightAlignment, mBlockHeight),
+                        POSITIVE_INTEGERS, POSITIVE_LONGS, POSITIVE_RATIONALS);
+            }
+
+            mWidthAlignment = Math.max(widthAlignment, mWidthAlignment);
+            mHeightAlignment = Math.max(heightAlignment, mHeightAlignment);
+
+            mWidthRange = Utils.alignRange(mWidthRange, mWidthAlignment);
+            mHeightRange = Utils.alignRange(mHeightRange, mHeightAlignment);
+        }
+
+        private void updateLimits() {
+            // pixels -> blocks <- counts
+            mHorizontalBlockRange = mHorizontalBlockRange.intersect(
+                    Utils.factorRange(mWidthRange, mBlockWidth));
+            mHorizontalBlockRange = mHorizontalBlockRange.intersect(
+                    Range.create(
+                            mBlockCountRange.getLower() / mVerticalBlockRange.getUpper(),
+                            mBlockCountRange.getUpper() / mVerticalBlockRange.getLower()));
+            mVerticalBlockRange = mVerticalBlockRange.intersect(
+                    Utils.factorRange(mHeightRange, mBlockHeight));
+            mVerticalBlockRange = mVerticalBlockRange.intersect(
+                    Range.create(
+                            mBlockCountRange.getLower() / mHorizontalBlockRange.getUpper(),
+                            mBlockCountRange.getUpper() / mHorizontalBlockRange.getLower()));
+            mBlockCountRange = mBlockCountRange.intersect(
+                    Range.create(
+                            mHorizontalBlockRange.getLower()
+                                    * mVerticalBlockRange.getLower(),
+                            mHorizontalBlockRange.getUpper()
+                                    * mVerticalBlockRange.getUpper()));
+            mBlockAspectRatioRange = mBlockAspectRatioRange.intersect(
+                    new Rational(
+                            mHorizontalBlockRange.getLower(), mVerticalBlockRange.getUpper()),
+                    new Rational(
+                            mHorizontalBlockRange.getUpper(), mVerticalBlockRange.getLower()));
+
+            // blocks -> pixels
+            mWidthRange = mWidthRange.intersect(
+                    (mHorizontalBlockRange.getLower() - 1) * mBlockWidth + mWidthAlignment,
+                    mHorizontalBlockRange.getUpper() * mBlockWidth);
+            mHeightRange = mHeightRange.intersect(
+                    (mVerticalBlockRange.getLower() - 1) * mBlockHeight + mHeightAlignment,
+                    mVerticalBlockRange.getUpper() * mBlockHeight);
+            mAspectRatioRange = mAspectRatioRange.intersect(
+                    new Rational(mWidthRange.getLower(), mHeightRange.getUpper()),
+                    new Rational(mWidthRange.getUpper(), mHeightRange.getLower()));
+
+            mSmallerDimensionUpperLimit = Math.min(
+                    mSmallerDimensionUpperLimit,
+                    Math.min(mWidthRange.getUpper(), mHeightRange.getUpper()));
+
+            // blocks -> rate
+            mBlocksPerSecondRange = mBlocksPerSecondRange.intersect(
+                    mBlockCountRange.getLower() * (long)mFrameRateRange.getLower(),
+                    mBlockCountRange.getUpper() * (long)mFrameRateRange.getUpper());
+            mFrameRateRange = mFrameRateRange.intersect(
+                    (int)(mBlocksPerSecondRange.getLower()
+                            / mBlockCountRange.getUpper()),
+                    (int)(mBlocksPerSecondRange.getUpper()
+                            / (double)mBlockCountRange.getLower()));
+        }
+
+        private void applyMacroBlockLimits(
+                int maxHorizontalBlocks, int maxVerticalBlocks,
+                int maxBlocks, long maxBlocksPerSecond,
+                int blockWidth, int blockHeight,
+                int widthAlignment, int heightAlignment) {
+            applyAlignment(widthAlignment, heightAlignment);
+            applyBlockLimits(
+                    blockWidth, blockHeight, Range.create(1, maxBlocks),
+                    Range.create(1L, maxBlocksPerSecond),
+                    Range.create(
+                            new Rational(1, maxVerticalBlocks),
+                            new Rational(maxHorizontalBlocks, 1)));
+            mHorizontalBlockRange =
+                    mHorizontalBlockRange.intersect(
+                            1, maxHorizontalBlocks / (mBlockWidth / blockWidth));
+            mVerticalBlockRange =
+                    mVerticalBlockRange.intersect(
+                            1, maxVerticalBlocks / (mBlockHeight / blockHeight));
+        }
+
+        private void applyLevelLimits() {
+            int maxBlocksPerSecond = 0;
+            int maxBlocks = 0;
+            int maxBps = 0;
+            int maxDPBBlocks = 0;
+
+            int errors = ERROR_NONE_SUPPORTED;
+            CodecProfileLevel[] profileLevels = mParent.profileLevels;
+            String mime = mParent.getMimeType();
+
+            if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_AVC)) {
+                maxBlocks = 99;
+                maxBlocksPerSecond = 1485;
+                maxBps = 64000;
+                maxDPBBlocks = 396;
+                for (CodecProfileLevel profileLevel: profileLevels) {
+                    int MBPS = 0, FS = 0, BR = 0, DPB = 0;
+                    boolean supported = true;
+                    switch (profileLevel.level) {
+                        case CodecProfileLevel.AVCLevel1:
+                            MBPS =    1485; FS =    99; BR =     64; DPB =    396; break;
+                        case CodecProfileLevel.AVCLevel1b:
+                            MBPS =    1485; FS =    99; BR =    128; DPB =    396; break;
+                        case CodecProfileLevel.AVCLevel11:
+                            MBPS =    3000; FS =   396; BR =    192; DPB =    900; break;
+                        case CodecProfileLevel.AVCLevel12:
+                            MBPS =    6000; FS =   396; BR =    384; DPB =   2376; break;
+                        case CodecProfileLevel.AVCLevel13:
+                            MBPS =   11880; FS =   396; BR =    768; DPB =   2376; break;
+                        case CodecProfileLevel.AVCLevel2:
+                            MBPS =   11880; FS =   396; BR =   2000; DPB =   2376; break;
+                        case CodecProfileLevel.AVCLevel21:
+                            MBPS =   19800; FS =   792; BR =   4000; DPB =   4752; break;
+                        case CodecProfileLevel.AVCLevel22:
+                            MBPS =   20250; FS =  1620; BR =   4000; DPB =   8100; break;
+                        case CodecProfileLevel.AVCLevel3:
+                            MBPS =   40500; FS =  1620; BR =  10000; DPB =   8100; break;
+                        case CodecProfileLevel.AVCLevel31:
+                            MBPS =  108000; FS =  3600; BR =  14000; DPB =  18000; break;
+                        case CodecProfileLevel.AVCLevel32:
+                            MBPS =  216000; FS =  5120; BR =  20000; DPB =  20480; break;
+                        case CodecProfileLevel.AVCLevel4:
+                            MBPS =  245760; FS =  8192; BR =  20000; DPB =  32768; break;
+                        case CodecProfileLevel.AVCLevel41:
+                            MBPS =  245760; FS =  8192; BR =  50000; DPB =  32768; break;
+                        case CodecProfileLevel.AVCLevel42:
+                            MBPS =  522240; FS =  8704; BR =  50000; DPB =  34816; break;
+                        case CodecProfileLevel.AVCLevel5:
+                            MBPS =  589824; FS = 22080; BR = 135000; DPB = 110400; break;
+                        case CodecProfileLevel.AVCLevel51:
+                            MBPS =  983040; FS = 36864; BR = 240000; DPB = 184320; break;
+                        case CodecProfileLevel.AVCLevel52:
+                            MBPS = 2073600; FS = 36864; BR = 240000; DPB = 184320; break;
+                        default:
+                            Log.w(TAG, "Unrecognized level "
+                                    + profileLevel.level + " for " + mime);
+                            errors |= ERROR_UNRECOGNIZED;
+                    }
+                    switch (profileLevel.profile) {
+                        case CodecProfileLevel.AVCProfileHigh:
+                            BR *= 1250; break;
+                        case CodecProfileLevel.AVCProfileHigh10:
+                            BR *= 3000; break;
+                        case CodecProfileLevel.AVCProfileExtended:
+                        case CodecProfileLevel.AVCProfileHigh422:
+                        case CodecProfileLevel.AVCProfileHigh444:
+                            Log.w(TAG, "Unsupported profile "
+                                    + profileLevel.profile + " for " + mime);
+                            errors |= ERROR_UNSUPPORTED;
+                            supported = false;
+                            // fall through - treat as base profile
+                        case CodecProfileLevel.AVCProfileBaseline:
+                        case CodecProfileLevel.AVCProfileMain:
+                            BR *= 1000; break;
+                        default:
+                            Log.w(TAG, "Unrecognized profile "
+                                    + profileLevel.profile + " for " + mime);
+                            errors |= ERROR_UNRECOGNIZED;
+                            BR *= 1000;
+                    }
+                    if (supported) {
+                        errors &= ~ERROR_NONE_SUPPORTED;
+                    }
+                    maxBlocksPerSecond = Math.max(MBPS, maxBlocksPerSecond);
+                    maxBlocks = Math.max(FS, maxBlocks);
+                    maxBps = Math.max(BR, maxBps);
+                    maxDPBBlocks = Math.max(maxDPBBlocks, DPB);
+                }
+
+                int maxLengthInBlocks = (int)(Math.sqrt(maxBlocks * 8));
+                applyMacroBlockLimits(
+                        maxLengthInBlocks, maxLengthInBlocks,
+                        maxBlocks, maxBlocksPerSecond,
+                        16 /* blockWidth */, 16 /* blockHeight */,
+                        1 /* widthAlignment */, 1 /* heightAlignment */);
+            } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_MPEG4)) {
+                int maxWidth = 11, maxHeight = 9, maxRate = 15;
+                maxBlocks = 99;
+                maxBlocksPerSecond = 1485;
+                maxBps = 64000;
+                for (CodecProfileLevel profileLevel: profileLevels) {
+                    int MBPS = 0, FS = 0, BR = 0, FR = 0, W = 0, H = 0;
+                    boolean supported = true;
+                    switch (profileLevel.profile) {
+                        case CodecProfileLevel.MPEG4ProfileSimple:
+                            switch (profileLevel.level) {
+                                case CodecProfileLevel.MPEG4Level0:
+                                    FR = 15; W = 11; H =  9; MBPS =  1485; FS =  99; BR =  64; break;
+                                case CodecProfileLevel.MPEG4Level1:
+                                    FR = 30; W = 11; H =  9; MBPS =  1485; FS =  99; BR =  64; break;
+                                case CodecProfileLevel.MPEG4Level0b:
+                                    FR = 30; W = 11; H =  9; MBPS =  1485; FS =  99; BR = 128; break;
+                                case CodecProfileLevel.MPEG4Level2:
+                                    FR = 30; W = 22; H = 18; MBPS =  5940; FS = 396; BR = 128; break;
+                                case CodecProfileLevel.MPEG4Level3:
+                                    FR = 30; W = 22; H = 18; MBPS = 11880; FS = 396; BR = 384; break;
+                                case CodecProfileLevel.MPEG4Level4:
+                                case CodecProfileLevel.MPEG4Level4a:
+                                case CodecProfileLevel.MPEG4Level5:
+                                    // While MPEG4 SP does not have level 4 or 5, some vendors
+                                    // report it. Use the same limits as level 3, but mark as
+                                    // unsupported.
+                                    FR = 30; W = 22; H = 18; MBPS = 11880; FS = 396; BR = 384;
+                                    supported = false;
+                                    break;
+                                default:
+                                    Log.w(TAG, "Unrecognized profile/level "
+                                            + profileLevel.profile + "/"
+                                            + profileLevel.level + " for " + mime);
+                                    errors |= ERROR_UNRECOGNIZED;
+                            }
+                            break;
+                        case CodecProfileLevel.MPEG4ProfileAdvancedSimple:
+                            switch (profileLevel.level) {
+                                case CodecProfileLevel.MPEG4Level0:
+                                case CodecProfileLevel.MPEG4Level1:
+                                    FR = 30; W = 11; H =  9; MBPS =  2970; FS =   99; BR =  128; break;
+                                case CodecProfileLevel.MPEG4Level2:
+                                    FR = 30; W = 22; H = 18; MBPS =  5940; FS =  396; BR =  384; break;
+                                case CodecProfileLevel.MPEG4Level3:
+                                    FR = 30; W = 22; H = 18; MBPS = 11880; FS =  396; BR =  768; break;
+                                // case CodecProfileLevel.MPEG4Level3b:
+                                // TODO: MPEG4 level 3b is not defined in OMX
+                                //  MBPS = 11880; FS =  396; BR = 1500; break;
+                                case CodecProfileLevel.MPEG4Level4:
+                                case CodecProfileLevel.MPEG4Level4a:
+                                    // TODO: MPEG4 level 4a is not defined in spec
+                                    FR = 30; W = 44; H = 36; MBPS = 23760; FS =  792; BR = 3000; break;
+                                case CodecProfileLevel.MPEG4Level5:
+                                    FR = 30; W = 45; H = 36; MBPS = 48600; FS = 1620; BR = 8000; break;
+                                default:
+                                    Log.w(TAG, "Unrecognized profile/level "
+                                            + profileLevel.profile + "/"
+                                            + profileLevel.level + " for " + mime);
+                                    errors |= ERROR_UNRECOGNIZED;
+                            }
+                            break;
+                        case CodecProfileLevel.MPEG4ProfileMain:             // 2-4
+                        case CodecProfileLevel.MPEG4ProfileNbit:             // 2
+                        case CodecProfileLevel.MPEG4ProfileAdvancedRealTime: // 1-4
+                        case CodecProfileLevel.MPEG4ProfileCoreScalable:     // 1-3
+                        case CodecProfileLevel.MPEG4ProfileAdvancedCoding:   // 1-4
+                        case CodecProfileLevel.MPEG4ProfileCore:             // 1-2
+                        case CodecProfileLevel.MPEG4ProfileAdvancedCore:     // 1-4
+                        case CodecProfileLevel.MPEG4ProfileSimpleScalable:   // 0-2
+                        case CodecProfileLevel.MPEG4ProfileAdvancedScalable: // 1-3
+                        case CodecProfileLevel.MPEG4ProfileHybrid:           // 1-2
+                        case CodecProfileLevel.MPEG4ProfileBasicAnimated:    // 1-2
+                        case CodecProfileLevel.MPEG4ProfileScalableTexture:  // 1
+                        case CodecProfileLevel.MPEG4ProfileSimpleFace:       // 1-2
+                        case CodecProfileLevel.MPEG4ProfileSimpleFBA:        // 1-2
+                            Log.i(TAG, "Unsupported profile "
+                                    + profileLevel.profile + " for " + mime);
+                            errors |= ERROR_UNSUPPORTED;
+                            supported = false;
+                            break;
+                        default:
+                            Log.w(TAG, "Unrecognized profile "
+                                    + profileLevel.profile + " for " + mime);
+                            errors |= ERROR_UNRECOGNIZED;
+                    }
+                    if (supported) {
+                        errors &= ~ERROR_NONE_SUPPORTED;
+                    }
+                    maxBlocksPerSecond = Math.max(MBPS, maxBlocksPerSecond);
+                    maxBlocks = Math.max(FS, maxBlocks);
+                    maxBps = Math.max(BR * 1000, maxBps);
+                    maxWidth = Math.max(W, maxWidth);
+                    maxHeight = Math.max(H, maxHeight);
+                    maxRate = Math.max(FR, maxRate);
+                }
+                applyMacroBlockLimits(maxWidth, maxHeight,
+                        maxBlocks, maxBlocksPerSecond,
+                        16 /* blockWidth */, 16 /* blockHeight */,
+                        1 /* widthAlignment */, 1 /* heightAlignment */);
+                mFrameRateRange = mFrameRateRange.intersect(12, maxRate);
+            } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_H263)) {
+                int maxWidth = 11, maxHeight = 9, maxRate = 15;
+                maxBlocks = 99;
+                maxBlocksPerSecond = 1485;
+                maxBps = 64000;
+                for (CodecProfileLevel profileLevel: profileLevels) {
+                    int MBPS = 0, BR = 0, FR = 0, W = 0, H = 0;
+                    switch (profileLevel.level) {
+                        case CodecProfileLevel.H263Level10:
+                            FR = 15; W = 11; H =  9; BR =   1; MBPS =  W * H * FR; break;
+                        case CodecProfileLevel.H263Level20:
+                            // only supports CIF, 0..QCIF
+                            FR = 30; W = 22; H = 18; BR =   2; MBPS =  W * H * FR; break;
+                        case CodecProfileLevel.H263Level30:
+                            // only supports CIF, 0..QCIF
+                            FR = 30; W = 22; H = 18; BR =   6; MBPS =  W * H * FR; break;
+                        case CodecProfileLevel.H263Level40:
+                            // only supports CIF, 0..QCIF
+                            FR = 30; W = 22; H = 18; BR =  32; MBPS =  W * H * FR; break;
+                        case CodecProfileLevel.H263Level45:
+                            // only implies level 10 support
+                            FR = 30; W = 11; H =  9; BR =   2; MBPS =  W * H * FR; break;
+                        case CodecProfileLevel.H263Level50:
+                            // only supports 50fps for H > 15
+                            FR = 60; W = 22; H = 18; BR =  64; MBPS =  W * H * 50; break;
+                        case CodecProfileLevel.H263Level60:
+                            // only supports 50fps for H > 15
+                            FR = 60; W = 45; H = 18; BR = 128; MBPS =  W * H * 50; break;
+                        case CodecProfileLevel.H263Level70:
+                            // only supports 50fps for H > 30
+                            FR = 60; W = 45; H = 36; BR = 256; MBPS =  W * H * 50; break;
+                        default:
+                            Log.w(TAG, "Unrecognized profile/level " + profileLevel.profile
+                                    + "/" + profileLevel.level + " for " + mime);
+                            errors |= ERROR_UNRECOGNIZED;
+                    }
+                    switch (profileLevel.profile) {
+                        case CodecProfileLevel.H263ProfileBackwardCompatible:
+                        case CodecProfileLevel.H263ProfileBaseline:
+                        case CodecProfileLevel.H263ProfileH320Coding:
+                        case CodecProfileLevel.H263ProfileHighCompression:
+                        case CodecProfileLevel.H263ProfileHighLatency:
+                        case CodecProfileLevel.H263ProfileInterlace:
+                        case CodecProfileLevel.H263ProfileInternet:
+                        case CodecProfileLevel.H263ProfileISWV2:
+                        case CodecProfileLevel.H263ProfileISWV3:
+                            break;
+                        default:
+                            Log.w(TAG, "Unrecognized profile "
+                                    + profileLevel.profile + " for " + mime);
+                            errors |= ERROR_UNRECOGNIZED;
+                    }
+                    errors &= ~ERROR_NONE_SUPPORTED;
+                    maxBlocksPerSecond = Math.max(MBPS, maxBlocksPerSecond);
+                    maxBlocks = Math.max(W * H, maxBlocks);
+                    maxBps = Math.max(BR * 64000, maxBps);
+                    maxWidth = Math.max(W, maxWidth);
+                    maxHeight = Math.max(H, maxHeight);
+                    maxRate = Math.max(FR, maxRate);
+                }
+                applyMacroBlockLimits(maxWidth, maxHeight,
+                        maxBlocks, maxBlocksPerSecond,
+                        16 /* blockWidth */, 16 /* blockHeight */,
+                        1 /* widthAlignment */, 1 /* heightAlignment */);
+                mFrameRateRange = Range.create(1, maxRate);
+            } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_VP8) ||
+                    mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_VP9)) {
+                maxBlocks = maxBlocksPerSecond = Integer.MAX_VALUE;
+
+                // TODO: set to 100Mbps for now, need a number for VPX
+                maxBps = 100000000;
+
+                // profile levels are not indicative for VPx, but verify
+                // them nonetheless
+                for (CodecProfileLevel profileLevel: profileLevels) {
+                    switch (profileLevel.level) {
+                        case CodecProfileLevel.VP8Level_Version0:
+                        case CodecProfileLevel.VP8Level_Version1:
+                        case CodecProfileLevel.VP8Level_Version2:
+                        case CodecProfileLevel.VP8Level_Version3:
+                            break;
+                        default:
+                            Log.w(TAG, "Unrecognized level "
+                                    + profileLevel.level + " for " + mime);
+                            errors |= ERROR_UNRECOGNIZED;
+                    }
+                    switch (profileLevel.profile) {
+                        case CodecProfileLevel.VP8ProfileMain:
+                            break;
+                        default:
+                            Log.w(TAG, "Unrecognized profile "
+                                    + profileLevel.profile + " for " + mime);
+                            errors |= ERROR_UNRECOGNIZED;
+                    }
+                    errors &= ~ERROR_NONE_SUPPORTED;
+                }
+
+                final int blockSize =
+                    mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_VP8) ? 16 : 8;
+                applyMacroBlockLimits(Short.MAX_VALUE, Short.MAX_VALUE,
+                        maxBlocks, maxBlocksPerSecond, blockSize, blockSize,
+                        1 /* widthAlignment */, 1 /* heightAlignment */);
+            } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_HEVC)) {
+                maxBlocks = 36864;
+                maxBlocksPerSecond = maxBlocks * 15;
+                maxBps = 128000;
+                for (CodecProfileLevel profileLevel: profileLevels) {
+                    double FR = 0;
+                    int FS = 0;
+                    int BR = 0;
+                    switch (profileLevel.level) {
+                        case CodecProfileLevel.HEVCMainTierLevel1:
+                        case CodecProfileLevel.HEVCHighTierLevel1:
+                            FR =    15; FS =    36864; BR =    128; break;
+                        case CodecProfileLevel.HEVCMainTierLevel2:
+                        case CodecProfileLevel.HEVCHighTierLevel2:
+                            FR =    30; FS =   122880; BR =   1500; break;
+                        case CodecProfileLevel.HEVCMainTierLevel21:
+                        case CodecProfileLevel.HEVCHighTierLevel21:
+                            FR =    30; FS =   245760; BR =   3000; break;
+                        case CodecProfileLevel.HEVCMainTierLevel3:
+                        case CodecProfileLevel.HEVCHighTierLevel3:
+                            FR =    30; FS =   552960; BR =   6000; break;
+                        case CodecProfileLevel.HEVCMainTierLevel31:
+                        case CodecProfileLevel.HEVCHighTierLevel31:
+                            FR = 33.75; FS =   983040; BR =  10000; break;
+                        case CodecProfileLevel.HEVCMainTierLevel4:
+                            FR =    30; FS =  2228224; BR =  12000; break;
+                        case CodecProfileLevel.HEVCHighTierLevel4:
+                            FR =    30; FS =  2228224; BR =  30000; break;
+                        case CodecProfileLevel.HEVCMainTierLevel41:
+                            FR =    60; FS =  2228224; BR =  20000; break;
+                        case CodecProfileLevel.HEVCHighTierLevel41:
+                            FR =    60; FS =  2228224; BR =  50000; break;
+                        case CodecProfileLevel.HEVCMainTierLevel5:
+                            FR =    30; FS =  8912896; BR =  25000; break;
+                        case CodecProfileLevel.HEVCHighTierLevel5:
+                            FR =    30; FS =  8912896; BR = 100000; break;
+                        case CodecProfileLevel.HEVCMainTierLevel51:
+                            FR =    60; FS =  8912896; BR =  40000; break;
+                        case CodecProfileLevel.HEVCHighTierLevel51:
+                            FR =    60; FS =  8912896; BR = 160000; break;
+                        case CodecProfileLevel.HEVCMainTierLevel52:
+                            FR =   120; FS =  8912896; BR =  60000; break;
+                        case CodecProfileLevel.HEVCHighTierLevel52:
+                            FR =   120; FS =  8912896; BR = 240000; break;
+                        case CodecProfileLevel.HEVCMainTierLevel6:
+                            FR =    30; FS = 35651584; BR =  60000; break;
+                        case CodecProfileLevel.HEVCHighTierLevel6:
+                            FR =    30; FS = 35651584; BR = 240000; break;
+                        case CodecProfileLevel.HEVCMainTierLevel61:
+                            FR =    60; FS = 35651584; BR = 120000; break;
+                        case CodecProfileLevel.HEVCHighTierLevel61:
+                            FR =    60; FS = 35651584; BR = 480000; break;
+                        case CodecProfileLevel.HEVCMainTierLevel62:
+                            FR =   120; FS = 35651584; BR = 240000; break;
+                        case CodecProfileLevel.HEVCHighTierLevel62:
+                            FR =   120; FS = 35651584; BR = 800000; break;
+                        default:
+                            Log.w(TAG, "Unrecognized level "
+                                    + profileLevel.level + " for " + mime);
+                            errors |= ERROR_UNRECOGNIZED;
+                    }
+                    switch (profileLevel.profile) {
+                        case CodecProfileLevel.HEVCProfileMain:
+                        case CodecProfileLevel.HEVCProfileMain10:
+                            break;
+                        default:
+                            Log.w(TAG, "Unrecognized profile "
+                                    + profileLevel.profile + " for " + mime);
+                            errors |= ERROR_UNRECOGNIZED;
+                    }
+
+                    /* DPB logic:
+                    if      (width * height <= FS / 4)    DPB = 16;
+                    else if (width * height <= FS / 2)    DPB = 12;
+                    else if (width * height <= FS * 0.75) DPB = 8;
+                    else                                  DPB = 6;
+                    */
+
+                    errors &= ~ERROR_NONE_SUPPORTED;
+                    maxBlocksPerSecond = Math.max((int)(FR * FS), maxBlocksPerSecond);
+                    maxBlocks = Math.max(FS, maxBlocks);
+                    maxBps = Math.max(BR * 1000, maxBps);
+                }
+
+                int maxLengthInBlocks = (int)(Math.sqrt(maxBlocks * 8));
+                // CTBs are at least 8x8
+                maxBlocks = Utils.divUp(maxBlocks, 8 * 8);
+                maxBlocksPerSecond = Utils.divUp(maxBlocksPerSecond, 8 * 8);
+                maxLengthInBlocks = Utils.divUp(maxLengthInBlocks, 8);
+
+                applyMacroBlockLimits(
+                        maxLengthInBlocks, maxLengthInBlocks,
+                        maxBlocks, maxBlocksPerSecond,
+                        8 /* blockWidth */, 8 /* blockHeight */,
+                        1 /* widthAlignment */, 1 /* heightAlignment */);
+            } else {
+                Log.w(TAG, "Unsupported mime " + mime);
+                // using minimal bitrate here.  should be overriden by
+                // info from media_codecs.xml
+                maxBps = 64000;
+                errors |= ERROR_UNSUPPORTED;
+            }
+            mBitrateRange = Range.create(1, maxBps);
+            mParent.mError |= errors;
+        }
+    }
+
+    /**
+     * A class that supports querying the encoding capabilities of a codec.
+     */
+    public static final class EncoderCapabilities {
+        /**
+         * Returns the supported range of quality values.
+         *
+         * @hide
+         */
+        public Range<Integer> getQualityRange() {
+            return mQualityRange;
+        }
+
+        /**
+         * Returns the supported range of encoder complexity values.
+         * <p>
+         * Some codecs may support multiple complexity levels, where higher
+         * complexity values use more encoder tools (e.g. perform more
+         * intensive calculations) to improve the quality or the compression
+         * ratio.  Use a lower value to save power and/or time.
+         */
+        public Range<Integer> getComplexityRange() {
+            return mComplexityRange;
+        }
+
+        /** Constant quality mode */
+        public static final int BITRATE_MODE_CQ = 0;
+        /** Variable bitrate mode */
+        public static final int BITRATE_MODE_VBR = 1;
+        /** Constant bitrate mode */
+        public static final int BITRATE_MODE_CBR = 2;
+
+        private static final Feature[] bitrates = new Feature[] {
+            new Feature("VBR", BITRATE_MODE_VBR, true),
+            new Feature("CBR", BITRATE_MODE_CBR, false),
+            new Feature("CQ",  BITRATE_MODE_CQ,  false)
+        };
+
+        private static int parseBitrateMode(String mode) {
+            for (Feature feat: bitrates) {
+                if (feat.mName.equalsIgnoreCase(mode)) {
+                    return feat.mValue;
+                }
+            }
+            return 0;
+        }
+
+        /**
+         * Query whether a bitrate mode is supported.
+         */
+        public boolean isBitrateModeSupported(int mode) {
+            for (Feature feat: bitrates) {
+                if (mode == feat.mValue) {
+                    return (mBitControl & (1 << mode)) != 0;
+                }
+            }
+            return false;
+        }
+
+        private Range<Integer> mQualityRange;
+        private Range<Integer> mComplexityRange;
+        private CodecCapabilities mParent;
+
+        /* no public constructor */
+        private EncoderCapabilities() { }
+
+        /** @hide */
+        public static EncoderCapabilities create(
+                MediaFormat info, CodecCapabilities parent) {
+            EncoderCapabilities caps = new EncoderCapabilities();
+            caps.init(info, parent);
+            return caps;
+        }
+
+        /** @hide */
+        public void init(MediaFormat info, CodecCapabilities parent) {
+            // no support for complexity or quality yet
+            mParent = parent;
+            mComplexityRange = Range.create(0, 0);
+            mQualityRange = Range.create(0, 0);
+            mBitControl = (1 << BITRATE_MODE_VBR);
+
+            applyLevelLimits();
+            parseFromInfo(info);
+        }
+
+        private void applyLevelLimits() {
+            String mime = mParent.getMimeType();
+            if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_FLAC)) {
+                mComplexityRange = Range.create(0, 8);
+                mBitControl = (1 << BITRATE_MODE_CQ);
+            } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AMR_NB)
+                    || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AMR_WB)
+                    || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_G711_ALAW)
+                    || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_G711_MLAW)
+                    || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_MSGSM)) {
+                mBitControl = (1 << BITRATE_MODE_CBR);
+            }
+        }
+
+        private int mBitControl;
+        private Integer mDefaultComplexity;
+        private Integer mDefaultQuality;
+        private String mQualityScale;
+
+        private void parseFromInfo(MediaFormat info) {
+            Map<String, Object> map = info.getMap();
+
+            if (info.containsKey("complexity-range")) {
+                mComplexityRange = Utils
+                        .parseIntRange(info.getString("complexity-range"), mComplexityRange);
+                // TODO should we limit this to level limits?
+            }
+            if (info.containsKey("quality-range")) {
+                mQualityRange = Utils
+                        .parseIntRange(info.getString("quality-range"), mQualityRange);
+            }
+            if (info.containsKey("feature-bitrate-control")) {
+                for (String mode: info.getString("feature-bitrate-control").split(",")) {
+                    mBitControl |= parseBitrateMode(mode);
+                }
+            }
+
+            try {
+                mDefaultComplexity = Integer.parseInt((String)map.get("complexity-default"));
+            } catch (NumberFormatException e) { }
+
+            try {
+                mDefaultQuality = Integer.parseInt((String)map.get("quality-default"));
+            } catch (NumberFormatException e) { }
+
+            mQualityScale = (String)map.get("quality-scale");
+        }
+
+        private boolean supports(
+                Integer complexity, Integer quality, Integer profile) {
+            boolean ok = true;
+            if (ok && complexity != null) {
+                ok = mComplexityRange.contains(complexity);
+            }
+            if (ok && quality != null) {
+                ok = mQualityRange.contains(quality);
+            }
+            if (ok && profile != null) {
+                for (CodecProfileLevel pl: mParent.profileLevels) {
+                    if (pl.profile == profile) {
+                        profile = null;
+                        break;
+                    }
+                }
+                ok = profile == null;
+            }
+            return ok;
+        }
+
+        /** @hide */
+        public void setDefaultFormat(MediaFormat format) {
+            // don't list trivial quality/complexity as default for now
+            if (!mQualityRange.getUpper().equals(mQualityRange.getLower())
+                    && mDefaultQuality != null) {
+                format.setInteger(MediaFormat.KEY_QUALITY, mDefaultQuality);
+            }
+            if (!mComplexityRange.getUpper().equals(mComplexityRange.getLower())
+                    && mDefaultComplexity != null) {
+                format.setInteger(MediaFormat.KEY_COMPLEXITY, mDefaultComplexity);
+            }
+            // bitrates are listed in order of preference
+            for (Feature feat: bitrates) {
+                if ((mBitControl & (1 << feat.mValue)) != 0) {
+                    format.setInteger(MediaFormat.KEY_BITRATE_MODE, feat.mValue);
+                    break;
+                }
+            }
+        }
+
+        /** @hide */
+        public boolean supportsFormat(MediaFormat format) {
+            final Map<String, Object> map = format.getMap();
+            final String mime = mParent.getMimeType();
+
+            Integer mode = (Integer)map.get(MediaFormat.KEY_BITRATE_MODE);
+            if (mode != null && !isBitrateModeSupported(mode)) {
+                return false;
+            }
+
+            Integer complexity = (Integer)map.get(MediaFormat.KEY_COMPLEXITY);
+            if (MediaFormat.MIMETYPE_AUDIO_FLAC.equalsIgnoreCase(mime)) {
+                Integer flacComplexity =
+                    (Integer)map.get(MediaFormat.KEY_FLAC_COMPRESSION_LEVEL);
+                if (complexity == null) {
+                    complexity = flacComplexity;
+                } else if (flacComplexity != null && complexity != flacComplexity) {
+                    throw new IllegalArgumentException(
+                            "conflicting values for complexity and " +
+                            "flac-compression-level");
+                }
+            }
+
+            // other audio parameters
+            Integer profile = (Integer)map.get(MediaFormat.KEY_PROFILE);
+            if (MediaFormat.MIMETYPE_AUDIO_AAC.equalsIgnoreCase(mime)) {
+                Integer aacProfile = (Integer)map.get(MediaFormat.KEY_AAC_PROFILE);
+                if (profile == null) {
+                    profile = aacProfile;
+                } else if (aacProfile != null && aacProfile != profile) {
+                    throw new IllegalArgumentException(
+                            "conflicting values for profile and aac-profile");
+                }
+            }
+
+            Integer quality = (Integer)map.get(MediaFormat.KEY_QUALITY);
+
+            return supports(complexity, quality, profile);
         }
     };
 
diff --git a/media/java/android/media/MediaCodecList.java b/media/java/android/media/MediaCodecList.java
index d74f22d..5084c5c 100644
--- a/media/java/android/media/MediaCodecList.java
+++ b/media/java/android/media/MediaCodecList.java
@@ -36,6 +36,8 @@
     /**
      * Count the number of available (regular) codecs.
      *
+     * @deprecated Use {@link #getCodecInfos} instead.
+     *
      * @see #REGULAR_CODECS
      */
     public static final int getCodecCount() {
@@ -49,6 +51,8 @@
      * Return the {@link MediaCodecInfo} object for the codec at
      * the given {@code index} in the regular list.
      *
+     * @deprecated Use {@link #getCodecInfos} instead.
+     *
      * @see #REGULAR_CODECS
      */
     public static final MediaCodecInfo getCodecInfoAt(int index) {
@@ -116,13 +120,22 @@
 
     /**
      * Use in {@link #MediaCodecList} to enumerate only codecs that are suitable
-     * for normal playback and recording.
+     * for regular (buffer-to-buffer) decoding or encoding.
+     *
+     * <em>NOTE:</em> These are the codecs that are returned prior to API 21,
+     * using the now deprecated static methods.
      */
     public static final int REGULAR_CODECS = 0;
 
     /**
      * Use in {@link #MediaCodecList} to enumerate all codecs, even ones that are
-     * not suitable for normal playback or recording.
+     * not suitable for regular (buffer-to-buffer) decoding or encoding.  These
+     * include codecs, for example, that only work with special input or output
+     * surfaces, such as secure-only or tunneled-only codecs.
+     *
+     * @see MediaCodecInfo.CodecCapabilities#isFormatSupported
+     * @see MediaCodecInfo.CodecCapabilities#FEATURE_SecurePlayback
+     * @see MediaCodecInfo.CodecCapabilities#FEATURE_TunneledPlayback
      */
     public static final int ALL_CODECS = 1;
 
diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java
index fd79495..2036533 100644
--- a/media/java/android/media/MediaFormat.java
+++ b/media/java/android/media/MediaFormat.java
@@ -120,19 +120,6 @@
     private Map<String, Object> mMap;
 
     /**
-     * A key prefix used together with a {@link MediaCodecInfo.CodecCapabilities}
-     * feature name describing a required or optional feature for a codec capabilities
-     * query.
-     * The associated value is an integer, where non-0 value means the feature is
-     * requested to be present, while 0 value means the feature is requested to be not
-     * present.
-     * @see MediaCodecList#findDecoderForFormat
-     * @see MediaCodecList#findEncoderForFormat
-     * @see MediaCodecInfo.CodecCapabilities#isFormatSupported
-     */
-    public static final String KEY_FEATURE_ = "feature-";
-
-    /**
      * A key describing the mime type of the MediaFormat.
      * The associated value is a string.
      */
@@ -422,6 +409,8 @@
      * codec specific, but lower values generally result in more efficient
      * (smaller-sized) encoding.
      *
+     * @hide
+     *
      * @see MediaCodecInfo.CodecCapabilities.EncoderCapabilities#getQualityRange
      */
     public static final String KEY_QUALITY = "quality";
@@ -510,6 +499,21 @@
     }
 
     /**
+     * A key prefix used together with a {@link MediaCodecInfo.CodecCapabilities}
+     * feature name describing a required or optional feature for a codec capabilities
+     * query.
+     * The associated value is an integer, where non-0 value means the feature is
+     * requested to be present, while 0 value means the feature is requested to be not
+     * present.
+     * @see MediaCodecList#findDecoderForFormat
+     * @see MediaCodecList#findEncoderForFormat
+     * @see MediaCodecInfo.CodecCapabilities#isFormatSupported
+     *
+     * @hide
+     */
+    public static final String KEY_FEATURE_ = "feature-";
+
+    /**
      * Returns the value of an integer key.
      */
     public final int getInteger(String name) {
@@ -559,6 +563,23 @@
     }
 
     /**
+     * Returns whether a feature is to be enabled ({@code true}) or disabled
+     * ({@code false}).
+     *
+     * @param feature the name of a {@link MediaCodecInfo.CodecCapabilities} feature.
+     *
+     * @throws IllegalArgumentException if the feature was neither set to be enabled
+     *        nor to be disabled.
+     */
+    public boolean getFeatureEnabled(String feature) {
+        Integer enabled = (Integer)mMap.get(KEY_FEATURE_ + feature);
+        if (enabled == null) {
+            throw new IllegalArgumentException("feature is not specified");
+        }
+        return enabled != 0;
+    }
+
+    /**
      * Sets the value of an integer key.
      */
     public final void setInteger(String name, int value) {
@@ -594,6 +615,23 @@
     }
 
     /**
+     * Sets whether a feature is to be enabled ({@code true}) or disabled
+     * ({@code false}).
+     *
+     * If {@code enabled} is {@code true}, the feature is requested to be present.
+     * Otherwise, the feature is requested to be not present.
+     *
+     * @param feature the name of a {@link MediaCodecInfo.CodecCapabilities} feature.
+     *
+     * @see MediaCodecList#findDecoderForFormat
+     * @see MediaCodecList#findEncoderForFormat
+     * @see MediaCodecInfo.CodecCapabilities#isFormatSupported
+     */
+    public void setFeatureEnabled(String feature, boolean enabled) {
+        setInteger(KEY_FEATURE_ + feature, enabled ? 1 : 0);
+    }
+
+    /**
      * Creates a minimal audio format.
      * @param mime The mime type of the content.
      * @param sampleRate The sampling rate of the content.
diff --git a/packages/SystemUI/res/layout/zen_mode_panel.xml b/packages/SystemUI/res/layout/zen_mode_panel.xml
index 3949d7d..14bf10e 100644
--- a/packages/SystemUI/res/layout/zen_mode_panel.xml
+++ b/packages/SystemUI/res/layout/zen_mode_panel.xml
@@ -88,14 +88,4 @@
         android:orientation="vertical"
         android:paddingBottom="@dimen/qs_panel_padding" />
 
-    <TextView
-        android:id="@+id/zen_alarm_warning"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_marginBottom="@dimen/qs_panel_padding"
-        android:paddingLeft="@dimen/qs_panel_padding"
-        android:paddingRight="@dimen/qs_panel_padding"
-        android:paddingTop="4dp"
-        android:paddingBottom="4dp"
-        android:textAppearance="@style/TextAppearance.QS.Subhead" />
 </com.android.systemui.volume.ZenModePanel>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 726a54f..0e18979 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -200,5 +200,8 @@
 
     <!-- Tiles with feature timeouts: number of days to show after feature is used. -->
     <integer name="days_to_show_timeout_tiles">30</integer>
+
+    <!-- Number of times to show the strong alarm warning text in the volume dialog -->
+    <integer name="zen_mode_alarm_warning_threshold">5</integer>
 </resources>
 
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 189c052..b000a48 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -697,7 +697,7 @@
     <string name="description_direction_left">"Slide left for <xliff:g id="target_description" example="Unlock">%s</xliff:g>.</string>
 
     <!-- Zen mode: No interruptions title, with a warning about alarms. [CHAR LIMIT=60] -->
-    <string name="zen_no_interruptions_with_warning">No interruptions, including alarms</string>
+    <string name="zen_no_interruptions_with_warning">No interruptions. Not even alarms.</string>
 
     <!-- Zen mode: No interruptions. [CHAR LIMIT=40] -->
     <string name="zen_no_interruptions">No interruptions</string>
diff --git a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
index c99e1fd..ac7fc25 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
@@ -31,10 +31,9 @@
 import android.provider.Settings.Global;
 import android.service.notification.Condition;
 import android.service.notification.ZenModeConfig;
-import android.text.format.DateFormat;
-import android.text.format.Time;
 import android.util.AttributeSet;
 import android.util.Log;
+import android.util.MathUtils;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.animation.AnimationUtils;
@@ -49,10 +48,7 @@
 import com.android.systemui.R;
 import com.android.systemui.statusbar.policy.ZenModeController;
 
-import java.text.SimpleDateFormat;
 import java.util.Arrays;
-import java.util.Date;
-import java.util.Locale;
 import java.util.Objects;
 
 public class ZenModePanel extends LinearLayout {
@@ -79,10 +75,10 @@
     private final Context mContext;
     private final LayoutInflater mInflater;
     private final H mHandler = new H();
-    private final Favorites mFavorites;
+    private final Prefs mPrefs;
     private final Interpolator mFastOutSlowInInterpolator;
-    private final int mHardWarningColor;
-    private final int mSoftWarningColor;
+    private final int mSubheadWarningColor;
+    private final int mSubheadColor;
 
     private String mTag = TAG + "/" + Integer.toHexString(System.identityHashCode(this));
 
@@ -92,7 +88,6 @@
     private TextView mZenSubheadExpanded;
     private View mMoreSettings;
     private LinearLayout mZenConditions;
-    private TextView mAlarmWarning;
 
     private Callback mCallback;
     private ZenModeController mController;
@@ -103,21 +98,21 @@
     private boolean mExpanded;
     private boolean mHidden = false;
     private int mSessionZen;
+    private int mAttachedZen;
     private Condition mSessionExitCondition;
-    private long mNextAlarm;
     private Condition[] mConditions;
     private Condition mTimeCondition;
 
     public ZenModePanel(Context context, AttributeSet attrs) {
         super(context, attrs);
         mContext = context;
-        mFavorites = new Favorites();
+        mPrefs = new Prefs();
         mInflater = LayoutInflater.from(mContext.getApplicationContext());
         mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(mContext,
                 android.R.interpolator.fast_out_slow_in);
         final Resources res = mContext.getResources();
-        mHardWarningColor = res.getColor(R.color.system_warning_color);
-        mSoftWarningColor = res.getColor(R.color.qs_subhead);
+        mSubheadWarningColor = res.getColor(R.color.system_warning_color);
+        mSubheadColor = res.getColor(R.color.qs_subhead);
         if (DEBUG) Log.d(mTag, "new ZenModePanel");
     }
 
@@ -155,17 +150,16 @@
         });
 
         mZenConditions = (LinearLayout) findViewById(R.id.zen_conditions);
-        mAlarmWarning = (TextView) findViewById(R.id.zen_alarm_warning);
     }
 
     @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
         if (DEBUG) Log.d(mTag, "onAttachedToWindow");
-        mSessionZen = getSelectedZen(-1);
+        mAttachedZen = getSelectedZen(-1);
+        mSessionZen = mAttachedZen;
         mSessionExitCondition = copy(mExitCondition);
         refreshExitConditionText();
-        refreshNextAlarm();
         updateWidgets();
     }
 
@@ -173,6 +167,8 @@
     protected void onDetachedFromWindow() {
         super.onDetachedFromWindow();
         if (DEBUG) Log.d(mTag, "onDetachedFromWindow");
+        checkForAttachedZenChange();
+        mAttachedZen = -1;
         mSessionZen = -1;
         mSessionExitCondition = null;
         setExpanded(false);
@@ -184,6 +180,17 @@
         updateWidgets();
     }
 
+    private void checkForAttachedZenChange() {
+        final int selectedZen = getSelectedZen(-1);
+        if (DEBUG) Log.d(mTag, "selectedZen=" + selectedZen);
+        if (selectedZen != mAttachedZen) {
+            if (DEBUG) Log.d(mTag, "attachedZen: " + mAttachedZen + " -> " + selectedZen);
+            if (selectedZen == Global.ZEN_MODE_NO_INTERRUPTIONS) {
+                mPrefs.trackNoneSelected();
+            }
+        }
+    }
+
     private void setExpanded(boolean expanded) {
         if (expanded == mExpanded) return;
         mExpanded = expanded;
@@ -234,10 +241,6 @@
         updateWidgets();
     }
 
-    private Uri getExitConditionId() {
-        return getConditionId(mExitCondition);
-    }
-
     private static Uri getConditionId(Condition condition) {
         return condition != null ? condition.id : null;
     }
@@ -301,9 +304,7 @@
         final boolean zenOff = zen == Global.ZEN_MODE_OFF;
         final boolean zenImportant = zen == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
         final boolean zenNone = zen == Global.ZEN_MODE_NO_INTERRUPTIONS;
-        final boolean hasNextAlarm = mNextAlarm != 0;
         final boolean expanded = !mHidden && mExpanded;
-        final boolean showAlarmWarning = zenNone && expanded && hasNextAlarm;
 
         mZenButtons.setVisibility(mHidden ? GONE : VISIBLE);
         mZenSubhead.setVisibility(!mHidden && !zenOff ? VISIBLE : GONE);
@@ -311,26 +312,6 @@
         mZenSubheadCollapsed.setVisibility(!expanded ? VISIBLE : GONE);
         mMoreSettings.setVisibility(zenImportant && expanded ? VISIBLE : GONE);
         mZenConditions.setVisibility(!zenOff && expanded ? VISIBLE : GONE);
-        mAlarmWarning.setVisibility(zenNone && expanded && hasNextAlarm ? VISIBLE : GONE);
-        if (showAlarmWarning) {
-            final long exitTime = ZenModeConfig.tryParseCountdownConditionId(getExitConditionId());
-            final long now = System.currentTimeMillis();
-            final boolean alarmToday = time(mNextAlarm).yearDay == time(now).yearDay;
-            final String skeleton = (alarmToday ? "" : "E")
-                    + (DateFormat.is24HourFormat(mContext) ? "Hm" : "hma");
-            final String pattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), skeleton);
-            final String alarm = new SimpleDateFormat(pattern).format(new Date(mNextAlarm));
-            final boolean isWarning = exitTime > 0 && mNextAlarm > now && mNextAlarm < exitTime;
-            if (isWarning) {
-                mAlarmWarning.setText(mContext.getString(R.string.zen_alarm_warning, alarm));
-                mAlarmWarning.setTextColor(mHardWarningColor);
-            } else {
-                mAlarmWarning.setText(mContext.getString(alarmToday
-                        ? R.string.zen_alarm_information_time
-                        : R.string.zen_alarm_information_day_time, alarm));
-                mAlarmWarning.setTextColor(mSoftWarningColor);
-            }
-        }
 
         if (zenNone) {
             mZenSubheadExpanded.setText(R.string.zen_no_interruptions_with_warning);
@@ -339,12 +320,8 @@
             mZenSubheadExpanded.setText(R.string.zen_important_interruptions);
             mZenSubheadCollapsed.setText(mExitConditionText);
         }
-    }
-
-    private static Time time(long millis) {
-        final Time t = new Time();
-        t.set(millis);
-        return t;
+        mZenSubheadExpanded.setTextColor(zenNone && mPrefs.isNoneDangerous()
+                ? mSubheadWarningColor : mSubheadColor);
     }
 
     private Condition parseExistingTimeCondition(Condition condition) {
@@ -373,15 +350,6 @@
                 Condition.FLAG_RELEVANT_NOW);
     }
 
-    private void refreshNextAlarm() {
-        mNextAlarm = mController.getNextAlarm();
-    }
-
-    private void handleNextAlarmChanged() {
-        refreshNextAlarm();
-        updateWidgets();
-    }
-
     private void handleUpdateConditions(Condition[] conditions) {
         mConditions = conditions;
         handleUpdateConditions();
@@ -429,7 +397,7 @@
             }
         }
         if (DEBUG) Log.d(mTag, "Selecting a default");
-        final int favoriteIndex = mFavorites.getMinuteIndex();
+        final int favoriteIndex = mPrefs.getMinuteIndex();
         if (favoriteIndex == -1) {
             getConditionTagAt(FOREVER_CONDITION_INDEX).rb.setChecked(true);
         } else {
@@ -579,9 +547,9 @@
         }
         setExitCondition(condition);
         if (condition == null) {
-            mFavorites.setMinuteIndex(-1);
+            mPrefs.setMinuteIndex(-1);
         } else if (ZenModeConfig.isValidCountdownConditionId(condition.id) && mBucketIndex != -1) {
-            mFavorites.setMinuteIndex(mBucketIndex);
+            mPrefs.setMinuteIndex(mBucketIndex);
         }
         mSessionExitCondition = copy(condition);
     }
@@ -618,18 +586,12 @@
         public void onExitConditionChanged(Condition exitCondition) {
             mHandler.obtainMessage(H.EXIT_CONDITION_CHANGED, exitCondition).sendToTarget();
         }
-
-        @Override
-        public void onNextAlarmChanged() {
-            mHandler.sendEmptyMessage(H.NEXT_ALARM_CHANGED);
-        }
     };
 
     private final class H extends Handler {
         private static final int UPDATE_CONDITIONS = 1;
         private static final int EXIT_CONDITION_CHANGED = 2;
         private static final int UPDATE_ZEN = 3;
-        private static final int NEXT_ALARM_CHANGED = 4;
 
         private H() {
             super(Looper.getMainLooper());
@@ -643,8 +605,6 @@
                 handleExitConditionChanged((Condition) msg.obj);
             } else if (msg.what == UPDATE_ZEN) {
                 handleUpdateZen(msg.arg1);
-            } else if (msg.what == NEXT_ALARM_CHANGED) {
-                handleNextAlarmChanged();
             }
         }
     }
@@ -661,14 +621,32 @@
         Condition condition;
     }
 
-    private final class Favorites implements OnSharedPreferenceChangeListener {
+    private final class Prefs implements OnSharedPreferenceChangeListener {
         private static final String KEY_MINUTE_INDEX = "minuteIndex";
+        private static final String KEY_NONE_SELECTED = "noneSelected";
+
+        private final int mNoneDangerousThreshold;
 
         private int mMinuteIndex;
+        private int mNoneSelected;
 
-        private Favorites() {
+        private Prefs() {
+            mNoneDangerousThreshold = mContext.getResources()
+                    .getInteger(R.integer.zen_mode_alarm_warning_threshold);
             prefs().registerOnSharedPreferenceChangeListener(this);
             updateMinuteIndex();
+            updateNoneSelected();
+        }
+
+        public boolean isNoneDangerous() {
+            return mNoneSelected < mNoneDangerousThreshold;
+        }
+
+        public void trackNoneSelected() {
+            mNoneSelected = clampNoneSelected(mNoneSelected + 1);
+            if (DEBUG) Log.d(mTag, "Setting none selected: " + mNoneSelected + " threshold="
+                    + mNoneDangerousThreshold);
+            prefs().edit().putInt(KEY_NONE_SELECTED, mNoneSelected).apply();
         }
 
         public int getMinuteIndex() {
@@ -676,9 +654,9 @@
         }
 
         public void setMinuteIndex(int minuteIndex) {
-            minuteIndex = clamp(minuteIndex);
+            minuteIndex = clampIndex(minuteIndex);
             if (minuteIndex == mMinuteIndex) return;
-            mMinuteIndex = clamp(minuteIndex);
+            mMinuteIndex = clampIndex(minuteIndex);
             if (DEBUG) Log.d(mTag, "Setting favorite minute index: " + mMinuteIndex);
             prefs().edit().putInt(KEY_MINUTE_INDEX, mMinuteIndex).apply();
         }
@@ -686,6 +664,7 @@
         @Override
         public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
             updateMinuteIndex();
+            updateNoneSelected();
         }
 
         private SharedPreferences prefs() {
@@ -693,12 +672,21 @@
         }
 
         private void updateMinuteIndex() {
-            mMinuteIndex = clamp(prefs().getInt(KEY_MINUTE_INDEX, DEFAULT_BUCKET_INDEX));
+            mMinuteIndex = clampIndex(prefs().getInt(KEY_MINUTE_INDEX, DEFAULT_BUCKET_INDEX));
             if (DEBUG) Log.d(mTag, "Favorite minute index: " + mMinuteIndex);
         }
 
-        private int clamp(int index) {
-            return Math.max(-1, Math.min(MINUTE_BUCKETS.length - 1, index));
+        private int clampIndex(int index) {
+            return MathUtils.constrain(index, -1, MINUTE_BUCKETS.length - 1);
+        }
+
+        private void updateNoneSelected() {
+            mNoneSelected = clampNoneSelected(prefs().getInt(KEY_NONE_SELECTED, 0));
+            if (DEBUG) Log.d(mTag, "None selected: " + mNoneSelected);
+        }
+
+        private int clampNoneSelected(int noneSelected) {
+            return MathUtils.constrain(noneSelected, 0, Integer.MAX_VALUE);
         }
     }