add MediaCodec XML minsdk=# attribute

Add a minsdk="#" attribute to the MediaCodec XML clause. This supports
the idea of a codec distributed via mainline but enabled only on certain
Android releases.

Bug: 242057615
Test: enable/minsdk combinations on Q/R/S/T/master devices
Test: atest XMLParserTest
Change-Id: I708aa6dcd49cf06fdab011e6611551c482027ebf
diff --git a/media/libstagefright/xmlparser/MediaCodecsXmlParser.cpp b/media/libstagefright/xmlparser/MediaCodecsXmlParser.cpp
index 67c6102..8c1ef3b 100644
--- a/media/libstagefright/xmlparser/MediaCodecsXmlParser.cpp
+++ b/media/libstagefright/xmlparser/MediaCodecsXmlParser.cpp
@@ -19,6 +19,8 @@
 
 #include <media/stagefright/xmlparser/MediaCodecsXmlParser.h>
 
+#include <android/api-level.h>
+
 #include <android-base/logging.h>
 #include <android-base/macros.h>
 #include <android-base/properties.h>
@@ -30,6 +32,7 @@
 
 #include <expat.h>
 #include <stdio.h>
+#include <stdlib.h>
 #include <string.h>
 #include <sys/stat.h>
 
@@ -360,7 +363,7 @@
 
         status_t updateMediaCodec(
                 const char *rank, const StringSet &domain, const StringSet &variants,
-                const char *enabled);
+                const char *enabled, const char *minsdk);
     };
 
     status_t parseXmlFilesInSearchDirs(
@@ -493,6 +496,9 @@
     }
 }
 
+// current SDK for this device; filled in when initializing the parser.
+static int mysdk = 0;
+
 MediaCodecsXmlParser::Impl::Parser::Parser(State *state, std::string path)
     : mState(state),
       mPath(path),
@@ -502,6 +508,20 @@
     if (end != std::string::npos) {
         mHrefBase = path.substr(0, end + 1);
     }
+
+#if defined(__ANDROID_API_U__)
+    // this is sdk calculation is intended only for devices >= U
+    static std::once_flag sCheckOnce;
+
+    std::call_once(sCheckOnce, [&](){
+        mysdk = android_get_device_api_level();
+
+        // work around main development branch being on same SDK as the last dessert release.
+        if (__ANDROID_API__ == __ANDROID_API_FUTURE__) {
+            mysdk++;
+        }
+    });
+#endif  // __ANDROID_API_U__
 }
 
 void MediaCodecsXmlParser::Impl::Parser::parseXmlFile() {
@@ -930,6 +950,7 @@
     const char *a_domain = nullptr;
     const char *a_variant = nullptr;
     const char *a_enabled = nullptr;
+    const char *a_minsdk = nullptr;
 
     size_t i = 0;
     while (attrs[i] != nullptr) {
@@ -953,6 +974,8 @@
             a_variant = attrs[++i];
         } else if (strEq(attrs[i], "enabled")) {
             a_enabled = attrs[++i];
+        } else if (strEq(attrs[i], "minsdk")) {
+            a_minsdk = attrs[++i];
         } else {
             PLOGD("MediaCodec: ignoring unrecognized attribute '%s'", attrs[i]);
             ++i;
@@ -981,7 +1004,7 @@
 
     return updateMediaCodec(
             a_rank, parseCommaSeparatedStringSet(a_domain),
-            parseCommaSeparatedStringSet(a_variant), a_enabled);
+            parseCommaSeparatedStringSet(a_variant), a_enabled, a_minsdk);
 }
 
 MediaCodecsXmlParser::Impl::Result
@@ -1035,7 +1058,7 @@
 
 status_t MediaCodecsXmlParser::Impl::Parser::updateMediaCodec(
         const char *rank, const StringSet &domains, const StringSet &variants,
-        const char *enabled) {
+        const char *enabled, const char *minsdk) {
     CHECK(mState->inCodec());
     CodecProperties &codec = mState->codec();
 
@@ -1048,6 +1071,7 @@
 
     codec.variantSet = variants;
 
+    // we allow sets of domains...
     for (const std::string &domain : domains) {
         if (domain.size() && domain.at(0) == '!') {
             codec.domainSet.erase(domain.substr(1));
@@ -1065,6 +1089,49 @@
             ALOGD("disabling %s", mState->codecName().c_str());
         }
     }
+
+    // evaluate against passed minsdk, with lots of logging to explain the logic
+    //
+    // if current sdk >= minsdk, we want to enable the codec
+    // this OVERRIDES any enabled="true|false" setting on the codec.
+    // (enabled=true minsdk=35 on a sdk 34 device results in a disabled codec)
+    //
+    // Although minsdk is not parsed before Android U, we can carry media_codecs.xml
+    // using this to devices earlier (e.g. as part of mainline). An example is appropriate.
+    //
+    // we have a codec that we want enabled in Android V (sdk=35), so we use:
+    //     <MediaCodec ..... enabled="false" minsdk="35" >
+    //
+    // on Q/R/S/T: it sees enabled=false, but ignores the unrecognized minsdk
+    //     so the codec will be disabled
+    // on U: it sees enabled=false, and sees minsdk=35, but U==34 and 34 < 35
+    //     so the codec will be disabled
+    // on V: it sees enabled=false, and sees minsdk=35, V==35 and 35 >= 35
+    //     so the codec will be enabled
+    //
+    // if we know the XML files will be used only on devices >= U, we can skip the enabled=false
+    // piece.  Android mainline's support horizons say we will be using the enabled=false for
+    // another 4-5 years after U.
+    //
+    if (minsdk != nullptr) {
+        char *p = nullptr;
+        int sdk = strtol(minsdk, &p, 0);
+        if (p == minsdk || sdk < 0) {
+            ALOGE("minsdk parsing '%s' yielded %d, mapping to 0", minsdk, sdk);
+            sdk = 0;
+        }
+        // minsdk="#" means: "enable if sdk is >= #, disable otherwise"
+        if (mysdk < sdk) {
+            ALOGI("codec %s disabled, device sdk %d < required %d",
+                mState->codecName().c_str(), mysdk, sdk);
+            codec.quirkSet.emplace("attribute::disabled");
+        } else {
+            ALOGI("codec %s enabled, device sdk %d >= required %d",
+                mState->codecName().c_str(), mysdk, sdk);
+            codec.quirkSet.erase("attribute::disabled");
+        }
+    }
+
     return OK;
 }
 
diff --git a/media/libstagefright/xmlparser/api/current.txt b/media/libstagefright/xmlparser/api/current.txt
index ecfd85e..95c347a 100644
--- a/media/libstagefright/xmlparser/api/current.txt
+++ b/media/libstagefright/xmlparser/api/current.txt
@@ -84,6 +84,7 @@
     method public java.util.List<media.codecs.Feature> getFeature_optional();
     method public java.util.List<media.codecs.Limit> getLimit_optional();
     method public java.util.List<media.codecs.Mapping> getMapping_optional();
+    method public String getMinsdk();
     method public String getName();
     method public java.util.List<media.codecs.Quirk> getQuirk_optional();
     method public String getRank();
@@ -95,6 +96,7 @@
     method public java.util.List<media.codecs.Variant> getVariant_optional();
     method public void setDomain(String);
     method public void setEnabled(String);
+    method public void setMinsdk(String);
     method public void setName(String);
     method public void setRank(String);
     method public void setType(String);
diff --git a/media/libstagefright/xmlparser/media_codecs.xsd b/media/libstagefright/xmlparser/media_codecs.xsd
index c9a7efc..33f3a27 100644
--- a/media/libstagefright/xmlparser/media_codecs.xsd
+++ b/media/libstagefright/xmlparser/media_codecs.xsd
@@ -74,6 +74,7 @@
         <xs:attribute name="domain" type="xs:string"/>
         <xs:attribute name="variant" type="xs:string"/>
         <xs:attribute name="enabled" type="xs:string"/>
+        <xs:attribute name="minsdk" type="xs:string"/>
     </xs:complexType>
     <xs:complexType name="Quirk">
         <xs:attribute name="name" type="xs:string"/>
diff --git a/media/libstagefright/xmlparser/test/XMLParserTest.cpp b/media/libstagefright/xmlparser/test/XMLParserTest.cpp
index 7629d97..2c5821e 100644
--- a/media/libstagefright/xmlparser/test/XMLParserTest.cpp
+++ b/media/libstagefright/xmlparser/test/XMLParserTest.cpp
@@ -145,6 +145,33 @@
            },
            {}, "");
 
+    // minsdk
+    setCodecProperties("test12.encoder", true, 12, {"attribute::disabled"}, {}, {}, "video/t12",
+           {
+                   pair<string, string>("tuning-enable-goal", "no"),
+           },
+           {}, "");
+    setCodecProperties("test13.encoder", true, 13, {"attribute::disabled"}, {}, {}, "video/t13",
+           {
+                   pair<string, string>("tuning-enable-goal", "no"),
+           },
+           {}, "");
+    setCodecProperties("test14.encoder", true, 14, {"attribute::disabled"}, {}, {}, "video/t14",
+           {
+                   pair<string, string>("tuning-enable-goal", "no"),
+           },
+           {}, "");
+    setCodecProperties("test15.encoder", true, 15, {}, {}, {}, "video/t15",
+           {
+                   pair<string, string>("tuning-enable-goal", "yes"),
+           },
+           {}, "");
+    setCodecProperties("test16.encoder", true, 16, {}, {}, {}, "video/t16",
+           {
+                   pair<string, string>("tuning-enable-goal", "yes"),
+           },
+           {}, "");
+
     setRoleProperties("audio_decoder.mp3", false, 1, "audio/mpeg", "test1.decoder",
                       {pair<string, string>("attribute::disabled", "present"),
                        pair<string, string>("rank", "4")});
@@ -191,6 +218,22 @@
                         pair<string, string>("tuning-pi", "3.1415")
                        });
 
+    // minsdk
+    setRoleProperties("video_encoder.t12", true, 12, "video/t12", "test12.encoder",
+                       {pair<string, string>("tuning-enable-goal", "no"),
+                        pair<string, string>("attribute::disabled", "present") });
+    setRoleProperties("video_encoder.t13", true, 13, "video/t13", "test13.encoder",
+                       {pair<string, string>("tuning-enable-goal", "no"),
+                        pair<string, string>("attribute::disabled", "present") });
+    setRoleProperties("video_encoder.t14", true, 14, "video/t14", "test14.encoder",
+                       {pair<string, string>("tuning-enable-goal", "no"),
+                        pair<string, string>("attribute::disabled", "present") });
+    setRoleProperties("video_encoder.t15", true, 15, "video/t15", "test15.encoder",
+                       {pair<string, string>("tuning-enable-goal", "yes")});
+    setRoleProperties("video_encoder.t16", true, 16, "video/t16", "test16.encoder",
+                       {pair<string, string>("tuning-enable-goal", "yes")});
+
+
     setServiceAttribute(
             {pair<string, string>("domain-telephony", "0"), pair<string, string>("domain-tv", "0"),
              pair<string, string>("setting2", "0"), pair<string, string>("variant-variant1", "0")});
diff --git a/media/libstagefright/xmlparser/test/testdata/media_codecs_unit_test.xml b/media/libstagefright/xmlparser/test/testdata/media_codecs_unit_test.xml
index 8cae423..e066927 100644
--- a/media/libstagefright/xmlparser/test/testdata/media_codecs_unit_test.xml
+++ b/media/libstagefright/xmlparser/test/testdata/media_codecs_unit_test.xml
@@ -88,5 +88,21 @@
             <Tuning name="hungry" value="yes"/>
             <Tuning name="pi" value="3.1415"/>
         </MediaCodec>
+        <!-- test minsdk -->
+        <MediaCodec name="test12.encoder" type="video/t12" minsdk="100">
+            <Tuning name="enable-goal" value="no"/>
+        </MediaCodec>
+        <MediaCodec name="test13.encoder" type="video/t13" enabled="false" minsdk="100">
+            <Tuning name="enable-goal" value="no"/>
+        </MediaCodec>
+        <MediaCodec name="test14.encoder" type="video/t14" enabled="true" minsdk="100">
+            <Tuning name="enable-goal" value="no"/>
+        </MediaCodec>
+        <MediaCodec name="test15.encoder" type="video/t15" minsdk="34">
+            <Tuning name="enable-goal" value="yes"/>
+        </MediaCodec>
+        <MediaCodec name="test16.encoder" type="video/t16" enabled="false" minsdk="34">
+            <Tuning name="enable-goal" value="yes"/>
+        </MediaCodec>
     </Encoders>
 </Included>