Merge "Add checkServerTrusted with OCSP and TlsData parameters to RootTrustManager" into main
diff --git a/core/java/android/os/OWNERS b/core/java/android/os/OWNERS
index f3bb514..727dcba 100644
--- a/core/java/android/os/OWNERS
+++ b/core/java/android/os/OWNERS
@@ -4,6 +4,8 @@
 
 # PowerManager
 per-file IPowerManager.aidl = file:/services/core/java/com/android/server/power/OWNERS
+per-file IScreenTimeoutPolicyListener.aidl = file:/services/core/java/com/android/server/power/OWNERS
+per-file IWakeLockCallback.aidl = file:/services/core/java/com/android/server/power/OWNERS
 per-file PowerManager.java = file:/services/core/java/com/android/server/power/OWNERS
 per-file PowerManagerInternal.java = file:/services/core/java/com/android/server/power/OWNERS
 
diff --git a/core/java/android/security/net/config/OWNERS b/core/java/android/security/net/config/OWNERS
index 85ce3c6..e945ff9 100644
--- a/core/java/android/security/net/config/OWNERS
+++ b/core/java/android/security/net/config/OWNERS
@@ -1,5 +1,6 @@
-# Bug component: 36824
-set noparent
+# Bug component: 1479456
 
-cbrubaker@google.com
+bessiej@google.com
 brambonne@google.com
+sandrom@google.com
+tweek@google.com
diff --git a/core/java/com/android/internal/util/LATENCY_TRACKER_OWNERS b/core/java/com/android/internal/util/LATENCY_TRACKER_OWNERS
index 7755000..3ed902f 100644
--- a/core/java/com/android/internal/util/LATENCY_TRACKER_OWNERS
+++ b/core/java/com/android/internal/util/LATENCY_TRACKER_OWNERS
@@ -1,3 +1,5 @@
 # TODO(b/274465475): Migrate LatencyTracker testing to its own module
 marcinoc@google.com
 ilkos@google.com
+jjaggi@google.com
+nicomazz@google.com
diff --git a/core/tests/coretests/src/android/net/http/OWNERS b/core/tests/coretests/src/android/net/http/OWNERS
new file mode 100644
index 0000000..c93a419
--- /dev/null
+++ b/core/tests/coretests/src/android/net/http/OWNERS
@@ -0,0 +1 @@
+include /core/java/android/net/http/OWNERS
diff --git a/media/java/android/media/FadeManagerConfiguration.java b/media/java/android/media/FadeManagerConfiguration.java
index 6d84e70..b91a5b5 100644
--- a/media/java/android/media/FadeManagerConfiguration.java
+++ b/media/java/android/media/FadeManagerConfiguration.java
@@ -673,6 +673,7 @@
         return config != null ? config.getDuration() : DURATION_NOT_SET;
     }
 
+    @Nullable
     private VolumeShaper.Configuration getVolumeShaperConfigFromWrapper(
             FadeVolumeShaperConfigsWrapper wrapper, boolean isFadeIn) {
         // if no volume shaper config is available, return null
diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java
index 69b26fd..782db35 100644
--- a/media/java/android/media/MediaCodecInfo.java
+++ b/media/java/android/media/MediaCodecInfo.java
@@ -244,7 +244,12 @@
      * {@link MediaCodecInfo#getCapabilitiesForType getCapabilitiesForType()}, passing a MIME type.
      */
     public static final class CodecCapabilities {
-        private static final String TAG = "CodecCapabilities";
+        public CodecCapabilities() {
+        }
+
+        // CLASSIFICATION
+        private String mMime;
+        private int mMaxSupportedInstances;
 
         // LEGACY FIELDS
 
@@ -622,6 +627,12 @@
          */
         public int[] colorFormats; // NOTE this array is modifiable by user
 
+        // FEATURES
+
+        private int mFlagsSupported;
+        private int mFlagsRequired;
+        private int mFlagsVerified;
+
         /**
          * <b>video decoder only</b>: codec supports seamless resolution changes.
          */
@@ -811,656 +822,6 @@
         @FlaggedApi(FLAG_NULL_OUTPUT_SURFACE)
         public static final String FEATURE_DetachedSurface = "detached-surface";
 
-        /** package private */ interface CodecCapsIntf {
-            public CodecCapsIntf dup();
-
-            public boolean isFeatureSupported(String name);
-
-            public boolean isFeatureRequired(String name);
-
-            public boolean isFormatSupported(MediaFormat format);
-
-            public MediaFormat getDefaultFormat();
-
-            public String getMimeType();
-
-            public int getMaxSupportedInstances();
-
-            public AudioCapabilities getAudioCapabilities();
-
-            public VideoCapabilities getVideoCapabilities();
-
-            public EncoderCapabilities getEncoderCapabilities();
-
-            public boolean isRegular();
-
-            public CodecProfileLevel[] getProfileLevels();
-
-            public int[] getColorFormats();
-        }
-
-        /* package private */ static final class CodecCapsLegacyImpl implements CodecCapsIntf {
-            // errors while reading profile levels - accessed from sister capabilities
-            int mError;
-
-            private CodecProfileLevel[] mProfileLevels;
-            private int[] mColorFormats;
-
-            // CLASSIFICATION
-            private String mMime;
-            private int mMaxSupportedInstances;
-
-            // FEATURES
-            private int mFlagsSupported;
-            private int mFlagsRequired;
-            private int mFlagsVerified;
-
-            // NEW-STYLE CAPABILITIES
-            private AudioCapabilities mAudioCaps;
-            private VideoCapabilities mVideoCaps;
-            private EncoderCapabilities mEncoderCaps;
-            private MediaFormat mDefaultFormat;
-
-            private MediaFormat mCapabilitiesInfo;
-
-            public CodecProfileLevel[] getProfileLevels() {
-                return mProfileLevels;
-            }
-
-            public int[] getColorFormats() {
-                return mColorFormats;
-            }
-
-            public CodecCapsLegacyImpl() {}
-
-            public CodecCapsLegacyImpl dup() {
-                CodecCapsLegacyImpl caps = new CodecCapsLegacyImpl();
-
-                caps.mProfileLevels = Arrays.copyOf(mProfileLevels, mProfileLevels.length);
-                caps.mColorFormats = Arrays.copyOf(mColorFormats, mColorFormats.length);
-
-                caps.mMime = mMime;
-                caps.mMaxSupportedInstances = mMaxSupportedInstances;
-                caps.mFlagsRequired = mFlagsRequired;
-                caps.mFlagsSupported = mFlagsSupported;
-                caps.mFlagsVerified = mFlagsVerified;
-                caps.mAudioCaps = mAudioCaps;
-                caps.mVideoCaps = mVideoCaps;
-                caps.mEncoderCaps = mEncoderCaps;
-                caps.mDefaultFormat = mDefaultFormat;
-                caps.mCapabilitiesInfo = mCapabilitiesInfo;
-
-                return caps;
-            }
-
-            public final boolean isFeatureSupported(String name) {
-                return checkFeature(name, mFlagsSupported);
-            }
-
-            public final boolean isFeatureRequired(String name) {
-                return checkFeature(name, mFlagsRequired);
-            }
-
-            // Flags are used for feature list creation so separate this into a private
-            // static class to delay reading the flags only when constructing the list.
-            private static class FeatureList {
-                private static Feature[] getDecoderFeatures() {
-                    ArrayList<Feature> features = new ArrayList();
-                    features.add(new Feature(FEATURE_AdaptivePlayback, (1 << 0), true));
-                    features.add(new Feature(FEATURE_SecurePlayback,   (1 << 1), false));
-                    features.add(new Feature(FEATURE_TunneledPlayback, (1 << 2), false));
-                    features.add(new Feature(FEATURE_PartialFrame,     (1 << 3), false));
-                    features.add(new Feature(FEATURE_FrameParsing,     (1 << 4), false));
-                    features.add(new Feature(FEATURE_MultipleFrames,   (1 << 5), false));
-                    features.add(new Feature(FEATURE_DynamicTimestamp, (1 << 6), false));
-                    features.add(new Feature(FEATURE_LowLatency,       (1 << 7), true));
-                    if (GetFlag(() -> android.media.codec.Flags.dynamicColorAspects())) {
-                        features.add(new Feature(FEATURE_DynamicColorAspects, (1 << 8), true));
-                    }
-                    if (GetFlag(() -> android.media.codec.Flags.nullOutputSurface())) {
-                        features.add(new Feature(FEATURE_DetachedSurface,     (1 << 9), true));
-                    }
-
-                    // feature to exclude codec from REGULAR codec list
-                    features.add(new Feature(FEATURE_SpecialCodec,     (1 << 30), false, true));
-
-                    return features.toArray(new Feature[0]);
-                };
-
-                private static Feature[] decoderFeatures = getDecoderFeatures();
-
-                private static Feature[] getEncoderFeatures() {
-                    ArrayList<Feature> features = new ArrayList();
-
-                    features.add(new Feature(FEATURE_IntraRefresh, (1 << 0), false));
-                    features.add(new Feature(FEATURE_MultipleFrames, (1 << 1), false));
-                    features.add(new Feature(FEATURE_DynamicTimestamp, (1 << 2), false));
-                    features.add(new Feature(FEATURE_QpBounds, (1 << 3), false));
-                    features.add(new Feature(FEATURE_EncodingStatistics, (1 << 4), false));
-                    features.add(new Feature(FEATURE_HdrEditing, (1 << 5), false));
-                    if (GetFlag(() -> android.media.codec.Flags.hlgEditing())) {
-                        features.add(new Feature(FEATURE_HlgEditing, (1 << 6), true));
-                    }
-                    if (GetFlag(() -> android.media.codec.Flags.regionOfInterest())) {
-                        features.add(new Feature(FEATURE_Roi, (1 << 7), true));
-                    }
-
-                    // feature to exclude codec from REGULAR codec list
-                    features.add(new Feature(FEATURE_SpecialCodec,     (1 << 30), false, true));
-
-                    return features.toArray(new Feature[0]);
-                };
-
-                private static Feature[] encoderFeatures = getEncoderFeatures();
-
-                public static Feature[] getFeatures(boolean isEncoder) {
-                    if (isEncoder) {
-                        return encoderFeatures;
-                    } else {
-                        return decoderFeatures;
-                    }
-                }
-            }
-
-            /** @hide */
-            public String[] validFeatures() {
-                Feature[] features = getValidFeatures();
-                String[] res = new String[features.length];
-                for (int i = 0; i < res.length; i++) {
-                    if (!features[i].mInternal) {
-                        res[i] = features[i].mName;
-                    }
-                }
-                return res;
-            }
-
-            private Feature[] getValidFeatures() {
-                return FeatureList.getFeatures(isEncoder());
-            }
-
-            private boolean checkFeature(String name, int flags) {
-                for (Feature feat: getValidFeatures()) {
-                    if (feat.mName.equals(name)) {
-                        return (flags & feat.mValue) != 0;
-                    }
-                }
-                return false;
-            }
-
-            public boolean isRegular() {
-                // regular codecs only require default features
-                for (Feature feat: getValidFeatures()) {
-                    if (!feat.mDefault && isFeatureRequired(feat.mName)) {
-                        return false;
-                    }
-                }
-                return true;
-            }
-
-            public final boolean isFormatSupported(MediaFormat format) {
-                final Map<String, Object> map = format.getMap();
-                final String mime = (String) map.get(MediaFormat.KEY_MIME);
-
-                // mime must match if present
-                if (mime != null && !mMime.equalsIgnoreCase(mime)) {
-                    return false;
-                }
-
-                // check feature support
-                for (Feature feat: getValidFeatures()) {
-                    if (feat.mInternal) {
-                        continue;
-                    }
-
-                    Integer yesNo = (Integer) map.get(MediaFormat.KEY_FEATURE_ + feat.mName);
-                    if (yesNo == null) {
-                        continue;
-                    }
-                    if ((yesNo == 1 && !isFeatureSupported(feat.mName))
-                            || (yesNo == 0 && isFeatureRequired(feat.mName))) {
-                        return false;
-                    }
-                }
-
-                Integer profile = (Integer) map.get(MediaFormat.KEY_PROFILE);
-                Integer level = (Integer) map.get(MediaFormat.KEY_LEVEL);
-
-                if (profile != null) {
-                    if (!supportsProfileLevel(profile, level)) {
-                        return false;
-                    }
-
-                    // If we recognize this profile, check that this format is supported by the
-                    // highest level supported by the codec for that profile. (Ignore specified
-                    // level beyond the above profile/level check as level is only used as a
-                    // guidance. E.g. AVC Level 1 CIF format is supported if codec supports
-                    // level 1.1 even though max size for Level 1 is QCIF. However, MPEG2 Simple
-                    // Profile 1080p format is not supported even if codec supports Main Profile
-                    // Level High, as Simple Profile does not support 1080p.
-                    CodecCapsLegacyImpl levelCaps = null;
-                    int maxLevel = 0;
-                    for (CodecProfileLevel pl : mProfileLevels) {
-                        if (pl.profile == profile && pl.level > maxLevel) {
-                            // H.263 levels are not completely ordered:
-                            // Level45 support only implies Level10 support
-                            if (!mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_H263)
-                                    || pl.level != CodecProfileLevel.H263Level45
-                                    || maxLevel == CodecProfileLevel.H263Level10) {
-                                maxLevel = pl.level;
-                            }
-                        }
-                    }
-                    levelCaps = createFromProfileLevel(mMime, profile, maxLevel);
-                    // We must remove the profile from this format otherwise
-                    // levelCaps.isFormatSupported will get into this same condition and loop
-                    // forever. Furthermore, since levelCaps does not contain features and bitrate
-                    // specific keys, keep only keys relevant for a level check.
-                    Map<String, Object> levelCriticalFormatMap = new HashMap<>(map);
-                    final Set<String> criticalKeys = isVideo()
-                        ? VideoCapabilities.VideoCapsLegacyImpl.VIDEO_LEVEL_CRITICAL_FORMAT_KEYS
-                        : isAudio()
-                        ? AudioCapabilities.AudioCapsLegacyImpl.AUDIO_LEVEL_CRITICAL_FORMAT_KEYS
-                        : null;
-
-                    // critical keys will always contain KEY_MIME, but should also contain others
-                    // to be meaningful
-                    if (criticalKeys != null && criticalKeys.size() > 1 && levelCaps != null) {
-                        levelCriticalFormatMap.keySet().retainAll(criticalKeys);
-
-                        MediaFormat levelCriticalFormat = new MediaFormat(levelCriticalFormatMap);
-                        if (!levelCaps.isFormatSupported(levelCriticalFormat)) {
-                            return false;
-                        }
-                    }
-                }
-                if (mAudioCaps != null && !mAudioCaps.supportsFormat(format)) {
-                    return false;
-                }
-                if (mVideoCaps != null && !mVideoCaps.supportsFormat(format)) {
-                    return false;
-                }
-                if (mEncoderCaps != null && !mEncoderCaps.supportsFormat(format)) {
-                    return false;
-                }
-                return true;
-            }
-
-            private static boolean supportsBitrate(
-                    Range<Integer> bitrateRange, MediaFormat format) {
-                Map<String, Object> map = format.getMap();
-
-                // consider max bitrate over average bitrate for support
-                Integer maxBitrate = (Integer)map.get(MediaFormat.KEY_MAX_BIT_RATE);
-                Integer bitrate = (Integer)map.get(MediaFormat.KEY_BIT_RATE);
-                if (bitrate == null) {
-                    bitrate = maxBitrate;
-                } else if (maxBitrate != null) {
-                    bitrate = Math.max(bitrate, maxBitrate);
-                }
-
-                if (bitrate != null && bitrate > 0) {
-                    return bitrateRange.contains(bitrate);
-                }
-
-                return true;
-            }
-
-            private boolean supportsProfileLevel(int profile, Integer level) {
-                for (CodecProfileLevel pl: mProfileLevels) {
-                    if (pl.profile != profile) {
-                        continue;
-                    }
-
-                    // No specific level requested
-                    if (level == null) {
-                        return true;
-                    }
-
-                    // AAC doesn't use levels
-                    if (mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AAC)) {
-                        return true;
-                    }
-
-                    // DTS doesn't use levels
-                    if (mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_DTS)
-                            || mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_DTS_HD)
-                            || mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_DTS_UHD)) {
-                        return true;
-                    }
-
-                    // H.263 levels are not completely ordered:
-                    // Level45 support only implies Level10 support
-                    if (mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_H263)) {
-                        if (pl.level != level && pl.level == CodecProfileLevel.H263Level45
-                                && level > CodecProfileLevel.H263Level10) {
-                            continue;
-                        }
-                    }
-
-                    // MPEG4 levels are not completely ordered:
-                    // Level1 support only implies Level0 (and not Level0b) support
-                    if (mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_MPEG4)) {
-                        if (pl.level != level && pl.level == CodecProfileLevel.MPEG4Level1
-                                && level > CodecProfileLevel.MPEG4Level0) {
-                            continue;
-                        }
-                    }
-
-                    // HEVC levels incorporate both tiers and levels. Verify tier support.
-                    if (mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_HEVC)) {
-                        boolean supportsHighTier =
-                            (pl.level & CodecProfileLevel.HEVCHighTierLevels) != 0;
-                        boolean checkingHighTier
-                                = (level & CodecProfileLevel.HEVCHighTierLevels) != 0;
-                        // high tier levels are only supported by other high tier levels
-                        if (checkingHighTier && !supportsHighTier) {
-                            continue;
-                        }
-                    }
-
-                    if (pl.level >= level) {
-                        // if we recognize the listed profile/level, we must also recognize the
-                        // profile/level arguments.
-                        if (createFromProfileLevel(mMime, profile, pl.level) != null) {
-                            return createFromProfileLevel(mMime, profile, level) != null;
-                        }
-                        return true;
-                    }
-                }
-                return false;
-            }
-
-            public MediaFormat getDefaultFormat() {
-                return mDefaultFormat;
-            }
-
-            public String getMimeType() {
-                return mMime;
-            }
-
-            public int getMaxSupportedInstances() {
-                return mMaxSupportedInstances;
-            }
-
-            private boolean isAudio() {
-                return mAudioCaps != null;
-            }
-
-            public AudioCapabilities getAudioCapabilities() {
-                return mAudioCaps;
-            }
-
-            private boolean isEncoder() {
-                return mEncoderCaps != null;
-            }
-
-            public EncoderCapabilities getEncoderCapabilities() {
-                return mEncoderCaps;
-            }
-
-            private boolean isVideo() {
-                return mVideoCaps != null;
-            }
-
-            public VideoCapabilities getVideoCapabilities() {
-                return mVideoCaps;
-            }
-
-            public static CodecCapsLegacyImpl createFromProfileLevel(
-                    String mime, int profile, int level) {
-                CodecProfileLevel pl = new CodecProfileLevel();
-                pl.profile = profile;
-                pl.level = level;
-                MediaFormat defaultFormat = new MediaFormat();
-                defaultFormat.setString(MediaFormat.KEY_MIME, mime);
-
-                CodecCapsLegacyImpl ret = new CodecCapsLegacyImpl(
-                        new CodecProfileLevel[] { pl }, new int[0], true /* encoder */,
-                        defaultFormat, new MediaFormat() /* info */);
-                if (ret.mError != 0) {
-                    return null;
-                }
-                return ret;
-            }
-
-            /* package private */ CodecCapsLegacyImpl(
-                    CodecProfileLevel[] profLevs, int[] colFmts,
-                    boolean encoder,
-                    Map<String, Object>defaultFormatMap,
-                    Map<String, Object>capabilitiesMap) {
-                this(profLevs, colFmts, encoder,
-                        new MediaFormat(defaultFormatMap),
-                        new MediaFormat(capabilitiesMap));
-            }
-
-            /* package private */ CodecCapsLegacyImpl(
-                    CodecProfileLevel[] profLevs, int[] colFmts, boolean encoder,
-                    MediaFormat defaultFormat, MediaFormat info) {
-                final Map<String, Object> map = info.getMap();
-                mColorFormats = colFmts;
-                mFlagsVerified = 0; // TODO: remove as it is unused
-                mDefaultFormat = defaultFormat;
-                mCapabilitiesInfo = info;
-                mMime = mDefaultFormat.getString(MediaFormat.KEY_MIME);
-
-                /* VP9 introduced profiles around 2016, so some VP9 codecs may not advertise any
-                supported profiles. Determine the level for them using the info they provide. */
-                if (profLevs.length == 0
-                        && mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_VP9)) {
-                    CodecProfileLevel profLev = new CodecProfileLevel();
-                    profLev.profile = CodecProfileLevel.VP9Profile0;
-                    profLev.level = VideoCapabilities.VideoCapsLegacyImpl.equivalentVP9Level(info);
-                    profLevs = new CodecProfileLevel[] { profLev };
-                }
-                mProfileLevels = profLevs;
-
-                if (mMime.toLowerCase().startsWith("audio/")) {
-                    mAudioCaps = AudioCapabilities.create(info, this);
-                    mAudioCaps.getDefaultFormat(mDefaultFormat);
-                } else if (mMime.toLowerCase().startsWith("video/")
-                        || mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_IMAGE_ANDROID_HEIC)) {
-                    mVideoCaps = VideoCapabilities.create(info, this);
-                }
-                if (encoder) {
-                    mEncoderCaps = EncoderCapabilities.create(info, this);
-                    mEncoderCaps.getDefaultFormat(mDefaultFormat);
-                }
-
-                final Map<String, Object> global = MediaCodecList.getGlobalSettings();
-                mMaxSupportedInstances = Utils.parseIntSafely(
-                        global.get("max-concurrent-instances"), DEFAULT_MAX_SUPPORTED_INSTANCES);
-
-                int maxInstances = Utils.parseIntSafely(
-                        map.get("max-concurrent-instances"), mMaxSupportedInstances);
-                mMaxSupportedInstances =
-                        Range.create(1, MAX_SUPPORTED_INSTANCES_LIMIT).clamp(maxInstances);
-
-                for (Feature feat: getValidFeatures()) {
-                    String key = MediaFormat.KEY_FEATURE_ + feat.mName;
-                    Integer yesNo = (Integer)map.get(key);
-                    if (yesNo == null) {
-                        continue;
-                    }
-                    if (yesNo > 0) {
-                        mFlagsRequired |= feat.mValue;
-                    }
-                    mFlagsSupported |= feat.mValue;
-                    if (!feat.mInternal) {
-                        mDefaultFormat.setInteger(key, 1);
-                    }
-                    // TODO restrict features by mFlagsVerified once all codecs reliably verify them
-                }
-            }
-        }
-
-        /* package private */ static final class CodecCapsNativeImpl implements CodecCapsIntf {
-            private long mNativeContext; // accessed by native methods
-
-            private CodecProfileLevel[] mProfileLevels;
-            private int[] mColorFormats;
-
-            private MediaFormat mDefaultFormat;
-            private AudioCapabilities mAudioCaps;
-            private VideoCapabilities mVideoCaps;
-            private EncoderCapabilities mEncoderCaps;
-
-            public static CodecCapsNativeImpl createFromProfileLevel(
-                    String mime, int profile, int level) {
-                return native_createFromProfileLevel(mime, profile, level);
-            }
-
-            /**
-             * Constructor used by JNI.
-             *
-             * The Java CodecCapabilities object keeps these subobjects to avoid recontructing.
-             */
-            /* package private */ CodecCapsNativeImpl(CodecProfileLevel[] profLevs, int[] colFmts,
-                    MediaFormat defaultFormat, AudioCapabilities audioCaps,
-                    VideoCapabilities videoCaps, EncoderCapabilities encoderCaps) {
-                mProfileLevels = profLevs;
-                mColorFormats = colFmts;
-                mDefaultFormat = defaultFormat;
-                mAudioCaps = audioCaps;
-                mVideoCaps = videoCaps;
-                mEncoderCaps = encoderCaps;
-            }
-
-            public CodecCapsNativeImpl dup() {
-                CodecCapsNativeImpl impl = native_dup();
-                return impl;
-            }
-
-            @Override
-            protected void finalize() {
-                native_finalize();
-            }
-
-            public CodecProfileLevel[] getProfileLevels() {
-                return mProfileLevels;
-            }
-
-            public int[] getColorFormats() {
-                return mColorFormats;
-            }
-
-            public final boolean isFeatureSupported(String name) {
-                return native_isFeatureSupported(name);
-            }
-
-            public final boolean isFeatureRequired(String name) {
-                return native_isFeatureRequired(name);
-            }
-
-            public boolean isRegular() {
-                return native_isRegular();
-            }
-
-            public final boolean isFormatSupported(MediaFormat format) {
-                if (format == null) {
-                    throw new NullPointerException();
-                }
-
-                Map<String, Object> formatMap = format.getMap();
-                String[] keys = new String[formatMap.size()];
-                Object[] values = new Object[formatMap.size()];
-
-                int i = 0;
-                for (Map.Entry<String, Object> entry: formatMap.entrySet()) {
-                    keys[i] = entry.getKey();
-                    values[i] = entry.getValue();
-                    ++i;
-                }
-
-                return native_isFormatSupported(keys, values);
-            }
-
-            public MediaFormat getDefaultFormat() {
-                return mDefaultFormat;
-            }
-
-            public String getMimeType() {
-                return native_getMimeType();
-            }
-
-            public int getMaxSupportedInstances() {
-                return native_getMaxSupportedInstances();
-            }
-
-            public AudioCapabilities getAudioCapabilities() {
-                return mAudioCaps;
-            }
-
-            public EncoderCapabilities getEncoderCapabilities() {
-                return mEncoderCaps;
-            }
-
-            public VideoCapabilities getVideoCapabilities() {
-                return mVideoCaps;
-            }
-
-            private static native void native_init();
-            private static native CodecCapsNativeImpl native_createFromProfileLevel(
-                    String mime, int profile, int level);
-            private native CodecCapsNativeImpl native_dup();
-            private native void native_finalize();
-            private native int native_getMaxSupportedInstances();
-            private native String native_getMimeType();
-            private native final boolean native_isFeatureRequired(String name);
-            private native final boolean native_isFeatureSupported(String name);
-            private native final boolean native_isFormatSupported(@Nullable String[] keys,
-                    @Nullable Object[] values);
-            private native boolean native_isRegular();
-
-            static {
-                System.loadLibrary("media_jni");
-                native_init();
-            }
-        }
-
-        private CodecCapsIntf mImpl;
-
-        /**
-         * 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.
-         * <p class=note> In {@link android.os.Build.VERSION_CODES#M}, calling this
-         * method without calling any method of the {@link MediaCodecList} class beforehand
-         * results in a {@link NullPointerException}.</p>
-         */
-        public static CodecCapabilities createFromProfileLevel(
-                String mime, int profile, int level) {
-            CodecCapsIntf impl;
-            if (GetFlag(() -> android.media.codec.Flags.nativeCapabilites())) {
-                impl = CodecCapsNativeImpl.createFromProfileLevel(mime, profile, level);
-            } else {
-                impl = CodecCapsLegacyImpl.createFromProfileLevel(mime, profile, level);
-            }
-            return new CodecCapabilities(impl);
-        }
-
-        public CodecCapabilities() {
-            mImpl = new CodecCapsLegacyImpl();
-        }
-
-        /** package private */ CodecCapabilities(CodecCapsIntf impl) {
-            mImpl = impl;
-            profileLevels = mImpl.getProfileLevels();
-            colorFormats = mImpl.getColorFormats();
-        }
-
-        /** @hide */
-        public CodecCapabilities dup() {
-            CodecCapabilities caps = new CodecCapabilities();
-
-            // profileLevels and colorFormats may be modified by client.
-            caps.profileLevels = Arrays.copyOf(profileLevels, profileLevels.length);
-            caps.colorFormats = Arrays.copyOf(colorFormats, colorFormats.length);
-
-            caps.mImpl = mImpl.dup();
-
-            return caps;
-        }
-
         /**
          * Query codec feature capabilities.
          * <p>
@@ -1469,7 +830,7 @@
          * features that are always on.
          */
         public final boolean isFeatureSupported(String name) {
-            return mImpl.isFeatureSupported(name);
+            return checkFeature(name, mFlagsSupported);
         }
 
         /**
@@ -1479,12 +840,104 @@
          * they are always turned on.
          */
         public final boolean isFeatureRequired(String name) {
-            return mImpl.isFeatureRequired(name);
+            return checkFeature(name, mFlagsRequired);
+        }
+
+        // Flags are used for feature list creation so separate this into a private
+        // static class to delay reading the flags only when constructing the list.
+        private static class FeatureList {
+            private static Feature[] getDecoderFeatures() {
+                ArrayList<Feature> features = new ArrayList();
+                features.add(new Feature(FEATURE_AdaptivePlayback, (1 << 0), true));
+                features.add(new Feature(FEATURE_SecurePlayback,   (1 << 1), false));
+                features.add(new Feature(FEATURE_TunneledPlayback, (1 << 2), false));
+                features.add(new Feature(FEATURE_PartialFrame,     (1 << 3), false));
+                features.add(new Feature(FEATURE_FrameParsing,     (1 << 4), false));
+                features.add(new Feature(FEATURE_MultipleFrames,   (1 << 5), false));
+                features.add(new Feature(FEATURE_DynamicTimestamp, (1 << 6), false));
+                features.add(new Feature(FEATURE_LowLatency,       (1 << 7), true));
+                if (GetFlag(() -> android.media.codec.Flags.dynamicColorAspects())) {
+                    features.add(new Feature(FEATURE_DynamicColorAspects, (1 << 8), true));
+                }
+                if (GetFlag(() -> android.media.codec.Flags.nullOutputSurface())) {
+                    features.add(new Feature(FEATURE_DetachedSurface,     (1 << 9), true));
+                }
+
+                // feature to exclude codec from REGULAR codec list
+                features.add(new Feature(FEATURE_SpecialCodec,     (1 << 30), false, true));
+
+                return features.toArray(new Feature[0]);
+            };
+
+            private static Feature[] decoderFeatures = getDecoderFeatures();
+
+            private static Feature[] getEncoderFeatures() {
+                ArrayList<Feature> features = new ArrayList();
+
+                features.add(new Feature(FEATURE_IntraRefresh, (1 << 0), false));
+                features.add(new Feature(FEATURE_MultipleFrames, (1 << 1), false));
+                features.add(new Feature(FEATURE_DynamicTimestamp, (1 << 2), false));
+                features.add(new Feature(FEATURE_QpBounds, (1 << 3), false));
+                features.add(new Feature(FEATURE_EncodingStatistics, (1 << 4), false));
+                features.add(new Feature(FEATURE_HdrEditing, (1 << 5), false));
+                if (GetFlag(() -> android.media.codec.Flags.hlgEditing())) {
+                    features.add(new Feature(FEATURE_HlgEditing, (1 << 6), true));
+                }
+                if (GetFlag(() -> android.media.codec.Flags.regionOfInterest())) {
+                    features.add(new Feature(FEATURE_Roi, (1 << 7), true));
+                }
+
+                // feature to exclude codec from REGULAR codec list
+                features.add(new Feature(FEATURE_SpecialCodec,     (1 << 30), false, true));
+
+                return features.toArray(new Feature[0]);
+            };
+
+            private static Feature[] encoderFeatures = getEncoderFeatures();
+
+            public static Feature[] getFeatures(boolean isEncoder) {
+                if (isEncoder) {
+                    return encoderFeatures;
+                } else {
+                    return decoderFeatures;
+                }
+            }
+        }
+
+        /** @hide */
+        public String[] validFeatures() {
+            Feature[] features = getValidFeatures();
+            String[] res = new String[features.length];
+            for (int i = 0; i < res.length; i++) {
+                if (!features[i].mInternal) {
+                    res[i] = features[i].mName;
+                }
+            }
+            return res;
+        }
+
+        private Feature[] getValidFeatures() {
+            return FeatureList.getFeatures(isEncoder());
+        }
+
+        private boolean checkFeature(String name, int flags) {
+            for (Feature feat: getValidFeatures()) {
+                if (feat.mName.equals(name)) {
+                    return (flags & feat.mValue) != 0;
+                }
+            }
+            return false;
         }
 
         /** @hide */
         public boolean isRegular() {
-            return mImpl.isRegular();
+            // regular codecs only require default features
+            for (Feature feat: getValidFeatures()) {
+                if (!feat.mDefault && isFeatureRequired(feat.mName)) {
+                    return false;
+                }
+            }
+            return true;
         }
 
         /**
@@ -1593,22 +1046,200 @@
          *         and feature requests.
          */
         public final boolean isFormatSupported(MediaFormat format) {
-            return mImpl.isFormatSupported(format);
+            final Map<String, Object> map = format.getMap();
+            final String mime = (String)map.get(MediaFormat.KEY_MIME);
+
+            // mime must match if present
+            if (mime != null && !mMime.equalsIgnoreCase(mime)) {
+                return false;
+            }
+
+            // check feature support
+            for (Feature feat: getValidFeatures()) {
+                if (feat.mInternal) {
+                    continue;
+                }
+
+                Integer yesNo = (Integer)map.get(MediaFormat.KEY_FEATURE_ + feat.mName);
+                if (yesNo == null) {
+                    continue;
+                }
+                if ((yesNo == 1 && !isFeatureSupported(feat.mName)) ||
+                        (yesNo == 0 && isFeatureRequired(feat.mName))) {
+                    return false;
+                }
+            }
+
+            Integer profile = (Integer)map.get(MediaFormat.KEY_PROFILE);
+            Integer level = (Integer)map.get(MediaFormat.KEY_LEVEL);
+
+            if (profile != null) {
+                if (!supportsProfileLevel(profile, level)) {
+                    return false;
+                }
+
+                // If we recognize this profile, check that this format is supported by the
+                // highest level supported by the codec for that profile. (Ignore specified
+                // level beyond the above profile/level check as level is only used as a
+                // guidance. E.g. AVC Level 1 CIF format is supported if codec supports level 1.1
+                // even though max size for Level 1 is QCIF. However, MPEG2 Simple Profile
+                // 1080p format is not supported even if codec supports Main Profile Level High,
+                // as Simple Profile does not support 1080p.
+                CodecCapabilities levelCaps = null;
+                int maxLevel = 0;
+                for (CodecProfileLevel pl : profileLevels) {
+                    if (pl.profile == profile && pl.level > maxLevel) {
+                        // H.263 levels are not completely ordered:
+                        // Level45 support only implies Level10 support
+                        if (!mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_H263)
+                                || pl.level != CodecProfileLevel.H263Level45
+                                || maxLevel == CodecProfileLevel.H263Level10) {
+                            maxLevel = pl.level;
+                        }
+                    }
+                }
+                levelCaps = createFromProfileLevel(mMime, profile, maxLevel);
+                // We must remove the profile from this format otherwise levelCaps.isFormatSupported
+                // will get into this same condition and loop forever. Furthermore, since levelCaps
+                // does not contain features and bitrate specific keys, keep only keys relevant for
+                // a level check.
+                Map<String, Object> levelCriticalFormatMap = new HashMap<>(map);
+                final Set<String> criticalKeys =
+                    isVideo() ? VideoCapabilities.VIDEO_LEVEL_CRITICAL_FORMAT_KEYS :
+                    isAudio() ? AudioCapabilities.AUDIO_LEVEL_CRITICAL_FORMAT_KEYS :
+                    null;
+
+                // critical keys will always contain KEY_MIME, but should also contain others to be
+                // meaningful
+                if (criticalKeys != null && criticalKeys.size() > 1 && levelCaps != null) {
+                    levelCriticalFormatMap.keySet().retainAll(criticalKeys);
+
+                    MediaFormat levelCriticalFormat = new MediaFormat(levelCriticalFormatMap);
+                    if (!levelCaps.isFormatSupported(levelCriticalFormat)) {
+                        return false;
+                    }
+                }
+            }
+            if (mAudioCaps != null && !mAudioCaps.supportsFormat(format)) {
+                return false;
+            }
+            if (mVideoCaps != null && !mVideoCaps.supportsFormat(format)) {
+                return false;
+            }
+            if (mEncoderCaps != null && !mEncoderCaps.supportsFormat(format)) {
+                return false;
+            }
+            return true;
         }
 
+        private static boolean supportsBitrate(
+                Range<Integer> bitrateRange, MediaFormat format) {
+            Map<String, Object> map = format.getMap();
+
+            // consider max bitrate over average bitrate for support
+            Integer maxBitrate = (Integer)map.get(MediaFormat.KEY_MAX_BIT_RATE);
+            Integer bitrate = (Integer)map.get(MediaFormat.KEY_BIT_RATE);
+            if (bitrate == null) {
+                bitrate = maxBitrate;
+            } else if (maxBitrate != null) {
+                bitrate = Math.max(bitrate, maxBitrate);
+            }
+
+            if (bitrate != null && bitrate > 0) {
+                return bitrateRange.contains(bitrate);
+            }
+
+            return true;
+        }
+
+        private boolean supportsProfileLevel(int profile, Integer level) {
+            for (CodecProfileLevel pl: profileLevels) {
+                if (pl.profile != profile) {
+                    continue;
+                }
+
+                // No specific level requested
+                if (level == null) {
+                    return true;
+                }
+
+                // AAC doesn't use levels
+                if (mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AAC)) {
+                    return true;
+                }
+
+                // DTS doesn't use levels
+                if (mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_DTS)
+                        || mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_DTS_HD)
+                        || mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_DTS_UHD)) {
+                    return true;
+                }
+
+                // H.263 levels are not completely ordered:
+                // Level45 support only implies Level10 support
+                if (mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_H263)) {
+                    if (pl.level != level && pl.level == CodecProfileLevel.H263Level45
+                            && level > CodecProfileLevel.H263Level10) {
+                        continue;
+                    }
+                }
+
+                // MPEG4 levels are not completely ordered:
+                // Level1 support only implies Level0 (and not Level0b) support
+                if (mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_MPEG4)) {
+                    if (pl.level != level && pl.level == CodecProfileLevel.MPEG4Level1
+                            && level > CodecProfileLevel.MPEG4Level0) {
+                        continue;
+                    }
+                }
+
+                // HEVC levels incorporate both tiers and levels. Verify tier support.
+                if (mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_HEVC)) {
+                    boolean supportsHighTier =
+                        (pl.level & CodecProfileLevel.HEVCHighTierLevels) != 0;
+                    boolean checkingHighTier = (level & CodecProfileLevel.HEVCHighTierLevels) != 0;
+                    // high tier levels are only supported by other high tier levels
+                    if (checkingHighTier && !supportsHighTier) {
+                        continue;
+                    }
+                }
+
+                if (pl.level >= level) {
+                    // if we recognize the listed profile/level, we must also recognize the
+                    // profile/level arguments.
+                    if (createFromProfileLevel(mMime, profile, pl.level) != null) {
+                        return createFromProfileLevel(mMime, profile, level) != null;
+                    }
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        // 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 MediaFormat getDefaultFormat() {
-            return mImpl.getDefaultFormat();
+            return mDefaultFormat;
         }
 
         /**
          * Returns the mime type for which this codec-capability object was created.
          */
         public String getMimeType() {
-            return mImpl.getMimeType();
+            return mMime;
         }
 
         /**
@@ -1620,28 +1251,157 @@
          * resources at time of use.
          */
         public int getMaxSupportedInstances() {
-            return mImpl.getMaxSupportedInstances();
+            return mMaxSupportedInstances;
+        }
+
+        private boolean isAudio() {
+            return mAudioCaps != null;
         }
 
         /**
          * Returns the audio capabilities or {@code null} if this is not an audio codec.
          */
         public AudioCapabilities getAudioCapabilities() {
-            return mImpl.getAudioCapabilities();
+            return mAudioCaps;
+        }
+
+        private boolean isEncoder() {
+            return mEncoderCaps != null;
         }
 
         /**
          * Returns the encoding capabilities or {@code null} if this is not an encoder.
          */
         public EncoderCapabilities getEncoderCapabilities() {
-            return mImpl.getEncoderCapabilities();
+            return mEncoderCaps;
+        }
+
+        private boolean isVideo() {
+            return mVideoCaps != null;
         }
 
         /**
          * Returns the video capabilities or {@code null} if this is not a video codec.
          */
         public VideoCapabilities getVideoCapabilities() {
-            return mImpl.getVideoCapabilities();
+            return mVideoCaps;
+        }
+
+        /** @hide */
+        public CodecCapabilities dup() {
+            CodecCapabilities caps = new CodecCapabilities();
+
+            // profileLevels and colorFormats may be modified by client.
+            caps.profileLevels = Arrays.copyOf(profileLevels, profileLevels.length);
+            caps.colorFormats = Arrays.copyOf(colorFormats, colorFormats.length);
+
+            caps.mMime = mMime;
+            caps.mMaxSupportedInstances = mMaxSupportedInstances;
+            caps.mFlagsRequired = mFlagsRequired;
+            caps.mFlagsSupported = mFlagsSupported;
+            caps.mFlagsVerified = mFlagsVerified;
+            caps.mAudioCaps = mAudioCaps;
+            caps.mVideoCaps = mVideoCaps;
+            caps.mEncoderCaps = mEncoderCaps;
+            caps.mDefaultFormat = mDefaultFormat;
+            caps.mCapabilitiesInfo = mCapabilitiesInfo;
+
+            return caps;
+        }
+
+        /**
+         * 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.
+         * <p class=note> In {@link android.os.Build.VERSION_CODES#M}, calling this
+         * method without calling any method of the {@link MediaCodecList} class beforehand
+         * results in a {@link NullPointerException}.</p>
+         */
+        public static CodecCapabilities createFromProfileLevel(
+                String mime, int profile, int level) {
+            CodecProfileLevel pl = new CodecProfileLevel();
+            pl.profile = profile;
+            pl.level = level;
+            MediaFormat defaultFormat = new MediaFormat();
+            defaultFormat.setString(MediaFormat.KEY_MIME, mime);
+
+            CodecCapabilities ret = new CodecCapabilities(
+                new CodecProfileLevel[] { pl }, new int[0], true /* encoder */,
+                defaultFormat, new MediaFormat() /* info */);
+            if (ret.mError != 0) {
+                return null;
+            }
+            return ret;
+        }
+
+        /* package private */ CodecCapabilities(
+                CodecProfileLevel[] profLevs, int[] colFmts,
+                boolean encoder,
+                Map<String, Object>defaultFormatMap,
+                Map<String, Object>capabilitiesMap) {
+            this(profLevs, colFmts, encoder,
+                    new MediaFormat(defaultFormatMap),
+                    new MediaFormat(capabilitiesMap));
+        }
+
+        private MediaFormat mCapabilitiesInfo;
+
+        /* package private */ CodecCapabilities(
+                CodecProfileLevel[] profLevs, int[] colFmts, boolean encoder,
+                MediaFormat defaultFormat, MediaFormat info) {
+            final Map<String, Object> map = info.getMap();
+            colorFormats = colFmts;
+            mFlagsVerified = 0; // TODO: remove as it is unused
+            mDefaultFormat = defaultFormat;
+            mCapabilitiesInfo = info;
+            mMime = mDefaultFormat.getString(MediaFormat.KEY_MIME);
+
+            /* VP9 introduced profiles around 2016, so some VP9 codecs may not advertise any
+               supported profiles. Determine the level for them using the info they provide. */
+            if (profLevs.length == 0 && mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_VP9)) {
+                CodecProfileLevel profLev = new CodecProfileLevel();
+                profLev.profile = CodecProfileLevel.VP9Profile0;
+                profLev.level = VideoCapabilities.equivalentVP9Level(info);
+                profLevs = new CodecProfileLevel[] { profLev };
+            }
+            profileLevels = profLevs;
+
+            if (mMime.toLowerCase().startsWith("audio/")) {
+                mAudioCaps = AudioCapabilities.create(info, this);
+                mAudioCaps.getDefaultFormat(mDefaultFormat);
+            } else if (mMime.toLowerCase().startsWith("video/")
+                    || mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_IMAGE_ANDROID_HEIC)) {
+                mVideoCaps = VideoCapabilities.create(info, this);
+            }
+            if (encoder) {
+                mEncoderCaps = EncoderCapabilities.create(info, this);
+                mEncoderCaps.getDefaultFormat(mDefaultFormat);
+            }
+
+            final Map<String, Object> global = MediaCodecList.getGlobalSettings();
+            mMaxSupportedInstances = Utils.parseIntSafely(
+                    global.get("max-concurrent-instances"), DEFAULT_MAX_SUPPORTED_INSTANCES);
+
+            int maxInstances = Utils.parseIntSafely(
+                    map.get("max-concurrent-instances"), mMaxSupportedInstances);
+            mMaxSupportedInstances =
+                    Range.create(1, MAX_SUPPORTED_INSTANCES_LIMIT).clamp(maxInstances);
+
+            for (Feature feat: getValidFeatures()) {
+                String key = MediaFormat.KEY_FEATURE_ + feat.mName;
+                Integer yesNo = (Integer)map.get(key);
+                if (yesNo == null) {
+                    continue;
+                }
+                if (yesNo > 0) {
+                    mFlagsRequired |= feat.mValue;
+                }
+                mFlagsSupported |= feat.mValue;
+                if (!feat.mInternal) {
+                    mDefaultFormat.setInteger(key, 1);
+                }
+                // TODO restrict features by mFlagsVerified once all codecs reliably verify them
+            }
         }
     }
 
@@ -1650,516 +1410,20 @@
      */
     public static final class AudioCapabilities {
         private static final String TAG = "AudioCapabilities";
+        private CodecCapabilities mParent;
+        private Range<Integer> mBitrateRange;
 
-        /* package private */ interface AudioCapsIntf {
-            public Range<Integer> getBitrateRange();
+        private int[] mSampleRates;
+        private Range<Integer>[] mSampleRateRanges;
+        private Range<Integer>[] mInputChannelRanges;
 
-            public int[] getSupportedSampleRates();
-
-            public Range<Integer>[] getSupportedSampleRateRanges();
-
-            public int getMaxInputChannelCount();
-
-            public int getMinInputChannelCount();
-
-            public Range<Integer>[] getInputChannelCountRanges();
-
-            public boolean isSampleRateSupported(int sampleRate);
-
-            public void getDefaultFormat(MediaFormat format);
-
-            public boolean supportsFormat(MediaFormat format);
-        }
-
-        /* package private */ static final class AudioCapsLegacyImpl implements AudioCapsIntf {
-            private CodecCapabilities.CodecCapsLegacyImpl mParent;
-            private Range<Integer> mBitrateRange;
-
-            private int[] mSampleRates;
-            private Range<Integer>[] mSampleRateRanges;
-            private Range<Integer>[] mInputChannelRanges;
-
-            private static final int MAX_INPUT_CHANNEL_COUNT = 30;
-
-            public Range<Integer> getBitrateRange() {
-                return mBitrateRange;
-            }
-
-            public int[] getSupportedSampleRates() {
-                return mSampleRates != null ? Arrays.copyOf(mSampleRates, mSampleRates.length)
-                        : null;
-            }
-
-            public Range<Integer>[] getSupportedSampleRateRanges() {
-                return Arrays.copyOf(mSampleRateRanges, mSampleRateRanges.length);
-            }
-
-            public int getMaxInputChannelCount() {
-                int overall_max = 0;
-                for (int i = mInputChannelRanges.length - 1; i >= 0; i--) {
-                    int lmax = mInputChannelRanges[i].getUpper();
-                    if (lmax > overall_max) {
-                        overall_max = lmax;
-                    }
-                }
-                return overall_max;
-            }
-
-            public int getMinInputChannelCount() {
-                int overall_min = MAX_INPUT_CHANNEL_COUNT;
-                for (int i = mInputChannelRanges.length - 1; i >= 0; i--) {
-                    int lmin = mInputChannelRanges[i].getLower();
-                    if (lmin < overall_min) {
-                        overall_min = lmin;
-                    }
-                }
-                return overall_min;
-            }
-
-            public Range<Integer>[] getInputChannelCountRanges() {
-                return Arrays.copyOf(mInputChannelRanges, mInputChannelRanges.length);
-            }
-
-            /* no public constructor */
-            private AudioCapsLegacyImpl() { }
-
-            public static AudioCapsLegacyImpl create(
-                    MediaFormat info, CodecCapabilities.CodecCapsLegacyImpl parent) {
-                if (GetFlag(() -> android.media.codec.Flags.nativeCapabilites())) {
-                    Log.d(TAG, "Legacy implementation is called while native flag is on.");
-                }
-
-                AudioCapsLegacyImpl caps = new AudioCapsLegacyImpl();
-                caps.init(info, parent);
-                return caps;
-            }
-
-            private void init(MediaFormat info, CodecCapabilities.CodecCapsLegacyImpl parent) {
-                mParent = parent;
-                initWithPlatformLimits();
-                applyLevelLimits();
-                parseFromInfo(info);
-            }
-
-            private void initWithPlatformLimits() {
-                mBitrateRange = Range.create(0, Integer.MAX_VALUE);
-                mInputChannelRanges = new Range[] {Range.create(1, MAX_INPUT_CHANNEL_COUNT)};
-                // mBitrateRange = Range.create(1, 320000);
-                final int minSampleRate = SystemProperties.
-                    getInt("ro.mediacodec.min_sample_rate", 7350);
-                final int maxSampleRate = SystemProperties.
-                    getInt("ro.mediacodec.max_sample_rate", 192000);
-                mSampleRateRanges = new Range[] { Range.create(minSampleRate, maxSampleRate) };
-                mSampleRates = null;
-            }
-
-            private boolean supports(Integer sampleRate, Integer inputChannels) {
-                // channels and sample rates are checked orthogonally
-                if (inputChannels != null) {
-                    int ix = Utils.binarySearchDistinctRanges(
-                            mInputChannelRanges, inputChannels);
-                    if (ix < 0) {
-                        return false;
-                    }
-                }
-                if (sampleRate != null) {
-                    int ix = Utils.binarySearchDistinctRanges(
-                            mSampleRateRanges, sampleRate);
-                    if (ix < 0) {
-                        return false;
-                    }
-                }
-                return true;
-            }
-
-            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 = MAX_INPUT_CHANNEL_COUNT;
-                CodecProfileLevel[] profileLevels = mParent.getProfileLevels();
-                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);
-                    sampleRateRange = Range.create(8000, 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, 192000);
-                    bitRates = Range.create(1, 10000000);
-                    maxChannels = AudioSystem.OUT_CHANNEL_COUNT_MAX;
-                } 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 if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AC3)) {
-                    maxChannels = 6;
-                } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_EAC3)) {
-                    maxChannels = 16;
-                } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_EAC3_JOC)) {
-                    sampleRates = new int[] { 48000 };
-                    bitRates = Range.create(32000, 6144000);
-                    maxChannels = 16;
-                } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AC4)) {
-                    sampleRates = new int[] { 44100, 48000, 96000, 192000 };
-                    bitRates = Range.create(16000, 2688000);
-                    maxChannels = 24;
-                } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_DTS)) {
-                    sampleRates = new int[] { 44100, 48000 };
-                    bitRates = Range.create(96000, 1524000);
-                    maxChannels = 6;
-                } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_DTS_HD)) {
-                    for (CodecProfileLevel profileLevel: profileLevels) {
-                        switch (profileLevel.profile) {
-                            case CodecProfileLevel.DTS_HDProfileLBR:
-                                sampleRates = new int[]{ 22050, 24000, 44100, 48000 };
-                                bitRates = Range.create(32000, 768000);
-                                break;
-                            case CodecProfileLevel.DTS_HDProfileHRA:
-                            case CodecProfileLevel.DTS_HDProfileMA:
-                                sampleRates
-                                        = new int[]{ 44100, 48000, 88200, 96000, 176400, 192000 };
-                                bitRates = Range.create(96000, 24500000);
-                                break;
-                            default:
-                                Log.w(TAG, "Unrecognized profile "
-                                        + profileLevel.profile + " for " + mime);
-                                mParent.mError |= ERROR_UNRECOGNIZED;
-                                sampleRates
-                                        = new int[]{ 44100, 48000, 88200, 96000, 176400, 192000 };
-                                bitRates = Range.create(96000, 24500000);
-                        }
-                    }
-                    maxChannels = 8;
-                } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_DTS_UHD)) {
-                    for (CodecProfileLevel profileLevel: profileLevels) {
-                        switch (profileLevel.profile) {
-                            case CodecProfileLevel.DTS_UHDProfileP2:
-                                sampleRates = new int[]{ 48000 };
-                                bitRates = Range.create(96000, 768000);
-                                maxChannels = 10;
-                                break;
-                            case CodecProfileLevel.DTS_UHDProfileP1:
-                                sampleRates
-                                        = new int[]{ 44100, 48000, 88200, 96000, 176400, 192000 };
-                                bitRates = Range.create(96000, 24500000);
-                                maxChannels = 32;
-                                break;
-                            default:
-                                Log.w(TAG, "Unrecognized profile "
-                                        + profileLevel.profile + " for " + mime);
-                                mParent.mError |= ERROR_UNRECOGNIZED;
-                                sampleRates
-                                        = new int[]{ 44100, 48000, 88200, 96000, 176400, 192000 };
-                                bitRates = Range.create(96000, 24500000);
-                                maxChannels = 32;
-                        }
-                    }
-                } 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 });
-                }
-
-                Range<Integer> channelRange = Range.create(1, maxChannels);
-
-                applyLimits(new Range[] { channelRange }, bitRates);
-            }
-
-            private void applyLimits(Range<Integer>[] inputChannels, Range<Integer> bitRates) {
-
-                // clamp & make a local copy
-                Range<Integer>[] myInputChannels = new Range[inputChannels.length];
-                for (int i = 0; i < inputChannels.length; i++) {
-                    int lower = inputChannels[i].clamp(1);
-                    int upper = inputChannels[i].clamp(MAX_INPUT_CHANNEL_COUNT);
-                    myInputChannels[i] = Range.create(lower, upper);
-                }
-
-                // sort, intersect with existing, & save channel list
-                sortDistinctRanges(myInputChannels);
-                Range<Integer>[] joinedChannelList =
-                                intersectSortedDistinctRanges(myInputChannels, mInputChannelRanges);
-                mInputChannelRanges = joinedChannelList;
-
-                if (bitRates != null) {
-                    mBitrateRange = mBitrateRange.intersect(bitRates);
-                }
-            }
-
-            private void parseFromInfo(MediaFormat info) {
-                int maxInputChannels = MAX_INPUT_CHANNEL_COUNT;
-                Range<Integer>[] channels = new Range[] { Range.create(1, maxInputChannels)};
-                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);
-                }
-
-                // we will prefer channel-ranges over max-channel-count
-                if (info.containsKey("channel-ranges")) {
-                    String[] channelStrings = info.getString("channel-ranges").split(",");
-                    Range<Integer>[] channelRanges = new Range[channelStrings.length];
-                    for (int i = 0; i < channelStrings.length; i++) {
-                        channelRanges[i] = Utils.parseIntRange(channelStrings[i], null);
-                    }
-                    channels = channelRanges;
-                } else if (info.containsKey("channel-range")) {
-                    Range<Integer> oneRange = Utils.parseIntRange(info.getString("channel-range"),
-                                                                null);
-                    channels = new Range[] { oneRange };
-                } else if (info.containsKey("max-channel-count")) {
-                    maxInputChannels = Utils.parseIntSafely(
-                            info.getString("max-channel-count"), maxInputChannels);
-                    if (maxInputChannels == 0) {
-                        channels = new Range[] {Range.create(0, 0)};
-                    } else {
-                        channels = new Range[] {Range.create(1, maxInputChannels)};
-                    }
-                } else if ((mParent.mError & ERROR_UNSUPPORTED) != 0) {
-                    maxInputChannels = 0;
-                    channels = new Range[] {Range.create(0, 0)};
-                }
-
-                if (info.containsKey("bitrate-range")) {
-                    bitRates = bitRates.intersect(
-                            Utils.parseIntRange(info.getString("bitrate-range"), bitRates));
-                }
-
-                applyLimits(channels, bitRates);
-            }
-
-            /** @hide */
-            public void getDefaultFormat(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 (getMaxInputChannelCount() == 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]);
-                }
-            }
-
-            /* package private */
-            // must not contain KEY_PROFILE
-            static final Set<String> AUDIO_LEVEL_CRITICAL_FORMAT_KEYS = Set.of(
-                    // We don't set level-specific limits for audio codecs today. Key candidates
-                    // would be sample rate, bit rate or channel count.
-                    // MediaFormat.KEY_SAMPLE_RATE,
-                    // MediaFormat.KEY_CHANNEL_COUNT,
-                    // MediaFormat.KEY_BIT_RATE,
-                    MediaFormat.KEY_MIME);
-
-            /** @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;
-                }
-
-                if (!CodecCapabilities.CodecCapsLegacyImpl.supportsBitrate(mBitrateRange, format)) {
-                    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;
-            }
-        }
-
-        /* package private */ static final class AudioCapsNativeImpl implements AudioCapsIntf {
-            private long mNativeContext; // accessed by native methods
-
-            private Range<Integer> mBitrateRange;
-            private int[] mSampleRates;
-            private Range<Integer>[] mSampleRateRanges;
-            private Range<Integer>[] mInputChannelRanges;
-
-            /**
-             * Constructor used by JNI.
-             *
-             * The Java AudioCapabilities object keeps these subobjects to avoid recontruction.
-             */
-            /* package private */ AudioCapsNativeImpl(Range<Integer> bitrateRange,
-                    int[] sampleRates, Range<Integer>[] sampleRateRanges,
-                    Range<Integer>[] inputChannelRanges) {
-                mBitrateRange = bitrateRange;
-                mSampleRates = sampleRates;
-                mSampleRateRanges = sampleRateRanges;
-                mInputChannelRanges = inputChannelRanges;
-            }
-
-            /* no public constructor */
-            private AudioCapsNativeImpl() { }
-
-            public Range<Integer> getBitrateRange() {
-                return mBitrateRange;
-            }
-
-            public int[] getSupportedSampleRates() {
-                return mSampleRates != null ? Arrays.copyOf(mSampleRates, mSampleRates.length)
-                        : null;
-            }
-
-            public Range<Integer>[] getSupportedSampleRateRanges() {
-                return Arrays.copyOf(mSampleRateRanges, mSampleRateRanges.length);
-            }
-
-            public Range<Integer>[] getInputChannelCountRanges() {
-                return Arrays.copyOf(mInputChannelRanges, mInputChannelRanges.length);
-            }
-
-            public int getMaxInputChannelCount() {
-                return native_getMaxInputChannelCount();
-            }
-
-            public int getMinInputChannelCount() {
-                return native_getMinInputChannelCount();
-            }
-
-            public boolean isSampleRateSupported(int sampleRate) {
-                return native_isSampleRateSupported(sampleRate);
-            }
-
-            // This API is for internal Java implementation only. Should not be called.
-            public void getDefaultFormat(MediaFormat format) {
-                throw new UnsupportedOperationException(
-                    "Java Implementation should not call native implemenatation");
-            }
-
-            // This API is for internal Java implementation only. Should not be called.
-            public boolean supportsFormat(MediaFormat format) {
-                throw new UnsupportedOperationException(
-                    "Java Implementation should not call native implemenatation");
-            }
-
-            private native int native_getMaxInputChannelCount();
-            private native int native_getMinInputChannelCount();
-            private native boolean native_isSampleRateSupported(int sampleRate);
-            private static native void native_init();
-
-            static {
-                System.loadLibrary("media_jni");
-                native_init();
-            }
-        }
-
-        private AudioCapsIntf mImpl;
-
-        /** @hide */
-        public static AudioCapabilities create(
-                MediaFormat info, CodecCapabilities.CodecCapsLegacyImpl parent) {
-            AudioCapsLegacyImpl impl = AudioCapsLegacyImpl.create(info, parent);
-            AudioCapabilities caps = new AudioCapabilities(impl);
-            return caps;
-        }
-
-        /* package private */ AudioCapabilities(AudioCapsIntf impl) {
-            mImpl = impl;
-        }
-
-        /* no public constructor */
-        private AudioCapabilities() { }
+        private static final int MAX_INPUT_CHANNEL_COUNT = 30;
 
         /**
          * Returns the range of supported bitrates in bits/second.
          */
         public Range<Integer> getBitrateRange() {
-            return mImpl.getBitrateRange();
+            return mBitrateRange;
         }
 
         /**
@@ -2168,7 +1432,7 @@
          * {@code null}.  The array is sorted in ascending order.
          */
         public int[] getSupportedSampleRates() {
-            return mImpl.getSupportedSampleRates();
+            return mSampleRates != null ? Arrays.copyOf(mSampleRates, mSampleRates.length) : null;
         }
 
         /**
@@ -2177,21 +1441,7 @@
          * distinct.
          */
         public Range<Integer>[] getSupportedSampleRateRanges() {
-            return mImpl.getSupportedSampleRateRanges();
-        }
-
-        /*
-         * Returns an array of ranges representing the number of input channels supported.
-         * The codec supports any number of input channels within this range.
-         *
-         * This supersedes the {@link #getMaxInputChannelCount} method.
-         *
-         * For many codecs, this will be a single range [1..N], for some N.
-         */
-        @SuppressLint("ArrayReturn")
-        @NonNull
-        public Range<Integer>[] getInputChannelCountRanges() {
-            return mImpl.getInputChannelCountRanges();
+            return Arrays.copyOf(mSampleRateRanges, mSampleRateRanges.length);
         }
 
         /**
@@ -2211,7 +1461,14 @@
          */
         @IntRange(from = 1, to = 255)
         public int getMaxInputChannelCount() {
-            return mImpl.getMaxInputChannelCount();
+            int overall_max = 0;
+            for (int i = mInputChannelRanges.length - 1; i >= 0; i--) {
+                int lmax = mInputChannelRanges[i].getUpper();
+                if (lmax > overall_max) {
+                    overall_max = lmax;
+                }
+            }
+            return overall_max;
         }
 
         /**
@@ -2223,24 +1480,364 @@
          */
         @IntRange(from = 1, to = 255)
         public int getMinInputChannelCount() {
-            return mImpl.getMinInputChannelCount();
+            int overall_min = MAX_INPUT_CHANNEL_COUNT;
+            for (int i = mInputChannelRanges.length - 1; i >= 0; i--) {
+                int lmin = mInputChannelRanges[i].getLower();
+                if (lmin < overall_min) {
+                    overall_min = lmin;
+                }
+            }
+            return overall_min;
+        }
+
+        /*
+         * Returns an array of ranges representing the number of input channels supported.
+         * The codec supports any number of input channels within this range.
+         *
+         * This supersedes the {@link #getMaxInputChannelCount} method.
+         *
+         * For many codecs, this will be a single range [1..N], for some N.
+         */
+        @SuppressLint("ArrayReturn")
+        @NonNull
+        public Range<Integer>[] getInputChannelCountRanges() {
+            return Arrays.copyOf(mInputChannelRanges, mInputChannelRanges.length);
+        }
+
+        /* no public constructor */
+        private AudioCapabilities() { }
+
+        /** @hide */
+        public static AudioCapabilities create(
+                MediaFormat info, CodecCapabilities parent) {
+            AudioCapabilities caps = new AudioCapabilities();
+            caps.init(info, parent);
+            return caps;
+        }
+
+        private void init(MediaFormat info, CodecCapabilities parent) {
+            mParent = parent;
+            initWithPlatformLimits();
+            applyLevelLimits();
+            parseFromInfo(info);
+        }
+
+        private void initWithPlatformLimits() {
+            mBitrateRange = Range.create(0, Integer.MAX_VALUE);
+            mInputChannelRanges = new Range[] {Range.create(1, MAX_INPUT_CHANNEL_COUNT)};
+            // mBitrateRange = Range.create(1, 320000);
+            final int minSampleRate = SystemProperties.
+                getInt("ro.mediacodec.min_sample_rate", 7350);
+            final int maxSampleRate = SystemProperties.
+                getInt("ro.mediacodec.max_sample_rate", 192000);
+            mSampleRateRanges = new Range[] { Range.create(minSampleRate, maxSampleRate) };
+            mSampleRates = null;
+        }
+
+        private boolean supports(Integer sampleRate, Integer inputChannels) {
+            // channels and sample rates are checked orthogonally
+            if (inputChannels != null) {
+                int ix = Utils.binarySearchDistinctRanges(
+                        mInputChannelRanges, inputChannels);
+                if (ix < 0) {
+                    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 mImpl.isSampleRateSupported(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 = MAX_INPUT_CHANNEL_COUNT;
+            CodecProfileLevel[] profileLevels = mParent.profileLevels;
+            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);
+                sampleRateRange = Range.create(8000, 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, 192000);
+                bitRates = Range.create(1, 10000000);
+                maxChannels = AudioSystem.OUT_CHANNEL_COUNT_MAX;
+            } 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 if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AC3)) {
+                maxChannels = 6;
+            } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_EAC3)) {
+                maxChannels = 16;
+            } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_EAC3_JOC)) {
+                sampleRates = new int[] { 48000 };
+                bitRates = Range.create(32000, 6144000);
+                maxChannels = 16;
+            } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AC4)) {
+                sampleRates = new int[] { 44100, 48000, 96000, 192000 };
+                bitRates = Range.create(16000, 2688000);
+                maxChannels = 24;
+            } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_DTS)) {
+                sampleRates = new int[] { 44100, 48000 };
+                bitRates = Range.create(96000, 1524000);
+                maxChannels = 6;
+            } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_DTS_HD)) {
+                for (CodecProfileLevel profileLevel: profileLevels) {
+                    switch (profileLevel.profile) {
+                        case CodecProfileLevel.DTS_HDProfileLBR:
+                            sampleRates = new int[]{ 22050, 24000, 44100, 48000 };
+                            bitRates = Range.create(32000, 768000);
+                            break;
+                        case CodecProfileLevel.DTS_HDProfileHRA:
+                        case CodecProfileLevel.DTS_HDProfileMA:
+                            sampleRates = new int[]{ 44100, 48000, 88200, 96000, 176400, 192000 };
+                            bitRates = Range.create(96000, 24500000);
+                            break;
+                        default:
+                            Log.w(TAG, "Unrecognized profile "
+                                    + profileLevel.profile + " for " + mime);
+                            mParent.mError |= ERROR_UNRECOGNIZED;
+                            sampleRates = new int[]{ 44100, 48000, 88200, 96000, 176400, 192000 };
+                            bitRates = Range.create(96000, 24500000);
+                    }
+                }
+                maxChannels = 8;
+            } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_DTS_UHD)) {
+                for (CodecProfileLevel profileLevel: profileLevels) {
+                    switch (profileLevel.profile) {
+                        case CodecProfileLevel.DTS_UHDProfileP2:
+                            sampleRates = new int[]{ 48000 };
+                            bitRates = Range.create(96000, 768000);
+                            maxChannels = 10;
+                            break;
+                        case CodecProfileLevel.DTS_UHDProfileP1:
+                            sampleRates = new int[]{ 44100, 48000, 88200, 96000, 176400, 192000 };
+                            bitRates = Range.create(96000, 24500000);
+                            maxChannels = 32;
+                            break;
+                        default:
+                            Log.w(TAG, "Unrecognized profile "
+                                    + profileLevel.profile + " for " + mime);
+                            mParent.mError |= ERROR_UNRECOGNIZED;
+                            sampleRates = new int[]{ 44100, 48000, 88200, 96000, 176400, 192000 };
+                            bitRates = Range.create(96000, 24500000);
+                            maxChannels = 32;
+                    }
+                }
+            } 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 });
+            }
+
+            Range<Integer> channelRange = Range.create(1, maxChannels);
+
+            applyLimits(new Range[] { channelRange }, bitRates);
+        }
+
+        private void applyLimits(Range<Integer>[] inputChannels, Range<Integer> bitRates) {
+
+            // clamp & make a local copy
+            Range<Integer>[] myInputChannels = new Range[inputChannels.length];
+            for (int i = 0; i < inputChannels.length; i++) {
+                int lower = inputChannels[i].clamp(1);
+                int upper = inputChannels[i].clamp(MAX_INPUT_CHANNEL_COUNT);
+                myInputChannels[i] = Range.create(lower, upper);
+            }
+
+            // sort, intersect with existing, & save channel list
+            sortDistinctRanges(myInputChannels);
+            Range<Integer>[] joinedChannelList =
+                            intersectSortedDistinctRanges(myInputChannels, mInputChannelRanges);
+            mInputChannelRanges = joinedChannelList;
+
+            if (bitRates != null) {
+                mBitrateRange = mBitrateRange.intersect(bitRates);
+            }
+        }
+
+        private void parseFromInfo(MediaFormat info) {
+            int maxInputChannels = MAX_INPUT_CHANNEL_COUNT;
+            Range<Integer>[] channels = new Range[] { Range.create(1, maxInputChannels)};
+            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);
+            }
+
+            // we will prefer channel-ranges over max-channel-count
+            if (info.containsKey("channel-ranges")) {
+                String[] channelStrings = info.getString("channel-ranges").split(",");
+                Range<Integer>[] channelRanges = new Range[channelStrings.length];
+                for (int i = 0; i < channelStrings.length; i++) {
+                    channelRanges[i] = Utils.parseIntRange(channelStrings[i], null);
+                }
+                channels = channelRanges;
+            } else if (info.containsKey("channel-range")) {
+                Range<Integer> oneRange = Utils.parseIntRange(info.getString("channel-range"),
+                                                              null);
+                channels = new Range[] { oneRange };
+            } else if (info.containsKey("max-channel-count")) {
+                maxInputChannels = Utils.parseIntSafely(
+                        info.getString("max-channel-count"), maxInputChannels);
+                if (maxInputChannels == 0) {
+                    channels = new Range[] {Range.create(0, 0)};
+                } else {
+                    channels = new Range[] {Range.create(1, maxInputChannels)};
+                }
+            } else if ((mParent.mError & ERROR_UNSUPPORTED) != 0) {
+                maxInputChannels = 0;
+                channels = new Range[] {Range.create(0, 0)};
+            }
+
+            if (info.containsKey("bitrate-range")) {
+                bitRates = bitRates.intersect(
+                        Utils.parseIntRange(info.getString("bitrate-range"), bitRates));
+            }
+
+            applyLimits(channels, bitRates);
         }
 
         /** @hide */
         public void getDefaultFormat(MediaFormat format) {
-            mImpl.getDefaultFormat(format);
+            // report settings that have only a single choice
+            if (mBitrateRange.getLower().equals(mBitrateRange.getUpper())) {
+                format.setInteger(MediaFormat.KEY_BIT_RATE, mBitrateRange.getLower());
+            }
+            if (getMaxInputChannelCount() == 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]);
+            }
         }
 
+        /* package private */
+        // must not contain KEY_PROFILE
+        static final Set<String> AUDIO_LEVEL_CRITICAL_FORMAT_KEYS = Set.of(
+                // We don't set level-specific limits for audio codecs today. Key candidates would
+                // be sample rate, bit rate or channel count.
+                // MediaFormat.KEY_SAMPLE_RATE,
+                // MediaFormat.KEY_CHANNEL_COUNT,
+                // MediaFormat.KEY_BIT_RATE,
+                MediaFormat.KEY_MIME);
+
         /** @hide */
         public boolean supportsFormat(MediaFormat format) {
-            return mImpl.supportsFormat(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;
+            }
+
+            if (!CodecCapabilities.supportsBitrate(mBitrateRange, format)) {
+                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;
         }
     }
 
@@ -2300,6 +1897,304 @@
      */
     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 Map<Size, Range<Long>> mMeasuredFrameRates;
+        private List<PerformancePoint> mPerformancePoints;
+        private Range<Integer> mFrameRateRange;
+
+        private int mBlockWidth;
+        private int mBlockHeight;
+        private int mWidthAlignment;
+        private int mHeightAlignment;
+        private int mSmallerDimensionUpperLimit;
+
+        private boolean mAllowMbOverride; // allow XML to override calculated limits
+
+        /**
+         * Returns the range of supported bitrates in bits/second.
+         */
+        public Range<Integer> getBitrateRange() {
+            return mBitrateRange;
+        }
+
+        /**
+         * Returns the range of supported video widths.
+         * <p class=note>
+         * 32-bit processes will not support resolutions larger than 4096x4096 due to
+         * the limited address space.
+         */
+        public Range<Integer> getSupportedWidths() {
+            return mWidthRange;
+        }
+
+        /**
+         * Returns the range of supported video heights.
+         * <p class=note>
+         * 32-bit processes will not support resolutions larger than 4096x4096 due to
+         * the limited address space.
+         */
+        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) {
+                // height is not supported because there are no suitable widths
+                Log.v(TAG, "could not get supported widths for " + height);
+                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) {
+                // width is not supported because there are no suitable heights
+                Log.v(TAG, "could not get supported heights for " + width);
+                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()));
+        }
+
+        private int getBlockCount(int width, int height) {
+            return Utils.divUp(width, mBlockWidth) * Utils.divUp(height, mBlockHeight);
+        }
+
+        @NonNull
+        private Size findClosestSize(int width, int height) {
+            int targetBlockCount = getBlockCount(width, height);
+            Size closestSize = null;
+            int minDiff = Integer.MAX_VALUE;
+            for (Size size : mMeasuredFrameRates.keySet()) {
+                int diff = Math.abs(targetBlockCount -
+                        getBlockCount(size.getWidth(), size.getHeight()));
+                if (diff < minDiff) {
+                    minDiff = diff;
+                    closestSize = size;
+                }
+            }
+            return closestSize;
+        }
+
+        private Range<Double> estimateFrameRatesFor(int width, int height) {
+            Size size = findClosestSize(width, height);
+            Range<Long> range = mMeasuredFrameRates.get(size);
+            Double ratio = getBlockCount(size.getWidth(), size.getHeight())
+                    / (double)Math.max(getBlockCount(width, height), 1);
+            return Range.create(range.getLower() * ratio, range.getUpper() * ratio);
+        }
+
+        /**
+         * Returns the range of achievable video frame rates for a video size.
+         * May return {@code null}, if the codec did not publish any measurement
+         * data.
+         * <p>
+         * This is a performance estimate provided by the device manufacturer based on statistical
+         * sampling of full-speed decoding and encoding measurements in various configurations
+         * of common video sizes supported by the codec. As such it should only be used to
+         * compare individual codecs on the device. The value is not suitable for comparing
+         * different devices or even different android releases for the same device.
+         * <p>
+         * <em>On {@link android.os.Build.VERSION_CODES#M} release</em> the returned range
+         * corresponds to the fastest frame rates achieved in the tested configurations. As
+         * such, it should not be used to gauge guaranteed or even average codec performance
+         * on the device.
+         * <p>
+         * <em>On {@link android.os.Build.VERSION_CODES#N} release</em> the returned range
+         * corresponds closer to sustained performance <em>in tested configurations</em>.
+         * One can expect to achieve sustained performance higher than the lower limit more than
+         * 50% of the time, and higher than half of the lower limit at least 90% of the time
+         * <em>in tested configurations</em>.
+         * Conversely, one can expect performance lower than twice the upper limit at least
+         * 90% of the time.
+         * <p class=note>
+         * Tested configurations use a single active codec. For use cases where multiple
+         * codecs are active, applications can expect lower and in most cases significantly lower
+         * performance.
+         * <p class=note>
+         * The returned range value is interpolated from the nearest frame size(s) tested.
+         * Codec performance is severely impacted by other activity on the device as well
+         * as environmental factors (such as battery level, temperature or power source), and can
+         * vary significantly even in a steady environment.
+         * <p class=note>
+         * Use this method in cases where only codec performance matters, e.g. to evaluate if
+         * a codec has any chance of meeting a performance target. Codecs are listed
+         * in {@link MediaCodecList} in the preferred order as defined by the device
+         * manufacturer. As such, applications should use the first suitable codec in the
+         * list to achieve the best balance between power use and performance.
+         *
+         * @param width the width of the video
+         * @param height the height of the video
+         *
+         * @throws IllegalArgumentException if the video size is not supported.
+         */
+        @Nullable
+        public Range<Double> getAchievableFrameRatesFor(int width, int height) {
+            if (!supports(width, height, null)) {
+                throw new IllegalArgumentException("unsupported size");
+            }
+
+            if (mMeasuredFrameRates == null || mMeasuredFrameRates.size() <= 0) {
+                Log.w(TAG, "Codec did not publish any measurement data.");
+                return null;
+            }
+
+            return estimateFrameRatesFor(width, height);
+        }
 
         /**
          * Video performance points are a set of standard performance points defined by number of
@@ -2329,24 +2224,6 @@
             }
 
             /**
-             * Width in macroblocks.
-             *
-             * @hide
-             */
-            /** package private */ int getWidth() {
-                return mWidth;
-            }
-
-            /**
-             * Height in macroblocks.
-             *
-             * @hide
-             */
-            /** package private */ int getHeight() {
-                return mHeight;
-            }
-
-            /**
              * Maximum frame rate in frames per second.
              *
              * @hide
@@ -2366,24 +2243,6 @@
                 return mMaxMacroBlockRate;
             }
 
-            /**
-             * Codec block width in macroblocks.
-             *
-             * @hide
-             */
-            /** package private */ int getBlockWidth() {
-                return mBlockSize.getWidth();
-            }
-
-            /**
-             * Codec block height in macroblocks.
-             *
-             * @hide
-             */
-            /** package private */ int getBlockHeight() {
-                return mBlockSize.getHeight();
-            }
-
             /** Convert to a debug string */
             public String toString() {
                 int blockWidth = 16 * mBlockSize.getWidth();
@@ -2471,20 +2330,6 @@
                 this(width, height, frameRate, frameRate /* maxFrameRate */, new Size(16, 16));
             }
 
-            /* package private */ PerformancePoint(int width, int height, int maxFrameRate,
-                    long maxMacroBlockRate, int blockSizeWidth, int blockSizeHeight) {
-                mWidth = width;
-                mHeight = height;
-                mMaxFrameRate = maxFrameRate;
-                mMaxMacroBlockRate = maxMacroBlockRate;
-                mBlockSize = new Size(blockSizeWidth, blockSizeHeight);
-            }
-
-            private PerformancePoint(PerformancePoint pp) {
-                this(pp.mWidth, pp.mHeight, pp.mMaxFrameRate, pp.mMaxMacroBlockRate,
-                        pp.mBlockSize.getWidth(), pp.mBlockSize.getHeight());
-            }
-
             /** Saturates a long value to int */
             private int saturateLongToInt(long value) {
                 if (value < Integer.MIN_VALUE) {
@@ -2538,18 +2383,14 @@
              * @return {@code true} if the performance point covers the other.
              */
             public boolean covers(@NonNull PerformancePoint other) {
-                if (GetFlag(() -> android.media.codec.Flags.nativeCapabilites())) {
-                    return native_covers(other);
-                } else {
-                    // convert performance points to common block size
-                    Size commonSize = getCommonBlockSize(other);
-                    PerformancePoint aligned = new PerformancePoint(this, commonSize);
-                    PerformancePoint otherAligned = new PerformancePoint(other, commonSize);
+                // convert performance points to common block size
+                Size commonSize = getCommonBlockSize(other);
+                PerformancePoint aligned = new PerformancePoint(this, commonSize);
+                PerformancePoint otherAligned = new PerformancePoint(other, commonSize);
 
-                    return (aligned.getMaxMacroBlocks() >= otherAligned.getMaxMacroBlocks()
-                            && aligned.mMaxFrameRate >= otherAligned.mMaxFrameRate
-                            && aligned.mMaxMacroBlockRate >= otherAligned.mMaxMacroBlockRate);
-                }
+                return (aligned.getMaxMacroBlocks() >= otherAligned.getMaxMacroBlocks()
+                        && aligned.mMaxFrameRate >= otherAligned.mMaxFrameRate
+                        && aligned.mMaxMacroBlockRate >= otherAligned.mMaxMacroBlockRate);
             }
 
             private @NonNull Size getCommonBlockSize(@NonNull PerformancePoint other) {
@@ -2563,28 +2404,17 @@
                 if (o instanceof PerformancePoint) {
                     // convert performance points to common block size
                     PerformancePoint other = (PerformancePoint)o;
-                    if (GetFlag(() -> android.media.codec.Flags.nativeCapabilites())) {
-                        return native_equals(other);
-                    } else {
-                        Size commonSize = getCommonBlockSize(other);
-                        PerformancePoint aligned = new PerformancePoint(this, commonSize);
-                        PerformancePoint otherAligned = new PerformancePoint(other, commonSize);
+                    Size commonSize = getCommonBlockSize(other);
+                    PerformancePoint aligned = new PerformancePoint(this, commonSize);
+                    PerformancePoint otherAligned = new PerformancePoint(other, commonSize);
 
-                        return (aligned.getMaxMacroBlocks() == otherAligned.getMaxMacroBlocks()
-                                && aligned.mMaxFrameRate == otherAligned.mMaxFrameRate
-                                && aligned.mMaxMacroBlockRate == otherAligned.mMaxMacroBlockRate);
-                    }
+                    return (aligned.getMaxMacroBlocks() == otherAligned.getMaxMacroBlocks()
+                            && aligned.mMaxFrameRate == otherAligned.mMaxFrameRate
+                            && aligned.mMaxMacroBlockRate == otherAligned.mMaxMacroBlockRate);
                 }
                 return false;
             }
 
-            private native boolean native_covers(PerformancePoint other);
-            private native boolean native_equals(PerformancePoint other);
-
-            static {
-                System.loadLibrary("media_jni");
-            }
-
             /** 480p 24fps */
             @NonNull
             public static final PerformancePoint SD_24 = new PerformancePoint(720, 480, 24);
@@ -2689,1812 +2519,6 @@
             public static final PerformancePoint UHD_240 = new PerformancePoint(3840, 2160, 240);
         }
 
-        /* package private */ interface VideoCapsIntf {
-            public Range<Integer> getBitrateRange();
-
-            public Range<Integer> getSupportedWidths();
-
-            public Range<Integer> getSupportedHeights();
-
-            public int getWidthAlignment();
-
-            public int getHeightAlignment();
-
-            public int getSmallerDimensionUpperLimit();
-
-            public Range<Integer> getSupportedFrameRates();
-
-            public Range<Integer> getSupportedWidthsFor(int height);
-
-            public Range<Integer> getSupportedHeightsFor(int width);
-
-            public Range<Double> getSupportedFrameRatesFor(int width, int height);
-
-            public Range<Double> getAchievableFrameRatesFor(int width, int height);
-
-            public boolean areSizeAndRateSupported(int width, int height, double frameRate);
-
-            public boolean isSizeSupported(int width, int height);
-
-            public boolean supportsFormat(MediaFormat format);
-
-            public List<PerformancePoint> getSupportedPerformancePoints();
-        }
-
-        /* package private */ static final class VideoCapsLegacyImpl implements VideoCapsIntf {
-            /* package private */
-            // must not contain KEY_PROFILE
-            static final Set<String> VIDEO_LEVEL_CRITICAL_FORMAT_KEYS = Set.of(
-                    MediaFormat.KEY_WIDTH,
-                    MediaFormat.KEY_HEIGHT,
-                    MediaFormat.KEY_FRAME_RATE,
-                    MediaFormat.KEY_BIT_RATE,
-                    MediaFormat.KEY_MIME);
-
-            private CodecCapabilities.CodecCapsLegacyImpl 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 Map<Size, Range<Long>> mMeasuredFrameRates;
-            private List<PerformancePoint> mPerformancePoints;
-            private Range<Integer> mFrameRateRange;
-
-            private int mBlockWidth;
-            private int mBlockHeight;
-            private int mWidthAlignment;
-            private int mHeightAlignment;
-            private int mSmallerDimensionUpperLimit;
-
-            private boolean mAllowMbOverride; // allow XML to override calculated limits
-
-            /* no public constructor */
-            private VideoCapsLegacyImpl() { }
-
-            @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
-            public static VideoCapsLegacyImpl create(
-                    MediaFormat info, CodecCapabilities.CodecCapsLegacyImpl parent) {
-                if (GetFlag(() -> android.media.codec.Flags.nativeCapabilites())) {
-                    Log.d(TAG, "Legacy implementation is called while native flag is on.");
-                }
-
-                VideoCapsLegacyImpl caps = new VideoCapsLegacyImpl();
-                caps.init(info, parent);
-                return caps;
-            }
-
-            private void init(MediaFormat info, CodecCapabilities.CodecCapsLegacyImpl parent) {
-                mParent = parent;
-                initWithPlatformLimits();
-                applyLevelLimits();
-                parseFromInfo(info);
-                updateLimits();
-            }
-
-            public Range<Integer> getBitrateRange() {
-                return mBitrateRange;
-            }
-
-            public Range<Integer> getSupportedWidths() {
-                return mWidthRange;
-            }
-
-            public Range<Integer> getSupportedHeights() {
-                return mHeightRange;
-            }
-
-            public int getWidthAlignment() {
-                return mWidthAlignment;
-            }
-
-            public int getHeightAlignment() {
-                return mHeightAlignment;
-            }
-
-            /** @hide */
-            public int getSmallerDimensionUpperLimit() {
-                return mSmallerDimensionUpperLimit;
-            }
-
-            public Range<Integer> getSupportedFrameRates() {
-                return mFrameRateRange;
-            }
-
-            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) {
-                    // height is not supported because there are no suitable widths
-                    Log.v(TAG, "could not get supported widths for " + height);
-                    throw new IllegalArgumentException("unsupported height");
-                }
-            }
-
-            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) {
-                    // width is not supported because there are no suitable heights
-                    Log.v(TAG, "could not get supported heights for " + width);
-                    throw new IllegalArgumentException("unsupported width");
-                }
-            }
-
-            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()));
-            }
-
-            private int getBlockCount(int width, int height) {
-                return Utils.divUp(width, mBlockWidth) * Utils.divUp(height, mBlockHeight);
-            }
-
-            @NonNull
-            private Size findClosestSize(int width, int height) {
-                int targetBlockCount = getBlockCount(width, height);
-                Size closestSize = null;
-                int minDiff = Integer.MAX_VALUE;
-                for (Size size : mMeasuredFrameRates.keySet()) {
-                    int diff = Math.abs(targetBlockCount -
-                            getBlockCount(size.getWidth(), size.getHeight()));
-                    if (diff < minDiff) {
-                        minDiff = diff;
-                        closestSize = size;
-                    }
-                }
-                return closestSize;
-            }
-
-            private Range<Double> estimateFrameRatesFor(int width, int height) {
-                Size size = findClosestSize(width, height);
-                Range<Long> range = mMeasuredFrameRates.get(size);
-                Double ratio = getBlockCount(size.getWidth(), size.getHeight())
-                        / (double)Math.max(getBlockCount(width, height), 1);
-                return Range.create(range.getLower() * ratio, range.getUpper() * ratio);
-            }
-
-            /** @throws IllegalArgumentException if the video size is not supported. */
-            @Nullable
-            public Range<Double> getAchievableFrameRatesFor(int width, int height) {
-                if (!supports(width, height, null)) {
-                    throw new IllegalArgumentException("unsupported size");
-                }
-
-                if (mMeasuredFrameRates == null || mMeasuredFrameRates.size() <= 0) {
-                    Log.w(TAG, "Codec did not publish any measurement data.");
-                    return null;
-                }
-
-                return estimateFrameRatesFor(width, height);
-            }
-
-            @Nullable
-            public List<PerformancePoint> getSupportedPerformancePoints() {
-                return mPerformancePoints;
-            }
-
-            public boolean areSizeAndRateSupported(
-                    int width, int height, double frameRate) {
-                return supports(width, height, frameRate);
-            }
-
-            public boolean isSizeSupported(int width, int height) {
-                return supports(width, height, null);
-            }
-
-            private boolean supports(Integer width, Integer height, Number 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.doubleValue()));
-                }
-                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.doubleValue();
-                        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);
-                Number rate = (Number)map.get(MediaFormat.KEY_FRAME_RATE);
-
-                if (!supports(width, height, rate)) {
-                    return false;
-                }
-
-                if (!CodecCapabilities.CodecCapsLegacyImpl.supportsBitrate(mBitrateRange, format)) {
-                    return false;
-                }
-
-                // we ignore color-format for now as it is not reliably reported by codec
-                return true;
-            }
-
-            /** @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 = BITRATE_RANGE;
-
-                mWidthRange  = getSizeRange();
-                mHeightRange = getSizeRange();
-                mFrameRateRange = FRAME_RATE_RANGE;
-
-                mHorizontalBlockRange = getSizeRange();
-                mVerticalBlockRange   = getSizeRange();
-
-                // full positive ranges are supported as these get calculated
-                mBlockCountRange      = POSITIVE_INTEGERS;
-                mBlocksPerSecondRange = POSITIVE_LONGS;
-
-                mBlockAspectRatioRange = POSITIVE_RATIONALS;
-                mAspectRatioRange      = POSITIVE_RATIONALS;
-
-                mWidthAlignment = 1;
-                mHeightAlignment = 1;
-                mBlockWidth = 1;
-                mBlockHeight = 1;
-                mSmallerDimensionUpperLimit = getSizeRange().getUpper();
-            }
-
-            private @Nullable List<PerformancePoint> getPerformancePoints(Map<String, Object> map) {
-                Vector<PerformancePoint> ret = new Vector<>();
-                final String prefix = "performance-point-";
-                Set<String> keys = map.keySet();
-                for (String key : keys) {
-                    // looking for: performance-point-WIDTHxHEIGHT-range
-                    if (!key.startsWith(prefix)) {
-                        continue;
-                    }
-                    String subKey = key.substring(prefix.length());
-                    if (subKey.equals("none") && ret.size() == 0) {
-                        // This means that component knowingly did not publish performance points.
-                        // This is different from when the component forgot to publish performance
-                        // points.
-                        return Collections.unmodifiableList(ret);
-                    }
-                    String[] temp = key.split("-");
-                    if (temp.length != 4) {
-                        continue;
-                    }
-                    String sizeStr = temp[2];
-                    Size size = Utils.parseSize(sizeStr, null);
-                    if (size == null || size.getWidth() * size.getHeight() <= 0) {
-                        continue;
-                    }
-                    Range<Long> range = Utils.parseLongRange(map.get(key), null);
-                    if (range == null || range.getLower() < 0 || range.getUpper() < 0) {
-                        continue;
-                    }
-                    PerformancePoint given = new PerformancePoint(
-                            size.getWidth(), size.getHeight(), range.getLower().intValue(),
-                            range.getUpper().intValue(), new Size(mBlockWidth, mBlockHeight));
-                    PerformancePoint rotated = new PerformancePoint(
-                            size.getHeight(), size.getWidth(), range.getLower().intValue(),
-                            range.getUpper().intValue(), new Size(mBlockWidth, mBlockHeight));
-                    ret.add(given);
-                    if (!given.covers(rotated)) {
-                        ret.add(rotated);
-                    }
-                }
-
-                // check if the component specified no performance point indication
-                if (ret.size() == 0) {
-                    return null;
-                }
-
-                // sort reversed by area first, then by frame rate
-                ret.sort((a, b) ->
-                        -((a.getMaxMacroBlocks() != b.getMaxMacroBlocks()) ?
-                                (a.getMaxMacroBlocks() < b.getMaxMacroBlocks() ? -1 : 1) :
-                        (a.getMaxMacroBlockRate() != b.getMaxMacroBlockRate()) ?
-                                (a.getMaxMacroBlockRate() < b.getMaxMacroBlockRate() ? -1 : 1) :
-                        (a.getMaxFrameRate() != b.getMaxFrameRate()) ?
-                                (a.getMaxFrameRate() < b.getMaxFrameRate() ? -1 : 1) : 0));
-
-                return Collections.unmodifiableList(ret);
-            }
-
-            private Map<Size, Range<Long>> getMeasuredFrameRates(Map<String, Object> map) {
-                Map<Size, Range<Long>> ret = new HashMap<Size, Range<Long>>();
-                final String prefix = "measured-frame-rate-";
-                Set<String> keys = map.keySet();
-                for (String key : keys) {
-                    // looking for: measured-frame-rate-WIDTHxHEIGHT-range
-                    if (!key.startsWith(prefix)) {
-                        continue;
-                    }
-                    String subKey = key.substring(prefix.length());
-                    String[] temp = key.split("-");
-                    if (temp.length != 5) {
-                        continue;
-                    }
-                    String sizeStr = temp[3];
-                    Size size = Utils.parseSize(sizeStr, null);
-                    if (size == null || size.getWidth() * size.getHeight() <= 0) {
-                        continue;
-                    }
-                    Range<Long> range = Utils.parseLongRange(map.get(key), null);
-                    if (range == null || range.getLower() < 0 || range.getUpper() < 0) {
-                        continue;
-                    }
-                    ret.put(size, range);
-                }
-                return ret;
-            }
-
-            private static Pair<Range<Integer>, Range<Integer>> parseWidthHeightRanges(Object o) {
-                Pair<Size, Size> range = Utils.parseSizeRange(o);
-                if (range != null) {
-                    try {
-                        return Pair.create(
-                                Range.create(range.first.getWidth(), range.second.getWidth()),
-                                Range.create(range.first.getHeight(), range.second.getHeight()));
-                    } catch (IllegalArgumentException e) {
-                        Log.w(TAG, "could not parse size range '" + o + "'");
-                    }
-                }
-                return null;
-            }
-
-            /** @hide */
-            public static int equivalentVP9Level(MediaFormat info) {
-                final Map<String, Object> map = info.getMap();
-
-                Size blockSize = Utils.parseSize(map.get("block-size"), new Size(8, 8));
-                int BS = blockSize.getWidth() * blockSize.getHeight();
-
-                Range<Integer> counts = Utils.parseIntRange(map.get("block-count-range"), null);
-                int FS = counts == null ? 0 : BS * counts.getUpper();
-
-                Range<Long> blockRates =
-                    Utils.parseLongRange(map.get("blocks-per-second-range"), null);
-                long SR = blockRates == null ? 0 : BS * blockRates.getUpper();
-
-                Pair<Range<Integer>, Range<Integer>> dimensionRanges =
-                    parseWidthHeightRanges(map.get("size-range"));
-                int D = dimensionRanges == null ? 0 : Math.max(
-                        dimensionRanges.first.getUpper(), dimensionRanges.second.getUpper());
-
-                Range<Integer> bitRates = Utils.parseIntRange(map.get("bitrate-range"), null);
-                int BR = bitRates == null ? 0 : Utils.divUp(bitRates.getUpper(), 1000);
-
-                if (SR <=      829440 && FS <=    36864 && BR <=    200 && D <=   512)
-                    return CodecProfileLevel.VP9Level1;
-                if (SR <=     2764800 && FS <=    73728 && BR <=    800 && D <=   768)
-                    return CodecProfileLevel.VP9Level11;
-                if (SR <=     4608000 && FS <=   122880 && BR <=   1800 && D <=   960)
-                    return CodecProfileLevel.VP9Level2;
-                if (SR <=     9216000 && FS <=   245760 && BR <=   3600 && D <=  1344)
-                    return CodecProfileLevel.VP9Level21;
-                if (SR <=    20736000 && FS <=   552960 && BR <=   7200 && D <=  2048)
-                    return CodecProfileLevel.VP9Level3;
-                if (SR <=    36864000 && FS <=   983040 && BR <=  12000 && D <=  2752)
-                    return CodecProfileLevel.VP9Level31;
-                if (SR <=    83558400 && FS <=  2228224 && BR <=  18000 && D <=  4160)
-                    return CodecProfileLevel.VP9Level4;
-                if (SR <=   160432128 && FS <=  2228224 && BR <=  30000 && D <=  4160)
-                    return CodecProfileLevel.VP9Level41;
-                if (SR <=   311951360 && FS <=  8912896 && BR <=  60000 && D <=  8384)
-                    return CodecProfileLevel.VP9Level5;
-                if (SR <=   588251136 && FS <=  8912896 && BR <= 120000 && D <=  8384)
-                    return CodecProfileLevel.VP9Level51;
-                if (SR <=  1176502272 && FS <=  8912896 && BR <= 180000 && D <=  8384)
-                    return CodecProfileLevel.VP9Level52;
-                if (SR <=  1176502272 && FS <= 35651584 && BR <= 180000 && D <= 16832)
-                    return CodecProfileLevel.VP9Level6;
-                if (SR <= 2353004544L && FS <= 35651584 && BR <= 240000 && D <= 16832)
-                    return CodecProfileLevel.VP9Level61;
-                if (SR <= 4706009088L && FS <= 35651584 && BR <= 480000 && D <= 16832)
-                    return CodecProfileLevel.VP9Level62;
-                // returning largest level
-                return CodecProfileLevel.VP9Level62;
-            }
-
-            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, bitRates = 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);
-                mMeasuredFrameRates = getMeasuredFrameRates(map);
-                mPerformancePoints = getPerformancePoints(map);
-                Pair<Range<Integer>, Range<Integer>> sizeRanges =
-                    parseWidthHeightRanges(map.get("size-range"));
-                if (sizeRanges != null) {
-                    widths = sizeRanges.first;
-                    heights = sizeRanges.second;
-                }
-                // 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 (map.containsKey("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;
-                    }
-                }
-                bitRates = Utils.parseIntRange(map.get("bitrate-range"), null);
-                if (bitRates != null) {
-                    try {
-                        bitRates = bitRates.intersect(BITRATE_RANGE);
-                    } catch (IllegalArgumentException e) {
-                        Log.w(TAG,  "bitrate range (" + bitRates
-                                + ") is out of limits: " + BITRATE_RANGE);
-                        bitRates = 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 || mAllowMbOverride) {
-                    // codec supports profiles that we don't know.
-                    // Use supplied values clipped to platform limits
-                    if (widths != null) {
-                        mWidthRange = getSizeRange().intersect(widths);
-                    }
-                    if (heights != null) {
-                        mHeightRange = getSizeRange().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);
-                    }
-                    if (bitRates != null) {
-                        // only allow bitrate override if unsupported profiles were encountered
-                        if ((mParent.mError & ERROR_UNSUPPORTED) != 0) {
-                            mBitrateRange = BITRATE_RANGE.intersect(bitRates);
-                        } else {
-                            mBitrateRange = mBitrateRange.intersect(bitRates);
-                        }
-                    }
-                } 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);
-                    }
-                    if (bitRates != null) {
-                        mBitrateRange = mBitrateRange.intersect(bitRates);
-                    }
-                }
-                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) {
-                applyMacroBlockLimits(
-                        1 /* minHorizontalBlocks */, 1 /* minVerticalBlocks */,
-                        maxHorizontalBlocks, maxVerticalBlocks,
-                        maxBlocks, maxBlocksPerSecond,
-                        blockWidth, blockHeight, widthAlignment, heightAlignment);
-            }
-
-            private void applyMacroBlockLimits(
-                    int minHorizontalBlocks, int minVerticalBlocks,
-                    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(
-                                Utils.divUp(minHorizontalBlocks, (mBlockWidth / blockWidth)),
-                                maxHorizontalBlocks / (mBlockWidth / blockWidth));
-                mVerticalBlockRange =
-                        mVerticalBlockRange.intersect(
-                                Utils.divUp(minVerticalBlocks, (mBlockHeight / blockHeight)),
-                                maxVerticalBlocks / (mBlockHeight / blockHeight));
-            }
-
-            private void applyLevelLimits() {
-                long maxBlocksPerSecond = 0;
-                int maxBlocks = 0;
-                int maxBps = 0;
-                int maxDPBBlocks = 0;
-
-                int errors = ERROR_NONE_SUPPORTED;
-                CodecProfileLevel[] profileLevels = mParent.getProfileLevels();
-                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;
-                            case CodecProfileLevel.AVCLevel6:
-                                MBPS =  4177920; FS = 139264; BR = 240000; DPB = 696320; break;
-                            case CodecProfileLevel.AVCLevel61:
-                                MBPS =  8355840; FS = 139264; BR = 480000; DPB = 696320; break;
-                            case CodecProfileLevel.AVCLevel62:
-                                MBPS = 16711680; FS = 139264; BR = 800000; DPB = 696320; break;
-                            default:
-                                Log.w(TAG, "Unrecognized level "
-                                        + profileLevel.level + " for " + mime);
-                                errors |= ERROR_UNRECOGNIZED;
-                        }
-                        switch (profileLevel.profile) {
-                            case CodecProfileLevel.AVCProfileConstrainedHigh:
-                            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.AVCProfileConstrainedBaseline:
-                            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_MPEG2)) {
-                    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.MPEG2ProfileSimple:
-                                switch (profileLevel.level) {
-                                    case CodecProfileLevel.MPEG2LevelML:
-                                        FR = 30; W = 45; H =  36; MBPS =  40500; FS =  1620; BR =  15000; break;
-                                    default:
-                                        Log.w(TAG, "Unrecognized profile/level "
-                                                + profileLevel.profile + "/"
-                                                + profileLevel.level + " for " + mime);
-                                        errors |= ERROR_UNRECOGNIZED;
-                                }
-                                break;
-                            case CodecProfileLevel.MPEG2ProfileMain:
-                                switch (profileLevel.level) {
-                                    case CodecProfileLevel.MPEG2LevelLL:
-                                        FR = 30; W = 22; H =  18; MBPS =  11880; FS =   396; BR =  4000; break;
-                                    case CodecProfileLevel.MPEG2LevelML:
-                                        FR = 30; W = 45; H =  36; MBPS =  40500; FS =  1620; BR = 15000; break;
-                                    case CodecProfileLevel.MPEG2LevelH14:
-                                        FR = 60; W = 90; H =  68; MBPS = 183600; FS =  6120; BR = 60000; break;
-                                    case CodecProfileLevel.MPEG2LevelHL:
-                                        FR = 60; W = 120; H = 68; MBPS = 244800; FS =  8160; BR = 80000; break;
-                                    case CodecProfileLevel.MPEG2LevelHP:
-                                        FR = 60; W = 120; H = 68; MBPS = 489600; FS =  8160; BR = 80000; break;
-                                    default:
-                                        Log.w(TAG, "Unrecognized profile/level "
-                                                + profileLevel.profile + "/"
-                                                + profileLevel.level + " for " + mime);
-                                        errors |= ERROR_UNRECOGNIZED;
-                                }
-                                break;
-                            case CodecProfileLevel.MPEG2Profile422:
-                            case CodecProfileLevel.MPEG2ProfileSNR:
-                            case CodecProfileLevel.MPEG2ProfileSpatial:
-                            case CodecProfileLevel.MPEG2ProfileHigh:
-                                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_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 strict = false; // true: W, H and FR are individual max limits
-                        boolean supported = true;
-                        switch (profileLevel.profile) {
-                            case CodecProfileLevel.MPEG4ProfileSimple:
-                                switch (profileLevel.level) {
-                                    case CodecProfileLevel.MPEG4Level0:
-                                        strict = true;
-                                        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:
-                                        strict = true;
-                                        FR = 15; 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.MPEG4Level4a:
-                                        FR = 30; W = 40; H = 30; MBPS = 36000; FS = 1200; BR = 4000; break;
-                                    case CodecProfileLevel.MPEG4Level5:
-                                        FR = 30; W = 45; H = 36; MBPS = 40500; FS = 1620; BR = 8000; break;
-                                    case CodecProfileLevel.MPEG4Level6:
-                                        FR = 30; W = 80; H = 45; MBPS = 108000; FS = 3600; BR = 12000; 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:
-                                        FR = 30; W = 22; H = 18; MBPS = 11880; FS =  396; BR = 1500; break;
-                                    case CodecProfileLevel.MPEG4Level4:
-                                        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.MPEG4ProfileHybrid:           // 1-2
-
-                            // Studio profiles are not supported by our codecs.
-
-                            // Only profiles that can decode simple object types are considered.
-                            // The following profiles are not able to.
-                            case CodecProfileLevel.MPEG4ProfileBasicAnimated:    // 1-2
-                            case CodecProfileLevel.MPEG4ProfileScalableTexture:  // 1
-                            case CodecProfileLevel.MPEG4ProfileSimpleFace:       // 1-2
-                            case CodecProfileLevel.MPEG4ProfileAdvancedScalable: // 1-3
-                            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);
-                        if (strict) {
-                            maxWidth = Math.max(W, maxWidth);
-                            maxHeight = Math.max(H, maxHeight);
-                            maxRate = Math.max(FR, maxRate);
-                        } else {
-                            // assuming max 60 fps frame rate and 1:2 aspect ratio
-                            int maxDim = (int)Math.sqrt(FS * 2);
-                            maxWidth = Math.max(maxDim, maxWidth);
-                            maxHeight = Math.max(maxDim, maxHeight);
-                            maxRate = Math.max(Math.max(FR, 60), 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;
-                    int minWidth = maxWidth, minHeight = maxHeight;
-                    int minAlignment = 16;
-                    maxBlocks = 99;
-                    maxBlocksPerSecond = 1485;
-                    maxBps = 64000;
-                    for (CodecProfileLevel profileLevel: profileLevels) {
-                        int MBPS = 0, BR = 0, FR = 0, W = 0, H = 0, minW = minWidth, minH = minHeight;
-                        boolean strict = false; // true: support only sQCIF, QCIF (maybe CIF)
-                        switch (profileLevel.level) {
-                            case CodecProfileLevel.H263Level10:
-                                strict = true; // only supports sQCIF & QCIF
-                                FR = 15; W = 11; H =  9; BR =   1; MBPS =  W * H * FR; break;
-                            case CodecProfileLevel.H263Level20:
-                                strict = true; // only supports sQCIF, QCIF & CIF
-                                FR = 30; W = 22; H = 18; BR =   2; MBPS =  W * H * 15; break;
-                            case CodecProfileLevel.H263Level30:
-                                strict = true; // only supports sQCIF, QCIF & CIF
-                                FR = 30; W = 22; H = 18; BR =   6; MBPS =  W * H * FR; break;
-                            case CodecProfileLevel.H263Level40:
-                                strict = true; // only supports sQCIF, QCIF & CIF
-                                FR = 30; W = 22; H = 18; BR =  32; MBPS =  W * H * FR; break;
-                            case CodecProfileLevel.H263Level45:
-                                // only implies level 10 support
-                                strict = profileLevel.profile == CodecProfileLevel.H263ProfileBaseline
-                                        || profileLevel.profile ==
-                                                CodecProfileLevel.H263ProfileBackwardCompatible;
-                                if (!strict) {
-                                    minW = 1; minH = 1; minAlignment = 4;
-                                }
-                                FR = 15; W = 11; H =  9; BR =   2; MBPS =  W * H * FR; break;
-                            case CodecProfileLevel.H263Level50:
-                                // only supports 50fps for H > 15
-                                minW = 1; minH = 1; minAlignment = 4;
-                                FR = 60; W = 22; H = 18; BR =  64; MBPS =  W * H * 50; break;
-                            case CodecProfileLevel.H263Level60:
-                                // only supports 50fps for H > 15
-                                minW = 1; minH = 1; minAlignment = 4;
-                                FR = 60; W = 45; H = 18; BR = 128; MBPS =  W * H * 50; break;
-                            case CodecProfileLevel.H263Level70:
-                                // only supports 50fps for H > 30
-                                minW = 1; minH = 1; minAlignment = 4;
-                                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;
-                        }
-                        if (strict) {
-                            // Strict levels define sub-QCIF min size and enumerated sizes. We
-                            // cannot express support for "only sQCIF & QCIF (& CIF)" using
-                            // VideoCapabilities but we can express "only QCIF (& CIF)", so set
-                            // minimume size at QCIF.minW = 8; minH = 6;
-                            minW = 11; minH = 9;
-                        } else {
-                            // any support for non-strict levels (including unrecognized profiles or
-                            // levels) allow custom frame size support beyond supported limits
-                            // (other than bitrate)
-                            mAllowMbOverride = true;
-                        }
-                        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);
-                        minWidth = Math.min(minW, minWidth);
-                        minHeight = Math.min(minH, minHeight);
-                    }
-                    // unless we encountered custom frame size support, limit size to QCIF and CIF
-                    // using aspect ratio.
-                    if (!mAllowMbOverride) {
-                        mBlockAspectRatioRange =
-                            Range.create(new Rational(11, 9), new Rational(11, 9));
-                    }
-                    applyMacroBlockLimits(
-                            minWidth, minHeight,
-                            maxWidth, maxHeight,
-                            maxBlocks, maxBlocksPerSecond,
-                            16 /* blockWidth */, 16 /* blockHeight */,
-                            minAlignment /* widthAlignment */, minAlignment /* heightAlignment */);
-                    mFrameRateRange = Range.create(1, maxRate);
-                } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_VP8)) {
-                    maxBlocks = Integer.MAX_VALUE;
-                    maxBlocksPerSecond = Integer.MAX_VALUE;
-
-                    // TODO: set to 100Mbps for now, need a number for VP8
-                    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 = 16;
-                    applyMacroBlockLimits(Short.MAX_VALUE, Short.MAX_VALUE,
-                            maxBlocks, maxBlocksPerSecond, blockSize, blockSize,
-                            1 /* widthAlignment */, 1 /* heightAlignment */);
-                } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_VP9)) {
-                    maxBlocksPerSecond = 829440;
-                    maxBlocks = 36864;
-                    maxBps = 200000;
-                    int maxDim = 512;
-
-                    for (CodecProfileLevel profileLevel: profileLevels) {
-                        long SR = 0; // luma sample rate
-                        int FS = 0;  // luma picture size
-                        int BR = 0;  // bit rate kbps
-                        int D = 0;   // luma dimension
-                        switch (profileLevel.level) {
-                            case CodecProfileLevel.VP9Level1:
-                                SR =      829440; FS =    36864; BR =    200; D =   512; break;
-                            case CodecProfileLevel.VP9Level11:
-                                SR =     2764800; FS =    73728; BR =    800; D =   768; break;
-                            case CodecProfileLevel.VP9Level2:
-                                SR =     4608000; FS =   122880; BR =   1800; D =   960; break;
-                            case CodecProfileLevel.VP9Level21:
-                                SR =     9216000; FS =   245760; BR =   3600; D =  1344; break;
-                            case CodecProfileLevel.VP9Level3:
-                                SR =    20736000; FS =   552960; BR =   7200; D =  2048; break;
-                            case CodecProfileLevel.VP9Level31:
-                                SR =    36864000; FS =   983040; BR =  12000; D =  2752; break;
-                            case CodecProfileLevel.VP9Level4:
-                                SR =    83558400; FS =  2228224; BR =  18000; D =  4160; break;
-                            case CodecProfileLevel.VP9Level41:
-                                SR =   160432128; FS =  2228224; BR =  30000; D =  4160; break;
-                            case CodecProfileLevel.VP9Level5:
-                                SR =   311951360; FS =  8912896; BR =  60000; D =  8384; break;
-                            case CodecProfileLevel.VP9Level51:
-                                SR =   588251136; FS =  8912896; BR = 120000; D =  8384; break;
-                            case CodecProfileLevel.VP9Level52:
-                                SR =  1176502272; FS =  8912896; BR = 180000; D =  8384; break;
-                            case CodecProfileLevel.VP9Level6:
-                                SR =  1176502272; FS = 35651584; BR = 180000; D = 16832; break;
-                            case CodecProfileLevel.VP9Level61:
-                                SR = 2353004544L; FS = 35651584; BR = 240000; D = 16832; break;
-                            case CodecProfileLevel.VP9Level62:
-                                SR = 4706009088L; FS = 35651584; BR = 480000; D = 16832; break;
-                            default:
-                                Log.w(TAG, "Unrecognized level "
-                                        + profileLevel.level + " for " + mime);
-                                errors |= ERROR_UNRECOGNIZED;
-                        }
-                        switch (profileLevel.profile) {
-                            case CodecProfileLevel.VP9Profile0:
-                            case CodecProfileLevel.VP9Profile1:
-                            case CodecProfileLevel.VP9Profile2:
-                            case CodecProfileLevel.VP9Profile3:
-                            case CodecProfileLevel.VP9Profile2HDR:
-                            case CodecProfileLevel.VP9Profile3HDR:
-                            case CodecProfileLevel.VP9Profile2HDR10Plus:
-                            case CodecProfileLevel.VP9Profile3HDR10Plus:
-                                break;
-                            default:
-                                Log.w(TAG, "Unrecognized profile "
-                                        + profileLevel.profile + " for " + mime);
-                                errors |= ERROR_UNRECOGNIZED;
-                        }
-                        errors &= ~ERROR_NONE_SUPPORTED;
-                        maxBlocksPerSecond = Math.max(SR, maxBlocksPerSecond);
-                        maxBlocks = Math.max(FS, maxBlocks);
-                        maxBps = Math.max(BR * 1000, maxBps);
-                        maxDim = Math.max(D, maxDim);
-                    }
-
-                    final int blockSize = 8;
-                    int maxLengthInBlocks = Utils.divUp(maxDim, blockSize);
-                    maxBlocks = Utils.divUp(maxBlocks, blockSize * blockSize);
-                    maxBlocksPerSecond = Utils.divUp(maxBlocksPerSecond, blockSize * blockSize);
-
-                    applyMacroBlockLimits(
-                            maxLengthInBlocks, maxLengthInBlocks,
-                            maxBlocks, maxBlocksPerSecond,
-                            blockSize, blockSize,
-                            1 /* widthAlignment */, 1 /* heightAlignment */);
-                } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_HEVC)) {
-                    // CTBs are at least 8x8 so use 8x8 block size
-                    maxBlocks = 36864 >> 6; // 192x192 pixels == 576 8x8 blocks
-                    maxBlocksPerSecond = maxBlocks * 15;
-                    maxBps = 128000;
-                    for (CodecProfileLevel profileLevel: profileLevels) {
-                        double FR = 0;
-                        int FS = 0;
-                        int BR = 0;
-                        switch (profileLevel.level) {
-                            /* The HEVC spec talks only in a very convoluted manner about the
-                            existence of levels 1-3.1 for High tier, which could also be
-                            understood as 'decoders and encoders should treat these levels
-                            as if they were Main tier', so we do that. */
-                            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:
-                            case CodecProfileLevel.HEVCProfileMainStill:
-                            case CodecProfileLevel.HEVCProfileMain10HDR10:
-                            case CodecProfileLevel.HEVCProfileMain10HDR10Plus:
-                                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;
-                        */
-
-                        FS >>= 6; // convert pixels to blocks
-                        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));
-                    applyMacroBlockLimits(
-                            maxLengthInBlocks, maxLengthInBlocks,
-                            maxBlocks, maxBlocksPerSecond,
-                            8 /* blockWidth */, 8 /* blockHeight */,
-                            1 /* widthAlignment */, 1 /* heightAlignment */);
-                } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_AV1)) {
-                    maxBlocksPerSecond = 829440;
-                    maxBlocks = 36864;
-                    maxBps = 200000;
-                    int maxDim = 512;
-
-                    // Sample rate, Picture Size, Bit rate and luma dimension for AV1 Codec,
-                    // corresponding to the definitions in
-                    // "AV1 Bitstream & Decoding Process Specification", Annex A
-                    // found at https://aomedia.org/av1-bitstream-and-decoding-process-specification/
-                    for (CodecProfileLevel profileLevel: profileLevels) {
-                        long SR = 0; // luma sample rate
-                        int FS = 0;  // luma picture size
-                        int BR = 0;  // bit rate kbps
-                        int D = 0;   // luma D
-                        switch (profileLevel.level) {
-                            case CodecProfileLevel.AV1Level2:
-                                SR =     5529600; FS =   147456; BR =   1500; D =  2048; break;
-                            case CodecProfileLevel.AV1Level21:
-                            case CodecProfileLevel.AV1Level22:
-                            case CodecProfileLevel.AV1Level23:
-                                SR =    10454400; FS =   278784; BR =   3000; D =  2816; break;
-
-                            case CodecProfileLevel.AV1Level3:
-                                SR =    24969600; FS =   665856; BR =   6000; D =  4352; break;
-                            case CodecProfileLevel.AV1Level31:
-                            case CodecProfileLevel.AV1Level32:
-                            case CodecProfileLevel.AV1Level33:
-                                SR =    39938400; FS =  1065024; BR =  10000; D =  5504; break;
-
-                            case CodecProfileLevel.AV1Level4:
-                                SR =    77856768; FS =  2359296; BR =  12000; D =  6144; break;
-                            case CodecProfileLevel.AV1Level41:
-                            case CodecProfileLevel.AV1Level42:
-                            case CodecProfileLevel.AV1Level43:
-                                SR =   155713536; FS =  2359296; BR =  20000; D =  6144; break;
-
-                            case CodecProfileLevel.AV1Level5:
-                                SR =   273715200; FS =  8912896; BR =  30000; D =  8192; break;
-                            case CodecProfileLevel.AV1Level51:
-                                SR =   547430400; FS =  8912896; BR =  40000; D =  8192; break;
-                            case CodecProfileLevel.AV1Level52:
-                                SR =  1094860800; FS =  8912896; BR =  60000; D =  8192; break;
-                            case CodecProfileLevel.AV1Level53:
-                                SR =  1176502272; FS =  8912896; BR =  60000; D =  8192; break;
-
-                            case CodecProfileLevel.AV1Level6:
-                                SR =  1176502272; FS = 35651584; BR =  60000; D = 16384; break;
-                            case CodecProfileLevel.AV1Level61:
-                                SR = 2189721600L; FS = 35651584; BR = 100000; D = 16384; break;
-                            case CodecProfileLevel.AV1Level62:
-                                SR = 4379443200L; FS = 35651584; BR = 160000; D = 16384; break;
-                            case CodecProfileLevel.AV1Level63:
-                                SR = 4706009088L; FS = 35651584; BR = 160000; D = 16384; break;
-
-                            default:
-                                Log.w(TAG, "Unrecognized level "
-                                        + profileLevel.level + " for " + mime);
-                                errors |= ERROR_UNRECOGNIZED;
-                        }
-                        switch (profileLevel.profile) {
-                            case CodecProfileLevel.AV1ProfileMain8:
-                            case CodecProfileLevel.AV1ProfileMain10:
-                            case CodecProfileLevel.AV1ProfileMain10HDR10:
-                            case CodecProfileLevel.AV1ProfileMain10HDR10Plus:
-                                break;
-                            default:
-                                Log.w(TAG, "Unrecognized profile "
-                                        + profileLevel.profile + " for " + mime);
-                                errors |= ERROR_UNRECOGNIZED;
-                        }
-                        errors &= ~ERROR_NONE_SUPPORTED;
-                        maxBlocksPerSecond = Math.max(SR, maxBlocksPerSecond);
-                        maxBlocks = Math.max(FS, maxBlocks);
-                        maxBps = Math.max(BR * 1000, maxBps);
-                        maxDim = Math.max(D, maxDim);
-                    }
-
-                    final int blockSize = 8;
-                    int maxLengthInBlocks = Utils.divUp(maxDim, blockSize);
-                    maxBlocks = Utils.divUp(maxBlocks, blockSize * blockSize);
-                    maxBlocksPerSecond = Utils.divUp(maxBlocksPerSecond, blockSize * blockSize);
-                    applyMacroBlockLimits(
-                            maxLengthInBlocks, maxLengthInBlocks,
-                            maxBlocks, maxBlocksPerSecond,
-                            blockSize, blockSize,
-                            1 /* widthAlignment */, 1 /* heightAlignment */);
-                } else {
-                    Log.w(TAG, "Unsupported mime " + mime);
-                    // using minimal bitrate here.  should be overridden by
-                    // info from media_codecs.xml
-                    maxBps = 64000;
-                    errors |= ERROR_UNSUPPORTED;
-                }
-                mBitrateRange = Range.create(1, maxBps);
-                mParent.mError |= errors;
-            }
-        }
-
-        /* package private */ static final class VideoCapsNativeImpl implements VideoCapsIntf {
-            private long mNativeContext; // accessed by native methods
-
-            private Range<Integer> mBitrateRange;
-            private Range<Integer> mHeightRange;
-            private Range<Integer> mWidthRange;
-            private Range<Integer> mFrameRateRange;
-            private List<PerformancePoint> mPerformancePoints;
-
-            private int mWidthAlignment;
-            private int mHeightAlignment;
-
-            // Used by JNI to construct Java VideoCapsNativeImpl
-            /** package private */ VideoCapsNativeImpl(Range<Integer> bitrateRange,
-                    Range<Integer> widthRange, Range<Integer> heightRange,
-                    Range<Integer> frameRateRange, List<PerformancePoint> performancePoints,
-                    int widthAlignment, int heightAlignment) {
-                mBitrateRange = new Range<Integer>(bitrateRange.getLower(),
-                        bitrateRange.getUpper());
-                mWidthRange = new Range<Integer>(widthRange.getLower(), widthRange.getUpper());
-                mHeightRange = new Range<Integer>(heightRange.getLower(), heightRange.getUpper());
-                mFrameRateRange = new Range<Integer>(frameRateRange.getLower(),
-                        frameRateRange.getUpper());
-                mPerformancePoints = new ArrayList<PerformancePoint>();
-                for (PerformancePoint pp : performancePoints) {
-                    mPerformancePoints.add(new PerformancePoint(pp));
-                }
-                mWidthAlignment = widthAlignment;
-                mHeightAlignment = heightAlignment;
-            }
-
-            /* no public constructor */
-            private VideoCapsNativeImpl() { }
-
-            public Range<Integer> getBitrateRange() {
-                return mBitrateRange;
-            }
-
-            public Range<Integer> getSupportedWidths() {
-                return mWidthRange;
-            }
-
-            public Range<Integer> getSupportedHeights() {
-                return mHeightRange;
-            }
-
-            public int getWidthAlignment() {
-                return mWidthAlignment;
-            }
-
-            public int getHeightAlignment() {
-                return mHeightAlignment;
-            }
-
-            /** @hide */
-            public int getSmallerDimensionUpperLimit() {
-                return native_getSmallerDimensionUpperLimit();
-            }
-
-            public Range<Integer> getSupportedFrameRates() {
-                return mFrameRateRange;
-            }
-
-            @Nullable
-            public List<PerformancePoint> getSupportedPerformancePoints() {
-                return mPerformancePoints;
-            }
-
-            public Range<Integer> getSupportedWidthsFor(int height) {
-                return native_getSupportedWidthsFor(height);
-            }
-
-            public Range<Integer> getSupportedHeightsFor(int width) {
-                return native_getSupportedHeightsFor(width);
-            }
-
-            public Range<Double> getSupportedFrameRatesFor(int width, int height) {
-                return native_getSupportedFrameRatesFor(width, height);
-            }
-
-            /** @throws IllegalArgumentException if the video size is not supported. */
-            @Nullable
-            public Range<Double> getAchievableFrameRatesFor(int width, int height) {
-                return native_getAchievableFrameRatesFor(width, height);
-            }
-
-            public boolean areSizeAndRateSupported(int width, int height, double frameRate) {
-                return native_areSizeAndRateSupported(width, height, frameRate);
-            }
-
-            public boolean isSizeSupported(int width, int height) {
-                return native_isSizeSupported(width, height);
-            }
-
-            /** @hide */
-            public boolean supportsFormat(MediaFormat format) {
-                throw new UnsupportedOperationException(
-                    "Java Implementation should not call native implemenatation");
-            }
-
-            private native Range<Integer> native_getSupportedWidthsFor(int height);
-            private native Range<Integer> native_getSupportedHeightsFor(int width);
-            private native Range<Double> native_getSupportedFrameRatesFor(int width, int height);
-            private native Range<Double> native_getAchievableFrameRatesFor(int width, int height);
-            private native boolean native_areSizeAndRateSupported(
-                    int width, int height, double frameRate);
-            private native boolean native_isSizeSupported(int width, int height);
-            private native int native_getSmallerDimensionUpperLimit();
-
-            private static native void native_init();
-
-            static {
-                System.loadLibrary("media_jni");
-                native_init();
-            }
-        }
-
-        private VideoCapsIntf mImpl;
-
-        /** @hide */
-        public static VideoCapabilities create(
-                MediaFormat info, CodecCapabilities.CodecCapsLegacyImpl parent) {
-            VideoCapsLegacyImpl impl = VideoCapsLegacyImpl.create(info, parent);
-            VideoCapabilities caps = new VideoCapabilities(impl);
-            return caps;
-        }
-
-        /* package private */ VideoCapabilities(VideoCapsIntf impl) {
-            mImpl = impl;
-        }
-
-        /* no public constructor */
-        private VideoCapabilities() { }
-
-        /**
-         * Returns the range of supported bitrates in bits/second.
-         */
-        public Range<Integer> getBitrateRange() {
-            return mImpl.getBitrateRange();
-        }
-
-        /**
-         * Returns the range of supported video widths.
-         * <p class=note>
-         * 32-bit processes will not support resolutions larger than 4096x4096 due to
-         * the limited address space.
-         */
-        public Range<Integer> getSupportedWidths() {
-            return mImpl.getSupportedWidths();
-        }
-
-        /**
-         * Returns the range of supported video heights.
-         * <p class=note>
-         * 32-bit processes will not support resolutions larger than 4096x4096 due to
-         * the limited address space.
-         */
-        public Range<Integer> getSupportedHeights() {
-            return mImpl.getSupportedHeights();
-        }
-
-        /**
-         * 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 mImpl.getWidthAlignment();
-        }
-
-        /**
-         * 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 mImpl.getWidthAlignment();
-        }
-
-        /**
-         * 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 mImpl.getSmallerDimensionUpperLimit();
-        }
-
-        /**
-         * 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 mImpl.getSupportedFrameRates();
-        }
-
-        /**
-         * Returns the range of supported video widths for a video height.
-         * @param height the height of the video
-         */
-        public Range<Integer> getSupportedWidthsFor(int height) {
-            return mImpl.getSupportedWidthsFor(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) {
-            return mImpl.getSupportedHeightsFor(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) {
-            return mImpl.getSupportedFrameRatesFor(width, height);
-        }
-
-        /**
-         * Returns the range of achievable video frame rates for a video size.
-         * May return {@code null}, if the codec did not publish any measurement
-         * data.
-         * <p>
-         * This is a performance estimate provided by the device manufacturer based on statistical
-         * sampling of full-speed decoding and encoding measurements in various configurations
-         * of common video sizes supported by the codec. As such it should only be used to
-         * compare individual codecs on the device. The value is not suitable for comparing
-         * different devices or even different android releases for the same device.
-         * <p>
-         * <em>On {@link android.os.Build.VERSION_CODES#M} release</em> the returned range
-         * corresponds to the fastest frame rates achieved in the tested configurations. As
-         * such, it should not be used to gauge guaranteed or even average codec performance
-         * on the device.
-         * <p>
-         * <em>On {@link android.os.Build.VERSION_CODES#N} release</em> the returned range
-         * corresponds closer to sustained performance <em>in tested configurations</em>.
-         * One can expect to achieve sustained performance higher than the lower limit more than
-         * 50% of the time, and higher than half of the lower limit at least 90% of the time
-         * <em>in tested configurations</em>.
-         * Conversely, one can expect performance lower than twice the upper limit at least
-         * 90% of the time.
-         * <p class=note>
-         * Tested configurations use a single active codec. For use cases where multiple
-         * codecs are active, applications can expect lower and in most cases significantly lower
-         * performance.
-         * <p class=note>
-         * The returned range value is interpolated from the nearest frame size(s) tested.
-         * Codec performance is severely impacted by other activity on the device as well
-         * as environmental factors (such as battery level, temperature or power source), and can
-         * vary significantly even in a steady environment.
-         * <p class=note>
-         * Use this method in cases where only codec performance matters, e.g. to evaluate if
-         * a codec has any chance of meeting a performance target. Codecs are listed
-         * in {@link MediaCodecList} in the preferred order as defined by the device
-         * manufacturer. As such, applications should use the first suitable codec in the
-         * list to achieve the best balance between power use and performance.
-         *
-         * @param width the width of the video
-         * @param height the height of the video
-         *
-         * @throws IllegalArgumentException if the video size is not supported.
-         */
-        @Nullable
-        public Range<Double> getAchievableFrameRatesFor(int width, int height) {
-            return mImpl.getAchievableFrameRatesFor(width, height);
-        }
-
         /**
          * Returns the supported performance points. May return {@code null} if the codec did not
          * publish any performance point information (e.g. the vendor codecs have not been updated
@@ -4518,15 +2542,16 @@
          */
         @Nullable
         public List<PerformancePoint> getSupportedPerformancePoints() {
-            return mImpl.getSupportedPerformancePoints();
+            return mPerformancePoints;
         }
 
         /**
          * 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 mImpl.areSizeAndRateSupported(width, height, frameRate);
+        public boolean areSizeAndRateSupported(
+                int width, int height, double frameRate) {
+            return supports(width, height, frameRate);
         }
 
         /**
@@ -4534,16 +2559,1282 @@
          * {@code height}) is supported.
          */
         public boolean isSizeSupported(int width, int height) {
-            return mImpl.isSizeSupported(width, height);
+            return supports(width, height, null);
         }
 
+        private boolean supports(Integer width, Integer height, Number 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.doubleValue()));
+            }
+            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.doubleValue();
+                    ok = mBlocksPerSecondRange.contains(
+                            Utils.longRangeFor(blocksPerSec));
+                }
+            }
+            return ok;
+        }
+
+        /* package private */
+        // must not contain KEY_PROFILE
+        static final Set<String> VIDEO_LEVEL_CRITICAL_FORMAT_KEYS = Set.of(
+                MediaFormat.KEY_WIDTH,
+                MediaFormat.KEY_HEIGHT,
+                MediaFormat.KEY_FRAME_RATE,
+                MediaFormat.KEY_BIT_RATE,
+                MediaFormat.KEY_MIME);
+
         /**
          * @hide
-         * @throws java.lang.ClassCastException
-         * @throws java.lang.UnsupportedOperationException
-         */
+         * @throws java.lang.ClassCastException */
         public boolean supportsFormat(MediaFormat format) {
-            return mImpl.supportsFormat(format);
+            final Map<String, Object> map = format.getMap();
+            Integer width = (Integer)map.get(MediaFormat.KEY_WIDTH);
+            Integer height = (Integer)map.get(MediaFormat.KEY_HEIGHT);
+            Number rate = (Number)map.get(MediaFormat.KEY_FRAME_RATE);
+
+            if (!supports(width, height, rate)) {
+                return false;
+            }
+
+            if (!CodecCapabilities.supportsBitrate(mBitrateRange, format)) {
+                return false;
+            }
+
+            // we ignore color-format for now as it is not reliably reported by codec
+            return true;
+        }
+
+        /* no public constructor */
+        private VideoCapabilities() { }
+
+        /** @hide */
+        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+        public static VideoCapabilities create(
+                MediaFormat info, CodecCapabilities parent) {
+            VideoCapabilities caps = new VideoCapabilities();
+            caps.init(info, parent);
+            return caps;
+        }
+
+        private 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 = BITRATE_RANGE;
+
+            mWidthRange  = getSizeRange();
+            mHeightRange = getSizeRange();
+            mFrameRateRange = FRAME_RATE_RANGE;
+
+            mHorizontalBlockRange = getSizeRange();
+            mVerticalBlockRange   = getSizeRange();
+
+            // full positive ranges are supported as these get calculated
+            mBlockCountRange      = POSITIVE_INTEGERS;
+            mBlocksPerSecondRange = POSITIVE_LONGS;
+
+            mBlockAspectRatioRange = POSITIVE_RATIONALS;
+            mAspectRatioRange      = POSITIVE_RATIONALS;
+
+            mWidthAlignment = 1;
+            mHeightAlignment = 1;
+            mBlockWidth = 1;
+            mBlockHeight = 1;
+            mSmallerDimensionUpperLimit = getSizeRange().getUpper();
+        }
+
+        private @Nullable List<PerformancePoint> getPerformancePoints(Map<String, Object> map) {
+            Vector<PerformancePoint> ret = new Vector<>();
+            final String prefix = "performance-point-";
+            Set<String> keys = map.keySet();
+            for (String key : keys) {
+                // looking for: performance-point-WIDTHxHEIGHT-range
+                if (!key.startsWith(prefix)) {
+                    continue;
+                }
+                String subKey = key.substring(prefix.length());
+                if (subKey.equals("none") && ret.size() == 0) {
+                    // This means that component knowingly did not publish performance points.
+                    // This is different from when the component forgot to publish performance
+                    // points.
+                    return Collections.unmodifiableList(ret);
+                }
+                String[] temp = key.split("-");
+                if (temp.length != 4) {
+                    continue;
+                }
+                String sizeStr = temp[2];
+                Size size = Utils.parseSize(sizeStr, null);
+                if (size == null || size.getWidth() * size.getHeight() <= 0) {
+                    continue;
+                }
+                Range<Long> range = Utils.parseLongRange(map.get(key), null);
+                if (range == null || range.getLower() < 0 || range.getUpper() < 0) {
+                    continue;
+                }
+                PerformancePoint given = new PerformancePoint(
+                        size.getWidth(), size.getHeight(), range.getLower().intValue(),
+                        range.getUpper().intValue(), new Size(mBlockWidth, mBlockHeight));
+                PerformancePoint rotated = new PerformancePoint(
+                        size.getHeight(), size.getWidth(), range.getLower().intValue(),
+                        range.getUpper().intValue(), new Size(mBlockWidth, mBlockHeight));
+                ret.add(given);
+                if (!given.covers(rotated)) {
+                    ret.add(rotated);
+                }
+            }
+
+            // check if the component specified no performance point indication
+            if (ret.size() == 0) {
+                return null;
+            }
+
+            // sort reversed by area first, then by frame rate
+            ret.sort((a, b) ->
+                     -((a.getMaxMacroBlocks() != b.getMaxMacroBlocks()) ?
+                               (a.getMaxMacroBlocks() < b.getMaxMacroBlocks() ? -1 : 1) :
+                       (a.getMaxMacroBlockRate() != b.getMaxMacroBlockRate()) ?
+                               (a.getMaxMacroBlockRate() < b.getMaxMacroBlockRate() ? -1 : 1) :
+                       (a.getMaxFrameRate() != b.getMaxFrameRate()) ?
+                               (a.getMaxFrameRate() < b.getMaxFrameRate() ? -1 : 1) : 0));
+
+            return Collections.unmodifiableList(ret);
+        }
+
+        private Map<Size, Range<Long>> getMeasuredFrameRates(Map<String, Object> map) {
+            Map<Size, Range<Long>> ret = new HashMap<Size, Range<Long>>();
+            final String prefix = "measured-frame-rate-";
+            Set<String> keys = map.keySet();
+            for (String key : keys) {
+                // looking for: measured-frame-rate-WIDTHxHEIGHT-range
+                if (!key.startsWith(prefix)) {
+                    continue;
+                }
+                String subKey = key.substring(prefix.length());
+                String[] temp = key.split("-");
+                if (temp.length != 5) {
+                    continue;
+                }
+                String sizeStr = temp[3];
+                Size size = Utils.parseSize(sizeStr, null);
+                if (size == null || size.getWidth() * size.getHeight() <= 0) {
+                    continue;
+                }
+                Range<Long> range = Utils.parseLongRange(map.get(key), null);
+                if (range == null || range.getLower() < 0 || range.getUpper() < 0) {
+                    continue;
+                }
+                ret.put(size, range);
+            }
+            return ret;
+        }
+
+        private static Pair<Range<Integer>, Range<Integer>> parseWidthHeightRanges(Object o) {
+            Pair<Size, Size> range = Utils.parseSizeRange(o);
+            if (range != null) {
+                try {
+                    return Pair.create(
+                            Range.create(range.first.getWidth(), range.second.getWidth()),
+                            Range.create(range.first.getHeight(), range.second.getHeight()));
+                } catch (IllegalArgumentException e) {
+                    Log.w(TAG, "could not parse size range '" + o + "'");
+                }
+            }
+            return null;
+        }
+
+        /** @hide */
+        public static int equivalentVP9Level(MediaFormat info) {
+            final Map<String, Object> map = info.getMap();
+
+            Size blockSize = Utils.parseSize(map.get("block-size"), new Size(8, 8));
+            int BS = blockSize.getWidth() * blockSize.getHeight();
+
+            Range<Integer> counts = Utils.parseIntRange(map.get("block-count-range"), null);
+            int FS = counts == null ? 0 : BS * counts.getUpper();
+
+            Range<Long> blockRates =
+                Utils.parseLongRange(map.get("blocks-per-second-range"), null);
+            long SR = blockRates == null ? 0 : BS * blockRates.getUpper();
+
+            Pair<Range<Integer>, Range<Integer>> dimensionRanges =
+                parseWidthHeightRanges(map.get("size-range"));
+            int D = dimensionRanges == null ? 0 : Math.max(
+                    dimensionRanges.first.getUpper(), dimensionRanges.second.getUpper());
+
+            Range<Integer> bitRates = Utils.parseIntRange(map.get("bitrate-range"), null);
+            int BR = bitRates == null ? 0 : Utils.divUp(bitRates.getUpper(), 1000);
+
+            if (SR <=      829440 && FS <=    36864 && BR <=    200 && D <=   512)
+                return CodecProfileLevel.VP9Level1;
+            if (SR <=     2764800 && FS <=    73728 && BR <=    800 && D <=   768)
+                return CodecProfileLevel.VP9Level11;
+            if (SR <=     4608000 && FS <=   122880 && BR <=   1800 && D <=   960)
+                return CodecProfileLevel.VP9Level2;
+            if (SR <=     9216000 && FS <=   245760 && BR <=   3600 && D <=  1344)
+                return CodecProfileLevel.VP9Level21;
+            if (SR <=    20736000 && FS <=   552960 && BR <=   7200 && D <=  2048)
+                return CodecProfileLevel.VP9Level3;
+            if (SR <=    36864000 && FS <=   983040 && BR <=  12000 && D <=  2752)
+                return CodecProfileLevel.VP9Level31;
+            if (SR <=    83558400 && FS <=  2228224 && BR <=  18000 && D <=  4160)
+                return CodecProfileLevel.VP9Level4;
+            if (SR <=   160432128 && FS <=  2228224 && BR <=  30000 && D <=  4160)
+                return CodecProfileLevel.VP9Level41;
+            if (SR <=   311951360 && FS <=  8912896 && BR <=  60000 && D <=  8384)
+                return CodecProfileLevel.VP9Level5;
+            if (SR <=   588251136 && FS <=  8912896 && BR <= 120000 && D <=  8384)
+                return CodecProfileLevel.VP9Level51;
+            if (SR <=  1176502272 && FS <=  8912896 && BR <= 180000 && D <=  8384)
+                return CodecProfileLevel.VP9Level52;
+            if (SR <=  1176502272 && FS <= 35651584 && BR <= 180000 && D <= 16832)
+                return CodecProfileLevel.VP9Level6;
+            if (SR <= 2353004544L && FS <= 35651584 && BR <= 240000 && D <= 16832)
+                return CodecProfileLevel.VP9Level61;
+            if (SR <= 4706009088L && FS <= 35651584 && BR <= 480000 && D <= 16832)
+                return CodecProfileLevel.VP9Level62;
+            // returning largest level
+            return CodecProfileLevel.VP9Level62;
+        }
+
+        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, bitRates = 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);
+            mMeasuredFrameRates = getMeasuredFrameRates(map);
+            mPerformancePoints = getPerformancePoints(map);
+            Pair<Range<Integer>, Range<Integer>> sizeRanges =
+                parseWidthHeightRanges(map.get("size-range"));
+            if (sizeRanges != null) {
+                widths = sizeRanges.first;
+                heights = sizeRanges.second;
+            }
+            // 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 (map.containsKey("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;
+                }
+            }
+            bitRates = Utils.parseIntRange(map.get("bitrate-range"), null);
+            if (bitRates != null) {
+                try {
+                    bitRates = bitRates.intersect(BITRATE_RANGE);
+                } catch (IllegalArgumentException e) {
+                    Log.w(TAG,  "bitrate range (" + bitRates
+                            + ") is out of limits: " + BITRATE_RANGE);
+                    bitRates = 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 || mAllowMbOverride) {
+                // codec supports profiles that we don't know.
+                // Use supplied values clipped to platform limits
+                if (widths != null) {
+                    mWidthRange = getSizeRange().intersect(widths);
+                }
+                if (heights != null) {
+                    mHeightRange = getSizeRange().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);
+                }
+                if (bitRates != null) {
+                    // only allow bitrate override if unsupported profiles were encountered
+                    if ((mParent.mError & ERROR_UNSUPPORTED) != 0) {
+                        mBitrateRange = BITRATE_RANGE.intersect(bitRates);
+                    } else {
+                        mBitrateRange = mBitrateRange.intersect(bitRates);
+                    }
+                }
+            } 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);
+                }
+                if (bitRates != null) {
+                    mBitrateRange = mBitrateRange.intersect(bitRates);
+                }
+            }
+            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) {
+            applyMacroBlockLimits(
+                    1 /* minHorizontalBlocks */, 1 /* minVerticalBlocks */,
+                    maxHorizontalBlocks, maxVerticalBlocks,
+                    maxBlocks, maxBlocksPerSecond,
+                    blockWidth, blockHeight, widthAlignment, heightAlignment);
+        }
+
+        private void applyMacroBlockLimits(
+                int minHorizontalBlocks, int minVerticalBlocks,
+                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(
+                            Utils.divUp(minHorizontalBlocks, (mBlockWidth / blockWidth)),
+                            maxHorizontalBlocks / (mBlockWidth / blockWidth));
+            mVerticalBlockRange =
+                    mVerticalBlockRange.intersect(
+                            Utils.divUp(minVerticalBlocks, (mBlockHeight / blockHeight)),
+                            maxVerticalBlocks / (mBlockHeight / blockHeight));
+        }
+
+        private void applyLevelLimits() {
+            long 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;
+                        case CodecProfileLevel.AVCLevel6:
+                            MBPS =  4177920; FS = 139264; BR = 240000; DPB = 696320; break;
+                        case CodecProfileLevel.AVCLevel61:
+                            MBPS =  8355840; FS = 139264; BR = 480000; DPB = 696320; break;
+                        case CodecProfileLevel.AVCLevel62:
+                            MBPS = 16711680; FS = 139264; BR = 800000; DPB = 696320; break;
+                        default:
+                            Log.w(TAG, "Unrecognized level "
+                                    + profileLevel.level + " for " + mime);
+                            errors |= ERROR_UNRECOGNIZED;
+                    }
+                    switch (profileLevel.profile) {
+                        case CodecProfileLevel.AVCProfileConstrainedHigh:
+                        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.AVCProfileConstrainedBaseline:
+                        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_MPEG2)) {
+                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.MPEG2ProfileSimple:
+                            switch (profileLevel.level) {
+                                case CodecProfileLevel.MPEG2LevelML:
+                                    FR = 30; W = 45; H =  36; MBPS =  40500; FS =  1620; BR =  15000; break;
+                                default:
+                                    Log.w(TAG, "Unrecognized profile/level "
+                                            + profileLevel.profile + "/"
+                                            + profileLevel.level + " for " + mime);
+                                    errors |= ERROR_UNRECOGNIZED;
+                            }
+                            break;
+                        case CodecProfileLevel.MPEG2ProfileMain:
+                            switch (profileLevel.level) {
+                                case CodecProfileLevel.MPEG2LevelLL:
+                                    FR = 30; W = 22; H =  18; MBPS =  11880; FS =   396; BR =  4000; break;
+                                case CodecProfileLevel.MPEG2LevelML:
+                                    FR = 30; W = 45; H =  36; MBPS =  40500; FS =  1620; BR = 15000; break;
+                                case CodecProfileLevel.MPEG2LevelH14:
+                                    FR = 60; W = 90; H =  68; MBPS = 183600; FS =  6120; BR = 60000; break;
+                                case CodecProfileLevel.MPEG2LevelHL:
+                                    FR = 60; W = 120; H = 68; MBPS = 244800; FS =  8160; BR = 80000; break;
+                                case CodecProfileLevel.MPEG2LevelHP:
+                                    FR = 60; W = 120; H = 68; MBPS = 489600; FS =  8160; BR = 80000; break;
+                                default:
+                                    Log.w(TAG, "Unrecognized profile/level "
+                                            + profileLevel.profile + "/"
+                                            + profileLevel.level + " for " + mime);
+                                    errors |= ERROR_UNRECOGNIZED;
+                            }
+                            break;
+                        case CodecProfileLevel.MPEG2Profile422:
+                        case CodecProfileLevel.MPEG2ProfileSNR:
+                        case CodecProfileLevel.MPEG2ProfileSpatial:
+                        case CodecProfileLevel.MPEG2ProfileHigh:
+                            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_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 strict = false; // true: W, H and FR are individual max limits
+                    boolean supported = true;
+                    switch (profileLevel.profile) {
+                        case CodecProfileLevel.MPEG4ProfileSimple:
+                            switch (profileLevel.level) {
+                                case CodecProfileLevel.MPEG4Level0:
+                                    strict = true;
+                                    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:
+                                    strict = true;
+                                    FR = 15; 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.MPEG4Level4a:
+                                    FR = 30; W = 40; H = 30; MBPS = 36000; FS = 1200; BR = 4000; break;
+                                case CodecProfileLevel.MPEG4Level5:
+                                    FR = 30; W = 45; H = 36; MBPS = 40500; FS = 1620; BR = 8000; break;
+                                case CodecProfileLevel.MPEG4Level6:
+                                    FR = 30; W = 80; H = 45; MBPS = 108000; FS = 3600; BR = 12000; 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:
+                                    FR = 30; W = 22; H = 18; MBPS = 11880; FS =  396; BR = 1500; break;
+                                case CodecProfileLevel.MPEG4Level4:
+                                    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.MPEG4ProfileHybrid:           // 1-2
+
+                        // Studio profiles are not supported by our codecs.
+
+                        // Only profiles that can decode simple object types are considered.
+                        // The following profiles are not able to.
+                        case CodecProfileLevel.MPEG4ProfileBasicAnimated:    // 1-2
+                        case CodecProfileLevel.MPEG4ProfileScalableTexture:  // 1
+                        case CodecProfileLevel.MPEG4ProfileSimpleFace:       // 1-2
+                        case CodecProfileLevel.MPEG4ProfileAdvancedScalable: // 1-3
+                        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);
+                    if (strict) {
+                        maxWidth = Math.max(W, maxWidth);
+                        maxHeight = Math.max(H, maxHeight);
+                        maxRate = Math.max(FR, maxRate);
+                    } else {
+                        // assuming max 60 fps frame rate and 1:2 aspect ratio
+                        int maxDim = (int)Math.sqrt(FS * 2);
+                        maxWidth = Math.max(maxDim, maxWidth);
+                        maxHeight = Math.max(maxDim, maxHeight);
+                        maxRate = Math.max(Math.max(FR, 60), 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;
+                int minWidth = maxWidth, minHeight = maxHeight;
+                int minAlignment = 16;
+                maxBlocks = 99;
+                maxBlocksPerSecond = 1485;
+                maxBps = 64000;
+                for (CodecProfileLevel profileLevel: profileLevels) {
+                    int MBPS = 0, BR = 0, FR = 0, W = 0, H = 0, minW = minWidth, minH = minHeight;
+                    boolean strict = false; // true: support only sQCIF, QCIF (maybe CIF)
+                    switch (profileLevel.level) {
+                        case CodecProfileLevel.H263Level10:
+                            strict = true; // only supports sQCIF & QCIF
+                            FR = 15; W = 11; H =  9; BR =   1; MBPS =  W * H * FR; break;
+                        case CodecProfileLevel.H263Level20:
+                            strict = true; // only supports sQCIF, QCIF & CIF
+                            FR = 30; W = 22; H = 18; BR =   2; MBPS =  W * H * 15; break;
+                        case CodecProfileLevel.H263Level30:
+                            strict = true; // only supports sQCIF, QCIF & CIF
+                            FR = 30; W = 22; H = 18; BR =   6; MBPS =  W * H * FR; break;
+                        case CodecProfileLevel.H263Level40:
+                            strict = true; // only supports sQCIF, QCIF & CIF
+                            FR = 30; W = 22; H = 18; BR =  32; MBPS =  W * H * FR; break;
+                        case CodecProfileLevel.H263Level45:
+                            // only implies level 10 support
+                            strict = profileLevel.profile == CodecProfileLevel.H263ProfileBaseline
+                                    || profileLevel.profile ==
+                                            CodecProfileLevel.H263ProfileBackwardCompatible;
+                            if (!strict) {
+                                minW = 1; minH = 1; minAlignment = 4;
+                            }
+                            FR = 15; W = 11; H =  9; BR =   2; MBPS =  W * H * FR; break;
+                        case CodecProfileLevel.H263Level50:
+                            // only supports 50fps for H > 15
+                            minW = 1; minH = 1; minAlignment = 4;
+                            FR = 60; W = 22; H = 18; BR =  64; MBPS =  W * H * 50; break;
+                        case CodecProfileLevel.H263Level60:
+                            // only supports 50fps for H > 15
+                            minW = 1; minH = 1; minAlignment = 4;
+                            FR = 60; W = 45; H = 18; BR = 128; MBPS =  W * H * 50; break;
+                        case CodecProfileLevel.H263Level70:
+                            // only supports 50fps for H > 30
+                            minW = 1; minH = 1; minAlignment = 4;
+                            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;
+                    }
+                    if (strict) {
+                        // Strict levels define sub-QCIF min size and enumerated sizes. We cannot
+                        // express support for "only sQCIF & QCIF (& CIF)" using VideoCapabilities
+                        // but we can express "only QCIF (& CIF)", so set minimume size at QCIF.
+                        // minW = 8; minH = 6;
+                        minW = 11; minH = 9;
+                    } else {
+                        // any support for non-strict levels (including unrecognized profiles or
+                        // levels) allow custom frame size support beyond supported limits
+                        // (other than bitrate)
+                        mAllowMbOverride = true;
+                    }
+                    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);
+                    minWidth = Math.min(minW, minWidth);
+                    minHeight = Math.min(minH, minHeight);
+                }
+                // unless we encountered custom frame size support, limit size to QCIF and CIF
+                // using aspect ratio.
+                if (!mAllowMbOverride) {
+                    mBlockAspectRatioRange =
+                        Range.create(new Rational(11, 9), new Rational(11, 9));
+                }
+                applyMacroBlockLimits(
+                        minWidth, minHeight,
+                        maxWidth, maxHeight,
+                        maxBlocks, maxBlocksPerSecond,
+                        16 /* blockWidth */, 16 /* blockHeight */,
+                        minAlignment /* widthAlignment */, minAlignment /* heightAlignment */);
+                mFrameRateRange = Range.create(1, maxRate);
+            } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_VP8)) {
+                maxBlocks = Integer.MAX_VALUE;
+                maxBlocksPerSecond = Integer.MAX_VALUE;
+
+                // TODO: set to 100Mbps for now, need a number for VP8
+                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 = 16;
+                applyMacroBlockLimits(Short.MAX_VALUE, Short.MAX_VALUE,
+                        maxBlocks, maxBlocksPerSecond, blockSize, blockSize,
+                        1 /* widthAlignment */, 1 /* heightAlignment */);
+            } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_VP9)) {
+                maxBlocksPerSecond = 829440;
+                maxBlocks = 36864;
+                maxBps = 200000;
+                int maxDim = 512;
+
+                for (CodecProfileLevel profileLevel: profileLevels) {
+                    long SR = 0; // luma sample rate
+                    int FS = 0;  // luma picture size
+                    int BR = 0;  // bit rate kbps
+                    int D = 0;   // luma dimension
+                    switch (profileLevel.level) {
+                        case CodecProfileLevel.VP9Level1:
+                            SR =      829440; FS =    36864; BR =    200; D =   512; break;
+                        case CodecProfileLevel.VP9Level11:
+                            SR =     2764800; FS =    73728; BR =    800; D =   768; break;
+                        case CodecProfileLevel.VP9Level2:
+                            SR =     4608000; FS =   122880; BR =   1800; D =   960; break;
+                        case CodecProfileLevel.VP9Level21:
+                            SR =     9216000; FS =   245760; BR =   3600; D =  1344; break;
+                        case CodecProfileLevel.VP9Level3:
+                            SR =    20736000; FS =   552960; BR =   7200; D =  2048; break;
+                        case CodecProfileLevel.VP9Level31:
+                            SR =    36864000; FS =   983040; BR =  12000; D =  2752; break;
+                        case CodecProfileLevel.VP9Level4:
+                            SR =    83558400; FS =  2228224; BR =  18000; D =  4160; break;
+                        case CodecProfileLevel.VP9Level41:
+                            SR =   160432128; FS =  2228224; BR =  30000; D =  4160; break;
+                        case CodecProfileLevel.VP9Level5:
+                            SR =   311951360; FS =  8912896; BR =  60000; D =  8384; break;
+                        case CodecProfileLevel.VP9Level51:
+                            SR =   588251136; FS =  8912896; BR = 120000; D =  8384; break;
+                        case CodecProfileLevel.VP9Level52:
+                            SR =  1176502272; FS =  8912896; BR = 180000; D =  8384; break;
+                        case CodecProfileLevel.VP9Level6:
+                            SR =  1176502272; FS = 35651584; BR = 180000; D = 16832; break;
+                        case CodecProfileLevel.VP9Level61:
+                            SR = 2353004544L; FS = 35651584; BR = 240000; D = 16832; break;
+                        case CodecProfileLevel.VP9Level62:
+                            SR = 4706009088L; FS = 35651584; BR = 480000; D = 16832; break;
+                        default:
+                            Log.w(TAG, "Unrecognized level "
+                                    + profileLevel.level + " for " + mime);
+                            errors |= ERROR_UNRECOGNIZED;
+                    }
+                    switch (profileLevel.profile) {
+                        case CodecProfileLevel.VP9Profile0:
+                        case CodecProfileLevel.VP9Profile1:
+                        case CodecProfileLevel.VP9Profile2:
+                        case CodecProfileLevel.VP9Profile3:
+                        case CodecProfileLevel.VP9Profile2HDR:
+                        case CodecProfileLevel.VP9Profile3HDR:
+                        case CodecProfileLevel.VP9Profile2HDR10Plus:
+                        case CodecProfileLevel.VP9Profile3HDR10Plus:
+                            break;
+                        default:
+                            Log.w(TAG, "Unrecognized profile "
+                                    + profileLevel.profile + " for " + mime);
+                            errors |= ERROR_UNRECOGNIZED;
+                    }
+                    errors &= ~ERROR_NONE_SUPPORTED;
+                    maxBlocksPerSecond = Math.max(SR, maxBlocksPerSecond);
+                    maxBlocks = Math.max(FS, maxBlocks);
+                    maxBps = Math.max(BR * 1000, maxBps);
+                    maxDim = Math.max(D, maxDim);
+                }
+
+                final int blockSize = 8;
+                int maxLengthInBlocks = Utils.divUp(maxDim, blockSize);
+                maxBlocks = Utils.divUp(maxBlocks, blockSize * blockSize);
+                maxBlocksPerSecond = Utils.divUp(maxBlocksPerSecond, blockSize * blockSize);
+
+                applyMacroBlockLimits(
+                        maxLengthInBlocks, maxLengthInBlocks,
+                        maxBlocks, maxBlocksPerSecond,
+                        blockSize, blockSize,
+                        1 /* widthAlignment */, 1 /* heightAlignment */);
+            } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_HEVC)) {
+                // CTBs are at least 8x8 so use 8x8 block size
+                maxBlocks = 36864 >> 6; // 192x192 pixels == 576 8x8 blocks
+                maxBlocksPerSecond = maxBlocks * 15;
+                maxBps = 128000;
+                for (CodecProfileLevel profileLevel: profileLevels) {
+                    double FR = 0;
+                    int FS = 0;
+                    int BR = 0;
+                    switch (profileLevel.level) {
+                        /* The HEVC spec talks only in a very convoluted manner about the
+                           existence of levels 1-3.1 for High tier, which could also be
+                           understood as 'decoders and encoders should treat these levels
+                           as if they were Main tier', so we do that. */
+                        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:
+                        case CodecProfileLevel.HEVCProfileMainStill:
+                        case CodecProfileLevel.HEVCProfileMain10HDR10:
+                        case CodecProfileLevel.HEVCProfileMain10HDR10Plus:
+                            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;
+                    */
+
+                    FS >>= 6; // convert pixels to blocks
+                    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));
+                applyMacroBlockLimits(
+                        maxLengthInBlocks, maxLengthInBlocks,
+                        maxBlocks, maxBlocksPerSecond,
+                        8 /* blockWidth */, 8 /* blockHeight */,
+                        1 /* widthAlignment */, 1 /* heightAlignment */);
+            } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_AV1)) {
+                maxBlocksPerSecond = 829440;
+                maxBlocks = 36864;
+                maxBps = 200000;
+                int maxDim = 512;
+
+                // Sample rate, Picture Size, Bit rate and luma dimension for AV1 Codec,
+                // corresponding to the definitions in
+                // "AV1 Bitstream & Decoding Process Specification", Annex A
+                // found at https://aomedia.org/av1-bitstream-and-decoding-process-specification/
+                for (CodecProfileLevel profileLevel: profileLevels) {
+                    long SR = 0; // luma sample rate
+                    int FS = 0;  // luma picture size
+                    int BR = 0;  // bit rate kbps
+                    int D = 0;   // luma D
+                    switch (profileLevel.level) {
+                        case CodecProfileLevel.AV1Level2:
+                            SR =     5529600; FS =   147456; BR =   1500; D =  2048; break;
+                        case CodecProfileLevel.AV1Level21:
+                        case CodecProfileLevel.AV1Level22:
+                        case CodecProfileLevel.AV1Level23:
+                            SR =    10454400; FS =   278784; BR =   3000; D =  2816; break;
+
+                        case CodecProfileLevel.AV1Level3:
+                            SR =    24969600; FS =   665856; BR =   6000; D =  4352; break;
+                        case CodecProfileLevel.AV1Level31:
+                        case CodecProfileLevel.AV1Level32:
+                        case CodecProfileLevel.AV1Level33:
+                            SR =    39938400; FS =  1065024; BR =  10000; D =  5504; break;
+
+                        case CodecProfileLevel.AV1Level4:
+                            SR =    77856768; FS =  2359296; BR =  12000; D =  6144; break;
+                        case CodecProfileLevel.AV1Level41:
+                        case CodecProfileLevel.AV1Level42:
+                        case CodecProfileLevel.AV1Level43:
+                            SR =   155713536; FS =  2359296; BR =  20000; D =  6144; break;
+
+                        case CodecProfileLevel.AV1Level5:
+                            SR =   273715200; FS =  8912896; BR =  30000; D =  8192; break;
+                        case CodecProfileLevel.AV1Level51:
+                            SR =   547430400; FS =  8912896; BR =  40000; D =  8192; break;
+                        case CodecProfileLevel.AV1Level52:
+                            SR =  1094860800; FS =  8912896; BR =  60000; D =  8192; break;
+                        case CodecProfileLevel.AV1Level53:
+                            SR =  1176502272; FS =  8912896; BR =  60000; D =  8192; break;
+
+                        case CodecProfileLevel.AV1Level6:
+                            SR =  1176502272; FS = 35651584; BR =  60000; D = 16384; break;
+                        case CodecProfileLevel.AV1Level61:
+                            SR = 2189721600L; FS = 35651584; BR = 100000; D = 16384; break;
+                        case CodecProfileLevel.AV1Level62:
+                            SR = 4379443200L; FS = 35651584; BR = 160000; D = 16384; break;
+                        case CodecProfileLevel.AV1Level63:
+                            SR = 4706009088L; FS = 35651584; BR = 160000; D = 16384; break;
+
+                        default:
+                            Log.w(TAG, "Unrecognized level "
+                                    + profileLevel.level + " for " + mime);
+                            errors |= ERROR_UNRECOGNIZED;
+                    }
+                    switch (profileLevel.profile) {
+                        case CodecProfileLevel.AV1ProfileMain8:
+                        case CodecProfileLevel.AV1ProfileMain10:
+                        case CodecProfileLevel.AV1ProfileMain10HDR10:
+                        case CodecProfileLevel.AV1ProfileMain10HDR10Plus:
+                            break;
+                        default:
+                            Log.w(TAG, "Unrecognized profile "
+                                    + profileLevel.profile + " for " + mime);
+                            errors |= ERROR_UNRECOGNIZED;
+                    }
+                    errors &= ~ERROR_NONE_SUPPORTED;
+                    maxBlocksPerSecond = Math.max(SR, maxBlocksPerSecond);
+                    maxBlocks = Math.max(FS, maxBlocks);
+                    maxBps = Math.max(BR * 1000, maxBps);
+                    maxDim = Math.max(D, maxDim);
+                }
+
+                final int blockSize = 8;
+                int maxLengthInBlocks = Utils.divUp(maxDim, blockSize);
+                maxBlocks = Utils.divUp(maxBlocks, blockSize * blockSize);
+                maxBlocksPerSecond = Utils.divUp(maxBlocksPerSecond, blockSize * blockSize);
+                applyMacroBlockLimits(
+                        maxLengthInBlocks, maxLengthInBlocks,
+                        maxBlocks, maxBlocksPerSecond,
+                        blockSize, blockSize,
+                        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;
         }
     }
 
@@ -4551,288 +3842,6 @@
      * A class that supports querying the encoding capabilities of a codec.
      */
     public static final class EncoderCapabilities {
-        private static final String TAG = "EncoderCapabilities";
-
-        /** 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;
-        /** Constant bitrate mode with frame drops */
-        public static final int BITRATE_MODE_CBR_FD =  3;
-
-        /* package private */ interface EncoderCapsIntf {
-            public Range<Integer> getQualityRange();
-
-            public Range<Integer> getComplexityRange();
-
-            public boolean isBitrateModeSupported(int mode);
-
-            public void getDefaultFormat(MediaFormat format);
-
-            public boolean supportsFormat(MediaFormat format);
-        }
-
-        /* package private */ static final class EncoderCapsLegacyImpl implements EncoderCapsIntf {
-            private CodecCapabilities.CodecCapsLegacyImpl mParent;
-
-            private Range<Integer> mQualityRange;
-            private Range<Integer> mComplexityRange;
-
-            public Range<Integer> getQualityRange() {
-                return mQualityRange;
-            }
-
-            public Range<Integer> getComplexityRange() {
-                return mComplexityRange;
-            }
-
-            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),
-                new Feature("CBR-FD", BITRATE_MODE_CBR_FD, false)
-            };
-
-            private static int parseBitrateMode(String mode) {
-                for (Feature feat: bitrates) {
-                    if (feat.mName.equalsIgnoreCase(mode)) {
-                        return feat.mValue;
-                    }
-                }
-                return 0;
-            }
-
-            public boolean isBitrateModeSupported(int mode) {
-                for (Feature feat: bitrates) {
-                    if (mode == feat.mValue) {
-                        return (mBitControl & (1 << mode)) != 0;
-                    }
-                }
-                return false;
-            }
-
-            /* no public constructor */
-            private EncoderCapsLegacyImpl() { }
-
-            /** @hide */
-            public static EncoderCapsLegacyImpl create(
-                    MediaFormat info, CodecCapabilities.CodecCapsLegacyImpl parent) {
-                if (GetFlag(() -> android.media.codec.Flags.nativeCapabilites())) {
-                    Log.d(TAG, "Legacy implementation is called while native flag is on.");
-                }
-
-                EncoderCapsLegacyImpl caps = new EncoderCapsLegacyImpl();
-                caps.init(info, parent);
-                return caps;
-            }
-
-            private void init(MediaFormat info, CodecCapabilities.CodecCapsLegacyImpl 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-modes")) {
-                    mBitControl = 0;
-                    for (String mode: info.getString("feature-bitrate-modes").split(",")) {
-                        mBitControl |= (1 << 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.getProfileLevels()) {
-                        if (pl.profile == profile) {
-                            profile = null;
-                            break;
-                        }
-                    }
-                    ok = profile == null;
-                }
-                return ok;
-            }
-
-            /** @hide */
-            public void getDefaultFormat(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.equals(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.equals(profile)) {
-                        throw new IllegalArgumentException(
-                                "conflicting values for profile and aac-profile");
-                    }
-                }
-
-                Integer quality = (Integer)map.get(MediaFormat.KEY_QUALITY);
-
-                return supports(complexity, quality, profile);
-            }
-        }
-
-        /* package private */ static final class EncoderCapsNativeImpl implements EncoderCapsIntf {
-            private long mNativeContext; // accessed by native methods
-
-            private Range<Integer> mQualityRange;
-            private Range<Integer> mComplexityRange;
-
-            /* no public constructor */
-            private EncoderCapsNativeImpl() { }
-
-            // Constructor called from native
-            /* package private */ EncoderCapsNativeImpl(Range<Integer> qualityRange,
-                    Range<Integer> complexityRange) {
-                mQualityRange = qualityRange;
-                mComplexityRange = complexityRange;
-            }
-
-            public Range<Integer> getQualityRange() {
-                return mQualityRange;
-            }
-
-            public Range<Integer> getComplexityRange() {
-                return mComplexityRange;
-            }
-
-            public boolean isBitrateModeSupported(int mode) {
-                return native_isBitrateModeSupported(mode);
-            }
-
-            // This API is for internal Java implementation only. Should not be called.
-            public void getDefaultFormat(MediaFormat format) {
-                throw new UnsupportedOperationException(
-                    "Java Implementation should not call native implemenatation");
-            }
-
-            // This API is for internal Java implementation only. Should not be called.
-            public boolean supportsFormat(MediaFormat format) {
-                throw new UnsupportedOperationException(
-                    "Java Implementation should not call native implemenatation");
-            }
-
-            private native boolean native_isBitrateModeSupported(int mode);
-            private static native void native_init();
-
-            static {
-                System.loadLibrary("media_jni");
-                native_init();
-            }
-        }
-
-        private EncoderCapsIntf mImpl;
-
-        /** @hide */
-        public static EncoderCapabilities create(
-                MediaFormat info, CodecCapabilities.CodecCapsLegacyImpl parent) {
-            EncoderCapsLegacyImpl impl = EncoderCapsLegacyImpl.create(info, parent);
-            EncoderCapabilities caps = new EncoderCapabilities(impl);
-            return caps;
-        }
-
-        /* package private */ EncoderCapabilities(EncoderCapsIntf impl) {
-            mImpl = impl;
-        }
-
         /**
          * Returns the supported range of quality values.
          *
@@ -4840,7 +3849,7 @@
          * setting results in a better image quality and a lower compression ratio.
          */
         public Range<Integer> getQualityRange() {
-            return mImpl.getQualityRange();
+            return mQualityRange;
         }
 
         /**
@@ -4852,24 +3861,200 @@
          * ratio.  Use a lower value to save power and/or time.
          */
         public Range<Integer> getComplexityRange() {
-            return mImpl.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;
+        /** Constant bitrate mode with frame drops */
+        public static final int BITRATE_MODE_CBR_FD =  3;
+
+        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),
+            new Feature("CBR-FD", BITRATE_MODE_CBR_FD, 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) {
-            return mImpl.isBitrateModeSupported(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;
+        }
+
+        private 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-modes")) {
+                mBitControl = 0;
+                for (String mode: info.getString("feature-bitrate-modes").split(",")) {
+                    mBitControl |= (1 << 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 getDefaultFormat(MediaFormat format) {
-            mImpl.getDefaultFormat(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) {
-            return mImpl.supportsFormat(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.equals(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.equals(profile)) {
+                    throw new IllegalArgumentException(
+                            "conflicting values for profile and aac-profile");
+                }
+            }
+
+            Integer quality = (Integer)map.get(MediaFormat.KEY_QUALITY);
+
+            return supports(complexity, quality, profile);
         }
     };
 
@@ -5641,19 +4826,4 @@
                 mName, mCanonicalName, mFlags,
                 caps.toArray(new CodecCapabilities[caps.size()]));
     }
-
-    /* package private */ class GenericHelper {
-        private static Range<Integer> constructIntegerRange(int lower, int upper) {
-            return Range.create(Integer.valueOf(lower), Integer.valueOf(upper));
-        }
-
-        private static Range<Double> constructDoubleRange(double lower, double upper) {
-            return Range.create(Double.valueOf(lower), Double.valueOf(upper));
-        }
-
-        private static List<VideoCapabilities.PerformancePoint>
-                constructPerformancePointList(VideoCapabilities.PerformancePoint[] array) {
-            return Arrays.asList(array);
-        }
-    }
 }
diff --git a/media/jni/Android.bp b/media/jni/Android.bp
index af545d5..f09dc72 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -25,7 +25,6 @@
     min_sdk_version: "",
 
     srcs: [
-        "android_media_CodecCapabilities.cpp",
         "android_media_ImageWriter.cpp",
         "android_media_ImageReader.cpp",
         "android_media_JetPlayer.cpp",
@@ -65,7 +64,6 @@
         "libbinder",
         "libmedia",
         "libmedia_codeclist",
-        "libmedia_codeclist_capabilities",
         "libmedia_jni_utils",
         "libmedia_omx",
         "libmediametrics",
diff --git a/media/jni/android_media_CodecCapabilities.cpp b/media/jni/android_media_CodecCapabilities.cpp
deleted file mode 100644
index df0c826..0000000
--- a/media/jni/android_media_CodecCapabilities.cpp
+++ /dev/null
@@ -1,1015 +0,0 @@
-/*
- * Copyright 2024, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-//#define LOG_NDEBUG 0
-#define LOG_TAG "MediaCodec-JNI"
-
-#include "android_media_CodecCapabilities.h"
-#include "android_media_Streams.h"
-#include "android_runtime/AndroidRuntime.h"
-#include "jni.h"
-
-#include <media/AudioCapabilities.h>
-#include <media/CodecCapabilities.h>
-#include <media/EncoderCapabilities.h>
-#include <media/VideoCapabilities.h>
-#include <media/stagefright/foundation/ADebug.h>
-#include <media/stagefright/foundation/AMessage.h>
-#include <nativehelper/JNIHelp.h>
-#include <nativehelper/ScopedLocalRef.h>
-#include <utils/Log.h>
-
-namespace android {
-
-struct fields_t {
-    jfieldID audioCapsContext;
-    jfieldID videoCapsContext;
-    jfieldID encoderCapsContext;
-    jfieldID codecCapsContext;
-};
-static fields_t fields;
-
-// JCodecCapabilities
-
-JCodecCapabilities::JCodecCapabilities(std::shared_ptr<CodecCapabilities> codecCaps)
-        : mCodecCaps(codecCaps) {}
-
-std::shared_ptr<CodecCapabilities> JCodecCapabilities::getCodecCaps() const {
-    return mCodecCaps;
-}
-
-int32_t JCodecCapabilities::getMaxSupportedInstances() const {
-    return mCodecCaps->getMaxSupportedInstances();
-}
-
-std::string JCodecCapabilities::getMediaType() const {
-    return mCodecCaps->getMediaType();
-}
-
-bool JCodecCapabilities::isFeatureRequired(const std::string& name) const {
-    return mCodecCaps->isFeatureRequired(name);
-}
-
-bool JCodecCapabilities::isFeatureSupported(const std::string& name) const {
-    return mCodecCaps->isFeatureSupported(name);
-}
-
-bool JCodecCapabilities::isFormatSupported(const sp<AMessage> &format) const {
-    return mCodecCaps->isFormatSupported(format);
-}
-
-bool JCodecCapabilities::isRegular() const {
-    return mCodecCaps->isRegular();
-}
-
-// Setter
-
-static sp<JCodecCapabilities> setCodecCapabilities(JNIEnv *env, jobject thiz,
-        const sp<JCodecCapabilities>& jCodecCaps) {
-    sp<JCodecCapabilities> old
-            = (JCodecCapabilities*)env->GetLongField(thiz, fields.codecCapsContext);
-    if (jCodecCaps != NULL) {
-        jCodecCaps->incStrong(thiz);
-    }
-    if (old != NULL) {
-        old->decStrong(thiz);
-    }
-    env->SetLongField(thiz, fields.codecCapsContext, (jlong)jCodecCaps.get());
-    return old;
-}
-
-// Getters
-
-static AudioCapabilities* getAudioCapabilities(JNIEnv *env, jobject thiz) {
-    AudioCapabilities* const p = (AudioCapabilities*)env->GetLongField(
-            thiz, fields.audioCapsContext);
-    return p;
-}
-
-static VideoCapabilities* getVideoCapabilities(JNIEnv *env, jobject thiz) {
-    VideoCapabilities* const p = (VideoCapabilities*)env->GetLongField(
-            thiz, fields.videoCapsContext);
-    return p;
-}
-
-static EncoderCapabilities* getEncoderCapabilities(JNIEnv *env, jobject thiz) {
-    EncoderCapabilities* const p = (EncoderCapabilities*)env->GetLongField(
-            thiz, fields.encoderCapsContext);
-    return p;
-}
-
-static sp<JCodecCapabilities> getCodecCapabilities(JNIEnv *env, jobject thiz) {
-    JCodecCapabilities* const p = (JCodecCapabilities*)env->GetLongField(
-            thiz, fields.codecCapsContext);
-    return sp<JCodecCapabilities>(p);
-}
-
-// Utils
-
-static jobject convertToJavaIntRange(JNIEnv *env, const Range<int32_t>& range) {
-    jclass helperClazz = env->FindClass("android/media/MediaCodecInfo$GenericHelper");
-    jmethodID constructIntegerRangeID = env->GetStaticMethodID(helperClazz, "constructIntegerRange",
-            "(II)Landroid/util/Range;");
-    jobject jRange = env->CallStaticObjectMethod(helperClazz, constructIntegerRangeID,
-            range.lower(), range.upper());
-
-    return jRange;
-}
-
-static jobject convertToJavaDoubleRange(JNIEnv *env, const Range<double>& range) {
-    jclass helperClazz = env->FindClass("android/media/MediaCodecInfo$GenericHelper");
-    jmethodID constructDoubleRangeID = env->GetStaticMethodID(helperClazz, "constructDoubleRange",
-            "(DD)Landroid/util/Range;");
-    jobject jRange = env->CallStaticObjectMethod(helperClazz, constructDoubleRangeID,
-            range.lower(), range.upper());
-    return jRange;
-}
-
-static jobjectArray convertToJavaIntRangeArray(JNIEnv *env,
-        const std::vector<Range<int32_t>>& ranges) {
-    jclass rangeClazz = env->FindClass("android/util/Range");
-    CHECK(rangeClazz != NULL);
-    jobjectArray jRanges = env->NewObjectArray(ranges.size(), rangeClazz, NULL);
-    for (int i = 0; i < ranges.size(); i++) {
-        Range<int32_t> range = ranges.at(i);
-        jobject jRange = convertToJavaIntRange(env, range);
-        env->SetObjectArrayElement(jRanges, i, jRange);
-        env->DeleteLocalRef(jRange);
-        jRange = NULL;
-    }
-    return jRanges;
-}
-
-// Converters between Java objects and native instances
-
-// The Java AudioCapabilities object keep bitrateRange, sampleRates, sampleRateRanges
-// and inputChannelRanges in it to prevent reconstruction when called the getters functions.
-static jobject convertToJavaAudioCapabilities(
-        JNIEnv *env, std::shared_ptr<AudioCapabilities> audioCaps) {
-    if (audioCaps == nullptr) {
-        return NULL;
-    }
-
-    // construct Java bitrateRange
-    const Range<int32_t>& bitrateRange = audioCaps->getBitrateRange();
-    jobject jBitrateRange = convertToJavaIntRange(env, bitrateRange);
-
-    // construct Java sampleRates array
-    const std::vector<int32_t>& sampleRates = audioCaps->getSupportedSampleRates();
-    jintArray jSampleRates = env->NewIntArray(sampleRates.size());
-    for (size_t i = 0; i < sampleRates.size(); ++i) {
-        jint val = sampleRates.at(i);
-        env->SetIntArrayRegion(jSampleRates, i, 1, &val);
-    }
-
-    // construct Java sampleRateRanges
-    const std::vector<Range<int32_t>>& sampleRateRanges = audioCaps->getSupportedSampleRateRanges();
-    jobjectArray jSampleRateRanges = convertToJavaIntRangeArray(env, sampleRateRanges);
-
-    // construct Java inputChannelRanges
-    const std::vector<Range<int32_t>>& inputChannelRanges = audioCaps->getInputChannelCountRanges();
-    jobjectArray jInputChannelRanges = convertToJavaIntRangeArray(env, inputChannelRanges);
-
-    // construct Java AudioCapsNativeImpl
-    jclass audioCapsImplClazz
-            = env->FindClass("android/media/MediaCodecInfo$AudioCapabilities$AudioCapsNativeImpl");
-    CHECK(audioCapsImplClazz != NULL);
-    jmethodID audioCapsImplConstructID = env->GetMethodID(audioCapsImplClazz, "<init>",
-            "(Landroid/util/Range;"
-            "[I"
-            "[Landroid/util/Range;"
-            "[Landroid/util/Range;)V");
-    jobject jAudioCapsImpl = env->NewObject(audioCapsImplClazz, audioCapsImplConstructID,
-            jBitrateRange, jSampleRates, jSampleRateRanges, jInputChannelRanges);
-    // The native AudioCapabilities won't be destructed until process ends.
-    env->SetLongField(jAudioCapsImpl, fields.audioCapsContext, (jlong)audioCaps.get());
-
-    // construct Java AudioCapabilities
-    jclass audioCapsClazz
-            = env->FindClass("android/media/MediaCodecInfo$AudioCapabilities");
-    CHECK(audioCapsClazz != NULL);
-    jmethodID audioCapsConstructID = env->GetMethodID(audioCapsClazz, "<init>",
-            "(Landroid/media/MediaCodecInfo$AudioCapabilities$AudioCapsIntf;)V");
-    jobject jAudioCaps = env->NewObject(audioCapsClazz, audioCapsConstructID, jAudioCapsImpl);
-
-    env->DeleteLocalRef(jBitrateRange);
-    jBitrateRange = NULL;
-
-    env->DeleteLocalRef(jSampleRates);
-    jSampleRates = NULL;
-
-    env->DeleteLocalRef(jSampleRateRanges);
-    jSampleRateRanges = NULL;
-
-    env->DeleteLocalRef(jInputChannelRanges);
-    jInputChannelRanges = NULL;
-
-    env->DeleteLocalRef(jAudioCapsImpl);
-    jAudioCapsImpl = NULL;
-
-    return jAudioCaps;
-}
-
-// convert native PerformancePoints to Java objects
-static jobject convertToJavaPerformancePoints(JNIEnv *env,
-        const std::vector<VideoCapabilities::PerformancePoint>& performancePoints) {
-    jclass performancePointClazz = env->FindClass(
-            "android/media/MediaCodecInfo$VideoCapabilities$PerformancePoint");
-    CHECK(performancePointClazz != NULL);
-    jmethodID performancePointConstructID = env->GetMethodID(performancePointClazz, "<init>",
-            "(IIIJII)V");
-
-    jobjectArray jPerformancePoints = env->NewObjectArray(performancePoints.size(),
-            performancePointClazz, NULL);
-    int i = 0;
-    for (auto it = performancePoints.begin(); it != performancePoints.end(); ++it, ++i) {
-        jobject jPerformancePoint = env->NewObject(performancePointClazz,
-                performancePointConstructID, it->getWidth(),
-                it->getHeight(), it->getMaxFrameRate(),
-                it->getMaxMacroBlockRate(), it->getBlockSize().getWidth(),
-                it->getBlockSize().getHeight());
-
-        env->SetObjectArrayElement(jPerformancePoints, i, jPerformancePoint);
-
-        env->DeleteLocalRef(jPerformancePoint);
-    }
-
-    jclass helperClazz = env->FindClass("android/media/MediaCodecInfo$GenericHelper");
-    CHECK(helperClazz != NULL);
-    jmethodID asListID = env->GetStaticMethodID(helperClazz, "constructPerformancePointList",
-            "([Landroid/media/MediaCodecInfo$VideoCapabilities$PerformancePoint;)Ljava/util/List;");
-    CHECK(asListID != NULL);
-    jobject jList = env->CallStaticObjectMethod(helperClazz, asListID, jPerformancePoints);
-
-    return jList;
-}
-
-static VideoCapabilities::PerformancePoint convertToNativePerformancePoint(
-        JNIEnv *env, jobject pp) {
-    if (pp == NULL) {
-        jniThrowException(env, "java/lang/NullPointerException", NULL);
-    }
-
-    jclass clazz = env->FindClass(
-            "android/media/MediaCodecInfo$VideoCapabilities$PerformancePoint");
-    CHECK(clazz != NULL);
-    CHECK(env->IsInstanceOf(pp, clazz));
-
-    jmethodID getWidthID = env->GetMethodID(clazz, "getWidth", "()I");
-    CHECK(getWidthID != NULL);
-    jint width = env->CallIntMethod(pp, getWidthID);
-
-    jmethodID getHeightID = env->GetMethodID(clazz, "getHeight", "()I");
-    CHECK(getHeightID != NULL);
-    jint height = env->CallIntMethod(pp, getHeightID);
-
-    jmethodID getMaxFrameRateID = env->GetMethodID(clazz, "getMaxFrameRate", "()I");
-    CHECK(getMaxFrameRateID != NULL);
-    jint maxFrameRate = env->CallIntMethod(pp, getMaxFrameRateID);
-
-    jmethodID getMaxMacroBlockRateID = env->GetMethodID(clazz, "getMaxMacroBlockRate", "()J");
-    CHECK(getMaxMacroBlockRateID != NULL);
-    jlong maxMacroBlockRate = env->CallLongMethod(pp, getMaxMacroBlockRateID);
-
-    jmethodID getBlockWidthID = env->GetMethodID(clazz, "getBlockWidth", "()I");
-    CHECK(getBlockWidthID != NULL);
-    jint blockWidth = env->CallIntMethod(pp, getBlockWidthID);
-
-    jmethodID getBlockHeightID = env->GetMethodID(clazz, "getBlockHeight", "()I");
-    CHECK(getBlockHeightID != NULL);
-    jint blockHeight = env->CallIntMethod(pp, getBlockHeightID);
-
-    return VideoCapabilities::PerformancePoint(VideoSize(blockWidth, blockHeight),
-            width, height, maxFrameRate, maxMacroBlockRate);
-}
-
-static jobject convertToJavaVideoCapabilities(JNIEnv *env,
-        std::shared_ptr<VideoCapabilities> videoCaps) {
-    if (videoCaps == nullptr) {
-        return NULL;
-    }
-
-    // get Java bitrateRange
-    const Range<int32_t>& bitrateRange = videoCaps->getBitrateRange();
-    jobject jBitrateRange = convertToJavaIntRange(env, bitrateRange);
-
-    // get Java widthRange
-    const Range<int32_t>& widthRange = videoCaps->getSupportedWidths();
-    jobject jWidthRange = convertToJavaIntRange(env, widthRange);
-
-    // get Java heightRange
-    const Range<int32_t>& heightRange = videoCaps->getSupportedHeights();
-    jobject jHeightRange = convertToJavaIntRange(env, heightRange);
-
-    // get Java frameRateRange
-    const Range<int32_t>& frameRateRange = videoCaps->getSupportedFrameRates();
-    jobject jFrameRateRange = convertToJavaIntRange(env, frameRateRange);
-
-    // get Java performancePoints
-    const std::vector<VideoCapabilities::PerformancePoint>& performancePoints
-            = videoCaps->getSupportedPerformancePoints();
-    jobject jPerformancePoints = convertToJavaPerformancePoints(env, performancePoints);
-
-    // get width alignment
-    int32_t widthAlignment = videoCaps->getWidthAlignment();
-
-    // get height alignment
-    int32_t heightAlignment = videoCaps->getHeightAlignment();
-
-    // get Java VideoCapsNativeImpl
-    jclass videoCapsImplClazz = env->FindClass(
-            "android/media/MediaCodecInfo$VideoCapabilities$VideoCapsNativeImpl");
-    CHECK(videoCapsImplClazz != NULL);
-    jmethodID videoCapsImplConstructID = env->GetMethodID(videoCapsImplClazz, "<init>",
-            "(Landroid/util/Range;"
-            "Landroid/util/Range;"
-            "Landroid/util/Range;"
-            "Landroid/util/Range;"
-            "Ljava/util/List;II)V");
-    jobject jVideoCapsImpl = env->NewObject(videoCapsImplClazz, videoCapsImplConstructID,
-            jBitrateRange, jWidthRange, jHeightRange, jFrameRateRange, jPerformancePoints,
-            widthAlignment, heightAlignment);
-    // The native VideoCapabilities won't be destructed until process ends.
-    env->SetLongField(jVideoCapsImpl, fields.videoCapsContext, (jlong)videoCaps.get());
-
-    // get Java VideoCapabilities
-    jclass videoCapsClazz
-            = env->FindClass("android/media/MediaCodecInfo$VideoCapabilities");
-    CHECK(videoCapsClazz != NULL);
-    jmethodID videoCapsConstructID = env->GetMethodID(videoCapsClazz, "<init>",
-            "(Landroid/media/MediaCodecInfo$VideoCapabilities$VideoCapsIntf;)V");
-    jobject jVideoCaps = env->NewObject(videoCapsClazz, videoCapsConstructID, jVideoCapsImpl);
-
-    env->DeleteLocalRef(jBitrateRange);
-    jBitrateRange = NULL;
-
-    env->DeleteLocalRef(jWidthRange);
-    jWidthRange = NULL;
-
-    env->DeleteLocalRef(jHeightRange);
-    jHeightRange = NULL;
-
-    env->DeleteLocalRef(jFrameRateRange);
-    jFrameRateRange = NULL;
-
-    env->DeleteLocalRef(jPerformancePoints);
-    jPerformancePoints = NULL;
-
-    env->DeleteLocalRef(jVideoCapsImpl);
-    jVideoCapsImpl = NULL;
-
-    return jVideoCaps;
-}
-
-static jobject convertToJavaEncoderCapabilities(JNIEnv *env,
-        std::shared_ptr<EncoderCapabilities> encoderCaps) {
-    if (encoderCaps == nullptr) {
-        return NULL;
-    }
-
-    // get quality range
-    const Range<int>& qualityRange = encoderCaps->getQualityRange();
-    jobject jQualityRange = convertToJavaIntRange(env, qualityRange);
-
-    // get complexity range
-    const Range<int>& complexityRange = encoderCaps->getComplexityRange();
-    jobject jComplexityRange = convertToJavaIntRange(env, complexityRange);
-
-    // construct java EncoderCapsNativeImpl
-    jclass encoderCapsImplClazz = env->FindClass(
-            "android/media/MediaCodecInfo$EncoderCapabilities$EncoderCapsNativeImpl");
-    CHECK(encoderCapsImplClazz != NULL);
-    jmethodID encoderCapsImplConstructID = env->GetMethodID(encoderCapsImplClazz, "<init>",
-            "(Landroid/util/Range;Landroid/util/Range;)V");
-    jobject jEncoderCapsImpl = env->NewObject(encoderCapsImplClazz, encoderCapsImplConstructID,
-            jQualityRange, jComplexityRange);
-    // The native EncoderCapabilities won't be destructed until process ends.
-    env->SetLongField(jEncoderCapsImpl, fields.encoderCapsContext, (jlong)encoderCaps.get());
-
-    // construct java EncoderCapabilities object
-    jclass encoderCapsClazz
-            = env->FindClass("android/media/MediaCodecInfo$EncoderCapabilities");
-    CHECK(encoderCapsClazz != NULL);
-    jmethodID encoderCapsConstructID = env->GetMethodID(encoderCapsClazz, "<init>",
-            "(Landroid/media/MediaCodecInfo$EncoderCapabilities$EncoderCapsIntf;)V");
-    jobject jEncoderCaps = env->NewObject(encoderCapsClazz, encoderCapsConstructID,
-            jEncoderCapsImpl);
-
-    env->DeleteLocalRef(jQualityRange);
-    jQualityRange = NULL;
-
-    env->DeleteLocalRef(jComplexityRange);
-    jComplexityRange = NULL;
-
-    env->DeleteLocalRef(jEncoderCapsImpl);
-    jEncoderCapsImpl = NULL;
-
-    return jEncoderCaps;
-}
-
-// Java CodecCapsNativeImpl keeps the defaultFormat, profileLevels, colorFormats, audioCapabilities,
-// videoCapabilities and encoderCapabilities in it to prevent reconsturction when called by getter.
-static jobject convertToJavaCodecCapsNativeImpl(
-            JNIEnv *env, std::shared_ptr<CodecCapabilities> codecCaps) {
-    if (codecCaps == nullptr) {
-        jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
-        return NULL;
-    }
-
-    // Construct defaultFormat
-    sp<AMessage> defaultFormat = codecCaps->getDefaultFormat();
-
-    jobject formatMap = NULL;
-    if (ConvertMessageToMap(env, defaultFormat, &formatMap)) {
-        return NULL;
-    }
-
-    ScopedLocalRef<jclass> mediaFormatClass{env, env->FindClass("android/media/MediaFormat")};
-    ScopedLocalRef<jobject> jDefaultFormat{env, env->NewObject(
-            mediaFormatClass.get(),
-            env->GetMethodID(mediaFormatClass.get(), "<init>", "(Ljava/util/Map;)V"),
-            formatMap)};
-
-    env->DeleteLocalRef(formatMap);
-    formatMap = NULL;
-
-    // Construct Java ProfileLevelArray
-    std::vector<ProfileLevel> profileLevels = codecCaps->getProfileLevels();
-
-    jclass profileLevelClazz =
-        env->FindClass("android/media/MediaCodecInfo$CodecProfileLevel");
-    CHECK(profileLevelClazz != NULL);
-
-    jobjectArray profileLevelArray =
-            env->NewObjectArray(profileLevels.size(), profileLevelClazz, NULL);
-
-    jfieldID profileField =
-            env->GetFieldID(profileLevelClazz, "profile", "I");
-    jfieldID levelField =
-            env->GetFieldID(profileLevelClazz, "level", "I");
-
-    for (size_t i = 0; i < profileLevels.size(); ++i) {
-        const ProfileLevel &src = profileLevels.at(i);
-
-        jobject profileLevelObj = env->AllocObject(profileLevelClazz);
-
-        env->SetIntField(profileLevelObj, profileField, src.mProfile);
-        env->SetIntField(profileLevelObj, levelField, src.mLevel);
-
-        env->SetObjectArrayElement(profileLevelArray, i, profileLevelObj);
-
-        env->DeleteLocalRef(profileLevelObj);
-        profileLevelObj = NULL;
-    }
-
-    // Construct ColorFormatArray
-    std::vector<uint32_t> colorFormats = codecCaps->getColorFormats();
-
-    jintArray colorFormatsArray = env->NewIntArray(colorFormats.size());
-    env->SetIntArrayRegion(colorFormatsArray, 0, colorFormats.size(),
-            reinterpret_cast<jint*>(colorFormats.data()));
-
-    // Construct and set AudioCapabilities
-    std::shared_ptr<AudioCapabilities> audioCaps = codecCaps->getAudioCapabilities();
-    jobject jAudioCaps = convertToJavaAudioCapabilities(env, audioCaps);
-
-    // Set VideoCapabilities
-    std::shared_ptr<VideoCapabilities> videoCaps = codecCaps->getVideoCapabilities();
-    jobject jVideoCaps = convertToJavaVideoCapabilities(env, videoCaps);
-
-    // Set EncoderCapabilities
-    std::shared_ptr<EncoderCapabilities> encoderCaps = codecCaps->getEncoderCapabilities();
-    jobject jEncoderCaps = convertToJavaEncoderCapabilities(env, encoderCaps);
-
-    // Construct CodecCapsNativeImpl
-    jclass codecCapsImplClazz =
-        env->FindClass("android/media/MediaCodecInfo$CodecCapabilities$CodecCapsNativeImpl");
-    CHECK(codecCapsImplClazz != NULL);
-    jmethodID codecCapsImplConstructID = env->GetMethodID(codecCapsImplClazz, "<init>",
-                "([Landroid/media/MediaCodecInfo$CodecProfileLevel;[I"
-                "Landroid/media/MediaFormat;"
-                "Landroid/media/MediaCodecInfo$AudioCapabilities;"
-                "Landroid/media/MediaCodecInfo$VideoCapabilities;"
-                "Landroid/media/MediaCodecInfo$EncoderCapabilities;)V");
-    jobject javaCodecCapsImpl = env->NewObject(codecCapsImplClazz, codecCapsImplConstructID,
-            profileLevelArray, colorFormatsArray, jDefaultFormat.get(),
-            jAudioCaps, jVideoCaps, jEncoderCaps);
-
-    // Construct JCodecCapabilities and hold the codecCaps in it
-    sp<JCodecCapabilities> jCodecCaps = sp<JCodecCapabilities>::make(codecCaps);
-    setCodecCapabilities(env, javaCodecCapsImpl, jCodecCaps);
-
-    env->DeleteLocalRef(profileLevelArray);
-    profileLevelArray = NULL;
-
-    env->DeleteLocalRef(colorFormatsArray);
-    colorFormatsArray = NULL;
-
-    env->DeleteLocalRef(jAudioCaps);
-    jAudioCaps = NULL;
-
-    env->DeleteLocalRef(jVideoCaps);
-    jVideoCaps = NULL;
-
-    env->DeleteLocalRef(jEncoderCaps);
-    jEncoderCaps = NULL;
-
-    return javaCodecCapsImpl;
-}
-
-jobject convertToJavaCodecCapabiliites(
-        JNIEnv *env, std::shared_ptr<CodecCapabilities> codecCaps) {
-    if (codecCaps == nullptr) {
-        jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
-        return NULL;
-    }
-
-    jobject javaCodecCapsImpl = convertToJavaCodecCapsNativeImpl(env, codecCaps);
-
-    // Construct CodecCapabilities
-    jclass codecCapsClazz = env->FindClass("android/media/MediaCodecInfo$CodecCapabilities");
-    CHECK(codecCapsClazz != NULL);
-
-    jmethodID codecCapsConstructID = env->GetMethodID(codecCapsClazz, "<init>",
-            "(Landroid/media/MediaCodecInfo$CodecCapabilities$CodecCapsIntf;)V");
-    jobject javaCodecCaps = env->NewObject(codecCapsClazz, codecCapsConstructID, javaCodecCapsImpl);
-
-    return javaCodecCaps;
-}
-
-}  // namespace android
-
-// ----------------------------------------------------------------------------
-
-using namespace android;
-
-// AudioCapabilities
-
-static void android_media_AudioCapabilities_native_init(JNIEnv *env, jobject /* thiz */) {
-    jclass audioCapsImplClazz
-            = env->FindClass("android/media/MediaCodecInfo$AudioCapabilities$AudioCapsNativeImpl");
-    if (audioCapsImplClazz == NULL) {
-        return;
-    }
-
-    fields.audioCapsContext = env->GetFieldID(audioCapsImplClazz, "mNativeContext", "J");
-    if (fields.audioCapsContext == NULL) {
-        return;
-    }
-
-    env->DeleteLocalRef(audioCapsImplClazz);
-}
-
-static jint android_media_AudioCapabilities_getMaxInputChannelCount(JNIEnv *env, jobject thiz) {
-    AudioCapabilities* const audioCaps = getAudioCapabilities(env, thiz);
-    if (audioCaps == nullptr) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
-        return 0;
-    }
-
-    int32_t maxInputChannelCount = audioCaps->getMaxInputChannelCount();
-    return maxInputChannelCount;
-}
-
-static jint android_media_AudioCapabilities_getMinInputChannelCount(JNIEnv *env, jobject thiz) {
-    AudioCapabilities* const audioCaps = getAudioCapabilities(env, thiz);
-    if (audioCaps == nullptr) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
-        return 0;
-    }
-
-    int32_t minInputChannelCount = audioCaps->getMinInputChannelCount();
-    return minInputChannelCount;
-}
-
-static jboolean android_media_AudioCapabilities_isSampleRateSupported(JNIEnv *env, jobject thiz,
-        int sampleRate) {
-    AudioCapabilities* const audioCaps = getAudioCapabilities(env, thiz);
-    if (audioCaps == nullptr) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
-        return 0;
-    }
-
-    bool res = audioCaps->isSampleRateSupported(sampleRate);
-    return res;
-}
-
-// PerformancePoint
-
-static jboolean android_media_VideoCapabilities_PerformancePoint_covers(JNIEnv *env, jobject thiz,
-        jobject other) {
-    VideoCapabilities::PerformancePoint pp0 = convertToNativePerformancePoint(env, thiz);
-    VideoCapabilities::PerformancePoint pp1 = convertToNativePerformancePoint(env, other);
-
-    bool res = pp0.covers(pp1);
-    return res;
-}
-
-static jboolean android_media_VideoCapabilities_PerformancePoint_equals(JNIEnv *env, jobject thiz,
-        jobject other) {
-    VideoCapabilities::PerformancePoint pp0 = convertToNativePerformancePoint(env, thiz);
-    VideoCapabilities::PerformancePoint pp1 = convertToNativePerformancePoint(env, other);
-
-    bool res = pp0.equals(pp1);
-    return res;
-}
-
-// VideoCapabilities
-
-static void android_media_VideoCapabilities_native_init(JNIEnv *env, jobject /* thiz */) {
-    jclass clazz
-            = env->FindClass("android/media/MediaCodecInfo$VideoCapabilities$VideoCapsNativeImpl");
-    if (clazz == NULL) {
-        return;
-    }
-
-    fields.videoCapsContext = env->GetFieldID(clazz, "mNativeContext", "J");
-    if (fields.videoCapsContext == NULL) {
-        return;
-    }
-
-    env->DeleteLocalRef(clazz);
-}
-
-static jboolean android_media_VideoCapabilities_areSizeAndRateSupported(JNIEnv *env, jobject thiz,
-        int32_t width, int32_t height, double frameRate) {
-    VideoCapabilities* const videoCaps = getVideoCapabilities(env, thiz);
-    if (videoCaps == nullptr) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
-        return 0;
-    }
-
-    bool res = videoCaps->areSizeAndRateSupported(width, height, frameRate);
-    return res;
-}
-
-static jboolean android_media_VideoCapabilities_isSizeSupported(JNIEnv *env, jobject thiz,
-        int32_t width, int32_t height) {
-    VideoCapabilities* const videoCaps = getVideoCapabilities(env, thiz);
-    if (videoCaps == nullptr) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
-        return 0;
-    }
-
-    bool res = videoCaps->isSizeSupported(width, height);
-    return res;
-}
-
-static jobject android_media_VideoCapabilities_getAchievableFrameRatesFor(JNIEnv *env, jobject thiz,
-        int32_t width, int32_t height) {
-    VideoCapabilities* const videoCaps = getVideoCapabilities(env, thiz);
-    if (videoCaps == nullptr) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
-        return NULL;
-    }
-
-    std::optional<Range<double>> frameRates = videoCaps->getAchievableFrameRatesFor(width, height);
-    if (!frameRates) {
-        return NULL;
-    }
-    jobject jFrameRates = convertToJavaDoubleRange(env, frameRates.value());
-    return jFrameRates;
-}
-
-static jobject android_media_VideoCapabilities_getSupportedFrameRatesFor(JNIEnv *env, jobject thiz,
-        int32_t width, int32_t height) {
-    VideoCapabilities* const videoCaps = getVideoCapabilities(env, thiz);
-    if (videoCaps == nullptr) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
-        return NULL;
-    }
-
-    std::optional<Range<double>> frameRates = videoCaps->getSupportedFrameRatesFor(width, height);
-    if (!frameRates) {
-        return NULL;
-    }
-    jobject jFrameRates = convertToJavaDoubleRange(env, frameRates.value());
-    return jFrameRates;
-}
-
-static jobject android_media_VideoCapabilities_getSupportedWidthsFor(JNIEnv *env, jobject thiz,
-        int32_t height) {
-    VideoCapabilities* const videoCaps = getVideoCapabilities(env, thiz);
-    if (videoCaps == nullptr) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
-        return NULL;
-    }
-
-    std::optional<Range<int32_t>> supportedWidths = videoCaps->getSupportedWidthsFor(height);
-    if (!supportedWidths) {
-        return NULL;
-    }
-    jobject jSupportedWidths = convertToJavaIntRange(env, supportedWidths.value());
-
-    return jSupportedWidths;
-}
-
-static jobject android_media_VideoCapabilities_getSupportedHeightsFor(JNIEnv *env, jobject thiz,
-        int32_t width) {
-    VideoCapabilities* const videoCaps = getVideoCapabilities(env, thiz);
-    if (videoCaps == nullptr) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
-        return NULL;
-    }
-
-    std::optional<Range<int32_t>> supportedHeights = videoCaps->getSupportedHeightsFor(width);
-    if (!supportedHeights) {
-        return NULL;
-    }
-    jobject jSupportedHeights = convertToJavaIntRange(env, supportedHeights.value());
-
-    return jSupportedHeights;
-}
-
-static jint android_media_VideoCapabilities_getSmallerDimensionUpperLimit(JNIEnv *env,
-        jobject thiz) {
-    VideoCapabilities* const videoCaps = getVideoCapabilities(env, thiz);
-    if (videoCaps == nullptr) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
-        return 0;
-    }
-
-    int smallerDimensionUpperLimit = videoCaps->getSmallerDimensionUpperLimit();
-    return smallerDimensionUpperLimit;
-}
-
-// EncoderCapabilities
-
-static void android_media_EncoderCapabilities_native_init(JNIEnv *env, jobject /* thiz */) {
-    jclass clazz = env->FindClass(
-            "android/media/MediaCodecInfo$EncoderCapabilities$EncoderCapsNativeImpl");
-    if (clazz == NULL) {
-        return;
-    }
-
-    fields.encoderCapsContext = env->GetFieldID(clazz, "mNativeContext", "J");
-    if (fields.encoderCapsContext == NULL) {
-        return;
-    }
-
-    env->DeleteLocalRef(clazz);
-}
-
-static jboolean android_media_EncoderCapabilities_isBitrateModeSupported(JNIEnv *env, jobject thiz,
-        int mode) {
-    EncoderCapabilities* const encoderCaps = getEncoderCapabilities(env, thiz);
-    if (encoderCaps == nullptr) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
-        return 0;
-    }
-
-    bool res = encoderCaps->isBitrateModeSupported(mode);
-    return res;
-}
-
-// CodecCapabilities
-
-static void android_media_CodecCapabilities_native_init(JNIEnv *env, jobject /* thiz */) {
-    jclass codecCapsClazz
-            = env->FindClass("android/media/MediaCodecInfo$CodecCapabilities$CodecCapsNativeImpl");
-    if (codecCapsClazz == NULL) {
-        return;
-    }
-
-    fields.codecCapsContext = env->GetFieldID(codecCapsClazz, "mNativeContext", "J");
-    if (fields.codecCapsContext == NULL) {
-        return;
-    }
-
-    env->DeleteLocalRef(codecCapsClazz);
-}
-
-static jobject android_media_CodecCapabilities_createFromProfileLevel(JNIEnv *env,
-        jobject /* thiz */, jstring mediaType, jint profile, jint level) {
-    if (mediaType == NULL) {
-        jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
-        return NULL;
-    }
-
-    const char *mediaTypeStr = env->GetStringUTFChars(mediaType, nullptr);
-    if (mediaTypeStr == nullptr) {
-        return NULL;
-    }
-
-    std::shared_ptr<CodecCapabilities> codecCaps = CodecCapabilities::CreateFromProfileLevel(
-            mediaTypeStr, profile, level);
-
-    jobject javaCodecCapsImpl = convertToJavaCodecCapsNativeImpl(env, codecCaps);
-
-    env->ReleaseStringUTFChars(mediaType, mediaTypeStr);
-
-    return javaCodecCapsImpl;
-}
-
-static jobject android_media_CodecCapabilities_native_dup(JNIEnv *env, jobject thiz) {
-    sp<JCodecCapabilities> jCodecCaps = getCodecCapabilities(env, thiz);
-
-    // As the CodecCaps objects are ready ony, it is ok to use the default copy constructor.
-    // The duplicate CodecCaps will share the same subobjects with the existing one.
-    // The lifetime of subobjects are managed by the shared pointer and sp.
-    std::shared_ptr<CodecCapabilities> duplicate
-            = std::make_shared<CodecCapabilities>(*(jCodecCaps->getCodecCaps()));
-
-    jobject javaCodecCapsImpl = convertToJavaCodecCapsNativeImpl(env, duplicate);
-
-    return javaCodecCapsImpl;
-}
-
-static void android_media_CodecCapabilities_native_finalize(JNIEnv *env, jobject thiz) {
-    ALOGV("native_finalize");
-    setCodecCapabilities(env, thiz, NULL);
-}
-
-static jint android_media_CodecCapabilities_getMaxSupportedInstances(JNIEnv *env, jobject thiz) {
-    sp<JCodecCapabilities> codecCaps = getCodecCapabilities(env, thiz);
-    if (codecCaps == nullptr) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
-        return 0;
-    }
-
-    int maxSupportedInstances = codecCaps->getMaxSupportedInstances();
-    return maxSupportedInstances;
-}
-
-static jstring android_media_CodecCapabilities_getMimeType(JNIEnv *env, jobject thiz) {
-    sp<JCodecCapabilities> codecCaps = getCodecCapabilities(env, thiz);
-    if (codecCaps == nullptr) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
-        return NULL;
-    }
-
-    std::string mediaType = codecCaps->getMediaType();
-    return env->NewStringUTF(mediaType.c_str());
-}
-
-static jboolean android_media_CodecCapabilities_isFeatureRequired(
-        JNIEnv *env, jobject thiz, jstring name) {
-    sp<JCodecCapabilities> codecCaps = getCodecCapabilities(env, thiz);
-    if (codecCaps == nullptr) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
-        return false;
-    }
-
-    if (name == NULL) {
-        jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
-        return -ENOENT;
-    }
-
-    const char *nameStr = env->GetStringUTFChars(name, NULL);
-    if (nameStr == NULL) {
-        // Out of memory exception already pending.
-        return -ENOENT;
-    }
-
-    bool isFeatureRequired = codecCaps->isFeatureRequired(nameStr);
-
-    env->ReleaseStringUTFChars(name, nameStr);
-
-    return isFeatureRequired;
-}
-
-static jboolean android_media_CodecCapabilities_isFeatureSupported(
-        JNIEnv *env, jobject thiz, jstring name) {
-    sp<JCodecCapabilities> codecCaps = getCodecCapabilities(env, thiz);
-    if (codecCaps == nullptr) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
-        return false;
-    }
-
-    if (name == NULL) {
-        jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
-        return -ENOENT;
-    }
-
-    const char *nameStr = env->GetStringUTFChars(name, NULL);
-    if (nameStr == NULL) {
-        // Out of memory exception already pending.
-        return -ENOENT;
-    }
-
-    bool isFeatureSupported = codecCaps->isFeatureSupported(nameStr);
-
-    env->ReleaseStringUTFChars(name, nameStr);
-
-    return isFeatureSupported;
-}
-
-static jboolean android_media_CodecCapabilities_isFormatSupported(JNIEnv *env, jobject thiz,
-        jobjectArray keys, jobjectArray values) {
-    sp<JCodecCapabilities> codecCaps = getCodecCapabilities(env, thiz);
-    if (codecCaps == nullptr) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
-        return false;
-    }
-
-    sp<AMessage> format;
-    status_t err = ConvertKeyValueArraysToMessage(env, keys, values, &format);
-    if (err != OK) {
-        jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
-        return -ENOENT;;
-    }
-
-    return codecCaps->isFormatSupported(format);
-}
-
-static jboolean android_media_CodecCapabilities_isRegular(JNIEnv *env, jobject thiz) {
-    sp<JCodecCapabilities> codecCaps = getCodecCapabilities(env, thiz);
-    if (codecCaps == nullptr) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
-        return false;
-    }
-
-    bool res = codecCaps->isRegular();
-    return res;
-}
-
-// ----------------------------------------------------------------------------
-
-static const JNINativeMethod gAudioCapsMethods[] = {
-    {"native_init", "()V", (void *)android_media_AudioCapabilities_native_init},
-    {"native_getMaxInputChannelCount", "()I", (void *)android_media_AudioCapabilities_getMaxInputChannelCount},
-    {"native_getMinInputChannelCount", "()I", (void *)android_media_AudioCapabilities_getMinInputChannelCount},
-    {"native_isSampleRateSupported", "(I)Z", (void *)android_media_AudioCapabilities_isSampleRateSupported}
-};
-
-static const JNINativeMethod gPerformancePointMethods[] = {
-    {"native_covers", "(Landroid/media/MediaCodecInfo$VideoCapabilities$PerformancePoint;)Z", (void *)android_media_VideoCapabilities_PerformancePoint_covers},
-    {"native_equals", "(Landroid/media/MediaCodecInfo$VideoCapabilities$PerformancePoint;)Z", (void *)android_media_VideoCapabilities_PerformancePoint_equals},
-};
-
-static const JNINativeMethod gVideoCapsMethods[] = {
-    {"native_init", "()V", (void *)android_media_VideoCapabilities_native_init},
-    {"native_areSizeAndRateSupported", "(IID)Z", (void *)android_media_VideoCapabilities_areSizeAndRateSupported},
-    {"native_isSizeSupported", "(II)Z", (void *)android_media_VideoCapabilities_isSizeSupported},
-    {"native_getAchievableFrameRatesFor", "(II)Landroid/util/Range;", (void *)android_media_VideoCapabilities_getAchievableFrameRatesFor},
-    {"native_getSupportedFrameRatesFor", "(II)Landroid/util/Range;", (void *)android_media_VideoCapabilities_getSupportedFrameRatesFor},
-    {"native_getSupportedWidthsFor", "(I)Landroid/util/Range;", (void *)android_media_VideoCapabilities_getSupportedWidthsFor},
-    {"native_getSupportedHeightsFor", "(I)Landroid/util/Range;", (void *)android_media_VideoCapabilities_getSupportedHeightsFor},
-    {"native_getSmallerDimensionUpperLimit", "()I", (void *)android_media_VideoCapabilities_getSmallerDimensionUpperLimit}
-};
-
-static const JNINativeMethod gEncoderCapsMethods[] = {
-    {"native_init", "()V", (void *)android_media_EncoderCapabilities_native_init},
-    {"native_isBitrateModeSupported", "(I)Z", (void *)android_media_EncoderCapabilities_isBitrateModeSupported}
-};
-
-static const JNINativeMethod gCodecCapsMethods[] = {
-    { "native_init", "()V", (void *)android_media_CodecCapabilities_native_init },
-    { "native_createFromProfileLevel", "(Ljava/lang/String;II)Landroid/media/MediaCodecInfo$CodecCapabilities$CodecCapsNativeImpl;", (void *)android_media_CodecCapabilities_createFromProfileLevel },
-    { "native_dup", "()Landroid/media/MediaCodecInfo$CodecCapabilities$CodecCapsNativeImpl;", (void *)android_media_CodecCapabilities_native_dup },
-    { "native_finalize", "()V", (void *)android_media_CodecCapabilities_native_finalize },
-    { "native_getMaxSupportedInstances", "()I", (void *)android_media_CodecCapabilities_getMaxSupportedInstances },
-    { "native_getMimeType", "()Ljava/lang/String;", (void *)android_media_CodecCapabilities_getMimeType },
-    { "native_isFeatureRequired", "(Ljava/lang/String;)Z", (void *)android_media_CodecCapabilities_isFeatureRequired },
-    { "native_isFeatureSupported", "(Ljava/lang/String;)Z", (void *)android_media_CodecCapabilities_isFeatureSupported },
-    { "native_isFormatSupported", "([Ljava/lang/String;[Ljava/lang/Object;)Z", (void *)android_media_CodecCapabilities_isFormatSupported },
-    { "native_isRegular", "()Z", (void *)android_media_CodecCapabilities_isRegular },
-};
-
-int register_android_media_CodecCapabilities(JNIEnv *env) {
-    int result = AndroidRuntime::registerNativeMethods(env,
-            "android/media/MediaCodecInfo$AudioCapabilities$AudioCapsNativeImpl",
-            gAudioCapsMethods, NELEM(gAudioCapsMethods));
-    if (result != JNI_OK) {
-        return result;
-    }
-
-    result = AndroidRuntime::registerNativeMethods(env,
-            "android/media/MediaCodecInfo$VideoCapabilities$PerformancePoint",
-            gPerformancePointMethods, NELEM(gPerformancePointMethods));
-    if (result != JNI_OK) {
-        return result;
-    }
-
-    result = AndroidRuntime::registerNativeMethods(env,
-            "android/media/MediaCodecInfo$VideoCapabilities$VideoCapsNativeImpl",
-            gVideoCapsMethods, NELEM(gVideoCapsMethods));
-    if (result != JNI_OK) {
-        return result;
-    }
-
-    result = AndroidRuntime::registerNativeMethods(env,
-            "android/media/MediaCodecInfo$EncoderCapabilities$EncoderCapsNativeImpl",
-            gEncoderCapsMethods, NELEM(gEncoderCapsMethods));
-    if (result != JNI_OK) {
-        return result;
-    }
-
-    result = AndroidRuntime::registerNativeMethods(env,
-            "android/media/MediaCodecInfo$CodecCapabilities$CodecCapsNativeImpl",
-            gCodecCapsMethods, NELEM(gCodecCapsMethods));
-    return result;
-}
\ No newline at end of file
diff --git a/media/jni/android_media_CodecCapabilities.h b/media/jni/android_media_CodecCapabilities.h
deleted file mode 100644
index 5cca0b5..0000000
--- a/media/jni/android_media_CodecCapabilities.h
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright 2024, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef _ANDROID_MEDIA_CODECCAPABILITIES_H_
-#define _ANDROID_MEDIA_CODECCAPABILITIES_H_
-
-#include "jni.h"
-
-#include <media/CodecCapabilities.h>
-
-namespace android {
-
-struct JCodecCapabilities : public RefBase {
-    JCodecCapabilities(std::shared_ptr<CodecCapabilities> codecCaps);
-
-    std::shared_ptr<CodecCapabilities> getCodecCaps() const;
-
-    int32_t getMaxSupportedInstances() const;
-    std::string getMediaType() const;
-    bool isFeatureRequired(const std::string& name) const;
-    bool isFeatureSupported(const std::string& name) const;
-    bool isFormatSupported(const sp<AMessage> &format) const;
-    bool isRegular() const;
-
-private:
-    std::shared_ptr<CodecCapabilities> mCodecCaps;
-};
-
-jobject convertToJavaCodecCapabiliites(
-        JNIEnv *env, std::shared_ptr<CodecCapabilities> codecCaps);
-
-}
-
-#endif  // _ANDROID_MEDIA_CODECCAPABILITIES_H_
\ No newline at end of file
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index 1790670..8419ce7 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -16,14 +16,12 @@
 
 //#define LOG_NDEBUG 0
 #define LOG_TAG "MediaCodec-JNI"
-#include <android_media_codec.h>
 #include <utils/Log.h>
 
 #include <type_traits>
 
 #include "android_media_MediaCodec.h"
 
-#include "android_media_CodecCapabilities.h"
 #include "android_media_MediaCodecLinearBlock.h"
 #include "android_media_MediaCrypto.h"
 #include "android_media_MediaDescrambler.h"
@@ -140,8 +138,6 @@
 static struct {
     jclass capsClazz;
     jmethodID capsCtorId;
-    jclass cpasImplClazz;
-    jmethodID capsImplCtorId;
     jclass profileLevelClazz;
     jfieldID profileField;
     jfieldID levelField;
@@ -1000,12 +996,10 @@
         env->SetIntArrayRegion(colorFormatsArray.get(), i, 1, &val);
     }
 
-    jobject javaCodecCapsImpl = env->NewObject(
-            gCodecInfo.cpasImplClazz, gCodecInfo.capsImplCtorId,
+    return env->NewObject(
+            gCodecInfo.capsClazz, gCodecInfo.capsCtorId,
             profileLevelArray.get(), colorFormatsArray.get(), isEncoder,
             defaultFormatRef.get(), detailsRef.get());
-
-    return env->NewObject(gCodecInfo.capsClazz, gCodecInfo.capsCtorId, javaCodecCapsImpl);
 }
 
 status_t JMediaCodec::getCodecInfo(JNIEnv *env, jobject *codecInfoObject) const {
@@ -1033,18 +1027,11 @@
         env->NewObjectArray(mediaTypes.size(), gCodecInfo.capsClazz, NULL));
 
     for (size_t i = 0; i < mediaTypes.size(); i++) {
-        jobject jCodecCaps = NULL;
-        if (android::media::codec::provider_->native_capabilites()) {
-            const std::shared_ptr<CodecCapabilities> codecCaps
-                    = codecInfo->getCodecCapsFor(mediaTypes[i].c_str());
-            jCodecCaps = convertToJavaCodecCapabiliites(env, codecCaps);
-        } else {
-            const sp<MediaCodecInfo::Capabilities> caps =
-                    codecInfo->getCapabilitiesFor(mediaTypes[i].c_str());
-            jCodecCaps = getCodecCapabilitiesObject(
-                    env, mediaTypes[i].c_str(), isEncoder, caps);
-        }
-        ScopedLocalRef<jobject> capsObj(env, jCodecCaps);
+        const sp<MediaCodecInfo::Capabilities> caps =
+                codecInfo->getCapabilitiesFor(mediaTypes[i].c_str());
+
+        ScopedLocalRef<jobject> capsObj(env, getCodecCapabilitiesObject(
+                env, mediaTypes[i].c_str(), isEncoder, caps));
 
         env->SetObjectArrayElement(capsArrayObj.get(), i, capsObj.get());
     }
@@ -3890,20 +3877,10 @@
     gCodecInfo.capsClazz = (jclass)env->NewGlobalRef(clazz.get());
 
     method = env->GetMethodID(clazz.get(), "<init>",
-            "(Landroid/media/MediaCodecInfo$CodecCapabilities$CodecCapsIntf;)V");
-    CHECK(method != NULL);
-    gCodecInfo.capsCtorId = method;
-
-    clazz.reset(env->FindClass(
-            "android/media/MediaCodecInfo$CodecCapabilities$CodecCapsLegacyImpl"));
-    CHECK(clazz.get() != NULL);
-    gCodecInfo.cpasImplClazz = (jclass)env->NewGlobalRef(clazz.get());
-
-    method = env->GetMethodID(clazz.get(), "<init>",
             "([Landroid/media/MediaCodecInfo$CodecProfileLevel;[IZ"
             "Ljava/util/Map;Ljava/util/Map;)V");
     CHECK(method != NULL);
-    gCodecInfo.capsImplCtorId = method;
+    gCodecInfo.capsCtorId = method;
 
     clazz.reset(env->FindClass("android/media/MediaCodecInfo$CodecProfileLevel"));
     CHECK(clazz.get() != NULL);
diff --git a/media/jni/android_media_MediaCodecList.cpp b/media/jni/android_media_MediaCodecList.cpp
index 3522b35..07866ac 100644
--- a/media/jni/android_media_MediaCodecList.cpp
+++ b/media/jni/android_media_MediaCodecList.cpp
@@ -16,9 +16,6 @@
 
 //#define LOG_NDEBUG 0
 #define LOG_TAG "MediaCodec-JNI"
-
-#include <android_media_codec.h>
-
 #include <utils/Log.h>
 
 #include <media/stagefright/foundation/ADebug.h>
@@ -35,7 +32,6 @@
 #include "android_runtime/AndroidRuntime.h"
 #include "jni.h"
 #include <nativehelper/JNIHelp.h>
-#include "android_media_CodecCapabilities.h"
 #include "android_media_Streams.h"
 
 using namespace android;
@@ -249,114 +245,96 @@
         return NULL;
     }
 
-    jobject caps;
-    if (android::media::codec::provider_->native_capabilites()) {
-        std::shared_ptr<CodecCapabilities> codecCaps = info.info->getCodecCapsFor(typeStr);
-        caps = android::convertToJavaCodecCapabiliites(env, codecCaps);
-    } else {
-        Vector<MediaCodecInfo::ProfileLevel> profileLevels;
-        Vector<uint32_t> colorFormats;
+    Vector<MediaCodecInfo::ProfileLevel> profileLevels;
+    Vector<uint32_t> colorFormats;
 
-        sp<AMessage> defaultFormat = new AMessage();
-        defaultFormat->setString("mime", typeStr);
+    sp<AMessage> defaultFormat = new AMessage();
+    defaultFormat->setString("mime", typeStr);
 
-        // TODO query default-format also from codec/codec list
-        const sp<MediaCodecInfo::Capabilities> &capabilities =
-            info.info->getCapabilitiesFor(typeStr);
-        env->ReleaseStringUTFChars(type, typeStr);
-        typeStr = NULL;
-        if (capabilities == NULL) {
-            jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
-            return NULL;
-        }
-
-        capabilities->getSupportedColorFormats(&colorFormats);
-        capabilities->getSupportedProfileLevels(&profileLevels);
-        sp<AMessage> details = capabilities->getDetails();
-        bool isEncoder = info.info->isEncoder();
-
-        jobject defaultFormatObj = NULL;
-        if (ConvertMessageToMap(env, defaultFormat, &defaultFormatObj)) {
-            return NULL;
-        }
-
-        jobject infoObj = NULL;
-        if (ConvertMessageToMap(env, details, &infoObj)) {
-            env->DeleteLocalRef(defaultFormatObj);
-            return NULL;
-        }
-
-        jclass capsImplClazz = env->FindClass(
-                "android/media/MediaCodecInfo$CodecCapabilities$CodecCapsLegacyImpl");
-        CHECK(capsImplClazz != NULL);
-
-        jclass profileLevelClazz =
-            env->FindClass("android/media/MediaCodecInfo$CodecProfileLevel");
-        CHECK(profileLevelClazz != NULL);
-
-        jobjectArray profileLevelArray =
-            env->NewObjectArray(profileLevels.size(), profileLevelClazz, NULL);
-
-        jfieldID profileField =
-            env->GetFieldID(profileLevelClazz, "profile", "I");
-
-        jfieldID levelField =
-            env->GetFieldID(profileLevelClazz, "level", "I");
-
-        for (size_t i = 0; i < profileLevels.size(); ++i) {
-            const MediaCodecInfo::ProfileLevel &src = profileLevels.itemAt(i);
-
-            jobject profileLevelObj = env->AllocObject(profileLevelClazz);
-
-            env->SetIntField(profileLevelObj, profileField, src.mProfile);
-            env->SetIntField(profileLevelObj, levelField, src.mLevel);
-
-            env->SetObjectArrayElement(profileLevelArray, i, profileLevelObj);
-
-            env->DeleteLocalRef(profileLevelObj);
-            profileLevelObj = NULL;
-        }
-
-        jintArray colorFormatsArray = env->NewIntArray(colorFormats.size());
-
-        for (size_t i = 0; i < colorFormats.size(); ++i) {
-            jint val = colorFormats.itemAt(i);
-            env->SetIntArrayRegion(colorFormatsArray, i, 1, &val);
-        }
-
-        jmethodID capsImplConstructID = env->GetMethodID(capsImplClazz, "<init>",
-                "([Landroid/media/MediaCodecInfo$CodecProfileLevel;[IZ"
-                "Ljava/util/Map;Ljava/util/Map;)V");
-
-        jobject capsImpl = env->NewObject(capsImplClazz, capsImplConstructID,
-                profileLevelArray, colorFormatsArray, isEncoder,
-                defaultFormatObj, infoObj);
-
-        jclass capsClazz = env->FindClass(
-                "android/media/MediaCodecInfo$CodecCapabilities");
-        CHECK(capsClazz != NULL);
-
-        jmethodID capsConstructID = env->GetMethodID(capsClazz, "<init>",
-                "(Landroid/media/MediaCodecInfo$CodecCapabilities$CodecCapsIntf;)V");
-
-        caps = env->NewObject(capsClazz, capsConstructID, capsImpl);
-
-        env->DeleteLocalRef(profileLevelArray);
-        profileLevelArray = NULL;
-
-        env->DeleteLocalRef(colorFormatsArray);
-        colorFormatsArray = NULL;
-
-        env->DeleteLocalRef(defaultFormatObj);
-        defaultFormatObj = NULL;
-
-        env->DeleteLocalRef(infoObj);
-        infoObj = NULL;
-
-        env->DeleteLocalRef(capsImpl);
-        capsImpl = NULL;
+    // TODO query default-format also from codec/codec list
+    const sp<MediaCodecInfo::Capabilities> &capabilities =
+        info.info->getCapabilitiesFor(typeStr);
+    env->ReleaseStringUTFChars(type, typeStr);
+    typeStr = NULL;
+    if (capabilities == NULL) {
+        jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+        return NULL;
     }
 
+    capabilities->getSupportedColorFormats(&colorFormats);
+    capabilities->getSupportedProfileLevels(&profileLevels);
+    sp<AMessage> details = capabilities->getDetails();
+    bool isEncoder = info.info->isEncoder();
+
+    jobject defaultFormatObj = NULL;
+    if (ConvertMessageToMap(env, defaultFormat, &defaultFormatObj)) {
+        return NULL;
+    }
+
+    jobject infoObj = NULL;
+    if (ConvertMessageToMap(env, details, &infoObj)) {
+        env->DeleteLocalRef(defaultFormatObj);
+        return NULL;
+    }
+
+    jclass capsClazz =
+        env->FindClass("android/media/MediaCodecInfo$CodecCapabilities");
+    CHECK(capsClazz != NULL);
+
+    jclass profileLevelClazz =
+        env->FindClass("android/media/MediaCodecInfo$CodecProfileLevel");
+    CHECK(profileLevelClazz != NULL);
+
+    jobjectArray profileLevelArray =
+        env->NewObjectArray(profileLevels.size(), profileLevelClazz, NULL);
+
+    jfieldID profileField =
+        env->GetFieldID(profileLevelClazz, "profile", "I");
+
+    jfieldID levelField =
+        env->GetFieldID(profileLevelClazz, "level", "I");
+
+    for (size_t i = 0; i < profileLevels.size(); ++i) {
+        const MediaCodecInfo::ProfileLevel &src = profileLevels.itemAt(i);
+
+        jobject profileLevelObj = env->AllocObject(profileLevelClazz);
+
+        env->SetIntField(profileLevelObj, profileField, src.mProfile);
+        env->SetIntField(profileLevelObj, levelField, src.mLevel);
+
+        env->SetObjectArrayElement(profileLevelArray, i, profileLevelObj);
+
+        env->DeleteLocalRef(profileLevelObj);
+        profileLevelObj = NULL;
+    }
+
+    jintArray colorFormatsArray = env->NewIntArray(colorFormats.size());
+
+    for (size_t i = 0; i < colorFormats.size(); ++i) {
+        jint val = colorFormats.itemAt(i);
+        env->SetIntArrayRegion(colorFormatsArray, i, 1, &val);
+    }
+
+    jmethodID capsConstructID = env->GetMethodID(capsClazz, "<init>",
+            "([Landroid/media/MediaCodecInfo$CodecProfileLevel;[IZ"
+            "Ljava/util/Map;Ljava/util/Map;)V");
+
+    jobject caps = env->NewObject(capsClazz, capsConstructID,
+            profileLevelArray, colorFormatsArray, isEncoder,
+            defaultFormatObj, infoObj);
+
+    env->DeleteLocalRef(profileLevelArray);
+    profileLevelArray = NULL;
+
+    env->DeleteLocalRef(colorFormatsArray);
+    colorFormatsArray = NULL;
+
+    env->DeleteLocalRef(defaultFormatObj);
+    defaultFormatObj = NULL;
+
+    env->DeleteLocalRef(infoObj);
+    infoObj = NULL;
+
     return caps;
 }
 
diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp
index fcba150..d05ee55 100644
--- a/media/jni/android_media_MediaPlayer.cpp
+++ b/media/jni/android_media_MediaPlayer.cpp
@@ -1465,7 +1465,6 @@
 extern int register_android_media_ImageReader(JNIEnv *env);
 extern int register_android_media_ImageWriter(JNIEnv *env);
 extern int register_android_media_JetPlayer(JNIEnv *env);
-extern int register_android_media_CodecCapabilities(JNIEnv *env);
 extern int register_android_media_Crypto(JNIEnv *env);
 extern int register_android_media_Drm(JNIEnv *env);
 extern int register_android_media_Descrambler(JNIEnv *env);
@@ -1580,11 +1579,6 @@
         goto bail;
     }
 
-    if (register_android_media_CodecCapabilities(env) < 0) {
-        ALOGE("ERROR: CodecCapabilities native registration failed");
-        goto bail;
-    }
-
     if (register_android_media_Crypto(env) < 0) {
         ALOGE("ERROR: MediaCodec native registration failed");
         goto bail;
diff --git a/nfc/tests/src/android/nfc/NfcManagerTest.java b/nfc/tests/src/android/nfc/NfcManagerTest.java
new file mode 100644
index 0000000..06314cc
--- /dev/null
+++ b/nfc/tests/src/android/nfc/NfcManagerTest.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.nfc;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+@RunWith(AndroidJUnit4.class)
+public class NfcManagerTest {
+
+    private MockitoSession mMockitoSession;
+    private NfcManager mNfcManager;
+    @Mock
+    private Context mContext;
+
+    @Before
+    public void setUp() {
+        mMockitoSession = ExtendedMockito.mockitoSession()
+                .mockStatic(NfcAdapter.class)
+                .strictness(Strictness.LENIENT)
+                .startMocking();
+        MockitoAnnotations.initMocks(this);
+
+        when(NfcAdapter.getNfcAdapter(any())).thenReturn(mock(NfcAdapter.class));
+        when(mContext.getApplicationContext()).thenReturn(mContext);
+        mNfcManager = new NfcManager(mContext);
+    }
+
+    @After
+    public void tearDown() {
+        mMockitoSession.finishMocking();
+    }
+
+    @Test
+    public void testGetDefaultAdapter() {
+        NfcAdapter nfcAdapter = mNfcManager.getDefaultAdapter();
+        assertThat(nfcAdapter).isNotNull();
+    }
+}
diff --git a/nfc/tests/src/android/nfc/NfcRoutingTableEntryTest.java b/nfc/tests/src/android/nfc/NfcRoutingTableEntryTest.java
new file mode 100644
index 0000000..a90a716
--- /dev/null
+++ b/nfc/tests/src/android/nfc/NfcRoutingTableEntryTest.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.nfc;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public final class NfcRoutingTableEntryTest {
+
+    @Test
+    public void testAidEntry_GetAid() {
+        String expectedAid = "A00000061A02";
+        RoutingTableAidEntry entry = new RoutingTableAidEntry(1, expectedAid, 0);
+
+        assertEquals(expectedAid, entry.getAid());
+    }
+
+    @Test
+    public void testProtocolEntry_GetProtocol() {
+        RoutingTableProtocolEntry entry =
+                new RoutingTableProtocolEntry(1, RoutingTableProtocolEntry.PROTOCOL_T1T, 0);
+
+        assertEquals(RoutingTableProtocolEntry.PROTOCOL_T1T, entry.getProtocol());
+    }
+
+    @Test
+    public void testSystemCodeEntry_GetSystemCode() {
+        byte[] expectedSystemCode = {0x01, 0x02, 0x03};
+        RoutingTableSystemCodeEntry entry =
+                new RoutingTableSystemCodeEntry(1, expectedSystemCode, 0);
+
+        assertArrayEquals(expectedSystemCode, entry.getSystemCode());
+    }
+
+    @Test
+    public void testTechnologyEntry_GetTechnology_A() {
+        RoutingTableTechnologyEntry entry =
+                new RoutingTableTechnologyEntry(1, RoutingTableTechnologyEntry.TECHNOLOGY_A, 0);
+
+        assertEquals(RoutingTableTechnologyEntry.TECHNOLOGY_A, entry.getTechnology());
+    }
+}
diff --git a/nfc/tests/src/android/nfc/OemLogItemsTest.java b/nfc/tests/src/android/nfc/OemLogItemsTest.java
new file mode 100644
index 0000000..21ef804
--- /dev/null
+++ b/nfc/tests/src/android/nfc/OemLogItemsTest.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.nfc;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.time.Instant;
+
+@RunWith(JUnit4.class)
+public final class OemLogItemsTest {
+
+    @Test
+    public void testGetAction() {
+        OemLogItems item = new OemLogItems.Builder(OemLogItems.LOG_ACTION_RF_FIELD_STATE_CHANGED)
+                .build();
+        assertEquals(OemLogItems.LOG_ACTION_RF_FIELD_STATE_CHANGED, item.getAction());
+    }
+
+    @Test
+    public void testGetEvent() {
+        OemLogItems item = new OemLogItems.Builder(OemLogItems.LOG_ACTION_NFC_TOGGLE)
+                .setCallingEvent(OemLogItems.EVENT_ENABLE)
+                .build();
+        assertEquals(OemLogItems.EVENT_ENABLE, item.getEvent());
+    }
+
+    @Test
+    public void testGetCallingPid() {
+        OemLogItems item = new OemLogItems.Builder(OemLogItems.LOG_ACTION_NFC_TOGGLE)
+                .setCallingPid(1234)
+                .build();
+        assertEquals(1234, item.getCallingPid());
+    }
+
+    @Test
+    public void testGetCommandApdu() {
+        byte[] commandApdu = {0x01, 0x02, 0x03};
+        OemLogItems item = new OemLogItems.Builder(OemLogItems.LOG_ACTION_HCE_DATA)
+                .setApduCommand(commandApdu)
+                .build();
+        assertArrayEquals(commandApdu, item.getCommandApdu());
+    }
+
+    @Test
+    public void testGetResponseApdu() {
+        byte[] responseApdu = {0x04, 0x05, 0x06};
+        OemLogItems item = new OemLogItems.Builder(OemLogItems.LOG_ACTION_HCE_DATA)
+                .setApduResponse(responseApdu)
+                .build();
+        assertArrayEquals(responseApdu, item.getResponseApdu());
+    }
+
+    @Test
+    public void testGetRfFieldEventTimeMillis() {
+        Instant expectedTime = Instant.ofEpochSecond(1688768000, 123456789);
+        OemLogItems item = new OemLogItems.Builder(OemLogItems.LOG_ACTION_RF_FIELD_STATE_CHANGED)
+                .setRfFieldOnTime(expectedTime)
+                .build();
+        assertEquals(expectedTime, item.getRfFieldEventTimeMillis());
+    }
+
+    @Test
+    public void testGetTag() {
+        Tag mockTag = mock(Tag.class);
+        OemLogItems item = new OemLogItems.Builder(OemLogItems.LOG_ACTION_TAG_DETECTED)
+                .setTag(mockTag)
+                .build();
+        assertEquals(mockTag, item.getTag());
+    }
+}
diff --git a/nfc/tests/src/android/nfc/cardemulation/CardemulationTest.java b/nfc/tests/src/android/nfc/cardemulation/CardemulationTest.java
new file mode 100644
index 0000000..a215835
--- /dev/null
+++ b/nfc/tests/src/android/nfc/cardemulation/CardemulationTest.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.nfc.cardemulation;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.role.RoleManager;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.nfc.Constants;
+import android.nfc.INfcCardEmulation;
+import android.nfc.NfcAdapter;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.provider.Settings;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+@RunWith(AndroidJUnit4.class)
+public class CardemulationTest {
+
+    private CardEmulation mCardEmulation;
+    @Mock
+    private Context mContext;
+    @Mock
+    private INfcCardEmulation mINfcCardEmulation;
+    @Mock
+    private NfcAdapter mNfcAdapter;
+    @Mock
+    private PackageManager mPackageManager;
+    private MockitoSession mMockitoSession;
+
+    @Before
+    public void setUp() {
+        mMockitoSession = ExtendedMockito.mockitoSession()
+                .mockStatic(NfcAdapter.class)
+                .mockStatic(Settings.Secure.class)
+                .strictness(Strictness.LENIENT)
+                .startMocking();
+        MockitoAnnotations.initMocks(this);
+
+        when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION))
+                .thenReturn(true);
+        when(mContext.getApplicationContext()).thenReturn(mContext);
+        when(mContext.getPackageManager()).thenReturn(mPackageManager);
+        assertThat(mNfcAdapter).isNotNull();
+        when(mNfcAdapter.getCardEmulationService()).thenReturn(mINfcCardEmulation);
+        when(mNfcAdapter.getContext()).thenReturn(mContext);
+        mCardEmulation = CardEmulation.getInstance(mNfcAdapter);
+    }
+
+    @After
+    public void tearDown() {
+        mMockitoSession.finishMocking();
+    }
+
+    @Test
+    public void testIsDefaultServiceForCategory() throws RemoteException {
+        ComponentName componentName = mock(ComponentName.class);
+        UserHandle userHandle = mock(UserHandle.class);
+        when(userHandle.getIdentifier()).thenReturn(1);
+        when(mContext.getUser()).thenReturn(userHandle);
+        when(mINfcCardEmulation.isDefaultServiceForCategory(1, componentName,
+                "payment")).thenReturn(true);
+        boolean result = mCardEmulation.isDefaultServiceForCategory(componentName,
+                "payment");
+        assertThat(result).isTrue();
+        verify(mINfcCardEmulation).isDefaultServiceForCategory(1, componentName,
+                "payment");
+
+    }
+
+    @Test
+    public void testIsDefaultServiceForAid() throws RemoteException {
+        ComponentName componentName = mock(ComponentName.class);
+        UserHandle userHandle = mock(UserHandle.class);
+        when(userHandle.getIdentifier()).thenReturn(1);
+        when(mContext.getUser()).thenReturn(userHandle);
+        when(mINfcCardEmulation.isDefaultServiceForAid(1, componentName,
+                "payment")).thenReturn(true);
+        boolean result = mCardEmulation.isDefaultServiceForAid(componentName,
+                "payment");
+        assertThat(result).isTrue();
+        verify(mINfcCardEmulation).isDefaultServiceForAid(1, componentName,
+                "payment");
+    }
+
+    @Test
+    public void testCategoryAllowsForegroundPreference() throws Settings.SettingNotFoundException {
+        when(mContext.createContextAsUser(any(), anyInt())).thenReturn(mContext);
+        RoleManager roleManager = mock(RoleManager.class);
+        when(roleManager.isRoleAvailable(RoleManager.ROLE_WALLET)).thenReturn(false);
+        when(mContext.getSystemService(RoleManager.class)).thenReturn(roleManager);
+        ContentResolver contentResolver = mock(ContentResolver.class);
+        when(mContext.getContentResolver()).thenReturn(contentResolver);
+        when(Settings.Secure.getInt(contentResolver, Constants
+                .SETTINGS_SECURE_NFC_PAYMENT_FOREGROUND)).thenReturn(1);
+        boolean result = mCardEmulation.categoryAllowsForegroundPreference("payment");
+        assertThat(result).isTrue();
+    }
+
+    @Test
+    public void testGetSelectionModeForCategory() throws RemoteException {
+        when(mINfcCardEmulation.isDefaultPaymentRegistered()).thenReturn(true);
+        int result = mCardEmulation.getSelectionModeForCategory("payment");
+        assertThat(result).isEqualTo(0);
+    }
+
+    @Test
+    public void testSetShouldDefaultToObserveModeForService() throws RemoteException {
+        UserHandle userHandle = mock(UserHandle.class);
+        when(userHandle.getIdentifier()).thenReturn(1);
+        when(mContext.getUser()).thenReturn(userHandle);
+        ComponentName componentName = mock(ComponentName.class);
+        when(mINfcCardEmulation.setShouldDefaultToObserveModeForService(1, componentName, true))
+                .thenReturn(true);
+        boolean result = mCardEmulation
+                .setShouldDefaultToObserveModeForService(componentName, true);
+        assertThat(result).isTrue();
+        verify(mINfcCardEmulation).setShouldDefaultToObserveModeForService(1, componentName, true);
+    }
+
+    @Test
+    public void testRegisterPollingLoopFilterForService()throws RemoteException {
+        UserHandle userHandle = mock(UserHandle.class);
+        when(userHandle.getIdentifier()).thenReturn(1);
+        when(mContext.getUser()).thenReturn(userHandle);
+        ComponentName componentName = mock(ComponentName.class);
+        when(mINfcCardEmulation.registerPollingLoopFilterForService(anyInt(),
+                any(), anyString(), anyBoolean())).thenReturn(true);
+        boolean result = mCardEmulation.registerPollingLoopFilterForService(componentName,
+                "A0000000041010", true);
+        assertThat(result).isTrue();
+        verify(mINfcCardEmulation)
+                .registerPollingLoopFilterForService(anyInt(), any(), anyString(), anyBoolean());
+    }
+
+    @Test
+    public void testRemovePollingLoopFilterForService()throws RemoteException {
+        UserHandle userHandle = mock(UserHandle.class);
+        when(userHandle.getIdentifier()).thenReturn(1);
+        when(mContext.getUser()).thenReturn(userHandle);
+        ComponentName componentName = mock(ComponentName.class);
+        when(mINfcCardEmulation.removePollingLoopFilterForService(anyInt(), any(), anyString()))
+                .thenReturn(true);
+        boolean result = mCardEmulation
+                .removePollingLoopFilterForService(componentName, "A0000000041010");
+        assertThat(result).isTrue();
+        verify(mINfcCardEmulation).removePollingLoopFilterForService(anyInt(), any(), anyString());
+    }
+}
diff --git a/nfc/tests/src/android/nfc/dta/NfcDtaTest.java b/nfc/tests/src/android/nfc/dta/NfcDtaTest.java
new file mode 100644
index 0000000..38fb7ef
--- /dev/null
+++ b/nfc/tests/src/android/nfc/dta/NfcDtaTest.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.nfc.dta;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.nfc.INfcDta;
+import android.nfc.NfcAdapter;
+import android.os.RemoteException;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+public class NfcDtaTest {
+    private final String mServiceName = "serviceName";
+    private final int mServiceSap = 1;
+    private final int mMiu = 1;
+    private final int mRwSize = 1;
+    private final int mTestCaseId = 1;
+    @Mock
+    private NfcAdapter mMockNfcAdapter;
+    @Mock
+    private INfcDta mMockService;
+    @Mock
+    private Context mMockContext;
+
+    private NfcDta mNfcDta;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        when(mMockNfcAdapter.getContext()).thenReturn(mMockContext);
+        when(mMockNfcAdapter.getNfcDtaInterface()).thenReturn(mMockService);
+
+        mNfcDta = NfcDta.getInstance(mMockNfcAdapter);
+    }
+
+    @Test
+    public void testEnableData() throws RemoteException {
+        assertTrue(mNfcDta.enableDta());
+        verify(mMockService).enableDta();
+    }
+
+    @Test
+    public void testEnableDataWithRemoteException() throws RemoteException {
+        doThrow(new RemoteException()).when(mMockService).enableDta();
+
+        assertFalse(mNfcDta.enableDta());
+        verify(mMockService).enableDta();
+    }
+
+    @Test
+    public void testDisableData() throws RemoteException {
+        assertTrue(mNfcDta.disableDta());
+        verify(mMockService).disableDta();
+    }
+
+    @Test
+    public void testDisableDataWithRemoteException() throws RemoteException {
+        doThrow(new RemoteException()).when(mMockService).disableDta();
+
+        assertFalse(mNfcDta.disableDta());
+        verify(mMockService).disableDta();
+    }
+
+    @Test
+    public void testEnableServer() throws RemoteException {
+        when(mMockService.enableServer(mServiceName, mServiceSap, mMiu, mRwSize,
+                mTestCaseId)).thenReturn(true);
+
+        mNfcDta.enableServer(mServiceName, mServiceSap, mMiu, mRwSize, mTestCaseId);
+        verify(mMockService).enableServer(mServiceName, mServiceSap, mMiu, mRwSize, mTestCaseId);
+    }
+
+    @Test
+    public void testEnableServerWithRemoteException() throws RemoteException {
+        doThrow(new RemoteException()).when(mMockService).enableServer(mServiceName, mServiceSap,
+                mMiu,
+                mRwSize, mTestCaseId);
+
+        mNfcDta.enableServer(mServiceName, mServiceSap, mMiu, mRwSize, mTestCaseId);
+        verify(mMockService).enableServer(mServiceName, mServiceSap, mMiu, mRwSize, mTestCaseId);
+    }
+
+    @Test
+    public void testDisableServer() throws RemoteException {
+        assertTrue(mNfcDta.disableServer());
+        verify(mMockService).disableServer();
+    }
+
+    @Test
+    public void testDisableServerWithRemoteException() throws RemoteException {
+        doThrow(new RemoteException()).when(mMockService).disableServer();
+
+        assertFalse(mNfcDta.disableServer());
+        verify(mMockService).disableServer();
+    }
+
+    @Test
+    public void testEnableClient() throws RemoteException {
+        when(mMockService.enableClient(mServiceName, mMiu, mRwSize, mTestCaseId)).thenReturn(true);
+
+        mNfcDta.enableClient(mServiceName, mMiu, mRwSize, mTestCaseId);
+        verify(mMockService).enableClient(mServiceName, mMiu, mRwSize, mTestCaseId);
+    }
+
+    @Test
+    public void testEnableClientWithRemoteException() throws RemoteException {
+        doThrow(new RemoteException()).when(mMockService).enableClient(mServiceName, mMiu, mRwSize,
+                mTestCaseId);
+
+        mNfcDta.enableClient(mServiceName, mMiu, mRwSize, mTestCaseId);
+        verify(mMockService).enableClient(mServiceName, mMiu, mRwSize, mTestCaseId);
+    }
+
+    @Test
+    public void testDisableClient() throws RemoteException {
+        assertTrue(mNfcDta.disableClient());
+        verify(mMockService).disableClient();
+    }
+
+    @Test
+    public void testDisableClientWithRemoteException() throws RemoteException {
+        doThrow(new RemoteException()).when(mMockService).disableClient();
+
+        assertFalse(mNfcDta.disableClient());
+        verify(mMockService).disableClient();
+    }
+
+    @Test
+    public void testRegisterMessageService() throws RemoteException {
+        String msgServiceName = "sampleServiceName";
+        when(mMockService.registerMessageService(msgServiceName)).thenReturn(true);
+
+        mNfcDta.registerMessageService(msgServiceName);
+        verify(mMockService).registerMessageService(msgServiceName);
+    }
+
+    @Test
+    public void testRegisterMessageServiceWithRemoteException() throws RemoteException {
+        String msgServiceName = "sampleServiceName";
+        doThrow(new RemoteException()).when(mMockService).registerMessageService(msgServiceName);
+
+        assertFalse(mNfcDta.registerMessageService(msgServiceName));
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testGetInstanceWithNullPointerException() {
+        NfcDta.getInstance(null);
+    }
+
+    @Test(expected = UnsupportedOperationException.class)
+    public void testGetInstanceWithUnsupportedOperationExceptionForNfcAdapterContext() {
+        when(mMockNfcAdapter.getContext()).thenReturn(null);
+
+        NfcDta.getInstance(mMockNfcAdapter);
+    }
+}
diff --git a/nfc/tests/src/android/nfc/tech/NfcBTest.java b/nfc/tests/src/android/nfc/tech/NfcBTest.java
new file mode 100644
index 0000000..98d6070
--- /dev/null
+++ b/nfc/tests/src/android/nfc/tech/NfcBTest.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.nfc.tech;
+
+import static android.nfc.tech.NfcB.EXTRA_APPDATA;
+import static android.nfc.tech.NfcB.EXTRA_PROTINFO;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.nfc.INfcTag;
+import android.nfc.Tag;
+import android.nfc.TransceiveResult;
+import android.os.Bundle;
+import android.os.RemoteException;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.IOException;
+
+public class NfcBTest {
+    private final byte[] mSampleAppDate = new byte[] {1, 2, 3};
+    private final byte[] mSampleProtInfo = new byte[] {3, 2, 1};
+    @Mock
+    private Tag mMockTag;
+    @Mock
+    private Bundle mMockBundle;
+    @Mock
+    private INfcTag mMockTagService;
+    private NfcB mNfcB;
+
+    @Before
+    public void setUp() throws RemoteException {
+        MockitoAnnotations.initMocks(this);
+        when(mMockBundle.getByteArray(EXTRA_APPDATA)).thenReturn(mSampleAppDate);
+        when(mMockBundle.getByteArray(EXTRA_PROTINFO)).thenReturn(mSampleProtInfo);
+        when(mMockTag.getTechExtras(TagTechnology.NFC_B)).thenReturn(mMockBundle);
+
+        mNfcB = new NfcB(mMockTag);
+    }
+
+    @Test
+    public void testGetApplicationData() {
+        assertNotNull(mNfcB.getApplicationData());
+    }
+
+    @Test
+    public void testGetProtocolInfo() {
+        assertNotNull(mNfcB.getProtocolInfo());
+    }
+
+    @Test
+    public void testGetNfcBInstance() {
+        Tag tag = mock(Tag.class);
+        when(tag.hasTech(TagTechnology.NFC_B)).thenReturn(true);
+        when(tag.getTechExtras(TagTechnology.NFC_B)).thenReturn(mMockBundle);
+
+        assertNotNull(NfcB.get(tag));
+        verify(tag).hasTech(TagTechnology.NFC_B);
+        verify(tag).getTechExtras(TagTechnology.NFC_B);
+    }
+
+    @Test
+    public void testGetNfcBNullInstance() {
+        Tag tag = mock(Tag.class);
+        when(tag.hasTech(TagTechnology.NFC_B)).thenReturn(false);
+
+        assertNull(NfcB.get(tag));
+        verify(tag).hasTech(TagTechnology.NFC_B);
+        verify(tag, never()).getTechExtras(TagTechnology.NFC_B);
+    }
+
+
+    @Test
+    public void testTransceive() throws IOException, RemoteException {
+        byte[] sampleData = new byte[] {1, 2, 3, 4, 5};
+        TransceiveResult mockTransceiveResult = mock(TransceiveResult.class);
+        when(mMockTag.getConnectedTechnology()).thenReturn(TagTechnology.NFC_B);
+        when(mMockTag.getTagService()).thenReturn(mMockTagService);
+        when(mMockTag.getServiceHandle()).thenReturn(1);
+        when(mMockTagService.transceive(1, sampleData, true))
+                .thenReturn(mockTransceiveResult);
+        when(mockTransceiveResult.getResponseOrThrow()).thenReturn(sampleData);
+
+        mNfcB.transceive(sampleData);
+        verify(mMockTag).getTagService();
+        verify(mMockTag).getServiceHandle();
+    }
+
+    @Test
+    public void testGetMaxTransceiveLength() throws RemoteException {
+        when(mMockTag.getTagService()).thenReturn(mMockTagService);
+        when(mMockTagService.getMaxTransceiveLength(TagTechnology.NFC_B)).thenReturn(1);
+
+        mNfcB.getMaxTransceiveLength();
+        verify(mMockTag).getTagService();
+    }
+}
diff --git a/nfc/tests/src/android/nfc/tech/NfcFTest.java b/nfc/tests/src/android/nfc/tech/NfcFTest.java
new file mode 100644
index 0000000..31a6943
--- /dev/null
+++ b/nfc/tests/src/android/nfc/tech/NfcFTest.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.nfc.tech;
+
+import static android.nfc.tech.NfcF.EXTRA_PMM;
+import static android.nfc.tech.NfcF.EXTRA_SC;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.nfc.ErrorCodes;
+import android.nfc.INfcTag;
+import android.nfc.Tag;
+import android.nfc.TransceiveResult;
+import android.os.Bundle;
+import android.os.RemoteException;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.IOException;
+
+public class NfcFTest {
+    private final byte[] mSampleSystemCode = new byte[] {1, 2, 3};
+    private final byte[] mSampleManufacturer = new byte[] {3, 2, 1};
+    @Mock
+    private Tag mMockTag;
+    @Mock
+    private INfcTag mMockTagService;
+    @Mock
+    private Bundle mMockBundle;
+    private NfcF mNfcF;
+
+    @Before
+    public void setUp() throws RemoteException {
+        MockitoAnnotations.initMocks(this);
+        when(mMockBundle.getByteArray(EXTRA_SC)).thenReturn(mSampleSystemCode);
+        when(mMockBundle.getByteArray(EXTRA_PMM)).thenReturn(mSampleManufacturer);
+        when(mMockTag.getTechExtras(TagTechnology.NFC_F)).thenReturn(mMockBundle);
+
+        mNfcF = new NfcF(mMockTag);
+    }
+
+    @Test
+    public void testGetSystemCode() {
+        assertNotNull(mNfcF.getSystemCode());
+    }
+
+    @Test
+    public void testGetManufacturer() {
+        assertNotNull(mNfcF.getManufacturer());
+    }
+
+    @Test
+    public void testGetNfcFInstanceWithTech() {
+        Tag tag = mock(Tag.class);
+        when(tag.getTechExtras(TagTechnology.NFC_F)).thenReturn(mMockBundle);
+        when(tag.hasTech(TagTechnology.NFC_F)).thenReturn(true);
+
+        assertNotNull(NfcF.get(tag));
+        verify(tag).getTechExtras(TagTechnology.NFC_F);
+        verify(tag).hasTech(TagTechnology.NFC_F);
+    }
+
+    @Test
+    public void testGetNfcFInstanceWithoutTech() {
+        Tag tag = mock(Tag.class);
+        when(tag.hasTech(TagTechnology.NFC_F)).thenReturn(false);
+
+        assertNull(NfcF.get(tag));
+        verify(tag).hasTech(TagTechnology.NFC_F);
+        verify(tag, never()).getTechExtras(TagTechnology.NFC_F);
+    }
+
+    @Test
+    public void testTransceive() throws IOException, RemoteException {
+        byte[] sampleData = new byte[]{1, 2, 3, 4, 5};
+        TransceiveResult mockTransceiveResult = mock(TransceiveResult.class);
+        when(mMockTag.getConnectedTechnology()).thenReturn(TagTechnology.NFC_F);
+        when(mMockTag.getTagService()).thenReturn(mMockTagService);
+        when(mMockTag.getServiceHandle()).thenReturn(1);
+        when(mMockTagService.transceive(1, sampleData, true))
+                .thenReturn(mockTransceiveResult);
+        when(mockTransceiveResult.getResponseOrThrow()).thenReturn(sampleData);
+
+        mNfcF.transceive(sampleData);
+        verify(mMockTag).getTagService();
+        verify(mMockTag).getServiceHandle();
+    }
+
+    @Test
+    public void testGetMaxTransceiveLength() throws RemoteException {
+        when(mMockTag.getTagService()).thenReturn(mMockTagService);
+        when(mMockTagService.getMaxTransceiveLength(TagTechnology.NFC_F)).thenReturn(1);
+
+        mNfcF.getMaxTransceiveLength();
+        verify(mMockTag).getTagService();
+    }
+
+    @Test
+    public void testGetTimeout() {
+        when(mMockTag.getTagService()).thenReturn(mMockTagService);
+        try {
+            when(mMockTagService.getTimeout(TagTechnology.NFC_F)).thenReturn(2000);
+
+            assertEquals(2000, mNfcF.getTimeout());
+            verify(mMockTag).getTagService();
+            verify(mMockTagService).getTimeout(TagTechnology.NFC_F);
+        } catch (Exception e) {
+            fail("Unexpected exception during valid getTimeout: " + e.getMessage());
+        }
+    }
+
+    @Test
+    public void testGetTimeoutRemoteException() {
+        when(mMockTag.getTagService()).thenReturn(mMockTagService);
+        try {
+            when(mMockTagService.getTimeout(TagTechnology.NFC_F)).thenThrow(new RemoteException());
+
+            assertEquals(0, mNfcF.getTimeout());
+        } catch (Exception e) {
+            fail("Unexpected exception during RemoteException in getTimeout: " + e.getMessage());
+        }
+    }
+
+    @Test
+    public void testSetTimeout() {
+        when(mMockTag.getTagService()).thenReturn(mMockTagService);
+        try {
+            when(mMockTagService.setTimeout(TagTechnology.NFC_F, 1000)).thenReturn(
+                    ErrorCodes.SUCCESS);
+
+            mNfcF.setTimeout(1000);
+            verify(mMockTag).getTagService();
+            verify(mMockTagService).setTimeout(TagTechnology.NFC_F, 1000);
+        } catch (Exception e) {
+            fail("Unexpected exception during valid setTimeout: " + e.getMessage());
+        }
+    }
+
+    @Test
+    public void testSetTimeoutInvalidTimeout() {
+        when(mMockTag.getTagService()).thenReturn(mMockTagService);
+        try {
+            when(mMockTagService.setTimeout(TagTechnology.NFC_F, -1)).thenReturn(
+                    ErrorCodes.ERROR_TIMEOUT);
+
+            assertThrows(IllegalArgumentException.class, () -> mNfcF.setTimeout(-1));
+        } catch (Exception e) {
+            fail("Unexpected exception during invalid setTimeout: " + e.getMessage());
+        }
+    }
+
+    @Test
+    public void testSetTimeoutRemoteException() {
+        when(mMockTag.getTagService()).thenReturn(mMockTagService);
+        try {
+            when(mMockTagService.setTimeout(TagTechnology.NFC_F, 1000)).thenThrow(
+                    new RemoteException());
+
+            mNfcF.setTimeout(1000);
+            verify(mMockTag).getTagService();
+            verify(mMockTagService).setTimeout(TagTechnology.NFC_F, 1000);
+        } catch (Exception e) {
+            fail("Unexpected exception during RemoteException in setTimeout: " + e.getMessage());
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/OWNERS b/services/core/java/com/android/server/OWNERS
index c15cf34..6858e29 100644
--- a/services/core/java/com/android/server/OWNERS
+++ b/services/core/java/com/android/server/OWNERS
@@ -31,6 +31,7 @@
 per-file *TimeUpdate* = file:/services/core/java/com/android/server/timezonedetector/OWNERS
 per-file DynamicSystemService.java = file:/packages/DynamicSystemInstallationService/OWNERS
 per-file GestureLauncherService.java = file:platform/packages/apps/EmergencyInfo:/OWNERS
+per-file GestureLauncherService.java = file:/INPUT_OWNERS
 per-file MmsServiceBroker.java = file:/telephony/OWNERS
 per-file NetIdManager.java = file:/services/core/java/com/android/server/net/OWNERS
 per-file PackageWatchdog.java = file:/services/core/java/com/android/server/crashrecovery/OWNERS
diff --git a/services/core/java/com/android/server/am/PhantomProcessList.java b/services/core/java/com/android/server/am/PhantomProcessList.java
index 2ec1aed..5bd9c05 100644
--- a/services/core/java/com/android/server/am/PhantomProcessList.java
+++ b/services/core/java/com/android/server/am/PhantomProcessList.java
@@ -112,23 +112,10 @@
     private final ActivityManagerService mService;
     private final Handler mKillHandler;
 
-    private static final int CGROUP_V1 = 0;
-    private static final int CGROUP_V2 = 1;
-    private static final String[] CGROUP_PATH_PREFIXES = {
-        "/acct/uid_" /* cgroup v1 */,
-        "/sys/fs/cgroup/uid_" /* cgroup v2 */
-    };
-    private static final String CGROUP_PID_PREFIX = "/pid_";
-    private static final String CGROUP_PROCS = "/cgroup.procs";
-
-    @VisibleForTesting
-    int mCgroupVersion = CGROUP_V1;
-
     PhantomProcessList(final ActivityManagerService service) {
         mService = service;
         mKillHandler = service.mProcessList.sKillHandler;
         mInjector = new Injector();
-        probeCgroupVersion();
     }
 
     @VisibleForTesting
@@ -157,9 +144,15 @@
         final int appPid = app.getPid();
         InputStream input = mCgroupProcsFds.get(appPid);
         if (input == null) {
-            final String path = getCgroupFilePath(app.info.uid, appPid);
+            String path = null;
             try {
+                path = getCgroupFilePath(app.info.uid, appPid);
                 input = mInjector.openCgroupProcs(path);
+            } catch (IllegalArgumentException e) {
+                if (DEBUG_PROCESSES) {
+                    Slog.w(TAG, "Unable to obtain cgroup.procs path ", e);
+                }
+                return;
             } catch (FileNotFoundException | SecurityException e) {
                 if (DEBUG_PROCESSES) {
                     Slog.w(TAG, "Unable to open " + path, e);
@@ -207,18 +200,9 @@
         }
     }
 
-    private void probeCgroupVersion() {
-        for (int i = CGROUP_PATH_PREFIXES.length - 1; i >= 0; i--) {
-            if ((new File(CGROUP_PATH_PREFIXES[i] + Process.SYSTEM_UID)).exists()) {
-                mCgroupVersion = i;
-                break;
-            }
-        }
-    }
-
     @VisibleForTesting
     String getCgroupFilePath(int uid, int pid) {
-        return CGROUP_PATH_PREFIXES[mCgroupVersion] + uid + CGROUP_PID_PREFIX + pid + CGROUP_PROCS;
+        return nativeGetCgroupProcsPath(uid, pid);
     }
 
     static String getProcessName(int pid) {
@@ -605,4 +589,7 @@
             return PhantomProcessList.getProcessName(pid);
         }
     }
+
+    private static native String nativeGetCgroupProcsPath(int uid, int pid)
+            throws IllegalArgumentException;
 }
diff --git a/services/core/java/com/android/server/audio/FadeOutManager.java b/services/core/java/com/android/server/audio/FadeOutManager.java
index 4d5bce5..fedfe51 100644
--- a/services/core/java/com/android/server/audio/FadeOutManager.java
+++ b/services/core/java/com/android/server/audio/FadeOutManager.java
@@ -199,7 +199,9 @@
             for (AudioPlaybackConfiguration apc : players) {
                 final VolumeShaper.Configuration volShaper =
                         mFadeConfigurations.getFadeOutVolumeShaperConfig(apc.getAudioAttributes());
-                fa.addFade(apc, /* skipRamp= */ false, volShaper);
+                if (volShaper != null) {
+                    fa.addFade(apc, /* skipRamp= */ false, volShaper);
+                }
             }
         }
     }
@@ -249,7 +251,7 @@
             final VolumeShaper.Configuration volShaper =
                     mFadeConfigurations.getFadeOutVolumeShaperConfig(apc.getAudioAttributes());
             final FadedOutApp fa = mUidToFadedAppsMap.get(apc.getClientUid());
-            if (fa == null) {
+            if (fa == null || volShaper == null) {
                 return;
             }
             fa.addFade(apc, /* skipRamp= */ true, volShaper);
diff --git a/services/core/java/com/android/server/powerstats/PowerStatsLogger.java b/services/core/java/com/android/server/powerstats/PowerStatsLogger.java
index e80a86d..4fd026a 100644
--- a/services/core/java/com/android/server/powerstats/PowerStatsLogger.java
+++ b/services/core/java/com/android/server/powerstats/PowerStatsLogger.java
@@ -285,14 +285,20 @@
     }
 
     private void updateCacheFile(String cacheFilename, byte[] data) {
+        AtomicFile atomicCachedFile = null;
+        FileOutputStream fos = null;
         try {
-            final AtomicFile atomicCachedFile =
+            atomicCachedFile =
                     new AtomicFile(new File(mDataStoragePath, cacheFilename));
-            final FileOutputStream fos = atomicCachedFile.startWrite();
+            fos = atomicCachedFile.startWrite();
             fos.write(data);
             atomicCachedFile.finishWrite(fos);
         } catch (IOException e) {
             Slog.e(TAG, "Failed to write current data to cached file", e);
+            if (fos != null) {
+                atomicCachedFile.failWrite(fos);
+            }
+            return;
         }
     }
 
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 2c03902..97ae6be 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -79,6 +79,7 @@
         ":lib_oomConnection_native",
         ":lib_anrTimer_native",
         ":lib_lazilyRegisteredServices_native",
+        ":lib_phantomProcessList_native",
     ],
 
     include_dirs: [
@@ -264,3 +265,10 @@
         "com_android_server_vr_VrManagerService.cpp",
     ],
 }
+
+filegroup {
+    name: "lib_phantomProcessList_native",
+    srcs: [
+        "com_android_server_am_PhantomProcessList.cpp",
+    ],
+}
diff --git a/services/core/jni/com_android_server_am_PhantomProcessList.cpp b/services/core/jni/com_android_server_am_PhantomProcessList.cpp
new file mode 100644
index 0000000..0c5e6d8
--- /dev/null
+++ b/services/core/jni/com_android_server_am_PhantomProcessList.cpp
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <jni.h>
+#include <nativehelper/JNIHelp.h>
+#include <processgroup/processgroup.h>
+
+namespace android {
+namespace {
+
+jstring getCgroupProcsPath(JNIEnv* env, jobject clazz, jint uid, jint pid) {
+    if (uid < 0) {
+        jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", "uid is negative: %d", uid);
+        return nullptr;
+    }
+
+    std::string path;
+    if (!CgroupGetAttributePathForProcess("CgroupProcs", uid, pid, path)) {
+        path.clear();
+    }
+
+    return env->NewStringUTF(path.c_str());
+}
+
+const JNINativeMethod sMethods[] = {
+        {"nativeGetCgroupProcsPath", "(II)Ljava/lang/String;", (void*)getCgroupProcsPath},
+};
+
+} // anonymous namespace
+
+int register_android_server_am_PhantomProcessList(JNIEnv* env) {
+    const char* className = "com/android/server/am/PhantomProcessList";
+    return jniRegisterNativeMethods(env, className, sMethods, NELEM(sMethods));
+}
+
+} // namespace android
\ No newline at end of file
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index df37ec3..a12eb31 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -70,6 +70,7 @@
 int register_com_android_server_SystemClockTime(JNIEnv* env);
 int register_android_server_display_smallAreaDetectionController(JNIEnv* env);
 int register_com_android_server_accessibility_BrailleDisplayConnection(JNIEnv* env);
+int register_android_server_am_PhantomProcessList(JNIEnv* env);
 
 // Note: Consider adding new JNI entrypoints for optional services to
 // LazyJniRegistrar instead, and relying on lazy registration.
@@ -135,5 +136,6 @@
     register_com_android_server_SystemClockTime(env);
     register_android_server_display_smallAreaDetectionController(env);
     register_com_android_server_accessibility_BrailleDisplayConnection(env);
+    register_android_server_am_PhantomProcessList(env);
     return JNI_VERSION_1_4;
 }
diff --git a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
index d2c91ff..232bb83 100644
--- a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
@@ -286,14 +286,21 @@
                 return@forEach
             }
             var newFlags = oldFlags
+            val isSystemOrInstalled =
+                packageState.isSystem || packageState.getUserStateOrDefault(userId).isInstalled
             newFlags =
                 if (
-                    newFlags.hasBits(PermissionFlags.ROLE) ||
-                        newFlags.hasBits(PermissionFlags.PREGRANT)
+                    isSystemOrInstalled && (
+                        newFlags.hasBits(PermissionFlags.ROLE) ||
+                            newFlags.hasBits(PermissionFlags.PREGRANT)
+                    )
                 ) {
                     newFlags or PermissionFlags.RUNTIME_GRANTED
                 } else {
-                    newFlags andInv PermissionFlags.RUNTIME_GRANTED
+                    newFlags andInv (
+                        PermissionFlags.RUNTIME_GRANTED or PermissionFlags.ROLE or
+                            PermissionFlags.PREGRANT
+                    )
                 }
             newFlags = newFlags andInv USER_SETTABLE_MASK
             if (newFlags.hasBits(PermissionFlags.LEGACY_GRANTED)) {
diff --git a/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyPermissionResetTest.kt b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyPermissionResetTest.kt
index 1237095..8b357862d 100644
--- a/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyPermissionResetTest.kt
+++ b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyPermissionResetTest.kt
@@ -72,7 +72,8 @@
         } else {
             mockPackageState(
                 APP_ID_1,
-                mockAndroidPackage(PACKAGE_NAME_1, requestedPermissions = setOf(PERMISSION_NAME_0))
+                mockAndroidPackage(PACKAGE_NAME_1, requestedPermissions = setOf(PERMISSION_NAME_0)),
+                true
             )
         }
         setPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0, oldFlags)
diff --git a/services/tests/mockingservicestests/jni/Android.bp b/services/tests/mockingservicestests/jni/Android.bp
index 94d4b95..03bd73c 100644
--- a/services/tests/mockingservicestests/jni/Android.bp
+++ b/services/tests/mockingservicestests/jni/Android.bp
@@ -24,6 +24,7 @@
         ":lib_freezer_native",
         ":lib_oomConnection_native",
         ":lib_lazilyRegisteredServices_native",
+        ":lib_phantomProcessList_native",
         "onload.cpp",
     ],
 
diff --git a/services/tests/mockingservicestests/jni/onload.cpp b/services/tests/mockingservicestests/jni/onload.cpp
index 9b4c817..30fa7de 100644
--- a/services/tests/mockingservicestests/jni/onload.cpp
+++ b/services/tests/mockingservicestests/jni/onload.cpp
@@ -28,6 +28,7 @@
 int register_android_server_am_Freezer(JNIEnv* env);
 int register_android_server_am_OomConnection(JNIEnv* env);
 int register_android_server_utils_LazyJniRegistrar(JNIEnv* env);
+int register_android_server_am_PhantomProcessList(JNIEnv* env);
 };
 
 using namespace android;
@@ -46,5 +47,6 @@
     register_android_server_am_Freezer(env);
     register_android_server_am_OomConnection(env);
     register_android_server_utils_LazyJniRegistrar(env);
+    register_android_server_am_PhantomProcessList(env);
     return JNI_VERSION_1_4;
 }
diff --git a/services/tests/servicestests/src/com/android/server/OWNERS b/services/tests/servicestests/src/com/android/server/OWNERS
index d8a9400..69feb1d 100644
--- a/services/tests/servicestests/src/com/android/server/OWNERS
+++ b/services/tests/servicestests/src/com/android/server/OWNERS
@@ -6,5 +6,6 @@
 per-file *Network* = file:/services/core/java/com/android/server/net/OWNERS
 per-file BatteryServiceTest.java = file:platform/hardware/interfaces:/health/OWNERS
 per-file GestureLauncherServiceTest.java = file:platform/packages/apps/EmergencyInfo:/OWNERS
+per-file GestureLauncherServiceTest.java = file:/INPUT_OWNERS
 per-file PinnerServiceTest.java = file:/apct-tests/perftests/OWNERS
 per-file SecurityStateTest.java = file:/SECURITY_STATE_OWNERS