Merge "MediaSession2: Fix bug in SessionToken2 constructor"
diff --git a/cmds/screenrecord/screenrecord.cpp b/cmds/screenrecord/screenrecord.cpp
index 46bd8f0..9d2daab 100644
--- a/cmds/screenrecord/screenrecord.cpp
+++ b/cmds/screenrecord/screenrecord.cpp
@@ -50,6 +50,7 @@
 #include <media/stagefright/MediaCodec.h>
 #include <media/stagefright/MediaErrors.h>
 #include <media/stagefright/MediaMuxer.h>
+#include <media/stagefright/PersistentSurface.h>
 #include <media/ICrypto.h>
 #include <media/MediaCodecBuffer.h>
 
@@ -70,6 +71,7 @@
 static bool gVerbose = false;           // chatty on stdout
 static bool gRotate = false;            // rotate 90 degrees
 static bool gMonotonicTime = false;     // use system monotonic time for timestamps
+static bool gPersistentSurface = false; // use persistent surface
 static enum {
     FORMAT_MP4, FORMAT_H264, FORMAT_FRAMES, FORMAT_RAW_FRAMES
 } gOutputFormat = FORMAT_MP4;           // data format for output
@@ -199,10 +201,18 @@
 
     ALOGV("Creating encoder input surface");
     sp<IGraphicBufferProducer> bufferProducer;
-    err = codec->createInputSurface(&bufferProducer);
+    if (gPersistentSurface) {
+        sp<PersistentSurface> surface = MediaCodec::CreatePersistentInputSurface();
+        bufferProducer = surface->getBufferProducer();
+        err = codec->setInputSurface(surface);
+    } else {
+        err = codec->createInputSurface(&bufferProducer);
+    }
     if (err != NO_ERROR) {
         fprintf(stderr,
-            "ERROR: unable to create encoder input surface (err=%d)\n", err);
+            "ERROR: unable to %s encoder input surface (err=%d)\n",
+            gPersistentSurface ? "set" : "create",
+            err);
         codec->release();
         return err;
     }
@@ -920,6 +930,7 @@
         { "output-format",      required_argument,  NULL, 'o' },
         { "codec-name",         required_argument,  NULL, 'N' },
         { "monotonic-time",     no_argument,        NULL, 'm' },
+        { "persistent-surface", no_argument,        NULL, 'p' },
         { NULL,                 0,                  NULL, 0 }
     };
 
@@ -1005,6 +1016,9 @@
         case 'm':
             gMonotonicTime = true;
             break;
+        case 'p':
+            gPersistentSurface = true;
+            break;
         default:
             if (ic != '?') {
                 fprintf(stderr, "getopt_long returned unexpected value 0x%x\n", ic);
diff --git a/media/extractors/aac/AACExtractor.cpp b/media/extractors/aac/AACExtractor.cpp
index 1614ca4..9df0aaa 100644
--- a/media/extractors/aac/AACExtractor.cpp
+++ b/media/extractors/aac/AACExtractor.cpp
@@ -333,13 +333,20 @@
 
 static MediaExtractor* CreateExtractor(
         DataSourceBase *source,
-        const sp<AMessage>& meta) {
-    return new AACExtractor(source, meta);
+        void *meta) {
+    sp<AMessage> metaData = static_cast<AMessage *>(meta);
+    return new AACExtractor(source, metaData);
+}
+
+static void FreeMeta(void *meta) {
+    if (meta != nullptr) {
+        static_cast<AMessage *>(meta)->decStrong(nullptr);
+    }
 }
 
 static MediaExtractor::CreatorFunc Sniff(
-        DataSourceBase *source, String8 *mimeType, float *confidence,
-        sp<AMessage> *meta) {
+        DataSourceBase *source, float *confidence, void **meta,
+        MediaExtractor::FreeMetaFunc *freeMeta) {
     off64_t pos = 0;
 
     for (;;) {
@@ -377,11 +384,14 @@
 
     // ADTS syncword
     if ((header[0] == 0xff) && ((header[1] & 0xf6) == 0xf0)) {
-        *mimeType = MEDIA_MIMETYPE_AUDIO_AAC_ADTS;
         *confidence = 0.2;
 
-        *meta = new AMessage;
-        (*meta)->setInt64("offset", pos);
+        AMessage *msg = new AMessage;
+        msg->setInt64("offset", pos);
+        *meta = msg;
+        *freeMeta = &FreeMeta;
+        // ref count will be decreased in FreeMeta.
+        msg->incStrong(nullptr);
 
         return CreateExtractor;
     }
diff --git a/media/extractors/amr/AMRExtractor.cpp b/media/extractors/amr/AMRExtractor.cpp
index 547e3f5..eec18b5 100644
--- a/media/extractors/amr/AMRExtractor.cpp
+++ b/media/extractors/amr/AMRExtractor.cpp
@@ -122,7 +122,7 @@
       mOffsetTableLength(0) {
     String8 mimeType;
     float confidence;
-    if (!SniffAMR(mDataSource, &mimeType, &confidence, NULL)) {
+    if (!SniffAMR(mDataSource, &mimeType, &confidence)) {
         return;
     }
 
@@ -339,8 +339,7 @@
 ////////////////////////////////////////////////////////////////////////////////
 
 bool SniffAMR(
-        DataSourceBase *source, String8 *mimeType, float *confidence,
-        sp<AMessage> *) {
+        DataSourceBase *source, String8 *mimeType, float *confidence) {
     char header[9];
 
     if (source->readAt(0, header, sizeof(header)) != sizeof(header)) {
@@ -348,12 +347,16 @@
     }
 
     if (!memcmp(header, "#!AMR\n", 6)) {
-        *mimeType = MEDIA_MIMETYPE_AUDIO_AMR_NB;
+        if (mimeType != nullptr) {
+            *mimeType = MEDIA_MIMETYPE_AUDIO_AMR_NB;
+        }
         *confidence = 0.5;
 
         return true;
     } else if (!memcmp(header, "#!AMR-WB\n", 9)) {
-        *mimeType = MEDIA_MIMETYPE_AUDIO_AMR_WB;
+        if (mimeType != nullptr) {
+            *mimeType = MEDIA_MIMETYPE_AUDIO_AMR_WB;
+        }
         *confidence = 0.5;
 
         return true;
@@ -373,13 +376,13 @@
         "AMR Extractor",
         [](
                 DataSourceBase *source,
-                String8 *mimeType,
                 float *confidence,
-                sp<AMessage> *meta __unused) -> MediaExtractor::CreatorFunc {
-            if (SniffAMR(source, mimeType, confidence, meta)) {
+                void **,
+                MediaExtractor::FreeMetaFunc *) -> MediaExtractor::CreatorFunc {
+            if (SniffAMR(source, nullptr, confidence)) {
                 return [](
                         DataSourceBase *source,
-                        const sp<AMessage>& meta __unused) -> MediaExtractor* {
+                        void *) -> MediaExtractor* {
                     return new AMRExtractor(source);};
             }
             return NULL;
diff --git a/media/extractors/amr/AMRExtractor.h b/media/extractors/amr/AMRExtractor.h
index d6d49f2..b8b44ea 100644
--- a/media/extractors/amr/AMRExtractor.h
+++ b/media/extractors/amr/AMRExtractor.h
@@ -55,8 +55,7 @@
 };
 
 bool SniffAMR(
-        DataSourceBase *source, String8 *mimeType, float *confidence,
-        sp<AMessage> *);
+        DataSourceBase *source, String8 *mimeType, float *confidence);
 
 }  // namespace android
 
diff --git a/media/extractors/flac/FLACExtractor.cpp b/media/extractors/flac/FLACExtractor.cpp
index 8dbb5a1..2ce20db 100644
--- a/media/extractors/flac/FLACExtractor.cpp
+++ b/media/extractors/flac/FLACExtractor.cpp
@@ -968,9 +968,7 @@
 
 // Sniffer
 
-bool SniffFLAC(
-        DataSourceBase *source, String8 *mimeType, float *confidence,
-        sp<AMessage> *)
+bool SniffFLAC(DataSourceBase *source, float *confidence)
 {
     // first 4 is the signature word
     // second 4 is the sizeof STREAMINFO
@@ -983,7 +981,6 @@
         return false;
     }
 
-    *mimeType = MEDIA_MIMETYPE_AUDIO_FLAC;
     *confidence = 0.5;
 
     return true;
@@ -1001,13 +998,13 @@
             "FLAC Extractor",
             [](
                     DataSourceBase *source,
-                    String8 *mimeType,
                     float *confidence,
-                    sp<AMessage> *meta __unused) -> MediaExtractor::CreatorFunc {
-                if (SniffFLAC(source, mimeType, confidence, meta)) {
+                    void **,
+                    MediaExtractor::FreeMetaFunc *) -> MediaExtractor::CreatorFunc {
+                if (SniffFLAC(source, confidence)) {
                     return [](
                             DataSourceBase *source,
-                            const sp<AMessage>& meta __unused) -> MediaExtractor* {
+                            void *) -> MediaExtractor* {
                         return new FLACExtractor(source);};
                 }
                 return NULL;
diff --git a/media/extractors/flac/FLACExtractor.h b/media/extractors/flac/FLACExtractor.h
index ef07212..f41d878 100644
--- a/media/extractors/flac/FLACExtractor.h
+++ b/media/extractors/flac/FLACExtractor.h
@@ -56,8 +56,7 @@
 
 };
 
-bool SniffFLAC(DataSourceBase *source, String8 *mimeType,
-        float *confidence, sp<AMessage> *);
+bool SniffFLAC(DataSourceBase *source, float *confidence);
 
 }  // namespace android
 
diff --git a/media/extractors/midi/MidiExtractor.cpp b/media/extractors/midi/MidiExtractor.cpp
index 711c6a5..1e38194 100644
--- a/media/extractors/midi/MidiExtractor.cpp
+++ b/media/extractors/midi/MidiExtractor.cpp
@@ -307,13 +307,10 @@
 
 // Sniffer
 
-bool SniffMidi(
-        DataSourceBase *source, String8 *mimeType, float *confidence,
-        sp<AMessage> *)
+bool SniffMidi(DataSourceBase *source, float *confidence)
 {
     sp<MidiEngine> p = new MidiEngine(source, NULL, NULL);
     if (p->initCheck() == OK) {
-        *mimeType = MEDIA_MIMETYPE_AUDIO_MIDI;
         *confidence = 0.8;
         ALOGV("SniffMidi: yes");
         return true;
@@ -334,13 +331,13 @@
         "MIDI Extractor",
         [](
                 DataSourceBase *source,
-                String8 *mimeType,
                 float *confidence,
-                sp<AMessage> *meta __unused) -> MediaExtractor::CreatorFunc {
-            if (SniffMidi(source, mimeType, confidence, meta)) {
+                void **,
+                MediaExtractor::FreeMetaFunc *) -> MediaExtractor::CreatorFunc {
+            if (SniffMidi(source, confidence)) {
                 return [](
                         DataSourceBase *source,
-                        const sp<AMessage>& meta __unused) -> MediaExtractor* {
+                        void *) -> MediaExtractor* {
                     return new MidiExtractor(source);};
             }
             return NULL;
diff --git a/media/extractors/midi/MidiExtractor.h b/media/extractors/midi/MidiExtractor.h
index 91efd06..173a814 100644
--- a/media/extractors/midi/MidiExtractor.h
+++ b/media/extractors/midi/MidiExtractor.h
@@ -87,8 +87,7 @@
 
 };
 
-bool SniffMidi(DataSourceBase *source, String8 *mimeType,
-        float *confidence, sp<AMessage> *);
+bool SniffMidi(DataSourceBase *source, float *confidence);
 
 }  // namespace android
 
diff --git a/media/extractors/mkv/MatroskaExtractor.cpp b/media/extractors/mkv/MatroskaExtractor.cpp
index f61f7c7..7396113 100644
--- a/media/extractors/mkv/MatroskaExtractor.cpp
+++ b/media/extractors/mkv/MatroskaExtractor.cpp
@@ -1548,8 +1548,7 @@
 }
 
 bool SniffMatroska(
-        DataSourceBase *source, String8 *mimeType, float *confidence,
-        sp<AMessage> *) {
+        DataSourceBase *source, float *confidence) {
     DataSourceBaseReader reader(source);
     mkvparser::EBMLHeader ebmlHeader;
     long long pos;
@@ -1557,7 +1556,6 @@
         return false;
     }
 
-    mimeType->setTo(MEDIA_MIMETYPE_CONTAINER_MATROSKA);
     *confidence = 0.6;
 
     return true;
@@ -1575,13 +1573,13 @@
         "Matroska Extractor",
         [](
                 DataSourceBase *source,
-                String8 *mimeType,
                 float *confidence,
-                sp<AMessage> *meta __unused) -> MediaExtractor::CreatorFunc {
-            if (SniffMatroska(source, mimeType, confidence, meta)) {
+                void **,
+                MediaExtractor::FreeMetaFunc *) -> MediaExtractor::CreatorFunc {
+            if (SniffMatroska(source, confidence)) {
                 return [](
                         DataSourceBase *source,
-                        const sp<AMessage>& meta __unused) -> MediaExtractor* {
+                        void *) -> MediaExtractor* {
                     return new MatroskaExtractor(source);};
             }
             return NULL;
diff --git a/media/extractors/mp3/MP3Extractor.cpp b/media/extractors/mp3/MP3Extractor.cpp
index 25d4deb..e55084e 100644
--- a/media/extractors/mp3/MP3Extractor.cpp
+++ b/media/extractors/mp3/MP3Extractor.cpp
@@ -668,13 +668,20 @@
 
 static MediaExtractor* CreateExtractor(
         DataSourceBase *source,
-        const sp<AMessage>& meta) {
-    return new MP3Extractor(source, meta);
+        void *meta) {
+    sp<AMessage> metaData = static_cast<AMessage *>(meta);
+    return new MP3Extractor(source, metaData);
+}
+
+static void FreeMeta(void *meta) {
+    if (meta != nullptr) {
+        static_cast<AMessage *>(meta)->decStrong(nullptr);
+    }
 }
 
 static MediaExtractor::CreatorFunc Sniff(
-        DataSourceBase *source, String8 *mimeType,
-        float *confidence, sp<AMessage> *meta) {
+        DataSourceBase *source, float *confidence, void **meta,
+        MediaExtractor::FreeMetaFunc *freeMeta) {
     off64_t pos = 0;
     off64_t post_id3_pos;
     uint32_t header;
@@ -691,12 +698,15 @@
         return NULL;
     }
 
-    *meta = new AMessage;
-    (*meta)->setInt64("offset", pos);
-    (*meta)->setInt32("header", header);
-    (*meta)->setInt64("post-id3-offset", post_id3_pos);
+    AMessage *msg = new AMessage;
+    msg->setInt64("offset", pos);
+    msg->setInt32("header", header);
+    msg->setInt64("post-id3-offset", post_id3_pos);
+    *meta = msg;
+    *freeMeta = &FreeMeta;
+    // ref count will be decreased in FreeMeta.
+    msg->incStrong(nullptr);
 
-    *mimeType = MEDIA_MIMETYPE_AUDIO_MPEG;
     *confidence = 0.2f;
 
     return CreateExtractor;
diff --git a/media/extractors/mp4/MPEG4Extractor.cpp b/media/extractors/mp4/MPEG4Extractor.cpp
index 30dda13..3d7e45c 100644
--- a/media/extractors/mp4/MPEG4Extractor.cpp
+++ b/media/extractors/mp4/MPEG4Extractor.cpp
@@ -5382,8 +5382,7 @@
     return NULL;
 }
 
-static bool LegacySniffMPEG4(
-        DataSourceBase *source, String8 *mimeType, float *confidence) {
+static bool LegacySniffMPEG4(DataSourceBase *source, float *confidence) {
     uint8_t header[8];
 
     ssize_t n = source->readAt(4, header, sizeof(header));
@@ -5399,7 +5398,6 @@
         || !memcmp(header, "ftypkddi", 8) || !memcmp(header, "ftypM4VP", 8)
         || !memcmp(header, "ftypmif1", 8) || !memcmp(header, "ftypheic", 8)
         || !memcmp(header, "ftypmsf1", 8) || !memcmp(header, "ftyphevc", 8)) {
-        *mimeType = MEDIA_MIMETYPE_CONTAINER_MPEG4;
         *confidence = 0.4;
 
         return true;
@@ -5449,9 +5447,7 @@
 // Also try to identify where this file's metadata ends
 // (end of the 'moov' atom) and report it to the caller as part of
 // the metadata.
-static bool BetterSniffMPEG4(
-        DataSourceBase *source, String8 *mimeType, float *confidence,
-        sp<AMessage> *meta) {
+static bool BetterSniffMPEG4(DataSourceBase *source, float *confidence) {
     // We scan up to 128 bytes to identify this file as an MP4.
     static const off64_t kMaxScanOffset = 128ll;
 
@@ -5553,35 +5549,23 @@
         return false;
     }
 
-    *mimeType = MEDIA_MIMETYPE_CONTAINER_MPEG4;
     *confidence = 0.4f;
 
-    if (moovAtomEndOffset >= 0) {
-        *meta = new AMessage;
-        (*meta)->setInt64("meta-data-size", moovAtomEndOffset);
-
-        ALOGV("found metadata size: %lld", (long long)moovAtomEndOffset);
-    }
-
     return true;
 }
 
-static MediaExtractor* CreateExtractor(
-        DataSourceBase *source,
-        const sp<AMessage>& meta __unused) {
+static MediaExtractor* CreateExtractor(DataSourceBase *source, void *) {
     return new MPEG4Extractor(source);
 }
 
 static MediaExtractor::CreatorFunc Sniff(
-        DataSourceBase *source,
-        String8 *mimeType,
-        float *confidence,
-        sp<AMessage> *meta) {
-    if (BetterSniffMPEG4(source, mimeType, confidence, meta)) {
+        DataSourceBase *source, float *confidence, void **,
+        MediaExtractor::FreeMetaFunc *) {
+    if (BetterSniffMPEG4(source, confidence)) {
         return CreateExtractor;
     }
 
-    if (LegacySniffMPEG4(source, mimeType, confidence)) {
+    if (LegacySniffMPEG4(source, confidence)) {
         ALOGW("Identified supported mpeg4 through LegacySniffMPEG4.");
         return CreateExtractor;
     }
diff --git a/media/extractors/mpeg2/ExtractorBundle.cpp b/media/extractors/mpeg2/ExtractorBundle.cpp
index 443d685..8a0fa03 100644
--- a/media/extractors/mpeg2/ExtractorBundle.cpp
+++ b/media/extractors/mpeg2/ExtractorBundle.cpp
@@ -35,18 +35,18 @@
         "MPEG2-PS/TS Extractor",
         [](
                 DataSourceBase *source,
-                String8 *mimeType,
                 float *confidence,
-                sp<AMessage> *meta __unused) -> MediaExtractor::CreatorFunc {
-            if (SniffMPEG2TS(source, mimeType, confidence, meta)) {
+                void **,
+                MediaExtractor::FreeMetaFunc *) -> MediaExtractor::CreatorFunc {
+            if (SniffMPEG2TS(source, confidence)) {
                 return [](
                         DataSourceBase *source,
-                        const sp<AMessage>& meta __unused) -> MediaExtractor* {
+                        void *) -> MediaExtractor* {
                     return new MPEG2TSExtractor(source);};
-            } else if (SniffMPEG2PS(source, mimeType, confidence, meta)) {
+            } else if (SniffMPEG2PS(source, confidence)) {
                         return [](
                                 DataSourceBase *source,
-                                const sp<AMessage>& meta __unused) -> MediaExtractor* {
+                                void *) -> MediaExtractor* {
                             return new MPEG2PSExtractor(source);};
             }
             return NULL;
diff --git a/media/extractors/mpeg2/MPEG2PSExtractor.cpp b/media/extractors/mpeg2/MPEG2PSExtractor.cpp
index 697e44f..b4d0ee5 100644
--- a/media/extractors/mpeg2/MPEG2PSExtractor.cpp
+++ b/media/extractors/mpeg2/MPEG2PSExtractor.cpp
@@ -751,8 +751,7 @@
 ////////////////////////////////////////////////////////////////////////////////
 
 bool SniffMPEG2PS(
-        DataSourceBase *source, String8 *mimeType, float *confidence,
-        sp<AMessage> *) {
+        DataSourceBase *source, float *confidence) {
     uint8_t header[5];
     if (source->readAt(0, header, sizeof(header)) < (ssize_t)sizeof(header)) {
         return false;
@@ -764,8 +763,6 @@
 
     *confidence = 0.25f;  // Slightly larger than .mp3 extractor's confidence
 
-    mimeType->setTo(MEDIA_MIMETYPE_CONTAINER_MPEG2PS);
-
     return true;
 }
 
diff --git a/media/extractors/mpeg2/MPEG2PSExtractor.h b/media/extractors/mpeg2/MPEG2PSExtractor.h
index adf719a..2541f4d 100644
--- a/media/extractors/mpeg2/MPEG2PSExtractor.h
+++ b/media/extractors/mpeg2/MPEG2PSExtractor.h
@@ -71,9 +71,7 @@
     DISALLOW_EVIL_CONSTRUCTORS(MPEG2PSExtractor);
 };
 
-bool SniffMPEG2PS(
-        DataSourceBase *source, String8 *mimeType, float *confidence,
-        sp<AMessage> *);
+bool SniffMPEG2PS(DataSourceBase *source, float *confidence);
 
 }  // namespace android
 
diff --git a/media/extractors/mpeg2/MPEG2TSExtractor.cpp b/media/extractors/mpeg2/MPEG2TSExtractor.cpp
index a8a366b..3183064 100644
--- a/media/extractors/mpeg2/MPEG2TSExtractor.cpp
+++ b/media/extractors/mpeg2/MPEG2TSExtractor.cpp
@@ -645,9 +645,7 @@
 
 ////////////////////////////////////////////////////////////////////////////////
 
-bool SniffMPEG2TS(
-        DataSourceBase *source, String8 *mimeType, float *confidence,
-        sp<AMessage> *) {
+bool SniffMPEG2TS(DataSourceBase *source, float *confidence) {
     for (int i = 0; i < 5; ++i) {
         char header;
         if (source->readAt(kTSPacketSize * i, &header, 1) != 1
@@ -657,7 +655,6 @@
     }
 
     *confidence = 0.1f;
-    mimeType->setTo(MEDIA_MIMETYPE_CONTAINER_MPEG2TS);
 
     return true;
 }
diff --git a/media/extractors/mpeg2/MPEG2TSExtractor.h b/media/extractors/mpeg2/MPEG2TSExtractor.h
index fc15501..df07fac 100644
--- a/media/extractors/mpeg2/MPEG2TSExtractor.h
+++ b/media/extractors/mpeg2/MPEG2TSExtractor.h
@@ -99,9 +99,7 @@
     DISALLOW_EVIL_CONSTRUCTORS(MPEG2TSExtractor);
 };
 
-bool SniffMPEG2TS(
-        DataSourceBase *source, String8 *mimeType, float *confidence,
-        sp<AMessage> *);
+bool SniffMPEG2TS(DataSourceBase *source, float *confidence);
 
 }  // namespace android
 
diff --git a/media/extractors/ogg/OggExtractor.cpp b/media/extractors/ogg/OggExtractor.cpp
index 1d04bed..ab51e5e 100644
--- a/media/extractors/ogg/OggExtractor.cpp
+++ b/media/extractors/ogg/OggExtractor.cpp
@@ -1371,21 +1371,20 @@
 
 static MediaExtractor* CreateExtractor(
         DataSourceBase *source,
-        const sp<AMessage>& meta __unused) {
+        void *) {
     return new OggExtractor(source);
 }
 
 static MediaExtractor::CreatorFunc Sniff(
         DataSourceBase *source,
-        String8 *mimeType,
         float *confidence,
-        sp<AMessage> *) {
+        void **,
+        MediaExtractor::FreeMetaFunc *) {
     char tmp[4];
     if (source->readAt(0, tmp, 4) < 4 || memcmp(tmp, "OggS", 4)) {
         return NULL;
     }
 
-    mimeType->setTo(MEDIA_MIMETYPE_CONTAINER_OGG);
     *confidence = 0.2f;
 
     return CreateExtractor;
diff --git a/media/extractors/wav/WAVExtractor.cpp b/media/extractors/wav/WAVExtractor.cpp
index 105a37f..2c991a7 100644
--- a/media/extractors/wav/WAVExtractor.cpp
+++ b/media/extractors/wav/WAVExtractor.cpp
@@ -546,15 +546,15 @@
 
 static MediaExtractor* CreateExtractor(
         DataSourceBase *source,
-        const sp<AMessage>& meta __unused) {
+        void *) {
     return new WAVExtractor(source);
 }
 
 static MediaExtractor::CreatorFunc Sniff(
         DataSourceBase *source,
-        String8 *mimeType,
         float *confidence,
-        sp<AMessage> *) {
+        void **,
+        MediaExtractor::FreeMetaFunc *) {
     char header[12];
     if (source->readAt(0, header, sizeof(header)) < (ssize_t)sizeof(header)) {
         return NULL;
@@ -571,7 +571,6 @@
         return NULL;
     }
 
-    *mimeType = MEDIA_MIMETYPE_CONTAINER_WAV;
     *confidence = 0.3f;
 
     return CreateExtractor;
diff --git a/media/libmedia/NdkWrapper.cpp b/media/libmedia/NdkWrapper.cpp
index 942393d..936e92f 100644
--- a/media/libmedia/NdkWrapper.cpp
+++ b/media/libmedia/NdkWrapper.cpp
@@ -1057,6 +1057,13 @@
     return translateErrorCode(AMediaExtractor_setDataSource(mAMediaExtractor, location));
 }
 
+status_t AMediaExtractorWrapper::setDataSource(AMediaDataSource *source) {
+    if (mAMediaExtractor == NULL) {
+        return DEAD_OBJECT;
+    }
+    return translateErrorCode(AMediaExtractor_setDataSourceCustom(mAMediaExtractor, source));
+}
+
 size_t AMediaExtractorWrapper::getTrackCount() {
     if (mAMediaExtractor == NULL) {
         return 0;
@@ -1064,6 +1071,13 @@
     return AMediaExtractor_getTrackCount(mAMediaExtractor);
 }
 
+sp<AMediaFormatWrapper> AMediaExtractorWrapper::getFormat() {
+    if (mAMediaExtractor == NULL) {
+        return NULL;
+    }
+    return new AMediaFormatWrapper(AMediaExtractor_getFileFormat(mAMediaExtractor));
+}
+
 sp<AMediaFormatWrapper> AMediaExtractorWrapper::getTrackFormat(size_t idx) {
     if (mAMediaExtractor == NULL) {
         return NULL;
@@ -1085,6 +1099,26 @@
     return translateErrorCode(AMediaExtractor_unselectTrack(mAMediaExtractor, idx));
 }
 
+status_t AMediaExtractorWrapper::selectSingleTrack(size_t idx) {
+    if (mAMediaExtractor == NULL) {
+        return DEAD_OBJECT;
+    }
+    for (size_t i = 0; i < AMediaExtractor_getTrackCount(mAMediaExtractor); ++i) {
+        if (i == idx) {
+            media_status_t err = AMediaExtractor_selectTrack(mAMediaExtractor, i);
+            if (err != AMEDIA_OK) {
+                return translateErrorCode(err);
+            }
+        } else {
+            media_status_t err = AMediaExtractor_unselectTrack(mAMediaExtractor, i);
+            if (err != AMEDIA_OK) {
+                return translateErrorCode(err);
+            }
+        }
+    }
+    return OK;
+}
+
 ssize_t AMediaExtractorWrapper::readSampleData(const sp<ABuffer> &buffer) {
     if (mAMediaExtractor == NULL) {
         return -1;
@@ -1092,6 +1126,13 @@
     return AMediaExtractor_readSampleData(mAMediaExtractor, buffer->data(), buffer->capacity());
 }
 
+ssize_t AMediaExtractorWrapper::getSampleSize() {
+    if (mAMediaExtractor == NULL) {
+        return 0;
+    }
+    return AMediaExtractor_getSampleSize(mAMediaExtractor);
+}
+
 uint32_t AMediaExtractorWrapper::getSampleFlags() {
     if (mAMediaExtractor == NULL) {
         return 0;
@@ -1113,6 +1154,13 @@
     return AMediaExtractor_getSampleTime(mAMediaExtractor);
 }
 
+int64_t AMediaExtractorWrapper::getCachedDuration() {
+    if (mAMediaExtractor == NULL) {
+        return -1;
+    }
+    return AMediaExtractor_getCachedDuration(mAMediaExtractor);
+}
+
 bool AMediaExtractorWrapper::advance() {
     if (mAMediaExtractor == NULL) {
         return false;
@@ -1120,11 +1168,27 @@
     return AMediaExtractor_advance(mAMediaExtractor);
 }
 
-status_t AMediaExtractorWrapper::seekTo(int64_t seekPosUs, SeekMode mode) {
+status_t AMediaExtractorWrapper::seekTo(int64_t seekPosUs, MediaSource::ReadOptions::SeekMode mode) {
     if (mAMediaExtractor == NULL) {
         return DEAD_OBJECT;
     }
-    return AMediaExtractor_seekTo(mAMediaExtractor, seekPosUs, mode);
+
+    SeekMode aMode;
+    switch (mode) {
+        case MediaSource::ReadOptions::SEEK_PREVIOUS_SYNC: {
+            aMode = AMEDIAEXTRACTOR_SEEK_PREVIOUS_SYNC;
+            break;
+        }
+        case MediaSource::ReadOptions::SEEK_NEXT_SYNC: {
+            aMode = AMEDIAEXTRACTOR_SEEK_NEXT_SYNC;
+            break;
+        }
+        default: {
+            aMode = AMEDIAEXTRACTOR_SEEK_CLOSEST_SYNC;
+            break;
+        }
+    }
+    return AMediaExtractor_seekTo(mAMediaExtractor, seekPosUs, aMode);
 }
 
 PsshInfo* AMediaExtractorWrapper::getPsshInfo() {
@@ -1141,4 +1205,43 @@
     return new AMediaCodecCryptoInfoWrapper(AMediaExtractor_getSampleCryptoInfo(mAMediaExtractor));
 }
 
+ssize_t AMediaDataSourceWrapper::AMediaDataSourceWrapper_getSize(void *userdata) {
+    DataSource *source = static_cast<DataSource *>(userdata);
+    off64_t size = -1;
+    source->getSize(&size);
+    return size;
+}
+
+ssize_t AMediaDataSourceWrapper::AMediaDataSourceWrapper_readAt(void *userdata, off64_t offset, void * buf, size_t size) {
+    DataSource *source = static_cast<DataSource *>(userdata);
+    return source->readAt(offset, buf, size);
+}
+
+void AMediaDataSourceWrapper::AMediaDataSourceWrapper_close(void *userdata) {
+    DataSource *source = static_cast<DataSource *>(userdata);
+    source->close();
+}
+
+AMediaDataSourceWrapper::AMediaDataSourceWrapper(const sp<DataSource> &dataSource)
+    : mDataSource(dataSource),
+      mAMediaDataSource(AMediaDataSource_new()) {
+    ALOGV("setDataSource (source: %p)", dataSource.get());
+    AMediaDataSource_setUserdata(mAMediaDataSource, dataSource.get());
+    AMediaDataSource_setReadAt(mAMediaDataSource, AMediaDataSourceWrapper_readAt);
+    AMediaDataSource_setGetSize(mAMediaDataSource, AMediaDataSourceWrapper_getSize);
+    AMediaDataSource_setClose(mAMediaDataSource, AMediaDataSourceWrapper_close);
+}
+
+AMediaDataSourceWrapper::~AMediaDataSourceWrapper() {
+    if (mAMediaDataSource == NULL) {
+        return;
+    }
+    AMediaDataSource_delete(mAMediaDataSource);
+    mAMediaDataSource = NULL;
+}
+
+AMediaDataSource* AMediaDataSourceWrapper::getAMediaDataSource() {
+    return mAMediaDataSource;
+}
+
 }  // namespace android
diff --git a/media/libmedia/include/media/NdkWrapper.h b/media/libmedia/include/media/NdkWrapper.h
index 00e0fd4..49d728d 100644
--- a/media/libmedia/include/media/NdkWrapper.h
+++ b/media/libmedia/include/media/NdkWrapper.h
@@ -18,6 +18,9 @@
 
 #define NDK_WRAPPER_H_
 
+#include <media/DataSource.h>
+#include <media/MediaSource.h>
+#include <media/NdkMediaDataSource.h>
 #include <media/NdkMediaError.h>
 #include <media/NdkMediaExtractor.h>
 #include <media/hardware/CryptoAPI.h>
@@ -286,25 +289,35 @@
 
     status_t setDataSource(const char *location);
 
+    status_t setDataSource(AMediaDataSource *);
+
     size_t getTrackCount();
 
+    sp<AMediaFormatWrapper> getFormat();
+
     sp<AMediaFormatWrapper> getTrackFormat(size_t idx);
 
     status_t selectTrack(size_t idx);
 
     status_t unselectTrack(size_t idx);
 
+    status_t selectSingleTrack(size_t idx);
+
     ssize_t readSampleData(const sp<ABuffer> &buffer);
 
+    ssize_t getSampleSize();
+
     uint32_t getSampleFlags();
 
     int getSampleTrackIndex();
 
     int64_t getSampleTime();
 
+    int64_t getCachedDuration();
+
     bool advance();
 
-    status_t seekTo(int64_t seekPosUs, SeekMode mode);
+    status_t seekTo(int64_t seekPosUs, MediaSource::ReadOptions::SeekMode mode);
 
     // the returned PsshInfo is still owned by this wrapper.
     PsshInfo* getPsshInfo();
@@ -320,6 +333,31 @@
     DISALLOW_EVIL_CONSTRUCTORS(AMediaExtractorWrapper);
 };
 
+struct AMediaDataSourceWrapper : public RefBase {
+
+    static status_t translate_error(media_status_t err);
+
+    static ssize_t AMediaDataSourceWrapper_getSize(void *userdata);
+
+    static ssize_t AMediaDataSourceWrapper_readAt(void *userdata, off64_t offset, void * buf, size_t size);
+
+    static void AMediaDataSourceWrapper_close(void *userdata);
+
+    AMediaDataSourceWrapper(const sp<DataSource> &dataSource);
+
+    AMediaDataSource *getAMediaDataSource();
+
+protected:
+    virtual ~AMediaDataSourceWrapper();
+
+private:
+    sp<DataSource> mDataSource;
+
+    AMediaDataSource *mAMediaDataSource;
+
+    DISALLOW_EVIL_CONSTRUCTORS(AMediaDataSourceWrapper);
+};
+
 }  // namespace android
 
 #endif  // NDK_WRAPPER_H_
diff --git a/media/libmedia/include/media/OMXBuffer.h b/media/libmedia/include/media/OMXBuffer.h
index 3e84858..9c9f5e7 100644
--- a/media/libmedia/include/media/OMXBuffer.h
+++ b/media/libmedia/include/media/OMXBuffer.h
@@ -91,6 +91,7 @@
 
 private:
     friend struct OMXNodeInstance;
+    friend struct C2OMXNode;
 
     // This is needed temporarily for OMX HIDL transition.
     friend inline bool (::android::hardware::media::omx::V1_0::implementation::
diff --git a/media/libmediaextractor/MediaBufferGroup.cpp b/media/libmediaextractor/MediaBufferGroup.cpp
index cb62d92..22f01a5 100644
--- a/media/libmediaextractor/MediaBufferGroup.cpp
+++ b/media/libmediaextractor/MediaBufferGroup.cpp
@@ -17,9 +17,13 @@
 #define LOG_TAG "MediaBufferGroup"
 #include <utils/Log.h>
 
+#include <list>
+
+#include <binder/MemoryDealer.h>
 #include <media/stagefright/foundation/ADebug.h>
 #include <media/stagefright/MediaBuffer.h>
 #include <media/stagefright/MediaBufferGroup.h>
+#include <utils/threads.h>
 
 namespace android {
 
@@ -32,17 +36,26 @@
 static const size_t kSharedMemoryThreshold = MIN(
         (size_t)MediaBuffer::kSharedMemThreshold, (size_t)(4 * 1024));
 
-MediaBufferGroup::MediaBufferGroup(size_t growthLimit) :
-    mGrowthLimit(growthLimit) {
+struct MediaBufferGroup::InternalData {
+    Mutex mLock;
+    Condition mCondition;
+    size_t mGrowthLimit;  // Do not automatically grow group larger than this.
+    std::list<MediaBuffer *> mBuffers;
+};
+
+MediaBufferGroup::MediaBufferGroup(size_t growthLimit)
+    : mInternal(new InternalData()) {
+    mInternal->mGrowthLimit = growthLimit;
 }
 
 MediaBufferGroup::MediaBufferGroup(size_t buffers, size_t buffer_size, size_t growthLimit)
-    : mGrowthLimit(growthLimit) {
+    : mInternal(new InternalData()) {
+    mInternal->mGrowthLimit = growthLimit;
 
-    if (mGrowthLimit > 0 && buffers > mGrowthLimit) {
+    if (mInternal->mGrowthLimit > 0 && buffers > mInternal->mGrowthLimit) {
         ALOGW("Preallocated buffers %zu > growthLimit %zu, increasing growthLimit",
-                buffers, mGrowthLimit);
-        mGrowthLimit = buffers;
+                buffers, mInternal->mGrowthLimit);
+        mInternal->mGrowthLimit = buffers;
     }
 
     if (buffer_size >= kSharedMemoryThreshold) {
@@ -81,7 +94,7 @@
 }
 
 MediaBufferGroup::~MediaBufferGroup() {
-    for (MediaBuffer *buffer : mBuffers) {
+    for (MediaBuffer *buffer : mInternal->mBuffers) {
         if (buffer->refcount() != 0) {
             const int localRefcount = buffer->localRefcount();
             const int remoteRefcount = buffer->remoteRefcount();
@@ -103,34 +116,35 @@
         buffer->setObserver(nullptr);
         buffer->release();
     }
+    delete mInternal;
 }
 
 void MediaBufferGroup::add_buffer(MediaBuffer *buffer) {
-    Mutex::Autolock autoLock(mLock);
+    Mutex::Autolock autoLock(mInternal->mLock);
 
     // if we're above our growth limit, release buffers if we can
-    for (auto it = mBuffers.begin();
-            mGrowthLimit > 0
-            && mBuffers.size() >= mGrowthLimit
-            && it != mBuffers.end();) {
+    for (auto it = mInternal->mBuffers.begin();
+            mInternal->mGrowthLimit > 0
+            && mInternal->mBuffers.size() >= mInternal->mGrowthLimit
+            && it != mInternal->mBuffers.end();) {
         if ((*it)->refcount() == 0) {
             (*it)->setObserver(nullptr);
             (*it)->release();
-            it = mBuffers.erase(it);
+            it = mInternal->mBuffers.erase(it);
         } else {
             ++it;
         }
     }
 
     buffer->setObserver(this);
-    mBuffers.emplace_back(buffer);
+    mInternal->mBuffers.emplace_back(buffer);
 }
 
 bool MediaBufferGroup::has_buffers() {
-    if (mBuffers.size() < mGrowthLimit) {
+    if (mInternal->mBuffers.size() < mInternal->mGrowthLimit) {
         return true; // We can add more buffers internally.
     }
-    for (MediaBuffer *buffer : mBuffers) {
+    for (MediaBuffer *buffer : mInternal->mBuffers) {
         if (buffer->refcount() == 0) {
             return true;
         }
@@ -140,12 +154,12 @@
 
 status_t MediaBufferGroup::acquire_buffer(
         MediaBuffer **out, bool nonBlocking, size_t requestedSize) {
-    Mutex::Autolock autoLock(mLock);
+    Mutex::Autolock autoLock(mInternal->mLock);
     for (;;) {
         size_t smallest = requestedSize;
         MediaBuffer *buffer = nullptr;
-        auto free = mBuffers.end();
-        for (auto it = mBuffers.begin(); it != mBuffers.end(); ++it) {
+        auto free = mInternal->mBuffers.end();
+        for (auto it = mInternal->mBuffers.begin(); it != mInternal->mBuffers.end(); ++it) {
             if ((*it)->refcount() == 0) {
                 const size_t size = (*it)->size();
                 if (size >= requestedSize) {
@@ -159,7 +173,8 @@
             }
         }
         if (buffer == nullptr
-                && (free != mBuffers.end() || mBuffers.size() < mGrowthLimit)) {
+                && (free != mInternal->mBuffers.end()
+                    || mInternal->mBuffers.size() < mInternal->mGrowthLimit)) {
             // We alloc before we free so failure leaves group unchanged.
             const size_t allocateSize = requestedSize < SIZE_MAX / 3 * 2 /* NB: ordering */ ?
                     requestedSize * 3 / 2 : requestedSize;
@@ -170,7 +185,7 @@
                 buffer = nullptr;
             } else {
                 buffer->setObserver(this);
-                if (free != mBuffers.end()) {
+                if (free != mInternal->mBuffers.end()) {
                     ALOGV("reallocate buffer, requested size %zu vs available %zu",
                             requestedSize, (*free)->size());
                     (*free)->setObserver(nullptr);
@@ -178,7 +193,7 @@
                     *free = buffer; // in-place replace
                 } else {
                     ALOGV("allocate buffer, requested size %zu", requestedSize);
-                    mBuffers.emplace_back(buffer);
+                    mInternal->mBuffers.emplace_back(buffer);
                 }
             }
         }
@@ -193,14 +208,18 @@
             return WOULD_BLOCK;
         }
         // All buffers are in use, block until one of them is returned.
-        mCondition.wait(mLock);
+        mInternal->mCondition.wait(mInternal->mLock);
     }
     // Never gets here.
 }
 
+size_t MediaBufferGroup::buffers() const {
+    return mInternal->mBuffers.size();
+}
+
 void MediaBufferGroup::signalBufferReturned(MediaBuffer *) {
-    Mutex::Autolock autoLock(mLock);
-    mCondition.signal();
+    Mutex::Autolock autoLock(mInternal->mLock);
+    mInternal->mCondition.signal();
 }
 
 }  // namespace android
diff --git a/media/libmediaextractor/include/media/MediaExtractor.h b/media/libmediaextractor/include/media/MediaExtractor.h
index 73c5f10..2e65620 100644
--- a/media/libmediaextractor/include/media/MediaExtractor.h
+++ b/media/libmediaextractor/include/media/MediaExtractor.h
@@ -29,8 +29,6 @@
 
 class DataSourceBase;
 class MetaData;
-class String8;
-struct AMessage;
 struct MediaSourceBase;
 
 
@@ -87,14 +85,16 @@
     virtual const char * name() { return "<unspecified>"; }
 
     typedef MediaExtractor* (*CreatorFunc)(
-            DataSourceBase *source, const sp<AMessage> &meta);
+            DataSourceBase *source, void *meta);
+    typedef void (*FreeMetaFunc)(void *meta);
 
-    // The sniffer can optionally fill in "meta" with an AMessage containing
-    // a dictionary of values that helps the corresponding extractor initialize
-    // its state without duplicating effort already exerted by the sniffer.
+    // The sniffer can optionally fill in an opaque object, "meta", that helps
+    // the corresponding extractor initialize its state without duplicating
+    // effort already exerted by the sniffer. If "freeMeta" is given, it will be
+    // called against the opaque object when it is no longer used.
     typedef CreatorFunc (*SnifferFunc)(
-            DataSourceBase *source, String8 *mimeType,
-            float *confidence, sp<AMessage> *meta);
+            DataSourceBase *source, float *confidence,
+            void **meta, FreeMetaFunc *freeMeta);
 
     typedef struct {
         const uint8_t b[16];
diff --git a/media/libmediaextractor/include/media/stagefright/MediaBufferGroup.h b/media/libmediaextractor/include/media/stagefright/MediaBufferGroup.h
index 3041181..63d0a18 100644
--- a/media/libmediaextractor/include/media/stagefright/MediaBufferGroup.h
+++ b/media/libmediaextractor/include/media/stagefright/MediaBufferGroup.h
@@ -19,8 +19,6 @@
 #define MEDIA_BUFFER_GROUP_H_
 
 #include <media/stagefright/MediaBuffer.h>
-#include <utils/Errors.h>
-#include <utils/threads.h>
 
 namespace android {
 
@@ -50,7 +48,7 @@
     status_t acquire_buffer(
             MediaBuffer **buffer, bool nonBlocking = false, size_t requestedSize = 0);
 
-    size_t buffers() const { return mBuffers.size(); }
+    size_t buffers() const;
 
     // If buffer is nullptr, have acquire_buffer() check for remote release.
     virtual void signalBufferReturned(MediaBuffer *buffer);
@@ -58,10 +56,8 @@
 private:
     friend class MediaBuffer;
 
-    Mutex mLock;
-    Condition mCondition;
-    size_t mGrowthLimit;  // Do not automatically grow group larger than this.
-    std::list<MediaBuffer *> mBuffers;
+    struct InternalData;
+    InternalData *mInternal;
 
     MediaBufferGroup(const MediaBufferGroup &);
     MediaBufferGroup &operator=(const MediaBufferGroup &);
diff --git a/media/libstagefright/Android.bp b/media/libstagefright/Android.bp
index 08b2775..905817f 100644
--- a/media/libstagefright/Android.bp
+++ b/media/libstagefright/Android.bp
@@ -60,6 +60,7 @@
         "AudioPresentationInfo.cpp",
         "AudioSource.cpp",
         "BufferImpl.cpp",
+        "C2OMXNode.cpp",
         "CCodec.cpp",
         "CCodecBufferChannel.cpp",
         "CodecBase.cpp",
@@ -130,7 +131,6 @@
         "libui",
         "libutils",
         "libmedia_helper",
-        "libstagefright_bufferqueue_helper",
         "libstagefright_codec2",
         "libstagefright_codec2_vndk",
         "libstagefright_foundation",
@@ -149,6 +149,10 @@
         "android.hardware.media.omx@1.0",
         "android.hardware.graphics.allocator@2.0",
         "android.hardware.graphics.mapper@2.0",
+
+        // TODO: do not link directly with impl
+        "android.hardware.media.c2@1.0-service-impl",
+        "libstagefright_bufferqueue_helper",
     ],
 
     static_libs: [
@@ -215,6 +219,7 @@
         "InterfaceUtils.cpp",
         "MediaClock.cpp",
         "MediaExtractorFactory.cpp",
+        "NdkUtils.cpp",
         "NuCachedSource2.cpp",
         "RemoteMediaExtractor.cpp",
         "RemoteMediaSource.cpp",
diff --git a/media/libstagefright/C2OMXNode.cpp b/media/libstagefright/C2OMXNode.cpp
new file mode 100644
index 0000000..e6f81af
--- /dev/null
+++ b/media/libstagefright/C2OMXNode.cpp
@@ -0,0 +1,281 @@
+/*
+ * Copyright 2018, 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.
+ */
+
+#ifdef __LP64__
+#define OMX_ANDROID_COMPILE_AS_32BIT_ON_64BIT_PLATFORMS
+#endif
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "C2OMXNode"
+#include <log/log.h>
+
+#include <C2AllocatorGralloc.h>
+#include <C2BlockInternal.h>
+#include <C2Component.h>
+#include <C2PlatformSupport.h>
+
+#include <OMX_Component.h>
+#include <OMX_Index.h>
+#include <OMX_IndexExt.h>
+
+#include <media/stagefright/omx/OMXUtils.h>
+#include <media/stagefright/MediaErrors.h>
+#include <ui/Fence.h>
+#include <ui/GraphicBuffer.h>
+
+#include "include/C2OMXNode.h"
+
+namespace android {
+
+namespace {
+
+class Buffer2D : public C2Buffer {
+public:
+    explicit Buffer2D(C2ConstGraphicBlock block) : C2Buffer({ block }) {}
+};
+
+}  // namespace
+
+C2OMXNode::C2OMXNode(const std::shared_ptr<C2Component> &comp) : mComp(comp) {}
+
+status_t C2OMXNode::freeNode() {
+    mComp.reset();
+    return OK;
+}
+
+status_t C2OMXNode::sendCommand(OMX_COMMANDTYPE cmd, OMX_S32 param) {
+    (void)cmd;
+    (void)param;
+    return ERROR_UNSUPPORTED;
+}
+
+status_t C2OMXNode::getParameter(OMX_INDEXTYPE index, void *params, size_t size) {
+    status_t err = ERROR_UNSUPPORTED;
+    switch ((uint32_t)index) {
+        case OMX_IndexParamConsumerUsageBits: {
+            // TODO: read from intf()
+            OMX_U32 *usage = (OMX_U32 *)params;
+            *usage = GRALLOC_USAGE_SW_READ_OFTEN;
+            err = OK;
+            break;
+        }
+        case OMX_IndexParamPortDefinition: {
+            if (size < sizeof(OMX_PARAM_PORTDEFINITIONTYPE)) {
+                return BAD_VALUE;
+            }
+            OMX_PARAM_PORTDEFINITIONTYPE *pDef = (OMX_PARAM_PORTDEFINITIONTYPE *)params;
+            // TODO: read these from intf()
+            pDef->nBufferCountActual = 16;
+            pDef->eDomain = OMX_PortDomainVideo;
+            pDef->format.video.nFrameWidth = 1080;
+            pDef->format.video.nFrameHeight = 1920;
+            err = OK;
+            break;
+        }
+        default:
+            break;
+    }
+    return err;
+}
+
+status_t C2OMXNode::setParameter(OMX_INDEXTYPE index, const void *params, size_t size) {
+    (void)index;
+    (void)params;
+    (void)size;
+    return ERROR_UNSUPPORTED;
+}
+
+status_t C2OMXNode::getConfig(OMX_INDEXTYPE index, void *config, size_t size) {
+    (void)index;
+    (void)config;
+    (void)size;
+    return ERROR_UNSUPPORTED;
+}
+
+status_t C2OMXNode::setConfig(OMX_INDEXTYPE index, const void *config, size_t size) {
+    (void)index;
+    (void)config;
+    (void)size;
+    return ERROR_UNSUPPORTED;
+}
+
+status_t C2OMXNode::setPortMode(OMX_U32 portIndex, IOMX::PortMode mode) {
+    (void)portIndex;
+    (void)mode;
+    return ERROR_UNSUPPORTED;
+}
+
+status_t C2OMXNode::prepareForAdaptivePlayback(
+        OMX_U32 portIndex, OMX_BOOL enable,
+        OMX_U32 maxFrameWidth, OMX_U32 maxFrameHeight) {
+    (void)portIndex;
+    (void)enable;
+    (void)maxFrameWidth;
+    (void)maxFrameHeight;
+    return ERROR_UNSUPPORTED;
+}
+
+status_t C2OMXNode::configureVideoTunnelMode(
+        OMX_U32 portIndex, OMX_BOOL tunneled,
+        OMX_U32 audioHwSync, native_handle_t **sidebandHandle) {
+    (void)portIndex;
+    (void)tunneled;
+    (void)audioHwSync;
+    *sidebandHandle = nullptr;
+    return ERROR_UNSUPPORTED;
+}
+
+status_t C2OMXNode::getGraphicBufferUsage(OMX_U32 portIndex, OMX_U32* usage) {
+    (void)portIndex;
+    *usage = 0;
+    return ERROR_UNSUPPORTED;
+}
+
+status_t C2OMXNode::setInputSurface(const sp<IOMXBufferSource> &bufferSource) {
+    c2_status_t err = GetCodec2PlatformAllocatorStore()->fetchAllocator(
+            C2PlatformAllocatorStore::GRALLOC,
+            &mAllocator);
+    if (err != OK) {
+        return UNKNOWN_ERROR;
+    }
+    mBufferSource = bufferSource;
+    return OK;
+}
+
+status_t C2OMXNode::allocateSecureBuffer(
+        OMX_U32 portIndex, size_t size, buffer_id *buffer,
+        void **bufferData, sp<NativeHandle> *nativeHandle) {
+    (void)portIndex;
+    (void)size;
+    (void)nativeHandle;
+    *buffer = 0;
+    *bufferData = nullptr;
+    return ERROR_UNSUPPORTED;
+}
+
+status_t C2OMXNode::useBuffer(
+        OMX_U32 portIndex, const OMXBuffer &omxBuf, buffer_id *buffer) {
+    (void)portIndex;
+    (void)omxBuf;
+    *buffer = 0;
+    return ERROR_UNSUPPORTED;
+}
+
+status_t C2OMXNode::freeBuffer(OMX_U32 portIndex, buffer_id buffer) {
+    (void)portIndex;
+    (void)buffer;
+    return ERROR_UNSUPPORTED;
+}
+
+status_t C2OMXNode::fillBuffer(
+        buffer_id buffer, const OMXBuffer &omxBuf, int fenceFd) {
+    (void)buffer;
+    (void)omxBuf;
+    (void)fenceFd;
+    return ERROR_UNSUPPORTED;
+}
+
+status_t C2OMXNode::emptyBuffer(
+        buffer_id buffer, const OMXBuffer &omxBuf,
+        OMX_U32 flags, OMX_TICKS timestamp, int fenceFd) {
+    // TODO: better fence handling
+    if (fenceFd >= 0) {
+        sp<Fence> fence = new Fence(fenceFd);
+        fence->waitForever(LOG_TAG);
+    }
+    std::shared_ptr<C2Component> comp = mComp.lock();
+    if (!comp) {
+        return NO_INIT;
+    }
+
+    uint32_t c2Flags = 0;
+    std::shared_ptr<C2GraphicBlock> block;
+
+    C2Handle *handle = nullptr;
+    if (omxBuf.mBufferType == OMXBuffer::kBufferTypeANWBuffer) {
+        std::shared_ptr<C2GraphicAllocation> alloc;
+        handle = WrapNativeCodec2GrallocHandle(
+                native_handle_clone(omxBuf.mGraphicBuffer->handle),
+                omxBuf.mGraphicBuffer->width,
+                omxBuf.mGraphicBuffer->height,
+                omxBuf.mGraphicBuffer->format,
+                omxBuf.mGraphicBuffer->usage,
+                omxBuf.mGraphicBuffer->stride);
+        c2_status_t err = mAllocator->priorGraphicAllocation(handle, &alloc);
+        if (err != OK) {
+            return UNKNOWN_ERROR;
+        }
+        block = _C2BlockFactory::CreateGraphicBlock(alloc);
+    } else if (flags & OMX_BUFFERFLAG_EOS) {
+        c2Flags = C2FrameData::FLAG_END_OF_STREAM;
+    } else {
+        return BAD_VALUE;
+    }
+
+    std::unique_ptr<C2Work> work(new C2Work);
+    work->input.flags = (C2FrameData::flags_t)c2Flags;
+    work->input.ordinal.timestamp = timestamp;
+    work->input.ordinal.frameIndex = mFrameIndex++;
+    work->input.buffers.clear();
+    if (block) {
+        std::shared_ptr<C2Buffer> c2Buffer(
+                // TODO: fence
+                new Buffer2D(block->share(
+                        C2Rect(block->width(), block->height()), ::android::C2Fence())),
+                [handle, buffer, source = getSource()](C2Buffer *ptr) {
+                    delete ptr;
+                    native_handle_delete(handle);
+                    // TODO: fence
+                    (void)source->onInputBufferEmptied(buffer, -1);
+                });
+        work->input.buffers.push_back(c2Buffer);
+    }
+    work->worklets.clear();
+    work->worklets.emplace_back(new C2Worklet);
+    std::list<std::unique_ptr<C2Work>> items;
+    items.push_back(std::move(work));
+
+    c2_status_t err = comp->queue_nb(&items);
+    if (err != C2_OK) {
+        return UNKNOWN_ERROR;
+    }
+
+    return OK;
+}
+
+status_t C2OMXNode::getExtensionIndex(
+        const char *parameterName, OMX_INDEXTYPE *index) {
+    (void)parameterName;
+    *index = OMX_IndexMax;
+    return ERROR_UNSUPPORTED;
+}
+
+status_t C2OMXNode::dispatchMessage(const omx_message& msg) {
+    if (msg.type != omx_message::EVENT) {
+        return ERROR_UNSUPPORTED;
+    }
+    if (msg.u.event_data.event != OMX_EventDataSpaceChanged) {
+        return ERROR_UNSUPPORTED;
+    }
+    // TODO: fill intf() with info inside |msg|.
+    return OK;
+}
+
+sp<IOMXBufferSource> C2OMXNode::getSource() {
+    return mBufferSource;
+}
+
+}  // namespace android
diff --git a/media/libstagefright/CCodec.cpp b/media/libstagefright/CCodec.cpp
index 84e98f8..bb70458 100644
--- a/media/libstagefright/CCodec.cpp
+++ b/media/libstagefright/CCodec.cpp
@@ -24,17 +24,23 @@
 #include <C2PlatformSupport.h>
 #include <C2V4l2Support.h>
 
+#include <android/IOMXBufferSource.h>
+#include <gui/bufferqueue/1.0/H2BGraphicBufferProducer.h>
 #include <gui/Surface.h>
+#include <media/stagefright/codec2/1.0/InputSurface.h>
 #include <media/stagefright/BufferProducerWrapper.h>
 #include <media/stagefright/CCodec.h>
 #include <media/stagefright/PersistentSurface.h>
 
+#include "include/C2OMXNode.h"
 #include "include/CCodecBufferChannel.h"
-
-using namespace std::chrono_literals;
+#include "include/InputSurfaceWrapper.h"
 
 namespace android {
 
+using namespace std::chrono_literals;
+using ::android::hardware::graphics::bufferqueue::V1_0::utils::H2BGraphicBufferProducer;
+
 namespace {
 
 class CCodecWatchdog : public AHandler {
@@ -146,6 +152,76 @@
     wp<CCodec> mCodec;
 };
 
+class C2InputSurfaceWrapper : public InputSurfaceWrapper {
+public:
+    explicit C2InputSurfaceWrapper(const sp<InputSurface> &surface) : mSurface(surface) {}
+    ~C2InputSurfaceWrapper() override = default;
+
+    status_t connect(const std::shared_ptr<C2Component> &comp) override {
+        if (mConnection != nullptr) {
+            return ALREADY_EXISTS;
+        }
+        mConnection = mSurface->connectToComponent(comp);
+        return OK;
+    }
+
+    void disconnect() override {
+        if (mConnection != nullptr) {
+            mConnection->disconnect();
+            mConnection.clear();
+        }
+    }
+
+private:
+    sp<InputSurface> mSurface;
+    sp<InputSurfaceConnection> mConnection;
+};
+
+class GraphicBufferSourceWrapper : public InputSurfaceWrapper {
+public:
+    explicit GraphicBufferSourceWrapper(const sp<IGraphicBufferSource> &source) : mSource(source) {}
+    ~GraphicBufferSourceWrapper() override = default;
+
+    status_t connect(const std::shared_ptr<C2Component> &comp) override {
+        // TODO: proper color aspect & dataspace
+        android_dataspace dataSpace = HAL_DATASPACE_BT709;
+
+        mNode = new C2OMXNode(comp);
+        mSource->configure(mNode, dataSpace);
+
+        // TODO: configure according to intf().
+
+        sp<IOMXBufferSource> source = mNode->getSource();
+        if (source == nullptr) {
+            return NO_INIT;
+        }
+        constexpr size_t kNumSlots = 16;
+        for (size_t i = 0; i < kNumSlots; ++i) {
+            source->onInputBufferAdded(i);
+        }
+        source->onOmxExecuting();
+        return OK;
+    }
+
+    void disconnect() override {
+        if (mNode == nullptr) {
+            return;
+        }
+        sp<IOMXBufferSource> source = mNode->getSource();
+        if (source == nullptr) {
+            ALOGD("GBSWrapper::disconnect: node is not configured with OMXBufferSource.");
+            return;
+        }
+        source->onOmxIdle();
+        source->onOmxLoaded();
+        mNode.clear();
+    }
+
+private:
+    sp<IGraphicBufferSource> mSource;
+    sp<C2OMXNode> mNode;
+};
+
 }  // namespace
 
 CCodec::CCodec()
@@ -300,18 +376,18 @@
 }
 
 void CCodec::createInputSurface() {
-    sp<IGraphicBufferProducer> producer;
-    sp<GraphicBufferSource> source(new GraphicBufferSource);
+    // TODO: get this from codec process
+    sp<InputSurface> surface(InputSurface::Create());
 
-    status_t err = source->initCheck();
+    // TODO: get proper error code.
+    status_t err = (surface == nullptr) ? UNKNOWN_ERROR : OK;
     if (err != OK) {
-        ALOGE("Failed to initialize graphic buffer source: %d", err);
+        ALOGE("Failed to initialize input surface: %d", err);
         mCallback->onInputSurfaceCreationFailed(err);
         return;
     }
-    producer = source->getIGraphicBufferProducer();
 
-    err = setupInputSurface(source);
+    err = setupInputSurface(std::make_shared<C2InputSurfaceWrapper>(surface));
     if (err != OK) {
         ALOGE("Failed to set up input surface: %d", err);
         mCallback->onInputSurfaceCreationFailed(err);
@@ -328,16 +404,16 @@
     mCallback->onInputSurfaceCreated(
             inputFormat,
             outputFormat,
-            new BufferProducerWrapper(producer));
+            new BufferProducerWrapper(new H2BGraphicBufferProducer(surface)));
 }
 
-status_t CCodec::setupInputSurface(const sp<GraphicBufferSource> &source) {
-    status_t err = mChannel->setGraphicBufferSource(source);
+status_t CCodec::setupInputSurface(const std::shared_ptr<InputSurfaceWrapper> &surface) {
+    status_t err = mChannel->setInputSurface(surface);
     if (err != OK) {
         return err;
     }
 
-    // TODO: configure |source| with other settings.
+    // TODO: configure |surface| with other settings.
     return OK;
 }
 
@@ -348,10 +424,22 @@
 }
 
 void CCodec::setInputSurface(const sp<PersistentSurface> &surface) {
-    // TODO
-    (void)surface;
+    status_t err = setupInputSurface(std::make_shared<GraphicBufferSourceWrapper>(
+            surface->getBufferSource()));
+    if (err != OK) {
+        ALOGE("Failed to set up input surface: %d", err);
+        mCallback->onInputSurfaceDeclined(err);
+        return;
+    }
 
-    mCallback->onInputSurfaceDeclined(ERROR_UNSUPPORTED);
+    sp<AMessage> inputFormat;
+    sp<AMessage> outputFormat;
+    {
+        Mutexed<Formats>::Locked formats(mFormats);
+        inputFormat = formats->inputFormat;
+        outputFormat = formats->outputFormat;
+    }
+    mCallback->onInputSurfaceAccepted(inputFormat, outputFormat);
 }
 
 void CCodec::initiateStart() {
diff --git a/media/libstagefright/CCodecBufferChannel.cpp b/media/libstagefright/CCodecBufferChannel.cpp
index 27060e1..6fba890 100644
--- a/media/libstagefright/CCodecBufferChannel.cpp
+++ b/media/libstagefright/CCodecBufferChannel.cpp
@@ -101,15 +101,6 @@
      * Release the buffer obtained from requestNewBuffer() and get the
      * associated C2Buffer object back. Returns empty shared_ptr if the
      * buffer is not on file.
-     *
-     * XXX: this is a quick hack to be removed
-     */
-    virtual std::shared_ptr<C2Buffer> releaseBufferIndex(size_t /* index */) { return nullptr; }
-
-    /**
-     * Release the buffer obtained from requestNewBuffer() and get the
-     * associated C2Buffer object back. Returns empty shared_ptr if the
-     * buffer is not on file.
      */
     virtual std::shared_ptr<C2Buffer> releaseBuffer(const sp<MediaCodecBuffer> &buffer) = 0;
 
@@ -134,47 +125,6 @@
     DISALLOW_EVIL_CONSTRUCTORS(InputBuffers);
 };
 
-class CCodecBufferChannel::InputBufferClient {
-public:
-    explicit InputBufferClient(
-            const std::shared_ptr<CCodecBufferChannel> &channel) : mChannel(channel) {}
-    virtual ~InputBufferClient() = default;
-
-    virtual void onInputBufferAdded(size_t index, const sp<MediaCodecBuffer> &buffer) {
-        std::shared_ptr<CCodecBufferChannel> channel = mChannel.lock();
-        if (!channel) {
-            return;
-        }
-        channel->mCallback->onInputBufferAvailable(index, buffer);
-    }
-
-    virtual void onStart() {
-        // no-op
-    }
-
-    virtual void onStop() {
-        // no-op
-    }
-
-    virtual void onRelease() {
-        // no-op
-    }
-
-    virtual void onInputBufferAvailable(size_t index, const sp<MediaCodecBuffer> &buffer) {
-        std::shared_ptr<CCodecBufferChannel> channel = mChannel.lock();
-        if (!channel) {
-            return;
-        }
-        channel->mCallback->onInputBufferAvailable(index, buffer);
-    }
-
-protected:
-    InputBufferClient() = default;
-    std::weak_ptr<CCodecBufferChannel> mChannel;
-
-    DISALLOW_EVIL_CONSTRUCTORS(InputBufferClient);
-};
-
 class CCodecBufferChannel::OutputBuffers : public CCodecBufferChannel::Buffers {
 public:
     OutputBuffers() = default;
@@ -227,8 +177,6 @@
 
 namespace {
 
-constexpr int32_t kMaskI32 = ~0;
-
 // TODO: get this info from component
 const static size_t kMinBufferArraySize = 16;
 const static size_t kLinearBufferSize = 524288;
@@ -422,23 +370,8 @@
 public:
     GraphicInputBuffers() = default;
 
-    bool requestNewBuffer(size_t *index, sp<MediaCodecBuffer> *buffer) override {
-        *buffer = nullptr;
-        for (size_t i = 0; i < mAvailable.size(); ++i) {
-            if (mAvailable[i]) {
-                *index = i;
-                mAvailable[i] = false;
-                return true;
-            }
-        }
-        *index = mAvailable.size();
-        mAvailable.push_back(false);
-        return true;
-    }
-
-    std::shared_ptr<C2Buffer> releaseBufferIndex(size_t index) override {
-        mAvailable[index] = true;
-        return nullptr;
+    bool requestNewBuffer(size_t *, sp<MediaCodecBuffer> *) override {
+        return false;
     }
 
     std::shared_ptr<C2Buffer> releaseBuffer(const sp<MediaCodecBuffer> &) override {
@@ -452,8 +385,11 @@
         return nullptr;
     }
 
-private:
-    std::vector<bool> mAvailable;
+    bool isArrayMode() const final { return true; }
+
+    void getArray(Vector<sp<MediaCodecBuffer>> *array) const final {
+        array->clear();
+    }
 };
 
 class OutputBuffersArray : public CCodecBufferChannel::OutputBuffers {
@@ -753,164 +689,8 @@
     }
 };
 
-class BufferQueueClient : public CCodecBufferChannel::InputBufferClient {
-public:
-    explicit BufferQueueClient(const sp<GraphicBufferSource> &source) : mSource(source) {}
-    virtual ~BufferQueueClient() = default;
-
-    void onInputBufferAdded(size_t index, const sp<MediaCodecBuffer> &buffer) override {
-        (void)buffer;
-        mSource->onInputBufferAdded(index & kMaskI32);
-    }
-
-    void onStart() override {
-        mSource->start();
-    }
-
-    void onStop() override {
-        mSource->stop();
-    }
-
-    void onRelease() override {
-        mSource->release();
-    }
-
-    void onInputBufferAvailable(size_t index, const sp<MediaCodecBuffer> &buffer) override {
-        ALOGV("onInputBufferEmptied index = %zu", index);
-        (void)buffer;
-        // TODO: can we really ignore fence here?
-        mSource->onInputBufferEmptied(index & kMaskI32, -1 /* fenceFd */);
-    }
-
-private:
-    sp<GraphicBufferSource> mSource;
-};
-
-class GraphicBlock : public C2GraphicBlock {
-    using C2GraphicBlock::C2GraphicBlock;
-    friend class ::android::CCodecBufferChannel;
-};
-
 }  // namespace
 
-class CCodecBufferChannel::C2ComponentWrapper : public ComponentWrapper {
-public:
-    explicit C2ComponentWrapper(
-            const std::shared_ptr<CCodecBufferChannel> &channel)
-        : mChannel(channel), mLastTimestamp(0) {}
-
-    virtual ~C2ComponentWrapper() {
-        for (const std::pair<int32_t, C2Handle *> &entry : mHandles) {
-            native_handle_delete(entry.second);
-        }
-    }
-
-    status_t submitBuffer(
-            int32_t bufferId, const sp<GraphicBuffer> &buffer,
-            int64_t timestamp, int fenceFd) override {
-        ALOGV("submitBuffer bufferId = %d", bufferId);
-        // TODO: Use fd to construct fence
-        (void)fenceFd;
-
-        std::shared_ptr<CCodecBufferChannel> channel = mChannel.lock();
-        if (!channel) {
-            return NO_INIT;
-        }
-
-        std::shared_ptr<C2Allocator> allocator = mAllocator.lock();
-        if (!allocator) {
-            c2_status_t err = GetCodec2PlatformAllocatorStore()->fetchAllocator(
-                    C2PlatformAllocatorStore::GRALLOC,
-                    &allocator);
-            if (err != OK) {
-                return UNKNOWN_ERROR;
-            }
-            mAllocator = allocator;
-        }
-
-        std::shared_ptr<C2GraphicAllocation> alloc;
-        C2Handle *handle = WrapNativeCodec2GrallocHandle(
-                buffer->handle, buffer->width, buffer->height,
-                buffer->format, buffer->usage, buffer->stride);
-        c2_status_t err = allocator->priorGraphicAllocation(handle, &alloc);
-        if (err != OK) {
-            return UNKNOWN_ERROR;
-        }
-
-        std::shared_ptr<C2GraphicBlock> block = _C2BlockFactory::CreateGraphicBlock(alloc);
-
-        std::unique_ptr<C2Work> work(new C2Work);
-        work->input.flags = (C2FrameData::flags_t)0;
-        work->input.ordinal.timestamp = timestamp;
-        work->input.ordinal.frameIndex = channel->mFrameIndex++;
-        work->input.buffers.clear();
-        work->input.buffers.emplace_back(new Buffer2D(
-                // TODO: fence
-                block->share(C2Rect(block->width(), block->height()), ::android::C2Fence())));
-        work->worklets.clear();
-        work->worklets.emplace_back(new C2Worklet);
-        std::list<std::unique_ptr<C2Work>> items;
-        items.push_back(std::move(work));
-
-        err = channel->mComponent->queue_nb(&items);
-        if (err != OK) {
-            native_handle_delete(handle);
-            return UNKNOWN_ERROR;
-        }
-
-        mLastTimestamp = timestamp;
-        if (mHandles.count(bufferId) > 0) {
-            native_handle_delete(mHandles[bufferId]);
-        }
-        mHandles[bufferId] = handle;
-
-        Mutexed<std::unique_ptr<InputBuffers>>::Locked buffers(channel->mInputBuffers);
-        ALOGV("releaseBufferIndex index = %d", bufferId);
-        (*buffers)->releaseBufferIndex(bufferId);
-
-        return OK;
-    }
-
-    status_t submitEos(int32_t bufferId) override {
-        std::shared_ptr<CCodecBufferChannel> channel = mChannel.lock();
-        if (!channel) {
-            return NO_INIT;
-        }
-
-        std::unique_ptr<C2Work> work(new C2Work);
-        work->input.flags = C2FrameData::FLAG_END_OF_STREAM;
-        work->input.ordinal.timestamp = mLastTimestamp;
-        work->input.ordinal.frameIndex = channel->mFrameIndex++;
-        work->input.buffers.clear();
-        work->input.buffers.push_back(nullptr);
-        work->worklets.clear();
-        work->worklets.emplace_back(new C2Worklet);
-        std::list<std::unique_ptr<C2Work>> items;
-        items.push_back(std::move(work));
-
-        c2_status_t err = channel->mComponent->queue_nb(&items);
-
-        Mutexed<std::unique_ptr<InputBuffers>>::Locked buffers(channel->mInputBuffers);
-        (*buffers)->releaseBufferIndex(bufferId);
-
-        return (err == C2_OK) ? OK : UNKNOWN_ERROR;
-    }
-
-    void dispatchDataSpaceChanged(
-            int32_t dataSpace, int32_t aspects, int32_t pixelFormat) override {
-        // TODO
-        (void)dataSpace;
-        (void)aspects;
-        (void)pixelFormat;
-    }
-
-private:
-    std::weak_ptr<CCodecBufferChannel> mChannel;
-    std::map<int32_t, C2Handle *> mHandles;
-    int64_t mLastTimestamp;
-    std::weak_ptr<C2Allocator> mAllocator;
-};
-
 CCodecBufferChannel::QueueGuard::QueueGuard(
         CCodecBufferChannel::QueueSync &sync) : mSync(sync) {
     std::unique_lock<std::mutex> l(mSync.mMutex);
@@ -967,13 +747,10 @@
     if (mCrypto != nullptr && mDealer != nullptr && mHeapSeqNum >= 0) {
         mCrypto->unsetHeap(mHeapSeqNum);
     }
-    // TODO: is this the right place?
-    mInputClient->onRelease();
 }
 
 void CCodecBufferChannel::setComponent(const std::shared_ptr<C2Component> &component) {
     mComponent = component;
-    mInputClient.reset(new InputBufferClient(shared_from_this()));
 
     C2StreamFormatConfig::input inputFormat(0u);
     C2StreamFormatConfig::output outputFormat(0u);
@@ -1026,16 +803,11 @@
     }
 }
 
-status_t CCodecBufferChannel::setGraphicBufferSource(
-        const sp<GraphicBufferSource> &source) {
-    ALOGV("setGraphicBufferSource");
-    mInputClient.reset(new BufferQueueClient(source));
-
-    // TODO: proper color aspect & dataspace
-    android_dataspace dataSpace = HAL_DATASPACE_BT709;
-    // TODO: read settings properly from the interface
-    return source->configure(new C2ComponentWrapper(
-            shared_from_this()), dataSpace, 16, 1080, 1920, GRALLOC_USAGE_SW_READ_OFTEN);
+status_t CCodecBufferChannel::setInputSurface(
+        const std::shared_ptr<InputSurfaceWrapper> &surface) {
+    ALOGV("setInputSurface");
+    mInputSurface = surface;
+    return OK;
 }
 
 status_t CCodecBufferChannel::queueInputBuffer(const sp<MediaCodecBuffer> &buffer) {
@@ -1100,12 +872,13 @@
     {
         Mutexed<std::unique_ptr<InputBuffers>>::Locked buffers(mInputBuffers);
         if (!(*buffers)->requestNewBuffer(&index, &inBuffer)) {
-            ALOGW("no new buffer available");
+            ALOGV("no new buffer available");
             inBuffer = nullptr;
+            return;
         }
     }
     ALOGV("new input index = %zu", index);
-    mInputClient->onInputBufferAvailable(index, inBuffer);
+    mCallback->onInputBufferAvailable(index, inBuffer);
 }
 
 status_t CCodecBufferChannel::renderOutputBuffer(
@@ -1182,7 +955,9 @@
     {
         Mutexed<std::unique_ptr<OutputBuffers>>::Locked buffers(mOutputBuffers);
         if((*buffers)->releaseBuffer(buffer)) {
+            buffers.unlock();
             feedInputBufferIfAvailable();
+            buffers.lock();
         }
     }
     return OK;
@@ -1221,29 +996,38 @@
     }
 
     mSync.start();
-    // TODO: use proper buffer depth instead of this random value
-    for (size_t i = 0; i < kMinBufferArraySize; ++i) {
-        size_t index;
-        sp<MediaCodecBuffer> buffer;
-        {
-            Mutexed<std::unique_ptr<InputBuffers>>::Locked buffers(mInputBuffers);
-            if (!(*buffers)->requestNewBuffer(&index, &buffer)) {
-                buffers.unlock();
-                ALOGE("start: cannot allocate memory");
-                mOnError(NO_MEMORY, ACTION_CODE_FATAL);
-                buffers.lock();
-                return;
+    if (mInputSurface == nullptr) {
+        // TODO: use proper buffer depth instead of this random value
+        for (size_t i = 0; i < kMinBufferArraySize; ++i) {
+            size_t index;
+            sp<MediaCodecBuffer> buffer;
+            {
+                Mutexed<std::unique_ptr<InputBuffers>>::Locked buffers(mInputBuffers);
+                if (!(*buffers)->requestNewBuffer(&index, &buffer)) {
+                    if (i == 0) {
+                        ALOGE("start: cannot allocate memory at all");
+                        buffers.unlock();
+                        mOnError(NO_MEMORY, ACTION_CODE_FATAL);
+                        buffers.lock();
+                    } else {
+                        ALOGV("start: cannot allocate memory, only %zu buffers allocated", i);
+                    }
+                    break;
+                }
             }
+            mCallback->onInputBufferAvailable(index, buffer);
         }
-        mInputClient->onInputBufferAdded(index, buffer);
+    } else {
+        (void)mInputSurface->connect(mComponent);
     }
-    mInputClient->onStart();
 }
 
 void CCodecBufferChannel::stop() {
     mSync.stop();
     mFirstValidFrameIndex = mFrameIndex.load();
-    mInputClient->onStop();
+    if (mInputSurface != nullptr) {
+        mInputSurface->disconnect();
+    }
 }
 
 void CCodecBufferChannel::flush(const std::list<std::unique_ptr<C2Work>> &flushedWork) {
diff --git a/media/libstagefright/MediaExtractorFactory.cpp b/media/libstagefright/MediaExtractorFactory.cpp
index bb72167..543c274 100644
--- a/media/libstagefright/MediaExtractorFactory.cpp
+++ b/media/libstagefright/MediaExtractorFactory.cpp
@@ -112,23 +112,25 @@
     // initialize source decryption if needed
     source->DrmInitialization(nullptr /* mime */);
 
-    sp<AMessage> meta;
-
+    void *meta = nullptr;
     MediaExtractor::CreatorFunc creator = NULL;
-    String8 tmp;
+    MediaExtractor::FreeMetaFunc freeMeta = nullptr;
     float confidence;
     sp<ExtractorPlugin> plugin;
-    creator = sniff(source.get(), &tmp, &confidence, &meta, plugin);
+    creator = sniff(source.get(), &confidence, &meta, &freeMeta, plugin);
     if (!creator) {
         ALOGV("FAILED to autodetect media content.");
         return NULL;
     }
 
-    mime = tmp.string();
-    ALOGV("Autodetected media content as '%s' with confidence %.2f",
-         mime, confidence);
-
     MediaExtractor *ret = creator(source.get(), meta);
+    if (meta != nullptr && freeMeta != nullptr) {
+        freeMeta(meta);
+    }
+
+    ALOGV("Created an extractor '%s' with confidence %.2f",
+         ret != nullptr ? ret->name() : "<null>", confidence);
+
     return CreateIMediaExtractorFromMediaExtractor(ret, source, plugin);
 }
 
@@ -165,11 +167,10 @@
 
 // static
 MediaExtractor::CreatorFunc MediaExtractorFactory::sniff(
-        DataSourceBase *source, String8 *mimeType, float *confidence, sp<AMessage> *meta,
-        sp<ExtractorPlugin> &plugin) {
-    *mimeType = "";
+        DataSourceBase *source, float *confidence, void **meta,
+        MediaExtractor::FreeMetaFunc *freeMeta, sp<ExtractorPlugin> &plugin) {
     *confidence = 0.0f;
-    meta->clear();
+    *meta = nullptr;
 
     std::shared_ptr<List<sp<ExtractorPlugin>>> plugins;
     {
@@ -183,16 +184,23 @@
     MediaExtractor::CreatorFunc curCreator = NULL;
     MediaExtractor::CreatorFunc bestCreator = NULL;
     for (auto it = plugins->begin(); it != plugins->end(); ++it) {
-        String8 newMimeType;
         float newConfidence;
-        sp<AMessage> newMeta;
-        if ((curCreator = (*it)->def.sniff(source, &newMimeType, &newConfidence, &newMeta))) {
+        void *newMeta = nullptr;
+        MediaExtractor::FreeMetaFunc newFreeMeta = nullptr;
+        if ((curCreator = (*it)->def.sniff(source, &newConfidence, &newMeta, &newFreeMeta))) {
             if (newConfidence > *confidence) {
-                *mimeType = newMimeType;
                 *confidence = newConfidence;
+                if (*meta != nullptr && *freeMeta != nullptr) {
+                    (*freeMeta)(*meta);
+                }
                 *meta = newMeta;
+                *freeMeta = newFreeMeta;
                 plugin = *it;
                 bestCreator = curCreator;
+            } else {
+                if (newMeta != nullptr && newFreeMeta != nullptr) {
+                    newFreeMeta(newMeta);
+                }
             }
         }
     }
diff --git a/media/libstagefright/NdkUtils.cpp b/media/libstagefright/NdkUtils.cpp
new file mode 100644
index 0000000..904fe72
--- /dev/null
+++ b/media/libstagefright/NdkUtils.cpp
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2018 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
+
+#include <media/stagefright/NdkUtils.h>
+#include <media/stagefright/Utils.h>
+#include <media/stagefright/foundation/AMessage.h>
+
+namespace android {
+
+sp<MetaData> convertMediaFormatWrapperToMetaData(const sp<AMediaFormatWrapper> &fmt) {
+    sp<AMessage> msg = fmt->toAMessage();
+    sp<MetaData> meta = new MetaData;
+    convertMessageToMetaData(msg, meta);
+    return meta;
+}
+
+}  // namespace android
+
diff --git a/media/libstagefright/bqhelper/Android.bp b/media/libstagefright/bqhelper/Android.bp
index b5b4a2a..388ed6b 100644
--- a/media/libstagefright/bqhelper/Android.bp
+++ b/media/libstagefright/bqhelper/Android.bp
@@ -43,6 +43,7 @@
 
     export_shared_lib_headers: [
         "libstagefright_foundation",
+        "libhidlmemory",
     ],
 
     cflags: [
diff --git a/media/libstagefright/bqhelper/include/media/stagefright/bqhelper/WGraphicBufferProducer.h b/media/libstagefright/bqhelper/include/media/stagefright/bqhelper/WGraphicBufferProducer.h
index 6f594fd..8ddf20f 100644
--- a/media/libstagefright/bqhelper/include/media/stagefright/bqhelper/WGraphicBufferProducer.h
+++ b/media/libstagefright/bqhelper/include/media/stagefright/bqhelper/WGraphicBufferProducer.h
@@ -20,7 +20,6 @@
 #include <hidl/MQDescriptor.h>
 #include <hidl/Status.h>
 
-#include <android-base/logging.h>
 #include <binder/Binder.h>
 #include <gui/IGraphicBufferProducer.h>
 #include <gui/IProducerListener.h>
@@ -52,6 +51,15 @@
 typedef ::android::IProducerListener BProducerListener;
 using ::android::BnGraphicBufferProducer;
 
+#ifndef LOG
+struct LOG_dummy {
+    template <typename T>
+    LOG_dummy& operator<< (const T&) { return *this; }
+};
+
+#define LOG(x)  LOG_dummy()
+#endif
+
 // Instantiate only if HGraphicBufferProducer is base of BASE.
 template <typename BASE,
           typename = typename std::enable_if<std::is_base_of<HGraphicBufferProducer, BASE>::value>::type>
diff --git a/media/libstagefright/codec2/1.0/Android.bp b/media/libstagefright/codec2/1.0/Android.bp
new file mode 100644
index 0000000..84d301a
--- /dev/null
+++ b/media/libstagefright/codec2/1.0/Android.bp
@@ -0,0 +1,43 @@
+cc_library_shared {
+    name: "android.hardware.media.c2@1.0-service-impl",
+    // relative_install_path: "hw",
+    // TODO: vendor: true,
+    vendor_available: true,
+    vndk: {
+        enabled: true,
+    },
+
+    srcs: [
+        //"ComponentAuth.cpp",
+        //"Component.cpp",
+        //"ComponentListener.cpp",
+        //"ComponentStore.cpp",
+        //"Configurable.cpp",
+        "InputSurface.cpp",
+        "InputSurfaceConnection.cpp",
+        //"types.cpp",
+    ],
+
+    include_dirs: [
+        "frameworks/av/media/libstagefright/codec2/include",
+        "frameworks/av/media/libstagefright/codec2/vndk/internal",
+    ],
+
+    shared_libs: [
+        "libcutils",
+	"libgui",
+        "libhidlbase",
+        "libhidltransport",
+        "liblog",
+        "libnativewindow",
+        "libstagefright_bufferqueue_helper",
+        "libstagefright_codec2_vndk",
+        "libui",
+        "libutils",
+
+        //"android.hardware.media.c2@1.0",
+        "android.hardware.graphics.bufferqueue@1.0",
+        "android.hidl.token@1.0-utils",
+    ],
+}
+
diff --git a/media/libstagefright/codec2/1.0/InputSurface.cpp b/media/libstagefright/codec2/1.0/InputSurface.cpp
new file mode 100644
index 0000000..977d410
--- /dev/null
+++ b/media/libstagefright/codec2/1.0/InputSurface.cpp
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2018, 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 "InputSurface"
+#include <utils/Log.h>
+
+#include <C2AllocatorGralloc.h>
+#include <C2PlatformSupport.h>
+
+#include <media/stagefright/bqhelper/GraphicBufferSource.h>
+#include <media/stagefright/codec2/1.0/InputSurface.h>
+
+namespace android {
+namespace hardware {
+namespace media {
+namespace c2 {
+namespace V1_0 {
+namespace implementation {
+
+using ::android::GraphicBufferSource;
+
+sp<InputSurface> InputSurface::Create() {
+    sp<GraphicBufferSource> source = new GraphicBufferSource;
+    if (source->initCheck() != OK) {
+        return nullptr;
+    }
+    return new InputSurface(source->getIGraphicBufferProducer(), source);
+}
+
+InputSurface::InputSurface(
+        const sp<BGraphicBufferProducer> &base, const sp<GraphicBufferSource> &source)
+    : InputSurfaceBase(base),
+      mSource(source) {
+}
+
+sp<InputSurfaceConnection> InputSurface::connectToComponent(
+        const std::shared_ptr<C2Component> &comp) {
+    sp<InputSurfaceConnection> conn = new InputSurfaceConnection(mSource, comp);
+    if (!conn->init()) {
+        return nullptr;
+    }
+    return conn;
+}
+
+}  // namespace implementation
+}  // namespace V1_0
+}  // namespace c2
+}  // namespace media
+}  // namespace hardware
+}  // namespace android
diff --git a/media/libstagefright/codec2/1.0/InputSurfaceConnection.cpp b/media/libstagefright/codec2/1.0/InputSurfaceConnection.cpp
new file mode 100644
index 0000000..32d6404
--- /dev/null
+++ b/media/libstagefright/codec2/1.0/InputSurfaceConnection.cpp
@@ -0,0 +1,225 @@
+/*
+ * Copyright 2018, 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 "InputSurfaceConnection"
+#include <utils/Log.h>
+
+#include <C2AllocatorGralloc.h>
+#include <C2BlockInternal.h>
+#include <C2PlatformSupport.h>
+
+#include <gui/Surface.h>
+#include <media/stagefright/codec2/1.0/InputSurfaceConnection.h>
+#include <system/window.h>
+
+namespace android {
+namespace hardware {
+namespace media {
+namespace c2 {
+namespace V1_0 {
+namespace implementation {
+
+using ::android::status_t;
+
+namespace {
+
+class Buffer2D : public C2Buffer {
+public:
+    explicit Buffer2D(C2ConstGraphicBlock block) : C2Buffer({ block }) {}
+};
+
+}  // namespace
+
+constexpr int32_t kBufferCount = 16;
+
+class InputSurfaceConnection::Impl : public ComponentWrapper {
+public:
+    Impl(const sp<GraphicBufferSource> &source, const std::shared_ptr<C2Component> &comp)
+        : mSource(source), mComp(comp) {
+    }
+
+    virtual ~Impl() = default;
+
+    bool init() {
+        sp<GraphicBufferSource> source = mSource.promote();
+        if (source == nullptr) {
+            return false;
+        }
+        status_t err = source->initCheck();
+        if (err != OK) {
+            ALOGE("Impl::init: GBS init failed: %d", err);
+            return false;
+        }
+        // TODO: proper color aspect & dataspace
+        android_dataspace dataSpace = HAL_DATASPACE_BT709;
+        // TODO: read settings properly from the interface
+        err = source->configure(
+                this, dataSpace, kBufferCount, 1080, 1920, GRALLOC_USAGE_SW_READ_OFTEN);
+        if (err != OK) {
+            ALOGE("Impl::init: GBS configure failed: %d", err);
+            return false;
+        }
+        for (int32_t i = 0; i < kBufferCount; ++i) {
+            if (!source->onInputBufferAdded(i).isOk()) {
+                ALOGE("Impl::init: population GBS slots failed");
+                return false;
+            }
+        }
+        if (!source->start().isOk()) {
+            ALOGE("Impl::init: GBS start failed");
+            return false;
+        }
+        c2_status_t c2err = GetCodec2PlatformAllocatorStore()->fetchAllocator(
+                C2AllocatorStore::PLATFORM_START + 1,  // GRALLOC
+                &mAllocator);
+        if (c2err != OK) {
+            ALOGE("Impl::init: failed to fetch gralloc allocator: %d", c2err);
+            return false;
+        }
+        return true;
+    }
+
+    // From ComponentWrapper
+    status_t submitBuffer(
+            int32_t bufferId, const sp<GraphicBuffer> &buffer,
+            int64_t timestamp, int fenceFd) override {
+        ALOGV("Impl::submitBuffer bufferId = %d", bufferId);
+        // TODO: Use fd to construct fence
+        (void)fenceFd;
+
+        std::shared_ptr<C2Component> comp = mComp.lock();
+        if (!comp) {
+            return NO_INIT;
+        }
+
+        std::shared_ptr<C2GraphicAllocation> alloc;
+        C2Handle *handle = WrapNativeCodec2GrallocHandle(
+                buffer->handle, buffer->width, buffer->height,
+                buffer->format, buffer->usage, buffer->stride);
+        c2_status_t err = mAllocator->priorGraphicAllocation(handle, &alloc);
+        if (err != OK) {
+            return UNKNOWN_ERROR;
+        }
+        std::shared_ptr<C2GraphicBlock> block = _C2BlockFactory::CreateGraphicBlock(alloc);
+
+        std::unique_ptr<C2Work> work(new C2Work);
+        work->input.flags = (C2FrameData::flags_t)0;
+        work->input.ordinal.timestamp = timestamp;
+        work->input.ordinal.frameIndex = mFrameIndex++;
+        work->input.buffers.clear();
+        std::shared_ptr<C2Buffer> c2Buffer(
+                // TODO: fence
+                new Buffer2D(block->share(
+                        C2Rect(block->width(), block->height()), ::android::C2Fence())),
+                [handle, bufferId, src = mSource](C2Buffer *ptr) {
+                    delete ptr;
+                    native_handle_delete(handle);
+                    sp<GraphicBufferSource> source = src.promote();
+                    if (source != nullptr) {
+                        // TODO: fence
+                        (void)source->onInputBufferEmptied(bufferId, -1);
+                    }
+                });
+        work->input.buffers.push_back(c2Buffer);
+        work->worklets.clear();
+        work->worklets.emplace_back(new C2Worklet);
+        std::list<std::unique_ptr<C2Work>> items;
+        items.push_back(std::move(work));
+
+        err = comp->queue_nb(&items);
+        if (err != C2_OK) {
+            return UNKNOWN_ERROR;
+        }
+
+        mLastTimestamp = timestamp;
+
+        return OK;
+    }
+
+    status_t submitEos(int32_t) override {
+        std::shared_ptr<C2Component> comp = mComp.lock();
+        if (!comp) {
+            return NO_INIT;
+        }
+
+        std::unique_ptr<C2Work> work(new C2Work);
+        work->input.flags = C2FrameData::FLAG_END_OF_STREAM;
+        work->input.ordinal.timestamp = mLastTimestamp;
+        work->input.ordinal.frameIndex = mFrameIndex++;
+        work->input.buffers.clear();
+        work->worklets.clear();
+        work->worklets.emplace_back(new C2Worklet);
+        std::list<std::unique_ptr<C2Work>> items;
+        items.push_back(std::move(work));
+
+        c2_status_t err = comp->queue_nb(&items);
+        return (err == C2_OK) ? OK : UNKNOWN_ERROR;
+    }
+
+    void dispatchDataSpaceChanged(
+            int32_t dataSpace, int32_t aspects, int32_t pixelFormat) override {
+        // TODO
+        (void)dataSpace;
+        (void)aspects;
+        (void)pixelFormat;
+    }
+
+private:
+    wp<GraphicBufferSource> mSource;
+    std::weak_ptr<C2Component> mComp;
+
+    // Needed for ComponentWrapper implementation
+    int64_t mLastTimestamp;
+    std::shared_ptr<C2Allocator> mAllocator;
+    std::atomic_uint64_t mFrameIndex;
+};
+
+InputSurfaceConnection::InputSurfaceConnection(
+        const sp<GraphicBufferSource> &source,
+        const std::shared_ptr<C2Component> &comp)
+    : mSource(source),
+      mImpl(new Impl(source, comp)) {
+}
+
+InputSurfaceConnection::~InputSurfaceConnection() {
+    disconnect();
+}
+
+bool InputSurfaceConnection::init() {
+    if (mImpl == nullptr) {
+        return false;
+    }
+    return mImpl->init();
+}
+
+void InputSurfaceConnection::disconnect() {
+    ALOGV("disconnect");
+    if (mSource != nullptr) {
+        (void)mSource->stop();
+        (void)mSource->release();
+    }
+    mImpl.clear();
+    mSource.clear();
+    ALOGV("disconnected");
+}
+
+}  // namespace implementation
+}  // namespace V1_0
+}  // namespace c2
+}  // namespace media
+}  // namespace hardware
+}  // namespace android
diff --git a/media/libstagefright/codec2/Android.bp b/media/libstagefright/codec2/Android.bp
index ee5c3eb..e1ac44e 100644
--- a/media/libstagefright/codec2/Android.bp
+++ b/media/libstagefright/codec2/Android.bp
@@ -1,5 +1,6 @@
 cc_library_shared {
     name: "libstagefright_codec2",
+    vendor_available: true,
 
     tags: [
         "optional",
@@ -21,6 +22,16 @@
         "include",
     ],
 
+    header_libs: [
+        "libhardware_headers",
+        "libutils_headers",
+    ],
+
+    export_header_lib_headers: [
+        "libhardware_headers",
+        "libutils_headers",
+    ],
+
     sanitize: {
         misc_undefined: [
             "unsigned-integer-overflow",
@@ -37,6 +48,7 @@
 
 cc_library_shared {
     name: "libstagefright_simple_c2component",
+    vendor_available: true,
 
     tags: [
         "optional",
diff --git a/media/libstagefright/codec2/include/media/stagefright/codec2/1.0/InputSurface.h b/media/libstagefright/codec2/include/media/stagefright/codec2/1.0/InputSurface.h
new file mode 100644
index 0000000..e46d03c
--- /dev/null
+++ b/media/libstagefright/codec2/include/media/stagefright/codec2/1.0/InputSurface.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2018, 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_HARDWARE_MEDIA_C2_V1_0_INPUT_SURFACE_H
+#define ANDROID_HARDWARE_MEDIA_C2_V1_0_INPUT_SURFACE_H
+
+#include <memory>
+
+#include <C2Component.h>
+#include <media/stagefright/bqhelper/WGraphicBufferProducer.h>
+#include <media/stagefright/codec2/1.0/InputSurfaceConnection.h>
+
+namespace android {
+
+class GraphicBufferSource;
+
+namespace hardware {
+namespace media {
+namespace c2 {
+namespace V1_0 {
+namespace implementation {
+
+using ::android::sp;
+
+typedef ::android::hardware::graphics::bufferqueue::V1_0::IGraphicBufferProducer
+        HGraphicBufferProducer;
+typedef ::android::IGraphicBufferProducer BGraphicBufferProducer;
+
+// TODO: ::android::TWGraphicBufferProducer<IInputSurface>
+typedef ::android::TWGraphicBufferProducer<HGraphicBufferProducer> InputSurfaceBase;
+
+class InputSurface : public InputSurfaceBase {
+public:
+    virtual ~InputSurface() = default;
+
+    // Methods from IInputSurface
+    sp<InputSurfaceConnection> connectToComponent(
+            const std::shared_ptr<::android::C2Component> &comp);
+    // TODO: intf()
+
+    static sp<InputSurface> Create();
+
+private:
+    InputSurface(
+            const sp<BGraphicBufferProducer> &base,
+            const sp<::android::GraphicBufferSource> &source);
+
+    sp<::android::GraphicBufferSource> mSource;
+};
+
+}  // namespace implementation
+}  // namespace V1_0
+}  // namespace c2
+}  // namespace media
+}  // namespace hardware
+}  // namespace android
+
+#endif  // ANDROID_HARDWARE_MEDIA_C2_V1_0_INPUT_SURFACE_H
diff --git a/media/libstagefright/codec2/include/media/stagefright/codec2/1.0/InputSurfaceConnection.h b/media/libstagefright/codec2/include/media/stagefright/codec2/1.0/InputSurfaceConnection.h
new file mode 100644
index 0000000..fc19acd
--- /dev/null
+++ b/media/libstagefright/codec2/include/media/stagefright/codec2/1.0/InputSurfaceConnection.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2018, 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_HARDWARE_MEDIA_C2_V1_0_INPUT_SURFACE_CONNECTION_H
+#define ANDROID_HARDWARE_MEDIA_C2_V1_0_INPUT_SURFACE_CONNECTION_H
+
+#include <memory>
+
+#include <C2Component.h>
+#include <media/stagefright/bqhelper/GraphicBufferSource.h>
+#include <media/stagefright/bqhelper/WGraphicBufferProducer.h>
+#include <media/stagefright/codec2/1.0/InputSurfaceConnection.h>
+
+namespace android {
+
+class C2Allocator;
+
+namespace hardware {
+namespace media {
+namespace c2 {
+namespace V1_0 {
+namespace implementation {
+
+// TODO: inherit from IInputSurfaceConnection
+class InputSurfaceConnection : public RefBase {
+public:
+    virtual ~InputSurfaceConnection();
+
+    // From IInputSurfaceConnection
+    void disconnect();
+
+private:
+    friend class InputSurface;
+
+    // For InputSurface
+    InputSurfaceConnection(
+            const sp<GraphicBufferSource> &source, const std::shared_ptr<C2Component> &comp);
+    bool init();
+
+    InputSurfaceConnection() = delete;
+
+    class Impl;
+
+    sp<GraphicBufferSource> mSource;
+    sp<Impl> mImpl;
+
+    DISALLOW_EVIL_CONSTRUCTORS(InputSurfaceConnection);
+};
+
+}  // namespace implementation
+}  // namespace V1_0
+}  // namespace c2
+}  // namespace media
+}  // namespace hardware
+}  // namespace android
+
+#endif  // ANDROID_HARDWARE_MEDIA_C2_V1_0_INPUT_SURFACE_CONNECTION_H
diff --git a/media/libstagefright/codec2/vndk/Android.bp b/media/libstagefright/codec2/vndk/Android.bp
index cdd0488..d6cbe96 100644
--- a/media/libstagefright/codec2/vndk/Android.bp
+++ b/media/libstagefright/codec2/vndk/Android.bp
@@ -10,6 +10,10 @@
 
 cc_library_shared {
     name: "libstagefright_codec2_vndk",
+    vendor_available: true,
+    vndk: {
+        enabled: true,
+    },
 
     srcs: [
         "C2AllocatorIon.cpp",
@@ -23,12 +27,9 @@
         "include",
     ],
 
-    header_libs:[
-        "libstagefright_codec2_internal",
-    ],
-
     include_dirs: [
         "frameworks/av/media/libstagefright/codec2/include",
+        "frameworks/av/media/libstagefright/codec2/vndk/internal",
         "frameworks/native/include/media/hardware",
     ],
 
@@ -42,7 +43,6 @@
         "libhidlbase",
         "libion",
         "liblog",
-        "libmedia",
         "libstagefright_foundation",
         "libui",
         "libutils",
diff --git a/media/libstagefright/codecs/avcenc/C2SoftAvcEnc.cpp b/media/libstagefright/codecs/avcenc/C2SoftAvcEnc.cpp
index 911f0f8..9ea3589 100644
--- a/media/libstagefright/codecs/avcenc/C2SoftAvcEnc.cpp
+++ b/media/libstagefright/codecs/avcenc/C2SoftAvcEnc.cpp
@@ -15,7 +15,7 @@
  */
 
 #define LOG_NDEBUG 0
-#define LOG_TAG "C2SoftAvcEncEnc"
+#define LOG_TAG "C2SoftAvcEnc"
 #include <utils/Log.h>
 #include <utils/misc.h>
 
diff --git a/media/libstagefright/include/C2OMXNode.h b/media/libstagefright/include/C2OMXNode.h
new file mode 100644
index 0000000..3c007c4
--- /dev/null
+++ b/media/libstagefright/include/C2OMXNode.h
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2018, 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 C2_OMX_NODE_H_
+#define C2_OMX_NODE_H_
+
+#include <atomic>
+
+#include <android/IOMXBufferSource.h>
+#include <media/IOMX.h>
+#include <media/OMXBuffer.h>
+
+namespace android {
+
+/**
+ * IOmxNode implementation around codec 2.0 component, only to be used in
+ * IGraphicBufferSource::configure. Only subset of IOmxNode API is implemented
+ * and others are left as stub. As a result, one cannot expect this IOmxNode
+ * to work in any other usage than IGraphicBufferSource.
+ */
+struct C2OMXNode : public BnOMXNode {
+    // TODO: this should take android::hardware::media::c2::V1_0::IComponent
+    explicit C2OMXNode(const std::shared_ptr<C2Component> &comp);
+    ~C2OMXNode() override = default;
+
+    // IOMXNode
+    status_t freeNode() override;
+    status_t sendCommand(OMX_COMMANDTYPE cmd, OMX_S32 param) override;
+    status_t getParameter(
+            OMX_INDEXTYPE index, void *params, size_t size) override;
+    status_t setParameter(
+            OMX_INDEXTYPE index, const void *params, size_t size) override;
+    status_t getConfig(
+            OMX_INDEXTYPE index, void *params, size_t size) override;
+    status_t setConfig(
+            OMX_INDEXTYPE index, const void *params, size_t size) override;
+    status_t setPortMode(OMX_U32 port_index, IOMX::PortMode mode) override;
+    status_t prepareForAdaptivePlayback(
+            OMX_U32 portIndex, OMX_BOOL enable,
+            OMX_U32 maxFrameWidth, OMX_U32 maxFrameHeight) override;
+    status_t configureVideoTunnelMode(
+            OMX_U32 portIndex, OMX_BOOL tunneled,
+            OMX_U32 audioHwSync, native_handle_t **sidebandHandle) override;
+    status_t getGraphicBufferUsage(
+            OMX_U32 port_index, OMX_U32* usage) override;
+    status_t setInputSurface(
+            const sp<IOMXBufferSource> &bufferSource) override;
+    status_t allocateSecureBuffer(
+            OMX_U32 port_index, size_t size, buffer_id *buffer,
+            void **buffer_data, sp<NativeHandle> *native_handle) override;
+    status_t useBuffer(
+            OMX_U32 port_index, const OMXBuffer &omxBuf, buffer_id *buffer) override;
+    status_t freeBuffer(
+            OMX_U32 port_index, buffer_id buffer) override;
+    status_t fillBuffer(
+            buffer_id buffer, const OMXBuffer &omxBuf, int fenceFd) override;
+    status_t emptyBuffer(
+            buffer_id buffer, const OMXBuffer &omxBuf,
+            OMX_U32 flags, OMX_TICKS timestamp, int fenceFd) override;
+    status_t getExtensionIndex(
+            const char *parameter_name,
+            OMX_INDEXTYPE *index) override;
+    status_t dispatchMessage(const omx_message &msg) override;
+
+    sp<IOMXBufferSource> getSource();
+
+private:
+    std::weak_ptr<C2Component> mComp;
+    sp<IOMXBufferSource> mBufferSource;
+    std::shared_ptr<C2Allocator> mAllocator;
+    std::atomic_uint64_t mFrameIndex;
+};
+
+}  // namespace android
+
+#endif  // C2_OMX_NODE_H_
diff --git a/media/libstagefright/include/CCodecBufferChannel.h b/media/libstagefright/include/CCodecBufferChannel.h
index e64f984..eb3255f 100644
--- a/media/libstagefright/include/CCodecBufferChannel.h
+++ b/media/libstagefright/include/CCodecBufferChannel.h
@@ -14,9 +14,9 @@
  * limitations under the License.
  */
 
-#ifndef A_BUFFER_CHANNEL_H_
+#ifndef CCODEC_BUFFER_CHANNEL_H_
 
-#define A_BUFFER_CHANNEL_H_
+#define CCODEC_BUFFER_CHANNEL_H_
 
 #include <map>
 #include <memory>
@@ -26,13 +26,19 @@
 #include <C2Buffer.h>
 #include <C2Component.h>
 
-#include <media/stagefright/foundation/Mutexed.h>
 #include <media/stagefright/bqhelper/GraphicBufferSource.h>
+#include <media/stagefright/codec2/1.0/InputSurface.h>
+#include <media/stagefright/foundation/Mutexed.h>
 #include <media/stagefright/CodecBase.h>
 #include <media/ICrypto.h>
 
+#include "InputSurfaceWrapper.h"
+
 namespace android {
 
+using ::android::hardware::media::c2::V1_0::implementation::InputSurface;
+using ::android::hardware::media::c2::V1_0::implementation::InputSurfaceConnection;
+
 /**
  * BufferChannelBase implementation for CCodec.
  */
@@ -76,7 +82,7 @@
      * Set GraphicBufferSource object from which the component extracts input
      * buffers.
      */
-    status_t setGraphicBufferSource(const sp<GraphicBufferSource> &source);
+    status_t setInputSurface(const std::shared_ptr<InputSurfaceWrapper> &surface);
 
     /**
      * Start queueing buffers to the component. This object should never queue
@@ -103,7 +109,6 @@
     class Buffers;
     class InputBuffers;
     class OutputBuffers;
-    class InputBufferClient;
 
 private:
     class QueueGuard;
@@ -156,8 +161,6 @@
         bool mRunning;
     };
 
-    class C2ComponentWrapper;
-
     void feedInputBufferIfAvailable();
 
     QueueSync mSync;
@@ -166,7 +169,6 @@
     int32_t mHeapSeqNum;
 
     std::shared_ptr<C2Component> mComponent;
-    std::shared_ptr<InputBufferClient> mInputClient;
     std::function<void(status_t, enum ActionCode)> mOnError;
     std::shared_ptr<C2BlockPool> mInputAllocator;
     QueueSync mQueueSync;
@@ -180,6 +182,8 @@
     sp<MemoryDealer> makeMemoryDealer(size_t heapSize);
     Mutexed<sp<Surface>> mSurface;
 
+    std::shared_ptr<InputSurfaceWrapper> mInputSurface;
+
     inline bool hasCryptoOrDescrambler() {
         return mCrypto != NULL || mDescrambler != NULL;
     }
@@ -187,4 +191,4 @@
 
 }  // namespace android
 
-#endif  // A_BUFFER_CHANNEL_H_
+#endif  // CCODEC_BUFFER_CHANNEL_H_
diff --git a/media/libstagefright/include/InputSurfaceWrapper.h b/media/libstagefright/include/InputSurfaceWrapper.h
new file mode 100644
index 0000000..a4d8f29
--- /dev/null
+++ b/media/libstagefright/include/InputSurfaceWrapper.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2018, 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 INPUT_SURFACE_WRAPPER_H_
+
+#define INPUT_SURFACE_WRAPPER_H_
+
+namespace android {
+
+/**
+ * Wrapper interface around InputSurface.
+ */
+class InputSurfaceWrapper {
+public:
+    virtual ~InputSurfaceWrapper() = default;
+
+    /**
+     * Connect the surface with |comp| and start pushing buffers. A surface can
+     * connect to at most one component at a time.
+     *
+     * \return OK               successfully connected to |comp|
+     * \return ALREADY_EXISTS   already connected to another component.
+     */
+    virtual status_t connect(const std::shared_ptr<C2Component> &comp) = 0;
+
+    /**
+     * Disconnect the surface from the component if any.
+     */
+    virtual void disconnect() = 0;
+
+    // TODO: intf()
+};
+
+}  // namespace android
+
+#endif  // INPUT_SURFACE_WRAPPER_H_
diff --git a/media/libstagefright/include/media/stagefright/CCodec.h b/media/libstagefright/include/media/stagefright/CCodec.h
index 9307f3f..24ee0a3 100644
--- a/media/libstagefright/include/media/stagefright/CCodec.h
+++ b/media/libstagefright/include/media/stagefright/CCodec.h
@@ -24,7 +24,6 @@
 #include <android/native_window.h>
 #include <media/hardware/MetadataBufferType.h>
 #include <media/stagefright/foundation/Mutexed.h>
-#include <media/stagefright/bqhelper/GraphicBufferSource.h>
 #include <media/stagefright/CodecBase.h>
 #include <media/stagefright/FrameRenderTracker.h>
 #include <media/stagefright/MediaDefs.h>
@@ -36,6 +35,7 @@
 namespace android {
 
 class CCodecBufferChannel;
+class InputSurfaceWrapper;
 
 class CCodec : public CodecBase {
 public:
@@ -81,7 +81,7 @@
 
     void createInputSurface();
     void setInputSurface(const sp<PersistentSurface> &surface);
-    status_t setupInputSurface(const sp<GraphicBufferSource> &source);
+    status_t setupInputSurface(const std::shared_ptr<InputSurfaceWrapper> &surface);
 
     void setDeadline(const TimePoint &deadline);
 
diff --git a/media/libstagefright/include/media/stagefright/MediaExtractorFactory.h b/media/libstagefright/include/media/stagefright/MediaExtractorFactory.h
index 4d2f4f0..90c66eb 100644
--- a/media/libstagefright/include/media/stagefright/MediaExtractorFactory.h
+++ b/media/libstagefright/include/media/stagefright/MediaExtractorFactory.h
@@ -57,7 +57,7 @@
             const sp<ExtractorPlugin> &plugin, List<sp<ExtractorPlugin>> &pluginList);
 
     static MediaExtractor::CreatorFunc sniff(DataSourceBase *source,
-            String8 *mimeType, float *confidence, sp<AMessage> *meta,
+            float *confidence, void **meta, MediaExtractor::FreeMetaFunc *freeMeta,
             sp<ExtractorPlugin> &plugin);
 
     static void UpdateExtractors(const char *newUpdateApkPath);
diff --git a/media/libstagefright/include/media/stagefright/NdkUtils.h b/media/libstagefright/include/media/stagefright/NdkUtils.h
new file mode 100644
index 0000000..a68884a
--- /dev/null
+++ b/media/libstagefright/include/media/stagefright/NdkUtils.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2018 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 NDK_UTILS_H_
+
+#define NDK_UTILS_H_
+
+#include <media/stagefright/MetaData.h>
+#include <media/NdkWrapper.h>
+
+namespace android {
+
+sp<MetaData> convertMediaFormatWrapperToMetaData(
+        const sp<AMediaFormatWrapper> &fmt);
+
+}  // namespace android
+
+#endif  // NDK_UTILS_H_
diff --git a/media/ndk/NdkMediaDataSource.cpp b/media/ndk/NdkMediaDataSource.cpp
index f190f80..9d00e5e 100644
--- a/media/ndk/NdkMediaDataSource.cpp
+++ b/media/ndk/NdkMediaDataSource.cpp
@@ -44,7 +44,15 @@
 };
 
 NdkDataSource::NdkDataSource(AMediaDataSource *dataSource)
-    : mDataSource(dataSource) {
+    : mDataSource(AMediaDataSource_new()) {
+      AMediaDataSource_setReadAt(mDataSource, dataSource->readAt);
+      AMediaDataSource_setGetSize(mDataSource, dataSource->getSize);
+      AMediaDataSource_setClose(mDataSource, dataSource->close);
+      AMediaDataSource_setUserdata(mDataSource, dataSource->userdata);
+}
+
+NdkDataSource::~NdkDataSource() {
+    AMediaDataSource_delete(mDataSource);
 }
 
 status_t NdkDataSource::initCheck() const {
diff --git a/media/ndk/NdkMediaDataSourcePriv.h b/media/ndk/NdkMediaDataSourcePriv.h
index 65ddd2a..ea9c865 100644
--- a/media/ndk/NdkMediaDataSourcePriv.h
+++ b/media/ndk/NdkMediaDataSourcePriv.h
@@ -49,6 +49,9 @@
     virtual String8 getMIMEType() const;
     virtual void close();
 
+protected:
+    virtual ~NdkDataSource();
+
 private:
 
     Mutex mLock;
diff --git a/packages/MediaComponents/src/com/android/media/IMediaSession2.aidl b/packages/MediaComponents/src/com/android/media/IMediaSession2.aidl
index 7702bda..81e5547 100644
--- a/packages/MediaComponents/src/com/android/media/IMediaSession2.aidl
+++ b/packages/MediaComponents/src/com/android/media/IMediaSession2.aidl
@@ -63,4 +63,7 @@
     // Get library service specific
     //////////////////////////////////////////////////////////////////////////////////////////////
     void getBrowserRoot(IMediaSession2Callback callback, in Bundle rootHints);
+    void getItem(IMediaSession2Callback callback, String mediaId);
+    void getChildren(IMediaSession2Callback callback, String parentId, int page, int pageSize,
+            in Bundle options);
 }
diff --git a/packages/MediaComponents/src/com/android/media/IMediaSession2Callback.aidl b/packages/MediaComponents/src/com/android/media/IMediaSession2Callback.aidl
index a443bf8..7e76d1d 100644
--- a/packages/MediaComponents/src/com/android/media/IMediaSession2Callback.aidl
+++ b/packages/MediaComponents/src/com/android/media/IMediaSession2Callback.aidl
@@ -48,4 +48,7 @@
     // Browser sepcific
     //////////////////////////////////////////////////////////////////////////////////////////////
     void onGetRootResult(in Bundle rootHints, String rootMediaId, in Bundle rootExtra);
+    void onItemLoaded(String mediaId, in Bundle result);
+    void onChildrenLoaded(String parentId, int page, int pageSize, in Bundle options,
+            in List<Bundle> result);
 }
diff --git a/packages/MediaComponents/src/com/android/media/MediaBrowser2Impl.java b/packages/MediaComponents/src/com/android/media/MediaBrowser2Impl.java
index 7e928d7..5e88262 100644
--- a/packages/MediaComponents/src/com/android/media/MediaBrowser2Impl.java
+++ b/packages/MediaComponents/src/com/android/media/MediaBrowser2Impl.java
@@ -19,11 +19,13 @@
 import android.content.Context;
 import android.media.MediaBrowser2;
 import android.media.MediaBrowser2.BrowserCallback;
+import android.media.MediaItem2;
 import android.media.MediaSession2.CommandButton;
 import android.media.SessionToken2;
 import android.media.update.MediaBrowser2Provider;
 import android.os.Bundle;
 import android.os.RemoteException;
+import android.text.TextUtils;
 import android.util.Log;
 
 import java.util.List;
@@ -72,12 +74,47 @@
 
     @Override
     public void getItem_impl(String mediaId) {
-        // TODO(jaewan): Implement
+        if (mediaId == null) {
+            throw new IllegalArgumentException("mediaId shouldn't be null");
+        }
+
+        final IMediaSession2 binder = getSessionBinder();
+        if (binder != null) {
+            try {
+                binder.getItem(getControllerStub(), mediaId);
+            } catch (RemoteException e) {
+                // TODO(jaewan): Handle disconnect.
+                if (DEBUG) {
+                    Log.w(TAG, "Cannot connect to the service or the session is gone", e);
+                }
+            }
+        } else {
+            Log.w(TAG, "Session isn't active", new IllegalStateException());
+        }
     }
 
     @Override
     public void getChildren_impl(String parentId, int page, int pageSize, Bundle options) {
-        // TODO(jaewan): Implement
+        if (parentId == null) {
+            throw new IllegalArgumentException("parentId shouldn't be null");
+        }
+        if (page < 1 || pageSize < 1) {
+            throw new IllegalArgumentException("Neither page nor pageSize should be less than 1");
+        }
+
+        final IMediaSession2 binder = getSessionBinder();
+        if (binder != null) {
+            try {
+                binder.getChildren(getControllerStub(), parentId, page, pageSize, options);
+            } catch (RemoteException e) {
+                // TODO(jaewan): Handle disconnect.
+                if (DEBUG) {
+                    Log.w(TAG, "Cannot connect to the service or the session is gone", e);
+                }
+            }
+        } else {
+            Log.w(TAG, "Session isn't active", new IllegalStateException());
+        }
     }
 
     @Override
@@ -91,4 +128,17 @@
             mCallback.onGetRootResult(rootHints, rootMediaId, rootExtra);
         });
     }
+
+    public void onItemLoaded(String mediaId, MediaItem2 item) {
+        getCallbackExecutor().execute(() -> {
+            mCallback.onItemLoaded(mediaId, item);
+        });
+    }
+
+    public void onChildrenLoaded(String parentId, int page, int pageSize, Bundle options,
+            List<MediaItem2> result) {
+        getCallbackExecutor().execute(() -> {
+            mCallback.onChildrenLoaded(parentId, page, pageSize, options, result);
+        });
+    }
 }
diff --git a/packages/MediaComponents/src/com/android/media/MediaController2Impl.java b/packages/MediaComponents/src/com/android/media/MediaController2Impl.java
index 5ae37ee..5af4240 100644
--- a/packages/MediaComponents/src/com/android/media/MediaController2Impl.java
+++ b/packages/MediaComponents/src/com/android/media/MediaController2Impl.java
@@ -22,14 +22,14 @@
 import android.content.Intent;
 import android.content.ServiceConnection;
 import android.media.AudioAttributes;
+import android.media.MediaController2;
+import android.media.MediaController2.ControllerCallback;
 import android.media.MediaController2.PlaybackInfo;
 import android.media.MediaItem2;
 import android.media.MediaSession2;
 import android.media.MediaSession2.Command;
 import android.media.MediaSession2.CommandButton;
 import android.media.MediaSession2.CommandGroup;
-import android.media.MediaController2;
-import android.media.MediaController2.ControllerCallback;
 import android.media.MediaSession2.PlaylistParams;
 import android.media.MediaSessionService2;
 import android.media.PlaybackState2;
diff --git a/packages/MediaComponents/src/com/android/media/MediaSession2CallbackStub.java b/packages/MediaComponents/src/com/android/media/MediaSession2CallbackStub.java
index 07edf7e..1f4d12b 100644
--- a/packages/MediaComponents/src/com/android/media/MediaSession2CallbackStub.java
+++ b/packages/MediaComponents/src/com/android/media/MediaSession2CallbackStub.java
@@ -224,4 +224,47 @@
         }
         browser.onGetRootResult(rootHints, rootMediaId, rootExtra);
     }
+
+
+    @Override
+    public void onItemLoaded(String mediaId, Bundle itemBundle) throws RuntimeException {
+        final MediaBrowser2Impl browser;
+        try {
+            browser = getBrowser();
+        } catch (IllegalStateException e) {
+            Log.w(TAG, "Don't fail silently here. Highly likely a bug");
+            return;
+        }
+        if (browser == null) {
+            // TODO(jaewan): Revisit here. Could be a bug
+            return;
+        }
+        browser.onItemLoaded(mediaId,
+                MediaItem2Impl.fromBundle(browser.getContext(), itemBundle));
+    }
+
+    @Override
+    public void onChildrenLoaded(String parentId, int page, int pageSize, Bundle options,
+            List<Bundle> itemBundleList) throws RuntimeException {
+        final MediaBrowser2Impl browser;
+        try {
+            browser = getBrowser();
+        } catch (IllegalStateException e) {
+            Log.w(TAG, "Don't fail silently here. Highly likely a bug");
+            return;
+        }
+        if (browser == null) {
+            // TODO(jaewan): Revisit here. Could be a bug
+            return;
+        }
+
+        List<MediaItem2> result = null;
+        if (itemBundleList != null) {
+            result = new ArrayList<>();
+            for (Bundle bundle : itemBundleList) {
+                result.add(MediaItem2.fromBundle(browser.getContext(), bundle));
+            }
+        }
+        browser.onChildrenLoaded(parentId, page, pageSize, options, result);
+    }
 }
diff --git a/packages/MediaComponents/src/com/android/media/MediaSession2Impl.java b/packages/MediaComponents/src/com/android/media/MediaSession2Impl.java
index 74ac5b3..4a9a729 100644
--- a/packages/MediaComponents/src/com/android/media/MediaSession2Impl.java
+++ b/packages/MediaComponents/src/com/android/media/MediaSession2Impl.java
@@ -88,6 +88,22 @@
     private final int mRatingType;
     private final PendingIntent mSessionActivity;
 
+    // mPlayer is set to null when the session is closed, and we shouldn't throw an exception
+    // nor leave log always for using mPlayer when it's null. Here's the reason.
+    // When a MediaSession2 is closed, there could be a pended operation in the session callback
+    // executor that may want to access the player. Here's the sample code snippet for that.
+    //
+    //   public void onFoo() {
+    //     if (mPlayer == null) return; // first check
+    //     mSessionCallbackExecutor.executor(() -> {
+    //       // Error. Session may be closed and mPlayer can be null here.
+    //       mPlayer.foo();
+    //     });
+    //   }
+    //
+    // By adding protective code, we can also protect APIs from being called after the close()
+    //
+    // TODO(jaewan): Should we put volatile here?
     @GuardedBy("mLock")
     private MediaPlayerInterface mPlayer;
     @GuardedBy("mLock")
@@ -96,10 +112,6 @@
     private PlaybackInfo mPlaybackInfo;
     @GuardedBy("mLock")
     private MyPlaybackListener mListener;
-    @GuardedBy("mLock")
-    private PlaylistParams mPlaylistParams;
-    @GuardedBy("mLock")
-    private List<MediaItem2> mPlaylist;
 
     /**
      * Can be only called by the {@link Builder#build()}.
@@ -146,9 +158,7 @@
                     mContext.getPackageName(), null, id, mSessionStub).getInstance();
         }
 
-        setPlayerLocked(player);
-        mVolumeProvider = volumeProvider;
-        mPlaybackInfo = createPlaybackInfo(volumeProvider, player.getAudioAttributes());
+        setPlayer(player, volumeProvider);
 
         // Ask server for the sanity check, and starts
         // Sanity check for making session ID unique 'per package' cannot be done in here.
@@ -198,14 +208,7 @@
         if (player == null) {
             throw new IllegalArgumentException("player shouldn't be null");
         }
-        PlaybackInfo info =
-                createPlaybackInfo(null /* VolumeProvider */, player.getAudioAttributes());
-        synchronized (mLock) {
-            setPlayerLocked(player);
-            mVolumeProvider = null;
-            mPlaybackInfo = info;
-        }
-        mSessionStub.notifyPlaybackInfoChanged(info);
+        setPlayer(player, null);
     }
 
     @Override
@@ -218,25 +221,25 @@
         if (volumeProvider == null) {
             throw new IllegalArgumentException("volumeProvider shouldn't be null");
         }
+        setPlayer(player, volumeProvider);
+    }
+
+    private void setPlayer(MediaPlayerInterface player, VolumeProvider2 volumeProvider) {
         PlaybackInfo info = createPlaybackInfo(volumeProvider, player.getAudioAttributes());
         synchronized (mLock) {
-            setPlayerLocked(player);
+            if (mPlayer != null && mListener != null) {
+                // This might not work for a poorly implemented player.
+                mPlayer.removePlaybackListener(mListener);
+            }
+            mPlayer = player;
+            mListener = new MyPlaybackListener(this, player);
+            player.addPlaybackListener(mCallbackExecutor, mListener);
             mVolumeProvider = volumeProvider;
             mPlaybackInfo = info;
         }
         mSessionStub.notifyPlaybackInfoChanged(info);
     }
 
-    private void setPlayerLocked(MediaPlayerInterface player) {
-        if (mPlayer != null && mListener != null) {
-            // This might not work for a poorly implemented player.
-            mPlayer.removePlaybackListener(mListener);
-        }
-        mPlayer = player;
-        mListener = new MyPlaybackListener(this, player);
-        player.addPlaybackListener(mCallbackExecutor, mListener);
-    }
-
     private PlaybackInfo createPlaybackInfo(VolumeProvider2 volumeProvider, AudioAttributes attrs) {
         PlaybackInfo info;
         if (volumeProvider == null) {
@@ -320,36 +323,56 @@
     @Override
     public void play_impl() {
         ensureCallingThread();
-        ensurePlayer();
-        mPlayer.play();
+        final MediaPlayerInterface player = mPlayer;
+        if (player != null) {
+            player.play();
+        } else if (DEBUG) {
+            Log.d(TAG, "API calls after the close()", new IllegalStateException());
+        }
     }
 
     @Override
     public void pause_impl() {
         ensureCallingThread();
-        ensurePlayer();
-        mPlayer.pause();
+        final MediaPlayerInterface player = mPlayer;
+        if (player != null) {
+            player.pause();
+        } else if (DEBUG) {
+            Log.d(TAG, "API calls after the close()", new IllegalStateException());
+        }
     }
 
     @Override
     public void stop_impl() {
         ensureCallingThread();
-        ensurePlayer();
-        mPlayer.stop();
+        final MediaPlayerInterface player = mPlayer;
+        if (player != null) {
+            player.stop();
+        } else if (DEBUG) {
+            Log.d(TAG, "API calls after the close()", new IllegalStateException());
+        }
     }
 
     @Override
     public void skipToPrevious_impl() {
         ensureCallingThread();
-        ensurePlayer();
-        mPlayer.skipToPrevious();
+        final MediaPlayerInterface player = mPlayer;
+        if (player != null) {
+            player.skipToPrevious();
+        } else if (DEBUG) {
+            Log.d(TAG, "API calls after the close()", new IllegalStateException());
+        }
     }
 
     @Override
     public void skipToNext_impl() {
         ensureCallingThread();
-        ensurePlayer();
-        mPlayer.skipToNext();
+        final MediaPlayerInterface player = mPlayer;
+        if (player != null) {
+            player.skipToNext();
+        } else if (DEBUG) {
+            Log.d(TAG, "API calls after the close()", new IllegalStateException());
+        }
     }
 
     @Override
@@ -367,21 +390,27 @@
     @Override
     public void setPlaylistParams_impl(PlaylistParams params) {
         if (params == null) {
-            throw new IllegalArgumentException("PlaylistParams should not be null!");
+            throw new IllegalArgumentException("params shouldn't be null");
         }
         ensureCallingThread();
-        ensurePlayer();
-        synchronized (mLock) {
-            mPlaylistParams = params;
+        final MediaPlayerInterface player = mPlayer;
+        if (player != null) {
+            player.setPlaylistParams(params);
+            mSessionStub.notifyPlaylistParamsChanged(params);
         }
-        mPlayer.setPlaylistParams(params);
-        mSessionStub.notifyPlaylistParamsChanged(params);
     }
 
     @Override
     public PlaylistParams getPlaylistParams_impl() {
-        // TODO: Do we need to synchronize here for preparing Controller2.setPlaybackParams?
-        return mPlaylistParams;
+        final MediaPlayerInterface player = mPlayer;
+        if (player != null) {
+            // TODO(jaewan): Is it safe to be called on any thread?
+            //               Otherwise MediaSession2 should cache parameter of setPlaylistParams.
+            return player.getPlaylistParams();
+        } else if (DEBUG) {
+            Log.d(TAG, "API calls after the close()", new IllegalStateException());
+        }
+        return null;
     }
 
     //////////////////////////////////////////////////////////////////////////////////////
@@ -412,57 +441,84 @@
     @Override
     public void setPlaylist_impl(List<MediaItem2> playlist) {
         if (playlist == null) {
-            throw new IllegalArgumentException("Playlist should not be null!");
+            throw new IllegalArgumentException("playlist shouldn't be null");
         }
         ensureCallingThread();
-        ensurePlayer();
-        synchronized (mLock) {
-            mPlaylist = playlist;
+        final MediaPlayerInterface player = mPlayer;
+        if (player != null) {
+            player.setPlaylist(playlist);
+            mSessionStub.notifyPlaylistChanged(playlist);
+        } else if (DEBUG) {
+            Log.d(TAG, "API calls after the close()", new IllegalStateException());
         }
-        mPlayer.setPlaylist(playlist);
-        mSessionStub.notifyPlaylistChanged(playlist);
     }
 
     @Override
     public List<MediaItem2> getPlaylist_impl() {
-        synchronized (mLock) {
-            return mPlaylist;
+        final MediaPlayerInterface player = mPlayer;
+        if (player != null) {
+            // TODO(jaewan): Is it safe to be called on any thread?
+            //               Otherwise MediaSession2 should cache parameter of setPlaylist.
+            return player.getPlaylist();
+        } else if (DEBUG) {
+            Log.d(TAG, "API calls after the close()", new IllegalStateException());
         }
+        return null;
     }
 
     @Override
     public void prepare_impl() {
         ensureCallingThread();
-        ensurePlayer();
-        mPlayer.prepare();
+        final MediaPlayerInterface player = mPlayer;
+        if (player != null) {
+            player.prepare();
+        } else if (DEBUG) {
+            Log.d(TAG, "API calls after the close()", new IllegalStateException());
+        }
     }
 
     @Override
     public void fastForward_impl() {
         ensureCallingThread();
-        ensurePlayer();
-        mPlayer.fastForward();
+        final MediaPlayerInterface player = mPlayer;
+        if (player != null) {
+            player.fastForward();
+        } else if (DEBUG) {
+            Log.d(TAG, "API calls after the close()", new IllegalStateException());
+        }
     }
 
     @Override
     public void rewind_impl() {
         ensureCallingThread();
-        ensurePlayer();
-        mPlayer.rewind();
+        final MediaPlayerInterface player = mPlayer;
+        if (player != null) {
+            player.rewind();
+        } else if (DEBUG) {
+            Log.d(TAG, "API calls after the close()", new IllegalStateException());
+        }
     }
 
     @Override
     public void seekTo_impl(long pos) {
         ensureCallingThread();
-        ensurePlayer();
-        mPlayer.seekTo(pos);
+        final MediaPlayerInterface player = mPlayer;
+        if (player != null) {
+            player.seekTo(pos);
+        } else if (DEBUG) {
+            Log.d(TAG, "API calls after the close()", new IllegalStateException());
+        }
     }
 
     @Override
     public void setCurrentPlaylistItem_impl(int index) {
         ensureCallingThread();
-        ensurePlayer();
-        mPlayer.setCurrentPlaylistItem(index);
+        final MediaPlayerInterface player = mPlayer;
+        if (player != null) {
+            player.setCurrentPlaylistItem(index);
+        } else if (DEBUG) {
+            Log.d(TAG, "API calls after the close()", new IllegalStateException());
+        }
     }
 
     @Override
@@ -497,10 +553,15 @@
     @Override
     public PlaybackState2 getPlaybackState_impl() {
         ensureCallingThread();
-        ensurePlayer();
-        // TODO(jaewan): Is it safe to be called on any thread?
-        //               Otherwise we should cache the result from listener.
-        return mPlayer.getPlaybackState();
+        final MediaPlayerInterface player = mPlayer;
+        if (player != null) {
+            // TODO(jaewan): Is it safe to be called on any thread?
+            //               Otherwise MediaSession2 should cache the result from listener.
+            return player.getPlaybackState();
+        } else if (DEBUG) {
+            Log.d(TAG, "API calls after the close()", new IllegalStateException());
+        }
+        return null;
     }
 
     ///////////////////////////////////////////////////
@@ -527,14 +588,6 @@
         }*/
     }
 
-    private void ensurePlayer() {
-        // TODO(jaewan): Should we pend command instead? Follow the decision from MP2.
-        //               Alternatively we can add a API like setAcceptsPendingCommands(boolean).
-        if (mPlayer == null) {
-            throw new IllegalStateException("Player isn't set");
-        }
-    }
-
     private void notifyPlaybackStateChangedNotLocked(PlaybackState2 state) {
         List<PlaybackListenerHolder> listeners = new ArrayList<>();
         synchronized (mLock) {
diff --git a/packages/MediaComponents/src/com/android/media/MediaSession2Stub.java b/packages/MediaComponents/src/com/android/media/MediaSession2Stub.java
index 43ad49d..f160ef8 100644
--- a/packages/MediaComponents/src/com/android/media/MediaSession2Stub.java
+++ b/packages/MediaComponents/src/com/android/media/MediaSession2Stub.java
@@ -539,9 +539,95 @@
         });
     }
 
+    @Override
+    public void getItem(IMediaSession2Callback caller, String mediaId) throws RuntimeException {
+        final MediaLibrarySessionImpl sessionImpl = getLibrarySession();
+        final ControllerInfo controller = getController(caller);
+        if (controller == null) {
+            if (DEBUG) {
+                Log.d(TAG, "getItem() from a controller that hasn't connected. Ignore");
+            }
+            return;
+        }
+        if (mediaId == null) {
+            if (DEBUG) {
+                Log.d(TAG, "mediaId shouldn't be null");
+            }
+            return;
+        }
+        sessionImpl.getCallbackExecutor().execute(() -> {
+            final MediaLibrarySessionImpl session = getLibrarySession();
+            if (session == null) {
+                return;
+            }
+            final ControllerInfoImpl controllerImpl = ControllerInfoImpl.from(controller);
+            MediaItem2 result = session.getCallback().onLoadItem(controller, mediaId);
+            try {
+                controllerImpl.getControllerBinder().onItemLoaded(
+                        mediaId, result == null ? null : result.toBundle());
+            } catch (RemoteException e) {
+                // Controller may be died prematurely.
+                // TODO(jaewan): Handle this.
+            }
+        });
+    }
+
+    @Override
+    public void getChildren(IMediaSession2Callback caller, String parentId, int page,
+            int pageSize, Bundle options) throws RuntimeException {
+        final MediaLibrarySessionImpl sessionImpl = getLibrarySession();
+        final ControllerInfo controller = getController(caller);
+        if (controller == null) {
+            if (DEBUG) {
+                Log.d(TAG, "getChildren() from a controller that hasn't connected. Ignore");
+            }
+            return;
+        }
+        if (parentId == null) {
+            Log.d(TAG, "parentId shouldn't be null");
+            return;
+        }
+        if (page < 1 || pageSize < 1) {
+            Log.d(TAG, "Neither page nor pageSize should be less than 1");
+            return;
+        }
+
+        sessionImpl.getCallbackExecutor().execute(() -> {
+            final MediaLibrarySessionImpl session = getLibrarySession();
+            if (session == null) {
+                return;
+            }
+            final ControllerInfoImpl controllerImpl = ControllerInfoImpl.from(controller);
+            List<MediaItem2> result = session.getCallback().onLoadChildren(
+                    controller, parentId, page, pageSize, options);
+            if (result != null && result.size() > pageSize) {
+                throw new IllegalArgumentException("onLoadChildren() shouldn't return media items "
+                        + "more than pageSize. result.size()=" + result.size() + " pageSize="
+                        + pageSize);
+            }
+
+            List<Bundle> bundleList = null;
+            if (result != null) {
+                bundleList = new ArrayList<>();
+                for (MediaItem2 item : result) {
+                    bundleList.add(item == null ? null : item.toBundle());
+                }
+            }
+
+            try {
+                controllerImpl.getControllerBinder().onChildrenLoaded(
+                        parentId, page, pageSize, options, bundleList);
+            } catch (RemoteException e) {
+                // Controller may be died prematurely.
+                // TODO(jaewan): Handle this.
+            }
+        });
+    }
+
     //////////////////////////////////////////////////////////////////////////////////////////////
     // APIs for MediaSession2Impl
     //////////////////////////////////////////////////////////////////////////////////////////////
+
     // TODO(jaewan): Need a way to get controller with permissions
     public List<ControllerInfo> getControllers() {
         ArrayList<ControllerInfo> controllers = new ArrayList<>();
diff --git a/packages/MediaComponents/test/src/android/media/MediaBrowser2Test.java b/packages/MediaComponents/test/src/android/media/MediaBrowser2Test.java
index eff4c3b..b60fde3 100644
--- a/packages/MediaComponents/test/src/android/media/MediaBrowser2Test.java
+++ b/packages/MediaComponents/test/src/android/media/MediaBrowser2Test.java
@@ -18,7 +18,9 @@
 
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertNotNull;
 import static junit.framework.Assert.assertTrue;
+import static junit.framework.Assert.assertNull;
 
 import android.annotation.Nullable;
 import android.content.Context;
@@ -65,6 +67,9 @@
     interface TestBrowserCallbackInterface extends TestControllerCallbackInterface {
         // Browser specific callbacks
         default void onGetRootResult(Bundle rootHints, String rootMediaId, Bundle rootExtra) {}
+        default void onItemLoaded(String mediaId, MediaItem2 result) {}
+        default void onChildrenLoaded(String parentId, int page, int pageSize, Bundle options,
+                List<MediaItem2> result) {}
     }
 
     @Test
@@ -90,6 +95,129 @@
         assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
     }
 
+    @Test
+    public void testGetItem() throws InterruptedException {
+        final String mediaId = MockMediaLibraryService2.MEDIA_ID_GET_ITEM;
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        final TestControllerCallbackInterface callback = new TestBrowserCallbackInterface() {
+            @Override
+            public void onItemLoaded(String mediaIdOut, MediaItem2 result) {
+                assertEquals(mediaId, mediaIdOut);
+                assertNotNull(result);
+                assertEquals(mediaId, result.getMediaId());
+                latch.countDown();
+            }
+        };
+
+        final SessionToken2 token = MockMediaLibraryService2.getToken(mContext);
+        MediaBrowser2 browser = (MediaBrowser2) createController(token, true, callback);
+        browser.getItem(mediaId);
+        assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+    }
+
+    @Test
+    public void testGetItemNullResult() throws InterruptedException {
+        final String mediaId = "random_media_id";
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        final TestControllerCallbackInterface callback = new TestBrowserCallbackInterface() {
+            @Override
+            public void onItemLoaded(String mediaIdOut, MediaItem2 result) {
+                assertEquals(mediaId, mediaIdOut);
+                assertNull(result);
+                latch.countDown();
+            }
+        };
+
+        final SessionToken2 token = MockMediaLibraryService2.getToken(mContext);
+        MediaBrowser2 browser = (MediaBrowser2) createController(token, true, callback);
+        browser.getItem(mediaId);
+        assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+    }
+
+    @Test
+    public void testGetChildren() throws InterruptedException {
+        final String parentId = MockMediaLibraryService2.PARENT_ID;
+        final int page = 4;
+        final int pageSize = 10;
+        final Bundle options = new Bundle();
+        options.putString(TAG, TAG);
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        final TestControllerCallbackInterface callback = new TestBrowserCallbackInterface() {
+            @Override
+            public void onChildrenLoaded(String parentIdOut, int pageOut, int pageSizeOut,
+                    Bundle optionsOut, List<MediaItem2> result) {
+                assertEquals(parentId, parentIdOut);
+                assertEquals(page, pageOut);
+                assertEquals(pageSize, pageSizeOut);
+                assertTrue(TestUtils.equals(options, optionsOut));
+                assertNotNull(result);
+
+                int fromIndex = (page - 1) * pageSize;
+                int toIndex = Math.min(page * pageSize,
+                        MockMediaLibraryService2.GET_CHILDREN_RESULT.size());
+
+                // Compare the given results with originals.
+                for (int originalIndex = fromIndex; originalIndex < toIndex; originalIndex++) {
+                    int relativeIndex = originalIndex - fromIndex;
+                    assertEquals(
+                            MockMediaLibraryService2.GET_CHILDREN_RESULT.get(originalIndex)
+                                    .getMediaId(),
+                            result.get(relativeIndex).getMediaId());
+                }
+                latch.countDown();
+            }
+        };
+
+        final SessionToken2 token = MockMediaLibraryService2.getToken(mContext);
+        MediaBrowser2 browser = (MediaBrowser2) createController(token, true, callback);
+        browser.getChildren(parentId, page, pageSize, options);
+        assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+    }
+
+    @Test
+    public void testGetChildrenEmptyResult() throws InterruptedException {
+        final String parentId = MockMediaLibraryService2.PARENT_ID_NO_CHILDREN;
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        final TestControllerCallbackInterface callback = new TestBrowserCallbackInterface() {
+            @Override
+            public void onChildrenLoaded(String parentIdOut, int pageOut, int pageSizeOut,
+                    Bundle optionsOut, List<MediaItem2> result) {
+                assertNotNull(result);
+                assertEquals(0, result.size());
+                latch.countDown();
+            }
+        };
+
+        final SessionToken2 token = MockMediaLibraryService2.getToken(mContext);
+        MediaBrowser2 browser = (MediaBrowser2) createController(token, true, callback);
+        browser.getChildren(parentId, 1, 1, null);
+        assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+    }
+
+    @Test
+    public void testGetChildrenNullResult() throws InterruptedException {
+        final String parentId = MockMediaLibraryService2.PARENT_ID_ERROR;
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        final TestControllerCallbackInterface callback = new TestBrowserCallbackInterface() {
+            @Override
+            public void onChildrenLoaded(String parentIdOut, int pageOut, int pageSizeOut,
+                    Bundle optionsOut, List<MediaItem2> result) {
+                assertNull(result);
+                latch.countDown();
+            }
+        };
+
+        final SessionToken2 token = MockMediaLibraryService2.getToken(mContext);
+        MediaBrowser2 browser = (MediaBrowser2) createController(token, true, callback);
+        browser.getChildren(parentId, 1, 1, null);
+        assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+    }
+
     public static class TestBrowserCallback extends BrowserCallback
             implements WaitForConnectionInterface {
         private final TestControllerCallbackInterface mCallbackProxy;
@@ -151,6 +279,24 @@
         }
 
         @Override
+        public void onItemLoaded(String mediaId, MediaItem2 result) {
+            super.onItemLoaded(mediaId, result);
+            if (mCallbackProxy instanceof TestBrowserCallbackInterface) {
+                ((TestBrowserCallbackInterface) mCallbackProxy).onItemLoaded(mediaId, result);
+            }
+        }
+
+        @Override
+        public void onChildrenLoaded(String parentId, int page, int pageSize, Bundle options,
+                List<MediaItem2> result) {
+            super.onChildrenLoaded(parentId, page, pageSize, options, result);
+            if (mCallbackProxy instanceof TestBrowserCallbackInterface) {
+                ((TestBrowserCallbackInterface) mCallbackProxy)
+                        .onChildrenLoaded(parentId, page, pageSize, options, result);
+            }
+        }
+
+        @Override
         public void waitForConnect(boolean expect) throws InterruptedException {
             if (expect) {
                 assertTrue(connectLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
diff --git a/packages/MediaComponents/test/src/android/media/MediaController2Test.java b/packages/MediaComponents/test/src/android/media/MediaController2Test.java
index 07baf58..7bf0fd2 100644
--- a/packages/MediaComponents/test/src/android/media/MediaController2Test.java
+++ b/packages/MediaComponents/test/src/android/media/MediaController2Test.java
@@ -21,9 +21,11 @@
 import android.content.Intent;
 import android.media.MediaPlayerInterface.PlaybackListener;
 import android.media.MediaSession2.Command;
+import android.media.MediaSession2.CommandGroup;
 import android.media.MediaSession2.ControllerInfo;
 import android.media.MediaSession2.PlaylistParams;
 import android.media.MediaSession2.SessionCallback;
+import android.media.TestServiceRegistry.SessionCallbackProxy;
 import android.media.TestUtils.SyncHandler;
 import android.net.Uri;
 import android.os.Bundle;
@@ -34,6 +36,7 @@
 import android.support.test.filters.FlakyTest;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
+import android.text.TextUtils;
 
 import org.junit.After;
 import org.junit.Before;
@@ -618,24 +621,32 @@
     @Ignore
     @Test
     public void testConnectToService_sessionService() throws InterruptedException {
-        connectToService(TestUtils.getServiceToken(mContext, MockMediaSessionService2.ID));
-        testConnectToService();
+        testConnectToService(MockMediaSessionService2.ID);
     }
 
     // TODO(jaewan): Reenable when session manager detects app installs
     @Ignore
     @Test
     public void testConnectToService_libraryService() throws InterruptedException {
-        connectToService(TestUtils.getServiceToken(mContext, MockMediaLibraryService2.ID));
-        testConnectToService();
+        testConnectToService(MockMediaLibraryService2.ID);
     }
 
-    public void testConnectToService() throws InterruptedException {
-        TestServiceRegistry serviceInfo = TestServiceRegistry.getInstance();
-        ControllerInfo info = serviceInfo.getOnConnectControllerInfo();
-        assertEquals(mContext.getPackageName(), info.getPackageName());
-        assertEquals(Process.myUid(), info.getUid());
-        assertFalse(info.isTrusted());
+    public void testConnectToService(String id) throws InterruptedException {
+        final CountDownLatch latch = new CountDownLatch(1);
+        final SessionCallbackProxy proxy = new SessionCallbackProxy(mContext) {
+            @Override
+            public CommandGroup onConnect(ControllerInfo controller) {
+                if (Process.myUid() == controller.getUid()) {
+                    assertEquals(mContext.getPackageName(), controller.getPackageName());
+                    assertFalse(controller.isTrusted());
+                    latch.countDown();;
+                }
+                return super.onConnect(controller);
+            }
+        };
+        TestServiceRegistry.getInstance().setSessionCallbackProxy(proxy);
+        mController = createController(TestUtils.getServiceToken(mContext, id));
+        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
 
         // Test command from controller to session service
         mController.play();
@@ -701,27 +712,26 @@
     @Ignore
     @Test
     public void testClose_sessionService() throws InterruptedException {
-        connectToService(TestUtils.getServiceToken(mContext, MockMediaSessionService2.ID));
-        testCloseFromService();
+        testCloseFromService(MockMediaSessionService2.ID);
     }
 
     // TODO(jaewan): Reenable when session manager detects app installs
     @Ignore
     @Test
     public void testClose_libraryService() throws InterruptedException {
-        connectToService(TestUtils.getServiceToken(mContext, MockMediaSessionService2.ID));
-        testCloseFromService();
+        testCloseFromService(MockMediaLibraryService2.ID);
     }
 
-    private void testCloseFromService() throws InterruptedException {
-        final String id = mController.getSessionToken().getId();
+    private void testCloseFromService(String id) throws InterruptedException {
         final CountDownLatch latch = new CountDownLatch(1);
-        TestServiceRegistry.getInstance().setServiceInstanceChangedCallback((service) -> {
-            if (service == null) {
-                // Destroying..
+        final SessionCallbackProxy proxy = new SessionCallbackProxy(mContext) {
+            @Override
+            public void onServiceDestroyed() {
                 latch.countDown();
             }
-        });
+        };
+        TestServiceRegistry.getInstance().setSessionCallbackProxy(proxy);
+        mController = createController(TestUtils.getServiceToken(mContext, id));
         mController.close();
         // Wait until close triggers onDestroy() of the session service.
         assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
diff --git a/packages/MediaComponents/test/src/android/media/MockMediaLibraryService2.java b/packages/MediaComponents/test/src/android/media/MockMediaLibraryService2.java
index ec69ff6..5fabebc 100644
--- a/packages/MediaComponents/test/src/android/media/MockMediaLibraryService2.java
+++ b/packages/MediaComponents/test/src/android/media/MockMediaLibraryService2.java
@@ -23,9 +23,17 @@
 import android.content.Context;
 import android.media.MediaSession2.CommandGroup;
 import android.media.MediaSession2.ControllerInfo;
+import android.media.TestServiceRegistry.SessionCallbackProxy;
 import android.media.TestUtils.SyncHandler;
 import android.os.Bundle;
 import android.os.Process;
+import android.util.Log;
+
+import java.io.FileDescriptor;
+import java.util.ArrayList;
+import java.util.List;
+
+import java.util.concurrent.Executor;
 
 import javax.annotation.concurrent.GuardedBy;
 
@@ -38,6 +46,20 @@
 
     public static final String ROOT_ID = "rootId";
     public static final Bundle EXTRA = new Bundle();
+
+    public static final String MEDIA_ID_GET_ITEM = "media_id_get_item";
+
+    public static final String PARENT_ID = "parent_id";
+    public static final String PARENT_ID_NO_CHILDREN = "parent_id_no_children";
+    public static final String PARENT_ID_ERROR = "parent_id_error";
+    public static final List<MediaItem2> GET_CHILDREN_RESULT = new ArrayList<>();
+
+    private static final int CHILDREN_COUNT = 100;
+    private static final DataSourceDesc DATA_SOURCE_DESC =
+            new DataSourceDesc.Builder().setDataSource(new FileDescriptor()).build();
+
+    private static final String TAG = "MockMediaLibrarySvc2";
+
     static {
         EXTRA.putString(ROOT_ID, ROOT_ID);
     }
@@ -46,20 +68,36 @@
 
     private MediaLibrarySession mSession;
 
+    public MockMediaLibraryService2() {
+        super();
+        GET_CHILDREN_RESULT.clear();
+        String mediaIdPrefix = "media_id_";
+        for (int i = 0; i < CHILDREN_COUNT; i++) {
+            GET_CHILDREN_RESULT.add(createMediaItem(mediaIdPrefix + i));
+        }
+    }
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        TestServiceRegistry.getInstance().setServiceInstance(this);
+    }
+
     @Override
     public MediaLibrarySession onCreateSession(String sessionId) {
         final MockPlayer player = new MockPlayer(1);
         final SyncHandler handler = (SyncHandler) TestServiceRegistry.getInstance().getHandler();
-        try {
-            handler.postAndSync(() -> {
-                TestLibrarySessionCallback callback = new TestLibrarySessionCallback();
-                mSession = new MediaLibrarySessionBuilder(MockMediaLibraryService2.this,
-                        player, (runnable) -> handler.post(runnable), callback)
-                        .setId(sessionId).build();
-            });
-        } catch (InterruptedException e) {
-            fail(e.toString());
+        final Executor executor = (runnable) -> handler.post(runnable);
+        SessionCallbackProxy sessionCallbackProxy = TestServiceRegistry.getInstance()
+                .getSessionCallbackProxy();
+        if (sessionCallbackProxy == null) {
+            // Ensures non-null
+            sessionCallbackProxy = new SessionCallbackProxy(this) {};
         }
+        TestLibrarySessionCallback callback =
+                new TestLibrarySessionCallback(sessionCallbackProxy);
+        mSession = new MediaLibrarySessionBuilder(MockMediaLibraryService2.this, player,
+                executor, callback).setId(sessionId).build();
         return mSession;
     }
 
@@ -81,24 +119,76 @@
     }
 
     private class TestLibrarySessionCallback extends MediaLibrarySessionCallback {
-        public TestLibrarySessionCallback() {
+        private final SessionCallbackProxy mCallbackProxy;
+
+        public TestLibrarySessionCallback(SessionCallbackProxy callbackProxy) {
             super(MockMediaLibraryService2.this);
+            mCallbackProxy = callbackProxy;
         }
 
         @Override
         public CommandGroup onConnect(ControllerInfo controller) {
-            if (Process.myUid() != controller.getUid()) {
-                // It's system app wants to listen changes. Ignore.
-                return super.onConnect(controller);
-            }
-            TestServiceRegistry.getInstance().setServiceInstance(
-                    MockMediaLibraryService2.this, controller);
-            return super.onConnect(controller);
+            return mCallbackProxy.onConnect(controller);
         }
 
         @Override
         public LibraryRoot onGetRoot(ControllerInfo controller, Bundle rootHints) {
             return new LibraryRoot(MockMediaLibraryService2.this, ROOT_ID, EXTRA);
         }
+
+        @Override
+        public MediaItem2 onLoadItem(ControllerInfo controller, String mediaId) {
+            if (MEDIA_ID_GET_ITEM.equals(mediaId)) {
+                return createMediaItem(mediaId);
+            } else {
+                return null;
+            }
+        }
+
+        @Override
+        public List<MediaItem2> onLoadChildren(ControllerInfo controller, String parentId, int page,
+                int pageSize, Bundle options) {
+            if (PARENT_ID.equals(parentId)) {
+                return getPaginatedResult(GET_CHILDREN_RESULT, page, pageSize);
+            } else if (PARENT_ID_ERROR.equals(parentId)) {
+                return null;
+            }
+            // Includes the case of PARENT_ID_NO_CHILDREN.
+            return new ArrayList<>();
+        }
+    }
+
+    private List<MediaItem2> getPaginatedResult(List<MediaItem2> items, int page, int pageSize) {
+        if (items == null) {
+            return null;
+        } else if (items.size() == 0) {
+            return new ArrayList<>();
+        }
+
+        final int totalItemCount = items.size();
+        int fromIndex = (page - 1) * pageSize;
+        int toIndex = Math.min(page * pageSize, totalItemCount);
+
+        List<MediaItem2> paginatedResult = new ArrayList<>();
+        try {
+            // The case of (fromIndex >= totalItemCount) will throw exception below.
+            paginatedResult = items.subList(fromIndex, toIndex);
+        } catch (IndexOutOfBoundsException | IllegalArgumentException ex) {
+            Log.d(TAG, "Result is empty for given pagination arguments: totalItemCount="
+                    + totalItemCount + ", page=" + page + ", pageSize=" + pageSize, ex);
+        }
+        return paginatedResult;
+    }
+
+    private MediaItem2 createMediaItem(String mediaId) {
+        Context context = MockMediaLibraryService2.this;
+        return new MediaItem2(
+                context,
+                mediaId,
+                DATA_SOURCE_DESC,
+                new MediaMetadata2.Builder(context)
+                        .putString(MediaMetadata2.METADATA_KEY_MEDIA_ID, mediaId)
+                        .build(),
+                0 /* Flags */);
     }
 }
\ No newline at end of file
diff --git a/packages/MediaComponents/test/src/android/media/MockMediaSessionService2.java b/packages/MediaComponents/test/src/android/media/MockMediaSessionService2.java
index c8ed184..12c2c9f 100644
--- a/packages/MediaComponents/test/src/android/media/MockMediaSessionService2.java
+++ b/packages/MediaComponents/test/src/android/media/MockMediaSessionService2.java
@@ -22,11 +22,11 @@
 import android.app.NotificationChannel;
 import android.app.NotificationManager;
 import android.content.Context;
+import android.media.MediaSession2.CommandGroup;
 import android.media.MediaSession2.ControllerInfo;
 import android.media.MediaSession2.SessionCallback;
+import android.media.TestServiceRegistry.SessionCallbackProxy;
 import android.media.TestUtils.SyncHandler;
-import android.media.session.PlaybackState;
-import android.os.Process;
 
 import java.util.concurrent.Executor;
 
@@ -45,29 +45,32 @@
     private NotificationManager mNotificationManager;
 
     @Override
+    public void onCreate() {
+        super.onCreate();
+        TestServiceRegistry.getInstance().setServiceInstance(this);
+        mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+    }
+
+    @Override
     public MediaSession2 onCreateSession(String sessionId) {
         final MockPlayer player = new MockPlayer(1);
         final SyncHandler handler = (SyncHandler) TestServiceRegistry.getInstance().getHandler();
         final Executor executor = (runnable) -> handler.post(runnable);
-        try {
-            handler.postAndSync(() -> {
-                mSession = new MediaSession2.Builder(MockMediaSessionService2.this, player)
-                        .setSessionCallback(executor, new MySessionCallback())
-                        .setId(sessionId).build();
-            });
-        } catch (InterruptedException e) {
-            fail(e.toString());
+        SessionCallbackProxy sessionCallbackProxy = TestServiceRegistry.getInstance()
+                .getSessionCallbackProxy();
+        if (sessionCallbackProxy == null) {
+            // Ensures non-null
+            sessionCallbackProxy = new SessionCallbackProxy(this) {};
         }
+        TestSessionServiceCallback callback =
+                new TestSessionServiceCallback(sessionCallbackProxy);
+        mSession = new MediaSession2.Builder(this, player)
+                .setSessionCallback(executor, callback)
+                .setId(sessionId).build();
         return mSession;
     }
 
     @Override
-    public void onCreate() {
-        super.onCreate();
-        mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
-    }
-
-    @Override
     public void onDestroy() {
         TestServiceRegistry.getInstance().cleanUp();
         super.onDestroy();
@@ -90,20 +93,17 @@
         return new MediaNotification(this, DEFAULT_MEDIA_NOTIFICATION_ID, notification);
     }
 
-    private class MySessionCallback extends SessionCallback {
-        public MySessionCallback() {
+    private class TestSessionServiceCallback extends SessionCallback {
+        private final SessionCallbackProxy mCallbackProxy;
+
+        public TestSessionServiceCallback(SessionCallbackProxy callbackProxy) {
             super(MockMediaSessionService2.this);
+            mCallbackProxy = callbackProxy;
         }
 
         @Override
-        public MediaSession2.CommandGroup onConnect(ControllerInfo controller) {
-            if (Process.myUid() != controller.getUid()) {
-                // It's system app wants to listen changes. Ignore.
-                return super.onConnect(controller);
-            }
-            TestServiceRegistry.getInstance().setServiceInstance(
-                    MockMediaSessionService2.this, controller);
-            return super.onConnect(controller);
+        public CommandGroup onConnect(ControllerInfo controller) {
+            return mCallbackProxy.onConnect(controller);
         }
     }
 }
diff --git a/packages/MediaComponents/test/src/android/media/TestServiceRegistry.java b/packages/MediaComponents/test/src/android/media/TestServiceRegistry.java
index 6f5512e..3800c28 100644
--- a/packages/MediaComponents/test/src/android/media/TestServiceRegistry.java
+++ b/packages/MediaComponents/test/src/android/media/TestServiceRegistry.java
@@ -18,10 +18,12 @@
 
 import static org.junit.Assert.fail;
 
+import android.content.Context;
+import android.media.MediaSession2.CommandGroup;
 import android.media.MediaSession2.ControllerInfo;
 import android.media.TestUtils.SyncHandler;
 import android.os.Handler;
-import android.os.Looper;
+import android.os.Process;
 import android.support.annotation.GuardedBy;
 
 /**
@@ -31,8 +33,46 @@
  * It only support only one service at a time.
  */
 public class TestServiceRegistry {
-    public interface ServiceInstanceChangedCallback {
-        void OnServiceInstanceChanged(MediaSessionService2 service);
+    /**
+     * Proxy for both {@link MediaSession2.SessionCallback} and
+     * {@link MediaLibraryService2.MediaLibrarySessionCallback}.
+     */
+    public static abstract class SessionCallbackProxy {
+        private final Context mContext;
+
+        /**
+         * Constructor
+         */
+        public SessionCallbackProxy(Context context) {
+            mContext = context;
+        }
+
+        public final Context getContext() {
+            return mContext;
+        }
+
+        /**
+         * @param controller
+         * @return
+         */
+        public CommandGroup onConnect(ControllerInfo controller) {
+            if (Process.myUid() == controller.getUid()) {
+                CommandGroup commands = new CommandGroup(mContext);
+                commands.addAllPredefinedCommands();
+                return commands;
+            }
+            return null;
+        }
+
+        /**
+         * Called when enclosing service is created.
+         */
+        public void onServiceCreated(MediaSessionService2 service) { }
+
+        /**
+         * Called when enclosing service is destroyed.
+         */
+        public void onServiceDestroyed() { }
     }
 
     @GuardedBy("TestServiceRegistry.class")
@@ -42,9 +82,7 @@
     @GuardedBy("TestServiceRegistry.class")
     private SyncHandler mHandler;
     @GuardedBy("TestServiceRegistry.class")
-    private ControllerInfo mOnConnectControllerInfo;
-    @GuardedBy("TestServiceRegistry.class")
-    private ServiceInstanceChangedCallback mCallback;
+    private SessionCallbackProxy mCallbackProxy;
 
     public static TestServiceRegistry getInstance() {
         synchronized (TestServiceRegistry.class) {
@@ -61,28 +99,33 @@
         }
     }
 
-    public void setServiceInstanceChangedCallback(ServiceInstanceChangedCallback callback) {
-        synchronized (TestServiceRegistry.class) {
-            mCallback = callback;
-        }
-    }
-
     public Handler getHandler() {
         synchronized (TestServiceRegistry.class) {
             return mHandler;
         }
     }
 
-    public void setServiceInstance(MediaSessionService2 service, ControllerInfo controller) {
+    public void setSessionCallbackProxy(SessionCallbackProxy callbackProxy) {
+        synchronized (TestServiceRegistry.class) {
+            mCallbackProxy = callbackProxy;
+        }
+    }
+
+    public SessionCallbackProxy getSessionCallbackProxy() {
+        synchronized (TestServiceRegistry.class) {
+            return mCallbackProxy;
+        }
+    }
+
+    public void setServiceInstance(MediaSessionService2 service) {
         synchronized (TestServiceRegistry.class) {
             if (mService != null) {
                 fail("Previous service instance is still running. Clean up manually to ensure"
                         + " previoulsy running service doesn't break current test");
             }
             mService = service;
-            mOnConnectControllerInfo = controller;
-            if (mCallback != null) {
-                mCallback.OnServiceInstanceChanged(service);
+            if (mCallbackProxy != null) {
+                mCallbackProxy.onServiceCreated(service);
             }
         }
     }
@@ -93,28 +136,11 @@
         }
     }
 
-    public ControllerInfo getOnConnectControllerInfo() {
-        synchronized (TestServiceRegistry.class) {
-            return mOnConnectControllerInfo;
-        }
-    }
-
-
     public void cleanUp() {
         synchronized (TestServiceRegistry.class) {
-            final ServiceInstanceChangedCallback callback = mCallback;
+            final SessionCallbackProxy callbackProxy = mCallbackProxy;
             if (mService != null) {
-                try {
-                    if (mHandler.getLooper() == Looper.myLooper()) {
-                        mService.getSession().close();
-                    } else {
-                        mHandler.postAndSync(() -> {
-                            mService.getSession().close();
-                        });
-                    }
-                } catch (InterruptedException e) {
-                    // No-op. Service containing session will die, but shouldn't be a huge issue.
-                }
+                mService.getSession().close();
                 // stopSelf() would not kill service while the binder connection established by
                 // bindService() exists, and close() above will do the job instead.
                 // So stopSelf() isn't really needed, but just for sure.
@@ -124,11 +150,10 @@
             if (mHandler != null) {
                 mHandler.removeCallbacksAndMessages(null);
             }
-            mCallback = null;
-            mOnConnectControllerInfo = null;
+            mCallbackProxy = null;
 
-            if (callback != null) {
-                callback.OnServiceInstanceChanged(null);
+            if (callbackProxy != null) {
+                callbackProxy.onServiceDestroyed();
             }
         }
     }