Merge "codec2: use flexible YCbCr 420 888 format for GraphicBuffer"
diff --git a/camera/ndk/include/camera/NdkCameraMetadata.h b/camera/ndk/include/camera/NdkCameraMetadata.h
index f2aec98..bdb1587 100644
--- a/camera/ndk/include/camera/NdkCameraMetadata.h
+++ b/camera/ndk/include/camera/NdkCameraMetadata.h
@@ -36,6 +36,7 @@
 #ifndef _NDK_CAMERA_METADATA_H
 #define _NDK_CAMERA_METADATA_H
 
+#include <stdint.h>
 #include <sys/cdefs.h>
 
 #include "NdkCameraError.h"
diff --git a/camera/ndk/include/camera/NdkCameraMetadataTags.h b/camera/ndk/include/camera/NdkCameraMetadataTags.h
index d88a7a5..839b134 100644
--- a/camera/ndk/include/camera/NdkCameraMetadataTags.h
+++ b/camera/ndk/include/camera/NdkCameraMetadataTags.h
@@ -651,7 +651,8 @@
      * is used, all non-zero weights will have the same effect. A region with 0 weight is
      * ignored.</p>
      * <p>If all regions have 0 weight, then no specific metering area needs to be used by the
-     * camera device.</p>
+     * camera device. The capture result will either be a zero weight region as well, or
+     * the region selected by the camera device as the focus area of interest.</p>
      * <p>If the metering region is outside the used ACAMERA_SCALER_CROP_REGION returned in
      * capture result metadata, the camera device will ignore the sections outside the crop
      * region and output only the intersection rectangle as the metering region in the result
diff --git a/drm/mediacas/plugins/clearkey/ClearKeyCasPlugin.cpp b/drm/mediacas/plugins/clearkey/ClearKeyCasPlugin.cpp
index e27631f..ed9534f 100644
--- a/drm/mediacas/plugins/clearkey/ClearKeyCasPlugin.cpp
+++ b/drm/mediacas/plugins/clearkey/ClearKeyCasPlugin.cpp
@@ -347,6 +347,9 @@
         return ERROR_CAS_CANNOT_HANDLE;
     }
 
+    scramblingControl = (DescramblerPlugin::ScramblingControl)
+        (scramblingControl & DescramblerPlugin::kScrambling_Mask_Key);
+
     AES_KEY contentKey;
 
     if (scramblingControl != DescramblerPlugin::kScrambling_Unscrambled) {
diff --git a/include/media/ExtractorUtils.h b/include/media/ExtractorUtils.h
new file mode 120000
index 0000000..e2dd082
--- /dev/null
+++ b/include/media/ExtractorUtils.h
@@ -0,0 +1 @@
+../../media/libmediaextractor/include/media/ExtractorUtils.h
\ No newline at end of file
diff --git a/media/extractors/mkv/MatroskaExtractor.cpp b/media/extractors/mkv/MatroskaExtractor.cpp
index 3f832bc..fc60fd4 100644
--- a/media/extractors/mkv/MatroskaExtractor.cpp
+++ b/media/extractors/mkv/MatroskaExtractor.cpp
@@ -22,6 +22,7 @@
 #include "MatroskaExtractor.h"
 
 #include <media/DataSourceBase.h>
+#include <media/ExtractorUtils.h>
 #include <media/MediaTrack.h>
 #include <media/stagefright/foundation/ADebug.h>
 #include <media/stagefright/foundation/AUtils.h>
@@ -1108,7 +1109,7 @@
     meta.setData(kKeyFlacMetadata, 0, codecPrivate, codecPrivateSize);
 
     int32_t maxInputSize = 64 << 10;
-    sp<FLACDecoder> flacDecoder = FLACDecoder::Create();
+    FLACDecoder *flacDecoder = FLACDecoder::Create();
     if (flacDecoder != NULL
             && flacDecoder->parseMetadata((const uint8_t*)codecPrivate, codecPrivateSize) == OK) {
         FLAC__StreamMetadata_StreamInfo streamInfo = flacDecoder->getStreamInfo();
@@ -1120,6 +1121,7 @@
                     && streamInfo.channels != 0
                     && ((streamInfo.bits_per_sample + 7) / 8) >
                         INT32_MAX / streamInfo.max_blocksize / streamInfo.channels) {
+                delete flacDecoder;
                 return ERROR_MALFORMED;
             }
             maxInputSize = ((streamInfo.bits_per_sample + 7) / 8)
@@ -1128,6 +1130,7 @@
     }
     meta.setInt32(kKeyMaxInputSize, maxInputSize);
 
+    delete flacDecoder;
     return OK;
 }
 
@@ -1143,13 +1146,13 @@
     }
 
     const mkvparser::Block::Frame &frame = block->GetFrame(0);
-    sp<ABuffer> abuf = new ABuffer(frame.len);
-    long n = frame.Read(mReader, abuf->data());
+    auto tmpData = heapbuffer<unsigned char>(frame.len);
+    long n = frame.Read(mReader, tmpData.get());
     if (n != 0) {
         return ERROR_MALFORMED;
     }
 
-    if (!MakeAVCCodecSpecificData(trackInfo->mMeta, abuf)) {
+    if (!MakeAVCCodecSpecificData(trackInfo->mMeta, tmpData.get(), frame.len)) {
         return ERROR_MALFORMED;
     }
 
diff --git a/media/extractors/ogg/OggExtractor.cpp b/media/extractors/ogg/OggExtractor.cpp
index 4d49013..b2fe69c 100644
--- a/media/extractors/ogg/OggExtractor.cpp
+++ b/media/extractors/ogg/OggExtractor.cpp
@@ -22,6 +22,7 @@
 
 #include <cutils/properties.h>
 #include <media/DataSourceBase.h>
+#include <media/ExtractorUtils.h>
 #include <media/MediaTrack.h>
 #include <media/VorbisComment.h>
 #include <media/stagefright/foundation/ABuffer.h>
@@ -990,21 +991,11 @@
     return OK;
 }
 
-struct TmpData {
-    uint8_t *data;
-    TmpData(size_t size) {
-        data = (uint8_t*) malloc(size);
-    }
-    ~TmpData() {
-        free(data);
-    }
-};
-
 status_t MyOpusExtractor::verifyOpusComments(MediaBufferBase *buffer) {
     // add artificial framing bit so we can reuse _vorbis_unpack_comment
     int32_t commentSize = buffer->range_length() + 1;
-    TmpData commentDataHolder(commentSize);
-    uint8_t *commentData = commentDataHolder.data;
+    auto tmp = heapbuffer<uint8_t>(commentSize);
+    uint8_t *commentData = tmp.get();
     if (commentData == nullptr) {
         return ERROR_MALFORMED;
     }
diff --git a/media/libaaudio/examples/loopback/src/LoopbackAnalyzer.h b/media/libaaudio/examples/loopback/src/LoopbackAnalyzer.h
index b83851a..1e282d1 100644
--- a/media/libaaudio/examples/loopback/src/LoopbackAnalyzer.h
+++ b/media/libaaudio/examples/loopback/src/LoopbackAnalyzer.h
@@ -410,8 +410,7 @@
 
 
 static void printAudioScope(float sample) {
-    const int maxStars = 80
-    ; // arbitrary, fits on one line
+    const int maxStars = 80; // arbitrary, fits on one line
     char c = '*';
     if (sample < -1.0) {
         sample = -1.0;
@@ -555,7 +554,7 @@
                 break;
 
             case STATE_WAITING_FOR_SILENCE:
-                // Output silence.
+                // Output silence and wait for the echos to die down.
                 numSamples = numFrames * outputChannelCount;
                 for (int i = 0; i < numSamples; i++) {
                     outputData[i] = 0;
diff --git a/media/libaaudio/examples/loopback/src/loopback.cpp b/media/libaaudio/examples/loopback/src/loopback.cpp
index f2254ce..39d079e 100644
--- a/media/libaaudio/examples/loopback/src/loopback.cpp
+++ b/media/libaaudio/examples/loopback/src/loopback.cpp
@@ -128,7 +128,7 @@
 
             myData->audioRecording.write(myData->inputData,
                                         myData->actualInputChannelCount,
-                                        numFrames);
+                                         framesRead);
 
             int32_t numSamples = framesRead * myData->actualInputChannelCount;
             convertPcm16ToFloat(myData->inputData, myData->conversionBuffer, numSamples);
@@ -161,17 +161,17 @@
 static void usage() {
     printf("Usage: aaudio_loopback [OPTION]...\n\n");
     AAudioArgsParser::usage();
-    printf("          -C{channels}      number of input channels\n");
-    printf("          -g{gain}          recirculating loopback gain\n");
-    printf("          -P{inPerf}        set input AAUDIO_PERFORMANCE_MODE*\n");
-    printf("              n for _NONE\n");
-    printf("              l for _LATENCY\n");
-    printf("              p for _POWER_SAVING\n");
-    printf("          -t{test}          select test mode\n");
-    printf("              m for sine magnitude\n");
-    printf("              e for echo latency (default)\n");
-    printf("              f for file latency, analyzes %s\n\n", FILENAME_ECHOS);
-    printf("          -X  use EXCLUSIVE mode for input\n");
+    printf("      -C{channels}      number of input channels\n");
+    printf("      -g{gain}          recirculating loopback gain\n");
+    printf("      -P{inPerf}        set input AAUDIO_PERFORMANCE_MODE*\n");
+    printf("          n for _NONE\n");
+    printf("          l for _LATENCY\n");
+    printf("          p for _POWER_SAVING\n");
+    printf("      -t{test}          select test mode\n");
+    printf("          m for sine magnitude\n");
+    printf("          e for echo latency (default)\n");
+    printf("          f for file latency, analyzes %s\n\n", FILENAME_ECHOS);
+    printf("      -X  use EXCLUSIVE mode for input\n");
     printf("Example:  aaudio_loopback -n2 -pl -Pl -x\n");
 }
 
@@ -448,7 +448,7 @@
     }
 
     if (loopbackData.loopbackProcessor->getResult() < 0) {
-        printf("Test failed!\n");
+        printf("ERROR: Could not get a good loopback signal. Probably because the volume was too low.\n");
     } else {
         printf("input error = %d = %s\n",
                loopbackData.inputError, AAudio_convertResultToText(loopbackData.inputError));
@@ -467,11 +467,16 @@
         }
 
         int written = loopbackData.loopbackProcessor->save(FILENAME_ECHOS);
-        printf("main() wrote %d mono samples to %s on Android device\n", written,
-               FILENAME_ECHOS);
-        printf("main() loopbackData.audioRecording.getSampleRate() = %d\n", loopbackData.audioRecording.getSampleRate());
+        if (written > 0) {
+            printf("main() wrote %8d mono samples to \"%s\" on Android device\n",
+                   written, FILENAME_ECHOS);
+        }
+
         written = loopbackData.audioRecording.save(FILENAME_ALL);
-        printf("main() wrote %d mono samples to %s on Android device\n", written, FILENAME_ALL);
+        if (written > 0) {
+            printf("main() wrote %8d mono samples to \"%s\" on Android device\n",
+                   written, FILENAME_ALL);
+        }
     }
 
 finish:
@@ -481,9 +486,8 @@
     delete[] loopbackData.inputData;
     delete[] outputData;
 
-    printf(RESULT_TAG "error = %d = %s\n", result, AAudio_convertResultToText(result));
+    printf(RESULT_TAG "result = %s\n", AAudio_convertResultToText(result));
     if ((result != AAUDIO_OK)) {
-        printf("error %d = %s\n", result, AAudio_convertResultToText(result));
         return EXIT_FAILURE;
     } else {
         printf("SUCCESS\n");
diff --git a/media/libaaudio/examples/utils/AAudioArgsParser.h b/media/libaaudio/examples/utils/AAudioArgsParser.h
index 4fc5b9f..eb6925a 100644
--- a/media/libaaudio/examples/utils/AAudioArgsParser.h
+++ b/media/libaaudio/examples/utils/AAudioArgsParser.h
@@ -19,7 +19,8 @@
 
 #define MAX_CHANNELS                     8
 
-#include <cctype>
+//#include <cctype>
+#include <dlfcn.h>
 #include <unistd.h>
 #include <stdio.h>
 #include <stdlib.h>
@@ -29,6 +30,63 @@
 
 #include "AAudioExampleUtils.h"
 
+
+static void (*s_setUsage)(AAudioStreamBuilder* builder, aaudio_usage_t usage) = nullptr;
+static void (*s_setContentType)(AAudioStreamBuilder* builder,
+                                aaudio_content_type_t contentType) = nullptr;
+static void (*s_setInputPreset)(AAudioStreamBuilder* builder,
+                                aaudio_input_preset_t inputPreset) = nullptr;
+
+static bool s_loadAttempted = false;
+static aaudio_usage_t (*s_getUsage)(AAudioStream *stream) = nullptr;
+static aaudio_content_type_t (*s_getContentType)(AAudioStream *stream) = nullptr;
+static aaudio_input_preset_t (*s_getInputPreset)(AAudioStream *stream) = nullptr;
+
+// Link to test functions in shared library.
+static void loadFutureFunctions() {
+    if (s_loadAttempted)  return; // only try once
+    s_loadAttempted = true;
+
+    void *handle = dlopen("libaaudio.so", RTLD_NOW);
+    if (handle != nullptr) {
+        s_setUsage = (void (*)(AAudioStreamBuilder *, aaudio_usage_t))
+                dlsym(handle, "AAudioStreamBuilder_setUsage");
+        if (s_setUsage == nullptr) goto error;
+
+        s_setContentType = (void (*)(AAudioStreamBuilder *, aaudio_content_type_t))
+                dlsym(handle, "AAudioStreamBuilder_setContentType");
+        if (s_setContentType == nullptr) goto error;
+
+        s_setInputPreset = (void (*)(AAudioStreamBuilder *, aaudio_input_preset_t))
+                dlsym(handle, "AAudioStreamBuilder_setInputPreset");
+        if (s_setInputPreset == nullptr) goto error;
+
+        s_getUsage = (aaudio_usage_t (*)(AAudioStream *))
+                dlsym(handle, "AAudioStream_getUsage");
+        if (s_getUsage == nullptr) goto error;
+
+        s_getContentType = (aaudio_content_type_t (*)(AAudioStream *))
+                dlsym(handle, "AAudioStream_getContentType");
+        if (s_getContentType == nullptr) goto error;
+
+        s_getInputPreset = (aaudio_input_preset_t (*)(AAudioStream *))
+                dlsym(handle, "AAudioStream_getInputPreset");
+        if (s_getInputPreset == nullptr) goto error;
+    }
+    return;
+
+error:
+    // prevent any calls to these functions
+    s_setUsage = nullptr;
+    s_setContentType = nullptr;
+    s_setInputPreset = nullptr;
+    s_getUsage = nullptr;
+    s_getContentType = nullptr;
+    s_getInputPreset = nullptr;
+    dlclose(handle);
+    return;
+}
+
 // TODO use this as a base class within AAudio
 class AAudioParameters {
 public:
@@ -140,9 +198,24 @@
         AAudioStreamBuilder_setDeviceId(builder, mDeviceId);
         AAudioStreamBuilder_setSharingMode(builder, mSharingMode);
         AAudioStreamBuilder_setPerformanceMode(builder, mPerformanceMode);
-        AAudioStreamBuilder_setUsage(builder, mUsage);
-        AAudioStreamBuilder_setContentType(builder, mContentType);
-        AAudioStreamBuilder_setInputPreset(builder, mInputPreset);
+
+        // Call P functions if supported.
+        loadFutureFunctions();
+        if (s_setUsage != nullptr) {
+            s_setUsage(builder, mUsage);
+        } else if (mUsage != AAUDIO_UNSPECIFIED){
+            printf("WARNING: setUsage not supported");
+        }
+        if (s_setContentType != nullptr) {
+            s_setContentType(builder, mContentType);
+        } else if (mUsage != AAUDIO_UNSPECIFIED){
+            printf("WARNING: setContentType not supported");
+        }
+        if (s_setInputPreset != nullptr) {
+            s_setInputPreset(builder, mInputPreset);
+        } else if (mUsage != AAUDIO_UNSPECIFIED){
+            printf("WARNING: setInputPreset not supported");
+        }
     }
 
 private:
@@ -332,14 +405,21 @@
         printf("  PerformanceMode: requested = %d, actual = %d\n",
                getPerformanceMode(), AAudioStream_getPerformanceMode(stream));
 
-        printf("  Usage:        requested = %d, actual = %d\n",
-               getUsage(), AAudioStream_getUsage(stream));
-        printf("  ContentType:  requested = %d, actual = %d\n",
-               getContentType(), AAudioStream_getContentType(stream));
+        loadFutureFunctions();
 
-        if (AAudioStream_getDirection(stream) == AAUDIO_DIRECTION_INPUT) {
-            printf("  InputPreset:  requested = %d, actual = %d\n",
-                   getInputPreset(), AAudioStream_getInputPreset(stream));
+        if (s_setUsage != nullptr) {
+            printf("  Usage:        requested = %d, actual = %d\n",
+                   getUsage(), s_getUsage(stream));
+        }
+        if (s_getContentType != nullptr) {
+            printf("  ContentType:  requested = %d, actual = %d\n",
+                   getContentType(), s_getContentType(stream));
+        }
+
+        if (AAudioStream_getDirection(stream) == AAUDIO_DIRECTION_INPUT
+            && s_getInputPreset != nullptr) {
+                printf("  InputPreset:  requested = %d, actual = %d\n",
+                       getInputPreset(), s_getInputPreset(stream));
         }
 
         printf("  Is MMAP used? %s\n", AAudioStream_isMMapUsed(stream)
diff --git a/media/libaaudio/src/client/AudioStreamInternal.cpp b/media/libaaudio/src/client/AudioStreamInternal.cpp
index b611160..6b25302 100644
--- a/media/libaaudio/src/client/AudioStreamInternal.cpp
+++ b/media/libaaudio/src/client/AudioStreamInternal.cpp
@@ -104,7 +104,7 @@
     request.setUserId(getuid());
     request.setProcessId(getpid());
     request.setSharingModeMatchRequired(isSharingModeMatchRequired());
-    request.setInService(mInService);
+    request.setInService(isInService());
 
     request.getConfiguration().setDeviceId(getDeviceId());
     request.getConfiguration().setSampleRate(getSampleRate());
@@ -118,11 +118,24 @@
 
     request.getConfiguration().setBufferCapacity(builder.getBufferCapacity());
 
+    mDeviceChannelCount = getSamplesPerFrame(); // Assume it will be the same. Update if not.
+
     mServiceStreamHandle = mServiceInterface.openStream(request, configurationOutput);
+    if (mServiceStreamHandle < 0
+            && request.getConfiguration().getSamplesPerFrame() == 1 // mono?
+            && getDirection() == AAUDIO_DIRECTION_OUTPUT
+            && !isInService()) {
+        // if that failed then try switching from mono to stereo if OUTPUT.
+        // Only do this in the client. Otherwise we end up with a mono mixer in the service
+        // that writes to a stereo MMAP stream.
+        ALOGD("%s - openStream() returned %d, try switching from MONO to STEREO",
+              __func__, mServiceStreamHandle);
+        request.getConfiguration().setSamplesPerFrame(2); // stereo
+        mServiceStreamHandle = mServiceInterface.openStream(request, configurationOutput);
+    }
     if (mServiceStreamHandle < 0) {
-        result = mServiceStreamHandle;
-        ALOGE("%s - openStream() returned %d", __func__, result);
-        return result;
+        ALOGE("%s - openStream() returned %d", __func__, mServiceStreamHandle);
+        return mServiceStreamHandle;
     }
 
     result = configurationOutput.validate();
@@ -130,8 +143,12 @@
         goto error;
     }
     // Save results of the open.
+    if (getSamplesPerFrame() == AAUDIO_UNSPECIFIED) {
+        setSamplesPerFrame(configurationOutput.getSamplesPerFrame());
+    }
+    mDeviceChannelCount = configurationOutput.getSamplesPerFrame();
+
     setSampleRate(configurationOutput.getSampleRate());
-    setSamplesPerFrame(configurationOutput.getSamplesPerFrame());
     setDeviceId(configurationOutput.getDeviceId());
     setSessionId(configurationOutput.getSessionId());
     setSharingMode(configurationOutput.getSharingMode());
@@ -160,7 +177,6 @@
         goto error;
     }
 
-
     // Validate result from server.
     framesPerBurst = mEndpointDescriptor.dataQueueDescriptor.framesPerBurst;
     if (framesPerBurst < MIN_FRAMES_PER_BURST || framesPerBurst > MAX_FRAMES_PER_BURST) {
diff --git a/media/libaaudio/src/client/AudioStreamInternal.h b/media/libaaudio/src/client/AudioStreamInternal.h
index 0f54f8c..0e0724b 100644
--- a/media/libaaudio/src/client/AudioStreamInternal.h
+++ b/media/libaaudio/src/client/AudioStreamInternal.h
@@ -138,7 +138,14 @@
     // Calculate timeout for an operation involving framesPerOperation.
     int64_t calculateReasonableTimeout(int32_t framesPerOperation);
 
-    aaudio_format_t          mDeviceFormat = AAUDIO_FORMAT_UNSPECIFIED;
+    aaudio_format_t getDeviceFormat() const { return mDeviceFormat; }
+
+    int32_t getDeviceChannelCount() const { return mDeviceChannelCount; }
+
+    /**
+     * @return true if running in audio service, versus in app process
+     */
+    bool isInService() const { return mInService; }
 
     IsochronousClockModel    mClockModel;      // timing model for chasing the HAL
 
@@ -187,6 +194,11 @@
     EndpointDescriptor       mEndpointDescriptor; // buffer description with resolved addresses
 
     int64_t                  mServiceLatencyNanos = 0;
+
+    // Sometimes the hardware is operating with a different format or channel count from the app.
+    // Then we require conversion in AAudio.
+    aaudio_format_t          mDeviceFormat = AAUDIO_FORMAT_UNSPECIFIED;
+    int32_t                  mDeviceChannelCount = 0;
 };
 
 } /* namespace aaudio */
diff --git a/media/libaaudio/src/client/AudioStreamInternalCapture.cpp b/media/libaaudio/src/client/AudioStreamInternalCapture.cpp
index 62f0fc8..0719fe1 100644
--- a/media/libaaudio/src/client/AudioStreamInternalCapture.cpp
+++ b/media/libaaudio/src/client/AudioStreamInternalCapture.cpp
@@ -176,16 +176,16 @@
         int32_t numSamples = framesToProcess * getSamplesPerFrame();
 
         // TODO factor this out into a utility function
-        if (mDeviceFormat == getFormat()) {
+        if (getDeviceFormat() == getFormat()) {
             memcpy(destination, wrappingBuffer.data[partIndex], numBytes);
-        } else if (mDeviceFormat == AAUDIO_FORMAT_PCM_I16
+        } else if (getDeviceFormat() == AAUDIO_FORMAT_PCM_I16
                    && getFormat() == AAUDIO_FORMAT_PCM_FLOAT) {
             AAudioConvert_pcm16ToFloat(
                     (const int16_t *) wrappingBuffer.data[partIndex],
                     (float *) destination,
                     numSamples,
                     1.0f);
-        } else if (mDeviceFormat == AAUDIO_FORMAT_PCM_FLOAT
+        } else if (getDeviceFormat() == AAUDIO_FORMAT_PCM_FLOAT
                    && getFormat() == AAUDIO_FORMAT_PCM_I16) {
             AAudioConvert_floatToPcm16(
                     (const float *) wrappingBuffer.data[partIndex],
diff --git a/media/libaaudio/src/client/AudioStreamInternalPlay.cpp b/media/libaaudio/src/client/AudioStreamInternalPlay.cpp
index a0a0a54..11b43c3 100644
--- a/media/libaaudio/src/client/AudioStreamInternalPlay.cpp
+++ b/media/libaaudio/src/client/AudioStreamInternalPlay.cpp
@@ -206,7 +206,7 @@
     // ALOGD("AudioStreamInternal::writeNowWithConversion(%p, %d)",
     //              buffer, numFrames);
     WrappingBuffer wrappingBuffer;
-    uint8_t *source = (uint8_t *) buffer;
+    uint8_t *byteBuffer = (uint8_t *) buffer;
     int32_t framesLeft = numFrames;
 
     mAudioEndpoint.getEmptyFramesAvailable(&wrappingBuffer);
@@ -220,69 +220,26 @@
             if (framesToWrite > framesAvailable) {
                 framesToWrite = framesAvailable;
             }
+
             int32_t numBytes = getBytesPerFrame() * framesToWrite;
-            int32_t numSamples = framesToWrite * getSamplesPerFrame();
             // Data conversion.
             float levelFrom;
             float levelTo;
-            bool ramping = mVolumeRamp.nextSegment(framesToWrite, &levelFrom, &levelTo);
-            // The formats are validated when the stream is opened so we do not have to
-            // check for illegal combinations here.
-            // TODO factor this out into a utility function
-            if (getFormat() == AAUDIO_FORMAT_PCM_FLOAT) {
-                if (mDeviceFormat == AAUDIO_FORMAT_PCM_FLOAT) {
-                    AAudio_linearRamp(
-                            (const float *) source,
-                            (float *) wrappingBuffer.data[partIndex],
-                            framesToWrite,
-                            getSamplesPerFrame(),
-                            levelFrom,
-                            levelTo);
-                } else if (mDeviceFormat == AAUDIO_FORMAT_PCM_I16) {
-                    if (ramping) {
-                        AAudioConvert_floatToPcm16(
-                                (const float *) source,
-                                (int16_t *) wrappingBuffer.data[partIndex],
-                                framesToWrite,
-                                getSamplesPerFrame(),
-                                levelFrom,
-                                levelTo);
-                    } else {
-                        AAudioConvert_floatToPcm16(
-                                (const float *) source,
-                                (int16_t *) wrappingBuffer.data[partIndex],
-                                numSamples,
-                                levelTo);
-                    }
-                }
-            } else if (getFormat() == AAUDIO_FORMAT_PCM_I16) {
-                if (mDeviceFormat == AAUDIO_FORMAT_PCM_FLOAT) {
-                    if (ramping) {
-                        AAudioConvert_pcm16ToFloat(
-                                (const int16_t *) source,
-                                (float *) wrappingBuffer.data[partIndex],
-                                framesToWrite,
-                                getSamplesPerFrame(),
-                                levelFrom,
-                                levelTo);
-                    } else {
-                        AAudioConvert_pcm16ToFloat(
-                                (const int16_t *) source,
-                                (float *) wrappingBuffer.data[partIndex],
-                                numSamples,
-                                levelTo);
-                    }
-                } else if (mDeviceFormat == AAUDIO_FORMAT_PCM_I16) {
-                    AAudio_linearRamp(
-                            (const int16_t *) source,
-                            (int16_t *) wrappingBuffer.data[partIndex],
-                            framesToWrite,
-                            getSamplesPerFrame(),
-                            levelFrom,
-                            levelTo);
-                }
-            }
-            source += numBytes;
+            mVolumeRamp.nextSegment(framesToWrite, &levelFrom, &levelTo);
+
+            AAudioDataConverter::FormattedData source(
+                    (void *)byteBuffer,
+                    getFormat(),
+                    getSamplesPerFrame());
+            AAudioDataConverter::FormattedData destination(
+                    wrappingBuffer.data[partIndex],
+                    getDeviceFormat(),
+                    getDeviceChannelCount());
+
+            AAudioDataConverter::convert(source, destination, framesToWrite,
+                                         levelFrom, levelTo);
+
+            byteBuffer += numBytes;
             framesLeft -= framesToWrite;
         } else {
             break;
diff --git a/media/libaaudio/src/utility/AAudioUtilities.cpp b/media/libaaudio/src/utility/AAudioUtilities.cpp
index 854c691..40b31b9 100644
--- a/media/libaaudio/src/utility/AAudioUtilities.cpp
+++ b/media/libaaudio/src/utility/AAudioUtilities.cpp
@@ -27,6 +27,7 @@
 #include <aaudio/AAudioTesting.h>
 #include <math.h>
 #include <system/audio-base.h>
+#include <assert.h>
 
 #include "utility/AAudioUtilities.h"
 
@@ -72,7 +73,7 @@
                                 int16_t *destination,
                                 int32_t numSamples,
                                 float amplitude) {
-    float scaler = amplitude;
+    const float scaler = amplitude;
     for (int i = 0; i < numSamples; i++) {
         float sample = *source++;
         *destination++ = clipAndClampFloatToPcm16(sample, scaler);
@@ -103,7 +104,7 @@
                                 float *destination,
                                 int32_t numSamples,
                                 float amplitude) {
-    float scaler = amplitude / SHORT_SCALE;
+    const float scaler = amplitude / SHORT_SCALE;
     for (int i = 0; i < numSamples; i++) {
         destination[i] = source[i] * scaler;
     }
@@ -117,7 +118,7 @@
                                 float amplitude1,
                                 float amplitude2) {
     float scaler = amplitude1 / SHORT_SCALE;
-    float delta = (amplitude2 - amplitude1) / (SHORT_SCALE * (float) numFrames);
+    const float delta = (amplitude2 - amplitude1) / (SHORT_SCALE * (float) numFrames);
     for (int frameIndex = 0; frameIndex < numFrames; frameIndex++) {
         for (int sampleIndex = 0; sampleIndex < samplesPerFrame; sampleIndex++) {
             *destination++ = *source++ * scaler;
@@ -134,7 +135,7 @@
                        float amplitude1,
                        float amplitude2) {
     float scaler = amplitude1;
-    float delta = (amplitude2 - amplitude1) / numFrames;
+    const float delta = (amplitude2 - amplitude1) / numFrames;
     for (int frameIndex = 0; frameIndex < numFrames; frameIndex++) {
         for (int sampleIndex = 0; sampleIndex < samplesPerFrame; sampleIndex++) {
             float sample = *source++;
@@ -158,7 +159,7 @@
                        float amplitude2) {
     // Because we are converting from int16 to 1nt16, we do not have to scale by 1/32768.
     float scaler = amplitude1;
-    float delta = (amplitude2 - amplitude1) / numFrames;
+    const float delta = (amplitude2 - amplitude1) / numFrames;
     for (int frameIndex = 0; frameIndex < numFrames; frameIndex++) {
         for (int sampleIndex = 0; sampleIndex < samplesPerFrame; sampleIndex++) {
             // No need to clip because int16_t range is inherently limited.
@@ -169,6 +170,255 @@
     }
 }
 
+// *************************************************************************************
+// Convert Mono To Stereo at the same time as converting format.
+void AAudioConvert_formatMonoToStereo(const float *source,
+                                      int16_t *destination,
+                                      int32_t numFrames,
+                                      float amplitude) {
+    const float scaler = amplitude;
+    for (int i = 0; i < numFrames; i++) {
+        float sample = *source++;
+        int16_t sample16 = clipAndClampFloatToPcm16(sample, scaler);
+        *destination++ = sample16;
+        *destination++ = sample16;
+    }
+}
+
+void AAudioConvert_formatMonoToStereo(const float *source,
+                                      int16_t *destination,
+                                      int32_t numFrames,
+                                      float amplitude1,
+                                      float amplitude2) {
+    // divide by numFrames so that we almost reach amplitude2
+    const float delta = (amplitude2 - amplitude1) / numFrames;
+    for (int frameIndex = 0; frameIndex < numFrames; frameIndex++) {
+        const float scaler = amplitude1 + (frameIndex * delta);
+        const float sample = *source++;
+        int16_t sample16 = clipAndClampFloatToPcm16(sample, scaler);
+        *destination++ = sample16;
+        *destination++ = sample16;
+    }
+}
+
+void AAudioConvert_formatMonoToStereo(const int16_t *source,
+                                      float *destination,
+                                      int32_t numFrames,
+                                      float amplitude) {
+    const float scaler = amplitude / SHORT_SCALE;
+    for (int i = 0; i < numFrames; i++) {
+        float sample = source[i] * scaler;
+        *destination++ = sample;
+        *destination++ = sample;
+    }
+}
+
+// This code assumes amplitude1 and amplitude2 are between 0.0 and 1.0
+void AAudioConvert_formatMonoToStereo(const int16_t *source,
+                                      float *destination,
+                                      int32_t numFrames,
+                                      float amplitude1,
+                                      float amplitude2) {
+    const float scaler1 = amplitude1 / SHORT_SCALE;
+    const float delta = (amplitude2 - amplitude1) / (SHORT_SCALE * (float) numFrames);
+    for (int frameIndex = 0; frameIndex < numFrames; frameIndex++) {
+        float scaler = scaler1 + (frameIndex * delta);
+        float sample = source[frameIndex] * scaler;
+        *destination++ = sample;
+        *destination++ = sample;
+    }
+}
+
+// This code assumes amplitude1 and amplitude2 are between 0.0 and 1.0
+void AAudio_linearRampMonoToStereo(const float *source,
+                                   float *destination,
+                                   int32_t numFrames,
+                                   float amplitude1,
+                                   float amplitude2) {
+    const float delta = (amplitude2 - amplitude1) / numFrames;
+    for (int frameIndex = 0; frameIndex < numFrames; frameIndex++) {
+        float sample = *source++;
+
+        // Clip to valid range of a float sample to prevent excessive volume.
+        if (sample > MAX_HEADROOM) sample = MAX_HEADROOM;
+        else if (sample < MIN_HEADROOM) sample = MIN_HEADROOM;
+
+        const float scaler = amplitude1 + (frameIndex * delta);
+        float sampleScaled = sample * scaler;
+        *destination++ = sampleScaled;
+        *destination++ = sampleScaled;
+    }
+}
+
+// This code assumes amplitude1 and amplitude2 are between 0.0 and 1.0
+void AAudio_linearRampMonoToStereo(const int16_t *source,
+                                   int16_t *destination,
+                                   int32_t numFrames,
+                                   float amplitude1,
+                                   float amplitude2) {
+    // Because we are converting from int16 to 1nt16, we do not have to scale by 1/32768.
+    const float delta = (amplitude2 - amplitude1) / numFrames;
+    for (int frameIndex = 0; frameIndex < numFrames; frameIndex++) {
+        const float scaler = amplitude1 + (frameIndex * delta);
+        // No need to clip because int16_t range is inherently limited.
+        const float sample =  *source++ * scaler;
+        int16_t sample16 = (int16_t) roundf(sample);
+        *destination++ = sample16;
+        *destination++ = sample16;
+    }
+}
+
+// *************************************************************************************
+void AAudioDataConverter::convert(
+        const FormattedData &source,
+        const FormattedData &destination,
+        int32_t numFrames,
+        float levelFrom,
+        float levelTo) {
+
+    if (source.channelCount == 1 && destination.channelCount == 2) {
+        convertMonoToStereo(source,
+                            destination,
+                            numFrames,
+                            levelFrom,
+                            levelTo);
+    } else {
+        // We only support mono to stereo conversion. Otherwise source and destination
+        // must match.
+        assert(source.channelCount == destination.channelCount);
+        convertChannelsMatch(source,
+                             destination,
+                             numFrames,
+                             levelFrom,
+                             levelTo);
+    }
+}
+
+void AAudioDataConverter::convertMonoToStereo(
+        const FormattedData &source,
+        const FormattedData &destination,
+        int32_t numFrames,
+        float levelFrom,
+        float levelTo) {
+
+    // The formats are validated when the stream is opened so we do not have to
+    // check for illegal combinations here.
+    if (source.format == AAUDIO_FORMAT_PCM_FLOAT) {
+        if (destination.format == AAUDIO_FORMAT_PCM_FLOAT) {
+            AAudio_linearRampMonoToStereo(
+                    (const float *) source.data,
+                    (float *) destination.data,
+                    numFrames,
+                    levelFrom,
+                    levelTo);
+        } else if (destination.format == AAUDIO_FORMAT_PCM_I16) {
+            if (levelFrom != levelTo) {
+                AAudioConvert_formatMonoToStereo(
+                        (const float *) source.data,
+                        (int16_t *) destination.data,
+                        numFrames,
+                        levelFrom,
+                        levelTo);
+            } else {
+                AAudioConvert_formatMonoToStereo(
+                        (const float *) source.data,
+                        (int16_t *) destination.data,
+                        numFrames,
+                        levelTo);
+            }
+        }
+    } else if (source.format == AAUDIO_FORMAT_PCM_I16) {
+        if (destination.format == AAUDIO_FORMAT_PCM_FLOAT) {
+            if (levelFrom != levelTo) {
+                AAudioConvert_formatMonoToStereo(
+                        (const int16_t *) source.data,
+                        (float *) destination.data,
+                        numFrames,
+                        levelFrom,
+                        levelTo);
+            } else {
+                AAudioConvert_formatMonoToStereo(
+                        (const int16_t *) source.data,
+                        (float *) destination.data,
+                        numFrames,
+                        levelTo);
+            }
+        } else if (destination.format == AAUDIO_FORMAT_PCM_I16) {
+            AAudio_linearRampMonoToStereo(
+                    (const int16_t *) source.data,
+                    (int16_t *) destination.data,
+                    numFrames,
+                    levelFrom,
+                    levelTo);
+        }
+    }
+}
+
+void AAudioDataConverter::convertChannelsMatch(
+        const FormattedData &source,
+        const FormattedData &destination,
+        int32_t numFrames,
+        float levelFrom,
+        float levelTo) {
+    const int32_t numSamples = numFrames * source.channelCount;
+
+    // The formats are validated when the stream is opened so we do not have to
+    // check for illegal combinations here.
+    if (source.format == AAUDIO_FORMAT_PCM_FLOAT) {
+        if (destination.format == AAUDIO_FORMAT_PCM_FLOAT) {
+            AAudio_linearRamp(
+                    (const float *) source.data,
+                    (float *) destination.data,
+                    numFrames,
+                    source.channelCount,
+                    levelFrom,
+                    levelTo);
+        } else if (destination.format == AAUDIO_FORMAT_PCM_I16) {
+            if (levelFrom != levelTo) {
+                AAudioConvert_floatToPcm16(
+                        (const float *) source.data,
+                        (int16_t *) destination.data,
+                        numFrames,
+                        source.channelCount,
+                        levelFrom,
+                        levelTo);
+            } else {
+                AAudioConvert_floatToPcm16(
+                        (const float *) source.data,
+                        (int16_t *) destination.data,
+                        numSamples,
+                        levelTo);
+            }
+        }
+    } else if (source.format == AAUDIO_FORMAT_PCM_I16) {
+        if (destination.format == AAUDIO_FORMAT_PCM_FLOAT) {
+            if (levelFrom != levelTo) {
+                AAudioConvert_pcm16ToFloat(
+                        (const int16_t *) source.data,
+                        (float *) destination.data,
+                        numFrames,
+                        source.channelCount,
+                        levelFrom,
+                        levelTo);
+            } else {
+                AAudioConvert_pcm16ToFloat(
+                        (const int16_t *) source.data,
+                        (float *) destination.data,
+                        numSamples,
+                        levelTo);
+            }
+        } else if (destination.format == AAUDIO_FORMAT_PCM_I16) {
+            AAudio_linearRamp(
+                    (const int16_t *) source.data,
+                    (int16_t *) destination.data,
+                    numFrames,
+                    source.channelCount,
+                    levelFrom,
+                    levelTo);
+        }
+    }
+}
+
 status_t AAudioConvert_aaudioToAndroidStatus(aaudio_result_t result) {
     // This covers the case for AAUDIO_OK and for positive results.
     if (result >= 0) {
diff --git a/media/libaaudio/src/utility/AAudioUtilities.h b/media/libaaudio/src/utility/AAudioUtilities.h
index dc6a671..cea88fb 100644
--- a/media/libaaudio/src/utility/AAudioUtilities.h
+++ b/media/libaaudio/src/utility/AAudioUtilities.h
@@ -159,6 +159,41 @@
                        float amplitude1,
                        float amplitude2);
 
+class AAudioDataConverter {
+public:
+
+    struct FormattedData {
+
+        FormattedData(void *data, aaudio_format_t format, int32_t channelCount)
+            : data(data)
+            , format(format)
+            , channelCount(channelCount) {}
+
+        const void            *data = nullptr;
+        const aaudio_format_t  format = AAUDIO_FORMAT_UNSPECIFIED;
+        const int32_t          channelCount = 1;
+    };
+
+    static void convert(const FormattedData &source,
+                        const FormattedData &destination,
+                        int32_t numFrames,
+                        float levelFrom,
+                        float levelTo);
+
+private:
+    static void convertMonoToStereo(const FormattedData &source,
+                                    const FormattedData &destination,
+                                    int32_t numFrames,
+                                    float levelFrom,
+                                    float levelTo);
+
+    static void convertChannelsMatch(const FormattedData &source,
+                                     const FormattedData &destination,
+                                     int32_t numFrames,
+                                     float levelFrom,
+                                     float levelTo);
+};
+
 /**
  * Calculate the number of bytes and prevent numeric overflow.
  * @param numFrames frame count
diff --git a/media/libaudioclient/ToneGenerator.cpp b/media/libaudioclient/ToneGenerator.cpp
index 5a33975..5716727 100644
--- a/media/libaudioclient/ToneGenerator.cpp
+++ b/media/libaudioclient/ToneGenerator.cpp
@@ -1030,7 +1030,7 @@
     bool lResult = false;
     status_t lStatus;
 
-    if ((toneType < 0) || (toneType >= NUM_TONES))
+    if (toneType >= NUM_TONES)
         return lResult;
 
     toneType = getToneForRegion(toneType);
diff --git a/media/libaudiohal/4.0/Android.bp b/media/libaudiohal/4.0/Android.bp
new file mode 100644
index 0000000..3d104ab
--- /dev/null
+++ b/media/libaudiohal/4.0/Android.bp
@@ -0,0 +1,57 @@
+cc_library_shared {
+    name: "libaudiohal@4.0",
+
+    srcs: [
+        "DeviceHalLocal.cpp",
+        "DevicesFactoryHalHybrid.cpp",
+        "DevicesFactoryHalLocal.cpp",
+        "StreamHalLocal.cpp",
+
+        "ConversionHelperHidl.cpp",
+        "DeviceHalHidl.cpp",
+        "DevicesFactoryHalHidl.cpp",
+        "EffectBufferHalHidl.cpp",
+        "EffectHalHidl.cpp",
+        "EffectsFactoryHalHidl.cpp",
+        "StreamHalHidl.cpp",
+    ],
+
+    export_include_dirs: ["include"],
+
+    cflags: [
+        "-Wall",
+        "-Wextra",
+        "-Werror",
+    ],
+    shared_libs: [
+        "libaudiohal_deathhandler",
+        "libaudioutils",
+        "libcutils",
+        "liblog",
+        "libutils",
+        "libhardware",
+        "libbase",
+        "libfmq",
+        "libhwbinder",
+        "libhidlbase",
+        "libhidlmemory",
+        "libhidltransport",
+        "android.hardware.audio@4.0",
+        "android.hardware.audio.common-util",
+        "android.hardware.audio.common@4.0",
+        "android.hardware.audio.common@4.0-util",
+        "android.hardware.audio.effect@4.0",
+        "android.hidl.allocator@1.0",
+        "android.hidl.memory@1.0",
+        "libmedia_helper",
+        "libmediautils",
+    ],
+    header_libs: [
+        "android.hardware.audio.common.util@all-versions",
+        "libaudiohal_headers"
+    ],
+
+    export_shared_lib_headers: [
+        "libfmq",
+    ],
+}
diff --git a/media/libaudiohal/4.0/ConversionHelperHidl.cpp b/media/libaudiohal/4.0/ConversionHelperHidl.cpp
new file mode 100644
index 0000000..a3cc28f
--- /dev/null
+++ b/media/libaudiohal/4.0/ConversionHelperHidl.cpp
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <string.h>
+
+#define LOG_TAG "HalHidl"
+#include <media/AudioParameter.h>
+#include <utils/Log.h>
+
+#include "ConversionHelperHidl.h"
+
+using ::android::hardware::audio::V4_0::Result;
+
+namespace android {
+namespace V4_0 {
+
+// static
+status_t ConversionHelperHidl::keysFromHal(const String8& keys, hidl_vec<hidl_string> *hidlKeys) {
+    AudioParameter halKeys(keys);
+    if (halKeys.size() == 0) return BAD_VALUE;
+    hidlKeys->resize(halKeys.size());
+    //FIXME:  keyStreamSupportedChannels and keyStreamSupportedSamplingRates come with a
+    // "keyFormat=<value>" pair. We need to transform it into a single key string so that it is
+    // carried over to the legacy HAL via HIDL.
+    String8 value;
+    bool keepFormatValue = halKeys.size() == 2 &&
+         (halKeys.get(String8(AudioParameter::keyStreamSupportedChannels), value) == NO_ERROR ||
+         halKeys.get(String8(AudioParameter::keyStreamSupportedSamplingRates), value) == NO_ERROR);
+
+    for (size_t i = 0; i < halKeys.size(); ++i) {
+        String8 key;
+        status_t status = halKeys.getAt(i, key);
+        if (status != OK) return status;
+        if (keepFormatValue && key == AudioParameter::keyFormat) {
+            AudioParameter formatParam;
+            halKeys.getAt(i, key, value);
+            formatParam.add(key, value);
+            key = formatParam.toString();
+        }
+        (*hidlKeys)[i] = key.string();
+    }
+    return OK;
+}
+
+// static
+status_t ConversionHelperHidl::parametersFromHal(
+        const String8& kvPairs, hidl_vec<ParameterValue> *hidlParams) {
+    AudioParameter params(kvPairs);
+    if (params.size() == 0) return BAD_VALUE;
+    hidlParams->resize(params.size());
+    for (size_t i = 0; i < params.size(); ++i) {
+        String8 key, value;
+        status_t status = params.getAt(i, key, value);
+        if (status != OK) return status;
+        (*hidlParams)[i].key = key.string();
+        (*hidlParams)[i].value = value.string();
+    }
+    return OK;
+}
+
+// static
+void ConversionHelperHidl::parametersToHal(
+        const hidl_vec<ParameterValue>& parameters, String8 *values) {
+    AudioParameter params;
+    for (size_t i = 0; i < parameters.size(); ++i) {
+        params.add(String8(parameters[i].key.c_str()), String8(parameters[i].value.c_str()));
+    }
+    values->setTo(params.toString());
+}
+
+ConversionHelperHidl::ConversionHelperHidl(const char* className)
+        : mClassName(className) {
+}
+
+// static
+status_t ConversionHelperHidl::analyzeResult(const Result& result) {
+    switch (result) {
+        case Result::OK: return OK;
+        case Result::INVALID_ARGUMENTS: return BAD_VALUE;
+        case Result::INVALID_STATE: return NOT_ENOUGH_DATA;
+        case Result::NOT_INITIALIZED: return NO_INIT;
+        case Result::NOT_SUPPORTED: return INVALID_OPERATION;
+        default: return NO_INIT;
+    }
+}
+
+void ConversionHelperHidl::emitError(const char* funcName, const char* description) {
+    ALOGE("%s %p %s: %s (from rpc)", mClassName, this, funcName, description);
+}
+
+}  // namespace V4_0
+}  // namespace android
diff --git a/media/libaudiohal/4.0/ConversionHelperHidl.h b/media/libaudiohal/4.0/ConversionHelperHidl.h
new file mode 100644
index 0000000..ddc8569
--- /dev/null
+++ b/media/libaudiohal/4.0/ConversionHelperHidl.h
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2016 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_CONVERSION_HELPER_HIDL_4_0_H
+#define ANDROID_HARDWARE_CONVERSION_HELPER_HIDL_4_0_H
+
+#include <android/hardware/audio/4.0/types.h>
+#include <hidl/HidlSupport.h>
+#include <utils/String8.h>
+
+using ::android::hardware::audio::V4_0::ParameterValue;
+using ::android::hardware::Return;
+using ::android::hardware::hidl_string;
+using ::android::hardware::hidl_vec;
+
+namespace android {
+namespace V4_0 {
+
+class ConversionHelperHidl {
+  protected:
+    static status_t keysFromHal(const String8& keys, hidl_vec<hidl_string> *hidlKeys);
+    static status_t parametersFromHal(const String8& kvPairs, hidl_vec<ParameterValue> *hidlParams);
+    static void parametersToHal(const hidl_vec<ParameterValue>& parameters, String8 *values);
+
+    ConversionHelperHidl(const char* className);
+
+    template<typename R, typename T>
+    status_t processReturn(const char* funcName, const Return<R>& ret, T *retval) {
+        if (ret.isOk()) {
+            // This way it also works for enum class to unscoped enum conversion.
+            *retval = static_cast<T>(static_cast<R>(ret));
+            return OK;
+        }
+        return processReturn(funcName, ret);
+    }
+
+    template<typename T>
+    status_t processReturn(const char* funcName, const Return<T>& ret) {
+        if (!ret.isOk()) {
+            emitError(funcName, ret.description().c_str());
+        }
+        return ret.isOk() ? OK : FAILED_TRANSACTION;
+    }
+
+    status_t processReturn(const char* funcName, const Return<hardware::audio::V4_0::Result>& ret) {
+        if (!ret.isOk()) {
+            emitError(funcName, ret.description().c_str());
+        }
+        return ret.isOk() ? analyzeResult(ret) : FAILED_TRANSACTION;
+    }
+
+    template<typename T>
+    status_t processReturn(
+            const char* funcName, const Return<T>& ret, hardware::audio::V4_0::Result retval) {
+        if (!ret.isOk()) {
+            emitError(funcName, ret.description().c_str());
+        }
+        return ret.isOk() ? analyzeResult(retval) : FAILED_TRANSACTION;
+    }
+
+  private:
+    const char* mClassName;
+
+    static status_t analyzeResult(const hardware::audio::V4_0::Result& result);
+
+    void emitError(const char* funcName, const char* description);
+};
+
+}  // namespace V4_0
+}  // namespace android
+
+#endif // ANDROID_HARDWARE_CONVERSION_HELPER_HIDL_4_0_H
diff --git a/media/libaudiohal/4.0/DeviceHalHidl.cpp b/media/libaudiohal/4.0/DeviceHalHidl.cpp
new file mode 100644
index 0000000..8da1051
--- /dev/null
+++ b/media/libaudiohal/4.0/DeviceHalHidl.cpp
@@ -0,0 +1,372 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdio.h>
+
+#define LOG_TAG "DeviceHalHidl"
+//#define LOG_NDEBUG 0
+
+#include <android/hardware/audio/4.0/IPrimaryDevice.h>
+#include <cutils/native_handle.h>
+#include <hwbinder/IPCThreadState.h>
+#include <utils/Log.h>
+
+#include <common/all-versions/VersionUtils.h>
+
+#include "DeviceHalHidl.h"
+#include "HidlUtils.h"
+#include "StreamHalHidl.h"
+#include "VersionUtils.h"
+
+using ::android::hardware::audio::common::V4_0::AudioConfig;
+using ::android::hardware::audio::common::V4_0::AudioDevice;
+using ::android::hardware::audio::common::V4_0::AudioInputFlag;
+using ::android::hardware::audio::common::V4_0::AudioOutputFlag;
+using ::android::hardware::audio::common::V4_0::AudioPatchHandle;
+using ::android::hardware::audio::common::V4_0::AudioPort;
+using ::android::hardware::audio::common::V4_0::AudioPortConfig;
+using ::android::hardware::audio::common::V4_0::AudioMode;
+using ::android::hardware::audio::common::V4_0::AudioSource;
+using ::android::hardware::audio::common::V4_0::HidlUtils;
+using ::android::hardware::audio::common::utils::mkEnumConverter;
+using ::android::hardware::audio::V4_0::DeviceAddress;
+using ::android::hardware::audio::V4_0::IPrimaryDevice;
+using ::android::hardware::audio::V4_0::ParameterValue;
+using ::android::hardware::audio::V4_0::Result;
+using ::android::hardware::audio::V4_0::SinkMetadata;
+using ::android::hardware::hidl_string;
+using ::android::hardware::hidl_vec;
+
+namespace android {
+namespace V4_0 {
+
+namespace {
+
+status_t deviceAddressFromHal(
+        audio_devices_t device, const char* halAddress, DeviceAddress* address) {
+    address->device = AudioDevice(device);
+
+    if (address == nullptr || strnlen(halAddress, AUDIO_DEVICE_MAX_ADDRESS_LEN) == 0) {
+        return OK;
+    }
+    const bool isInput = (device & AUDIO_DEVICE_BIT_IN) != 0;
+    if (isInput) device &= ~AUDIO_DEVICE_BIT_IN;
+    if ((!isInput && (device & AUDIO_DEVICE_OUT_ALL_A2DP) != 0)
+            || (isInput && (device & AUDIO_DEVICE_IN_BLUETOOTH_A2DP) != 0)) {
+        int status = sscanf(halAddress,
+                "%hhX:%hhX:%hhX:%hhX:%hhX:%hhX",
+                &address->address.mac[0], &address->address.mac[1], &address->address.mac[2],
+                &address->address.mac[3], &address->address.mac[4], &address->address.mac[5]);
+        return status == 6 ? OK : BAD_VALUE;
+    } else if ((!isInput && (device & AUDIO_DEVICE_OUT_IP) != 0)
+            || (isInput && (device & AUDIO_DEVICE_IN_IP) != 0)) {
+        int status = sscanf(halAddress,
+                "%hhu.%hhu.%hhu.%hhu",
+                &address->address.ipv4[0], &address->address.ipv4[1],
+                &address->address.ipv4[2], &address->address.ipv4[3]);
+        return status == 4 ? OK : BAD_VALUE;
+    } else if ((!isInput && (device & AUDIO_DEVICE_OUT_ALL_USB)) != 0
+            || (isInput && (device & AUDIO_DEVICE_IN_ALL_USB)) != 0) {
+        int status = sscanf(halAddress,
+                "card=%d;device=%d",
+                &address->address.alsa.card, &address->address.alsa.device);
+        return status == 2 ? OK : BAD_VALUE;
+    } else if ((!isInput && (device & AUDIO_DEVICE_OUT_BUS) != 0)
+            || (isInput && (device & AUDIO_DEVICE_IN_BUS) != 0)) {
+        if (halAddress != NULL) {
+            address->busAddress = halAddress;
+            return OK;
+        }
+        return BAD_VALUE;
+    } else if ((!isInput && (device & AUDIO_DEVICE_OUT_REMOTE_SUBMIX)) != 0
+            || (isInput && (device & AUDIO_DEVICE_IN_REMOTE_SUBMIX) != 0)) {
+        if (halAddress != NULL) {
+            address->rSubmixAddress = halAddress;
+            return OK;
+        }
+        return BAD_VALUE;
+    }
+    return OK;
+}
+
+}  // namespace
+
+DeviceHalHidl::DeviceHalHidl(const sp<IDevice>& device)
+        : ConversionHelperHidl("Device"), mDevice(device),
+          mPrimaryDevice(IPrimaryDevice::castFrom(device)) {
+}
+
+DeviceHalHidl::~DeviceHalHidl() {
+    if (mDevice != 0) {
+        mDevice.clear();
+        hardware::IPCThreadState::self()->flushCommands();
+    }
+}
+
+status_t DeviceHalHidl::getSupportedDevices(uint32_t*) {
+    // Obsolete.
+    return INVALID_OPERATION;
+}
+
+status_t DeviceHalHidl::initCheck() {
+    if (mDevice == 0) return NO_INIT;
+    return processReturn("initCheck", mDevice->initCheck());
+}
+
+status_t DeviceHalHidl::setVoiceVolume(float volume) {
+    if (mDevice == 0) return NO_INIT;
+    if (mPrimaryDevice == 0) return INVALID_OPERATION;
+    return processReturn("setVoiceVolume", mPrimaryDevice->setVoiceVolume(volume));
+}
+
+status_t DeviceHalHidl::setMasterVolume(float volume) {
+    if (mDevice == 0) return NO_INIT;
+    if (mPrimaryDevice == 0) return INVALID_OPERATION;
+    return processReturn("setMasterVolume", mPrimaryDevice->setMasterVolume(volume));
+}
+
+status_t DeviceHalHidl::getMasterVolume(float *volume) {
+    if (mDevice == 0) return NO_INIT;
+    if (mPrimaryDevice == 0) return INVALID_OPERATION;
+    Result retval;
+    Return<void> ret = mPrimaryDevice->getMasterVolume(
+            [&](Result r, float v) {
+                retval = r;
+                if (retval == Result::OK) {
+                    *volume = v;
+                }
+            });
+    return processReturn("getMasterVolume", ret, retval);
+}
+
+status_t DeviceHalHidl::setMode(audio_mode_t mode) {
+    if (mDevice == 0) return NO_INIT;
+    if (mPrimaryDevice == 0) return INVALID_OPERATION;
+    return processReturn("setMode", mPrimaryDevice->setMode(AudioMode(mode)));
+}
+
+status_t DeviceHalHidl::setMicMute(bool state) {
+    if (mDevice == 0) return NO_INIT;
+    return processReturn("setMicMute", mDevice->setMicMute(state));
+}
+
+status_t DeviceHalHidl::getMicMute(bool *state) {
+    if (mDevice == 0) return NO_INIT;
+    Result retval;
+    Return<void> ret = mDevice->getMicMute(
+            [&](Result r, bool mute) {
+                retval = r;
+                if (retval == Result::OK) {
+                    *state = mute;
+                }
+            });
+    return processReturn("getMicMute", ret, retval);
+}
+
+status_t DeviceHalHidl::setMasterMute(bool state) {
+    if (mDevice == 0) return NO_INIT;
+    return processReturn("setMasterMute", mDevice->setMasterMute(state));
+}
+
+status_t DeviceHalHidl::getMasterMute(bool *state) {
+    if (mDevice == 0) return NO_INIT;
+    Result retval;
+    Return<void> ret = mDevice->getMasterMute(
+            [&](Result r, bool mute) {
+                retval = r;
+                if (retval == Result::OK) {
+                    *state = mute;
+                }
+            });
+    return processReturn("getMasterMute", ret, retval);
+}
+
+status_t DeviceHalHidl::setParameters(const String8& kvPairs) {
+    if (mDevice == 0) return NO_INIT;
+    hidl_vec<ParameterValue> hidlParams;
+    status_t status = parametersFromHal(kvPairs, &hidlParams);
+    if (status != OK) return status;
+    // TODO: change the API so that context and kvPairs are separated
+    return processReturn("setParameters",
+                         utils::setParameters(mDevice, {} /* context */, hidlParams));
+}
+
+status_t DeviceHalHidl::getParameters(const String8& keys, String8 *values) {
+    values->clear();
+    if (mDevice == 0) return NO_INIT;
+    hidl_vec<hidl_string> hidlKeys;
+    status_t status = keysFromHal(keys, &hidlKeys);
+    if (status != OK) return status;
+    Result retval;
+    Return<void> ret = utils::getParameters(mDevice,
+            {} /* context */,
+            hidlKeys,
+            [&](Result r, const hidl_vec<ParameterValue>& parameters) {
+                retval = r;
+                if (retval == Result::OK) {
+                    parametersToHal(parameters, values);
+                }
+            });
+    return processReturn("getParameters", ret, retval);
+}
+
+status_t DeviceHalHidl::getInputBufferSize(
+        const struct audio_config *config, size_t *size) {
+    if (mDevice == 0) return NO_INIT;
+    AudioConfig hidlConfig;
+    HidlUtils::audioConfigFromHal(*config, &hidlConfig);
+    Result retval;
+    Return<void> ret = mDevice->getInputBufferSize(
+            hidlConfig,
+            [&](Result r, uint64_t bufferSize) {
+                retval = r;
+                if (retval == Result::OK) {
+                    *size = static_cast<size_t>(bufferSize);
+                }
+            });
+    return processReturn("getInputBufferSize", ret, retval);
+}
+
+status_t DeviceHalHidl::openOutputStream(
+        audio_io_handle_t handle,
+        audio_devices_t devices,
+        audio_output_flags_t flags,
+        struct audio_config *config,
+        const char *address,
+        sp<StreamOutHalInterface> *outStream) {
+    if (mDevice == 0) return NO_INIT;
+    DeviceAddress hidlDevice;
+    status_t status = deviceAddressFromHal(devices, address, &hidlDevice);
+    if (status != OK) return status;
+    AudioConfig hidlConfig;
+    HidlUtils::audioConfigFromHal(*config, &hidlConfig);
+    Result retval = Result::NOT_INITIALIZED;
+    Return<void> ret = mDevice->openOutputStream(
+            handle,
+            hidlDevice,
+            hidlConfig,
+            mkEnumConverter<AudioOutputFlag>(flags),
+            {} /* metadata */,
+            [&](Result r, const sp<IStreamOut>& result, const AudioConfig& suggestedConfig) {
+                retval = r;
+                if (retval == Result::OK) {
+                    *outStream = new StreamOutHalHidl(result);
+                }
+                HidlUtils::audioConfigToHal(suggestedConfig, config);
+            });
+    return processReturn("openOutputStream", ret, retval);
+}
+
+status_t DeviceHalHidl::openInputStream(
+        audio_io_handle_t handle,
+        audio_devices_t devices,
+        struct audio_config *config,
+        audio_input_flags_t flags,
+        const char *address,
+        audio_source_t source,
+        sp<StreamInHalInterface> *inStream) {
+    if (mDevice == 0) return NO_INIT;
+    DeviceAddress hidlDevice;
+    status_t status = deviceAddressFromHal(devices, address, &hidlDevice);
+    if (status != OK) return status;
+    AudioConfig hidlConfig;
+    HidlUtils::audioConfigFromHal(*config, &hidlConfig);
+    Result retval = Result::NOT_INITIALIZED;
+    // TODO: correctly propagate the tracks sources and volume
+    //       for now, only send the main source at 1dbfs
+    SinkMetadata metadata = {{{AudioSource(source), 1}}};
+    Return<void> ret = mDevice->openInputStream(
+            handle,
+            hidlDevice,
+            hidlConfig,
+            flags,
+            metadata,
+            [&](Result r, const sp<IStreamIn>& result, const AudioConfig& suggestedConfig) {
+                retval = r;
+                if (retval == Result::OK) {
+                    *inStream = new StreamInHalHidl(result);
+                }
+                HidlUtils::audioConfigToHal(suggestedConfig, config);
+            });
+    return processReturn("openInputStream", ret, retval);
+}
+
+status_t DeviceHalHidl::supportsAudioPatches(bool *supportsPatches) {
+    if (mDevice == 0) return NO_INIT;
+    return processReturn("supportsAudioPatches", mDevice->supportsAudioPatches(), supportsPatches);
+}
+
+status_t DeviceHalHidl::createAudioPatch(
+        unsigned int num_sources,
+        const struct audio_port_config *sources,
+        unsigned int num_sinks,
+        const struct audio_port_config *sinks,
+        audio_patch_handle_t *patch) {
+    if (mDevice == 0) return NO_INIT;
+    hidl_vec<AudioPortConfig> hidlSources, hidlSinks;
+    HidlUtils::audioPortConfigsFromHal(num_sources, sources, &hidlSources);
+    HidlUtils::audioPortConfigsFromHal(num_sinks, sinks, &hidlSinks);
+    Result retval;
+    Return<void> ret = mDevice->createAudioPatch(
+            hidlSources, hidlSinks,
+            [&](Result r, AudioPatchHandle hidlPatch) {
+                retval = r;
+                if (retval == Result::OK) {
+                    *patch = static_cast<audio_patch_handle_t>(hidlPatch);
+                }
+            });
+    return processReturn("createAudioPatch", ret, retval);
+}
+
+status_t DeviceHalHidl::releaseAudioPatch(audio_patch_handle_t patch) {
+    if (mDevice == 0) return NO_INIT;
+    return processReturn("releaseAudioPatch", mDevice->releaseAudioPatch(patch));
+}
+
+status_t DeviceHalHidl::getAudioPort(struct audio_port *port) {
+    if (mDevice == 0) return NO_INIT;
+    AudioPort hidlPort;
+    HidlUtils::audioPortFromHal(*port, &hidlPort);
+    Result retval;
+    Return<void> ret = mDevice->getAudioPort(
+            hidlPort,
+            [&](Result r, const AudioPort& p) {
+                retval = r;
+                if (retval == Result::OK) {
+                    HidlUtils::audioPortToHal(p, port);
+                }
+            });
+    return processReturn("getAudioPort", ret, retval);
+}
+
+status_t DeviceHalHidl::setAudioPortConfig(const struct audio_port_config *config) {
+    if (mDevice == 0) return NO_INIT;
+    AudioPortConfig hidlConfig;
+    HidlUtils::audioPortConfigFromHal(*config, &hidlConfig);
+    return processReturn("setAudioPortConfig", mDevice->setAudioPortConfig(hidlConfig));
+}
+
+status_t DeviceHalHidl::dump(int fd) {
+    if (mDevice == 0) return NO_INIT;
+    native_handle_t* hidlHandle = native_handle_create(1, 0);
+    hidlHandle->data[0] = fd;
+    Return<void> ret = mDevice->debug(hidlHandle, {} /* options */);
+    native_handle_delete(hidlHandle);
+    return processReturn("dump", ret);
+}
+
+} // namespace V4_0
+} // namespace android
diff --git a/media/libaudiohal/4.0/DeviceHalHidl.h b/media/libaudiohal/4.0/DeviceHalHidl.h
new file mode 100644
index 0000000..f460add
--- /dev/null
+++ b/media/libaudiohal/4.0/DeviceHalHidl.h
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2016 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_DEVICE_HAL_HIDL_4_0_H
+#define ANDROID_HARDWARE_DEVICE_HAL_HIDL_4_0_H
+
+#include <android/hardware/audio/4.0/IDevice.h>
+#include <android/hardware/audio/4.0/IPrimaryDevice.h>
+#include <media/audiohal/DeviceHalInterface.h>
+
+#include "ConversionHelperHidl.h"
+
+using ::android::hardware::audio::V4_0::IDevice;
+using ::android::hardware::audio::V4_0::IPrimaryDevice;
+using ::android::hardware::Return;
+
+namespace android {
+namespace V4_0 {
+
+class DeviceHalHidl : public DeviceHalInterface, public ConversionHelperHidl
+{
+  public:
+    // Sets the value of 'devices' to a bitmask of 1 or more values of audio_devices_t.
+    virtual status_t getSupportedDevices(uint32_t *devices);
+
+    // Check to see if the audio hardware interface has been initialized.
+    virtual status_t initCheck();
+
+    // Set the audio volume of a voice call. Range is between 0.0 and 1.0.
+    virtual status_t setVoiceVolume(float volume);
+
+    // Set the audio volume for all audio activities other than voice call.
+    virtual status_t setMasterVolume(float volume);
+
+    // Get the current master volume value for the HAL.
+    virtual status_t getMasterVolume(float *volume);
+
+    // Called when the audio mode changes.
+    virtual status_t setMode(audio_mode_t mode);
+
+    // Muting control.
+    virtual status_t setMicMute(bool state);
+    virtual status_t getMicMute(bool *state);
+    virtual status_t setMasterMute(bool state);
+    virtual status_t getMasterMute(bool *state);
+
+    // Set global audio parameters.
+    virtual status_t setParameters(const String8& kvPairs);
+
+    // Get global audio parameters.
+    virtual status_t getParameters(const String8& keys, String8 *values);
+
+    // Returns audio input buffer size according to parameters passed.
+    virtual status_t getInputBufferSize(const struct audio_config *config,
+            size_t *size);
+
+    // Creates and opens the audio hardware output stream. The stream is closed
+    // by releasing all references to the returned object.
+    virtual status_t openOutputStream(
+            audio_io_handle_t handle,
+            audio_devices_t devices,
+            audio_output_flags_t flags,
+            struct audio_config *config,
+            const char *address,
+            sp<StreamOutHalInterface> *outStream);
+
+    // Creates and opens the audio hardware input stream. The stream is closed
+    // by releasing all references to the returned object.
+    virtual status_t openInputStream(
+            audio_io_handle_t handle,
+            audio_devices_t devices,
+            struct audio_config *config,
+            audio_input_flags_t flags,
+            const char *address,
+            audio_source_t source,
+            sp<StreamInHalInterface> *inStream);
+
+    // Returns whether createAudioPatch and releaseAudioPatch operations are supported.
+    virtual status_t supportsAudioPatches(bool *supportsPatches);
+
+    // Creates an audio patch between several source and sink ports.
+    virtual status_t createAudioPatch(
+            unsigned int num_sources,
+            const struct audio_port_config *sources,
+            unsigned int num_sinks,
+            const struct audio_port_config *sinks,
+            audio_patch_handle_t *patch);
+
+    // Releases an audio patch.
+    virtual status_t releaseAudioPatch(audio_patch_handle_t patch);
+
+    // Fills the list of supported attributes for a given audio port.
+    virtual status_t getAudioPort(struct audio_port *port);
+
+    // Set audio port configuration.
+    virtual status_t setAudioPortConfig(const struct audio_port_config *config);
+
+    virtual status_t dump(int fd);
+
+  private:
+    friend class DevicesFactoryHalHidl;
+    sp<IDevice> mDevice;
+    sp<IPrimaryDevice> mPrimaryDevice;  // Null if it's not a primary device.
+
+    // Can not be constructed directly by clients.
+    explicit DeviceHalHidl(const sp<IDevice>& device);
+
+    // The destructor automatically closes the device.
+    virtual ~DeviceHalHidl();
+};
+
+} // namespace V4_0
+} // namespace android
+
+#endif // ANDROID_HARDWARE_DEVICE_HAL_HIDL_4_0_H
diff --git a/media/libaudiohal/4.0/DeviceHalLocal.cpp b/media/libaudiohal/4.0/DeviceHalLocal.cpp
new file mode 100644
index 0000000..e64eee1
--- /dev/null
+++ b/media/libaudiohal/4.0/DeviceHalLocal.cpp
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2016 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_TAG "DeviceHalLocal"
+//#define LOG_NDEBUG 0
+
+#include <utils/Log.h>
+
+#include "DeviceHalLocal.h"
+#include "StreamHalLocal.h"
+
+namespace android {
+namespace V4_0 {
+
+DeviceHalLocal::DeviceHalLocal(audio_hw_device_t *dev)
+        : mDev(dev) {
+}
+
+DeviceHalLocal::~DeviceHalLocal() {
+    int status = audio_hw_device_close(mDev);
+    ALOGW_IF(status, "Error closing audio hw device %p: %s", mDev, strerror(-status));
+    mDev = 0;
+}
+
+status_t DeviceHalLocal::getSupportedDevices(uint32_t *devices) {
+    if (mDev->get_supported_devices == NULL) return INVALID_OPERATION;
+    *devices = mDev->get_supported_devices(mDev);
+    return OK;
+}
+
+status_t DeviceHalLocal::initCheck() {
+    return mDev->init_check(mDev);
+}
+
+status_t DeviceHalLocal::setVoiceVolume(float volume) {
+    return mDev->set_voice_volume(mDev, volume);
+}
+
+status_t DeviceHalLocal::setMasterVolume(float volume) {
+    if (mDev->set_master_volume == NULL) return INVALID_OPERATION;
+    return mDev->set_master_volume(mDev, volume);
+}
+
+status_t DeviceHalLocal::getMasterVolume(float *volume) {
+    if (mDev->get_master_volume == NULL) return INVALID_OPERATION;
+    return mDev->get_master_volume(mDev, volume);
+}
+
+status_t DeviceHalLocal::setMode(audio_mode_t mode) {
+    return mDev->set_mode(mDev, mode);
+}
+
+status_t DeviceHalLocal::setMicMute(bool state) {
+    return mDev->set_mic_mute(mDev, state);
+}
+
+status_t DeviceHalLocal::getMicMute(bool *state) {
+    return mDev->get_mic_mute(mDev, state);
+}
+
+status_t DeviceHalLocal::setMasterMute(bool state) {
+    if (mDev->set_master_mute == NULL) return INVALID_OPERATION;
+    return mDev->set_master_mute(mDev, state);
+}
+
+status_t DeviceHalLocal::getMasterMute(bool *state) {
+    if (mDev->get_master_mute == NULL) return INVALID_OPERATION;
+    return mDev->get_master_mute(mDev, state);
+}
+
+status_t DeviceHalLocal::setParameters(const String8& kvPairs) {
+    return mDev->set_parameters(mDev, kvPairs.string());
+}
+
+status_t DeviceHalLocal::getParameters(const String8& keys, String8 *values) {
+    char *halValues = mDev->get_parameters(mDev, keys.string());
+    if (halValues != NULL) {
+        values->setTo(halValues);
+        free(halValues);
+    } else {
+        values->clear();
+    }
+    return OK;
+}
+
+status_t DeviceHalLocal::getInputBufferSize(
+        const struct audio_config *config, size_t *size) {
+    *size = mDev->get_input_buffer_size(mDev, config);
+    return OK;
+}
+
+status_t DeviceHalLocal::openOutputStream(
+        audio_io_handle_t handle,
+        audio_devices_t devices,
+        audio_output_flags_t flags,
+        struct audio_config *config,
+        const char *address,
+        sp<StreamOutHalInterface> *outStream) {
+    audio_stream_out_t *halStream;
+    ALOGV("open_output_stream handle: %d devices: %x flags: %#x"
+            "srate: %d format %#x channels %x address %s",
+            handle, devices, flags,
+            config->sample_rate, config->format, config->channel_mask,
+            address);
+    int openResut = mDev->open_output_stream(
+            mDev, handle, devices, flags, config, &halStream, address);
+    if (openResut == OK) {
+        *outStream = new StreamOutHalLocal(halStream, this);
+    }
+    ALOGV("open_output_stream status %d stream %p", openResut, halStream);
+    return openResut;
+}
+
+status_t DeviceHalLocal::openInputStream(
+        audio_io_handle_t handle,
+        audio_devices_t devices,
+        struct audio_config *config,
+        audio_input_flags_t flags,
+        const char *address,
+        audio_source_t source,
+        sp<StreamInHalInterface> *inStream) {
+    audio_stream_in_t *halStream;
+    ALOGV("open_input_stream handle: %d devices: %x flags: %#x "
+            "srate: %d format %#x channels %x address %s source %d",
+            handle, devices, flags,
+            config->sample_rate, config->format, config->channel_mask,
+            address, source);
+    int openResult = mDev->open_input_stream(
+            mDev, handle, devices, config, &halStream, flags, address, source);
+    if (openResult == OK) {
+        *inStream = new StreamInHalLocal(halStream, this);
+    }
+    ALOGV("open_input_stream status %d stream %p", openResult, inStream);
+    return openResult;
+}
+
+status_t DeviceHalLocal::supportsAudioPatches(bool *supportsPatches) {
+    *supportsPatches = version() >= AUDIO_DEVICE_API_VERSION_3_0;
+    return OK;
+}
+
+status_t DeviceHalLocal::createAudioPatch(
+        unsigned int num_sources,
+        const struct audio_port_config *sources,
+        unsigned int num_sinks,
+        const struct audio_port_config *sinks,
+        audio_patch_handle_t *patch) {
+    if (version() >= AUDIO_DEVICE_API_VERSION_3_0) {
+        return mDev->create_audio_patch(
+                mDev, num_sources, sources, num_sinks, sinks, patch);
+    } else {
+        return INVALID_OPERATION;
+    }
+}
+
+status_t DeviceHalLocal::releaseAudioPatch(audio_patch_handle_t patch) {
+    if (version() >= AUDIO_DEVICE_API_VERSION_3_0) {
+        return mDev->release_audio_patch(mDev, patch);
+    } else {
+        return INVALID_OPERATION;
+    }
+}
+
+status_t DeviceHalLocal::getAudioPort(struct audio_port *port) {
+    return mDev->get_audio_port(mDev, port);
+}
+
+status_t DeviceHalLocal::setAudioPortConfig(const struct audio_port_config *config) {
+    if (version() >= AUDIO_DEVICE_API_VERSION_3_0)
+        return mDev->set_audio_port_config(mDev, config);
+    else
+        return INVALID_OPERATION;
+}
+
+status_t DeviceHalLocal::dump(int fd) {
+    return mDev->dump(mDev, fd);
+}
+
+void DeviceHalLocal::closeOutputStream(struct audio_stream_out *stream_out) {
+    mDev->close_output_stream(mDev, stream_out);
+}
+
+void DeviceHalLocal::closeInputStream(struct audio_stream_in *stream_in) {
+    mDev->close_input_stream(mDev, stream_in);
+}
+
+} // namespace V4_0
+} // namespace android
diff --git a/media/libaudiohal/4.0/DeviceHalLocal.h b/media/libaudiohal/4.0/DeviceHalLocal.h
new file mode 100644
index 0000000..daafdc7
--- /dev/null
+++ b/media/libaudiohal/4.0/DeviceHalLocal.h
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2016 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_DEVICE_HAL_LOCAL_4_0_H
+#define ANDROID_HARDWARE_DEVICE_HAL_LOCAL_4_0_H
+
+#include <hardware/audio.h>
+#include <media/audiohal/DeviceHalInterface.h>
+
+namespace android {
+namespace V4_0 {
+
+class DeviceHalLocal : public DeviceHalInterface
+{
+  public:
+    // Sets the value of 'devices' to a bitmask of 1 or more values of audio_devices_t.
+    virtual status_t getSupportedDevices(uint32_t *devices);
+
+    // Check to see if the audio hardware interface has been initialized.
+    virtual status_t initCheck();
+
+    // Set the audio volume of a voice call. Range is between 0.0 and 1.0.
+    virtual status_t setVoiceVolume(float volume);
+
+    // Set the audio volume for all audio activities other than voice call.
+    virtual status_t setMasterVolume(float volume);
+
+    // Get the current master volume value for the HAL.
+    virtual status_t getMasterVolume(float *volume);
+
+    // Called when the audio mode changes.
+    virtual status_t setMode(audio_mode_t mode);
+
+    // Muting control.
+    virtual status_t setMicMute(bool state);
+    virtual status_t getMicMute(bool *state);
+    virtual status_t setMasterMute(bool state);
+    virtual status_t getMasterMute(bool *state);
+
+    // Set global audio parameters.
+    virtual status_t setParameters(const String8& kvPairs);
+
+    // Get global audio parameters.
+    virtual status_t getParameters(const String8& keys, String8 *values);
+
+    // Returns audio input buffer size according to parameters passed.
+    virtual status_t getInputBufferSize(const struct audio_config *config,
+            size_t *size);
+
+    // Creates and opens the audio hardware output stream. The stream is closed
+    // by releasing all references to the returned object.
+    virtual status_t openOutputStream(
+            audio_io_handle_t handle,
+            audio_devices_t devices,
+            audio_output_flags_t flags,
+            struct audio_config *config,
+            const char *address,
+            sp<StreamOutHalInterface> *outStream);
+
+    // Creates and opens the audio hardware input stream. The stream is closed
+    // by releasing all references to the returned object.
+    virtual status_t openInputStream(
+            audio_io_handle_t handle,
+            audio_devices_t devices,
+            struct audio_config *config,
+            audio_input_flags_t flags,
+            const char *address,
+            audio_source_t source,
+            sp<StreamInHalInterface> *inStream);
+
+    // Returns whether createAudioPatch and releaseAudioPatch operations are supported.
+    virtual status_t supportsAudioPatches(bool *supportsPatches);
+
+    // Creates an audio patch between several source and sink ports.
+    virtual status_t createAudioPatch(
+            unsigned int num_sources,
+            const struct audio_port_config *sources,
+            unsigned int num_sinks,
+            const struct audio_port_config *sinks,
+            audio_patch_handle_t *patch);
+
+    // Releases an audio patch.
+    virtual status_t releaseAudioPatch(audio_patch_handle_t patch);
+
+    // Fills the list of supported attributes for a given audio port.
+    virtual status_t getAudioPort(struct audio_port *port);
+
+    // Set audio port configuration.
+    virtual status_t setAudioPortConfig(const struct audio_port_config *config);
+
+    virtual status_t dump(int fd);
+
+    void closeOutputStream(struct audio_stream_out *stream_out);
+    void closeInputStream(struct audio_stream_in *stream_in);
+
+  private:
+    audio_hw_device_t *mDev;
+
+    friend class DevicesFactoryHalLocal;
+
+    // Can not be constructed directly by clients.
+    explicit DeviceHalLocal(audio_hw_device_t *dev);
+
+    // The destructor automatically closes the device.
+    virtual ~DeviceHalLocal();
+
+    uint32_t version() const { return mDev->common.version; }
+};
+
+} // namespace V4_0
+} // namespace android
+
+#endif // ANDROID_HARDWARE_DEVICE_HAL_LOCAL_4_0_H
diff --git a/media/libaudiohal/4.0/DevicesFactoryHalHidl.cpp b/media/libaudiohal/4.0/DevicesFactoryHalHidl.cpp
new file mode 100644
index 0000000..c83194e
--- /dev/null
+++ b/media/libaudiohal/4.0/DevicesFactoryHalHidl.cpp
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <string.h>
+
+#define LOG_TAG "DevicesFactoryHalHidl"
+//#define LOG_NDEBUG 0
+
+#include <android/hardware/audio/4.0/IDevice.h>
+#include <media/audiohal/hidl/HalDeathHandler.h>
+#include <utils/Log.h>
+
+#include "ConversionHelperHidl.h"
+#include "DeviceHalHidl.h"
+#include "DevicesFactoryHalHidl.h"
+
+using ::android::hardware::audio::V4_0::IDevice;
+using ::android::hardware::audio::V4_0::Result;
+using ::android::hardware::Return;
+
+namespace android {
+namespace V4_0 {
+
+DevicesFactoryHalHidl::DevicesFactoryHalHidl() {
+    mDevicesFactory = IDevicesFactory::getService();
+    if (mDevicesFactory != 0) {
+        // It is assumed that DevicesFactory is owned by AudioFlinger
+        // and thus have the same lifespan.
+        mDevicesFactory->linkToDeath(HalDeathHandler::getInstance(), 0 /*cookie*/);
+    } else {
+        ALOGE("Failed to obtain IDevicesFactory service, terminating process.");
+        exit(1);
+    }
+    // The MSD factory is optional
+    mDevicesFactoryMsd = IDevicesFactory::getService(AUDIO_HAL_SERVICE_NAME_MSD);
+    // TODO: Register death handler, and add 'restart' directive to audioserver.rc
+}
+
+DevicesFactoryHalHidl::~DevicesFactoryHalHidl() {
+}
+
+status_t DevicesFactoryHalHidl::openDevice(const char *name, sp<DeviceHalInterface> *device) {
+    if (mDevicesFactory == 0) return NO_INIT;
+    Result retval = Result::NOT_INITIALIZED;
+    Return<void> ret = mDevicesFactory->openDevice(
+            name,
+            [&](Result r, const sp<IDevice>& result) {
+                retval = r;
+                if (retval == Result::OK) {
+                    *device = new DeviceHalHidl(result);
+                }
+            });
+    if (ret.isOk()) {
+        if (retval == Result::OK) return OK;
+        else if (retval == Result::INVALID_ARGUMENTS) return BAD_VALUE;
+        else return NO_INIT;
+    }
+    return FAILED_TRANSACTION;
+}
+
+} // namespace V4_0
+} // namespace android
diff --git a/media/libaudiohal/4.0/DevicesFactoryHalHidl.h b/media/libaudiohal/4.0/DevicesFactoryHalHidl.h
new file mode 100644
index 0000000..114889b
--- /dev/null
+++ b/media/libaudiohal/4.0/DevicesFactoryHalHidl.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2016 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_DEVICES_FACTORY_HAL_HIDL_4_0_H
+#define ANDROID_HARDWARE_DEVICES_FACTORY_HAL_HIDL_4_0_H
+
+#include <android/hardware/audio/4.0/IDevicesFactory.h>
+#include <media/audiohal/DevicesFactoryHalInterface.h>
+#include <utils/Errors.h>
+#include <utils/RefBase.h>
+
+#include "DeviceHalHidl.h"
+
+using ::android::hardware::audio::V4_0::IDevicesFactory;
+
+namespace android {
+namespace V4_0 {
+
+class DevicesFactoryHalHidl : public DevicesFactoryHalInterface
+{
+  public:
+    // Opens a device with the specified name. To close the device, it is
+    // necessary to release references to the returned object.
+    virtual status_t openDevice(const char *name, sp<DeviceHalInterface> *device);
+
+  private:
+    friend class DevicesFactoryHalHybrid;
+
+    sp<IDevicesFactory> mDevicesFactory;
+    sp<IDevicesFactory> mDevicesFactoryMsd;
+
+    // Can not be constructed directly by clients.
+    DevicesFactoryHalHidl();
+
+    virtual ~DevicesFactoryHalHidl();
+};
+
+} // namespace V4_0
+} // namespace android
+
+#endif // ANDROID_HARDWARE_DEVICES_FACTORY_HAL_HIDL_4_0_H
diff --git a/media/libaudiohal/4.0/DevicesFactoryHalHybrid.cpp b/media/libaudiohal/4.0/DevicesFactoryHalHybrid.cpp
new file mode 100644
index 0000000..c43509c
--- /dev/null
+++ b/media/libaudiohal/4.0/DevicesFactoryHalHybrid.cpp
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "DevicesFactoryHalHybrid"
+//#define LOG_NDEBUG 0
+
+#include <libaudiohal/4.0/DevicesFactoryHalHybrid.h>
+#include "DevicesFactoryHalLocal.h"
+#include "DevicesFactoryHalHidl.h"
+
+namespace android {
+namespace V4_0 {
+
+DevicesFactoryHalHybrid::DevicesFactoryHalHybrid()
+        : mLocalFactory(new DevicesFactoryHalLocal()),
+          mHidlFactory(new DevicesFactoryHalHidl()) {
+}
+
+DevicesFactoryHalHybrid::~DevicesFactoryHalHybrid() {
+}
+
+status_t DevicesFactoryHalHybrid::openDevice(const char *name, sp<DeviceHalInterface> *device) {
+    if (mHidlFactory != 0 && strcmp(AUDIO_HARDWARE_MODULE_ID_A2DP, name) != 0) {
+        return mHidlFactory->openDevice(name, device);
+    }
+    return mLocalFactory->openDevice(name, device);
+}
+
+} // namespace V4_0
+} // namespace android
diff --git a/media/libaudiohal/4.0/DevicesFactoryHalLocal.cpp b/media/libaudiohal/4.0/DevicesFactoryHalLocal.cpp
new file mode 100644
index 0000000..e54edd4
--- /dev/null
+++ b/media/libaudiohal/4.0/DevicesFactoryHalLocal.cpp
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2016 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_TAG "DevicesFactoryHalLocal"
+//#define LOG_NDEBUG 0
+
+#include <string.h>
+
+#include <hardware/audio.h>
+#include <utils/Log.h>
+
+#include "DeviceHalLocal.h"
+#include "DevicesFactoryHalLocal.h"
+
+namespace android {
+namespace V4_0 {
+
+static status_t load_audio_interface(const char *if_name, audio_hw_device_t **dev)
+{
+    const hw_module_t *mod;
+    int rc;
+
+    rc = hw_get_module_by_class(AUDIO_HARDWARE_MODULE_ID, if_name, &mod);
+    if (rc) {
+        ALOGE("%s couldn't load audio hw module %s.%s (%s)", __func__,
+                AUDIO_HARDWARE_MODULE_ID, if_name, strerror(-rc));
+        goto out;
+    }
+    rc = audio_hw_device_open(mod, dev);
+    if (rc) {
+        ALOGE("%s couldn't open audio hw device in %s.%s (%s)", __func__,
+                AUDIO_HARDWARE_MODULE_ID, if_name, strerror(-rc));
+        goto out;
+    }
+    if ((*dev)->common.version < AUDIO_DEVICE_API_VERSION_MIN) {
+        ALOGE("%s wrong audio hw device version %04x", __func__, (*dev)->common.version);
+        rc = BAD_VALUE;
+        audio_hw_device_close(*dev);
+        goto out;
+    }
+    return OK;
+
+out:
+    *dev = NULL;
+    return rc;
+}
+
+status_t DevicesFactoryHalLocal::openDevice(const char *name, sp<DeviceHalInterface> *device) {
+    audio_hw_device_t *dev;
+    status_t rc = load_audio_interface(name, &dev);
+    if (rc == OK) {
+        *device = new DeviceHalLocal(dev);
+    }
+    return rc;
+}
+
+} // namespace V4_0
+} // namespace android
diff --git a/media/libaudiohal/4.0/DevicesFactoryHalLocal.h b/media/libaudiohal/4.0/DevicesFactoryHalLocal.h
new file mode 100644
index 0000000..bc1c521
--- /dev/null
+++ b/media/libaudiohal/4.0/DevicesFactoryHalLocal.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2016 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_DEVICES_FACTORY_HAL_LOCAL_4_0_H
+#define ANDROID_HARDWARE_DEVICES_FACTORY_HAL_LOCAL_4_0_H
+
+#include <media/audiohal/DevicesFactoryHalInterface.h>
+#include <utils/Errors.h>
+#include <utils/RefBase.h>
+
+#include "DeviceHalLocal.h"
+
+namespace android {
+namespace V4_0 {
+
+class DevicesFactoryHalLocal : public DevicesFactoryHalInterface
+{
+  public:
+    // Opens a device with the specified name. To close the device, it is
+    // necessary to release references to the returned object.
+    virtual status_t openDevice(const char *name, sp<DeviceHalInterface> *device);
+
+  private:
+    friend class DevicesFactoryHalHybrid;
+
+    // Can not be constructed directly by clients.
+    DevicesFactoryHalLocal() {}
+
+    virtual ~DevicesFactoryHalLocal() {}
+};
+
+} // namespace V4_0
+} // namespace android
+
+#endif // ANDROID_HARDWARE_DEVICES_FACTORY_HAL_LOCAL_4_0_H
diff --git a/media/libaudiohal/4.0/EffectBufferHalHidl.cpp b/media/libaudiohal/4.0/EffectBufferHalHidl.cpp
new file mode 100644
index 0000000..957c89f
--- /dev/null
+++ b/media/libaudiohal/4.0/EffectBufferHalHidl.cpp
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <atomic>
+
+#define LOG_TAG "EffectBufferHalHidl"
+//#define LOG_NDEBUG 0
+
+#include <android/hidl/allocator/1.0/IAllocator.h>
+#include <hidlmemory/mapping.h>
+#include <utils/Log.h>
+
+#include "ConversionHelperHidl.h"
+#include "EffectBufferHalHidl.h"
+
+using ::android::hardware::Return;
+using ::android::hidl::allocator::V1_0::IAllocator;
+
+namespace android {
+namespace V4_0 {
+
+// static
+uint64_t EffectBufferHalHidl::makeUniqueId() {
+    static std::atomic<uint64_t> counter{1};
+    return counter++;
+}
+
+status_t EffectBufferHalHidl::allocate(
+        size_t size, sp<EffectBufferHalInterface>* buffer) {
+    return mirror(nullptr, size, buffer);
+}
+
+status_t EffectBufferHalHidl::mirror(
+        void* external, size_t size, sp<EffectBufferHalInterface>* buffer) {
+    sp<EffectBufferHalInterface> tempBuffer = new EffectBufferHalHidl(size);
+    status_t result = static_cast<EffectBufferHalHidl*>(tempBuffer.get())->init();
+    if (result == OK) {
+        tempBuffer->setExternalData(external);
+        *buffer = tempBuffer;
+    }
+    return result;
+}
+
+EffectBufferHalHidl::EffectBufferHalHidl(size_t size)
+        : mBufferSize(size), mFrameCountChanged(false),
+          mExternalData(nullptr), mAudioBuffer{0, {nullptr}} {
+    mHidlBuffer.id = makeUniqueId();
+    mHidlBuffer.frameCount = 0;
+}
+
+EffectBufferHalHidl::~EffectBufferHalHidl() {
+}
+
+status_t EffectBufferHalHidl::init() {
+    sp<IAllocator> ashmem = IAllocator::getService("ashmem");
+    if (ashmem == 0) {
+        ALOGE("Failed to retrieve ashmem allocator service");
+        return NO_INIT;
+    }
+    status_t retval = NO_MEMORY;
+    Return<void> result = ashmem->allocate(
+            mBufferSize,
+            [&](bool success, const hidl_memory& memory) {
+                if (success) {
+                    mHidlBuffer.data = memory;
+                    retval = OK;
+                }
+            });
+    if (result.isOk() && retval == OK) {
+        mMemory = hardware::mapMemory(mHidlBuffer.data);
+        if (mMemory != 0) {
+            mMemory->update();
+            mAudioBuffer.raw = static_cast<void*>(mMemory->getPointer());
+            memset(mAudioBuffer.raw, 0, mMemory->getSize());
+            mMemory->commit();
+        } else {
+            ALOGE("Failed to map allocated ashmem");
+            retval = NO_MEMORY;
+        }
+    } else {
+        ALOGE("Failed to allocate %d bytes from ashmem", (int)mBufferSize);
+    }
+    return result.isOk() ? retval : FAILED_TRANSACTION;
+}
+
+audio_buffer_t* EffectBufferHalHidl::audioBuffer() {
+    return &mAudioBuffer;
+}
+
+void* EffectBufferHalHidl::externalData() const {
+    return mExternalData;
+}
+
+void EffectBufferHalHidl::setFrameCount(size_t frameCount) {
+    mHidlBuffer.frameCount = frameCount;
+    mAudioBuffer.frameCount = frameCount;
+    mFrameCountChanged = true;
+}
+
+bool EffectBufferHalHidl::checkFrameCountChange() {
+    bool result = mFrameCountChanged;
+    mFrameCountChanged = false;
+    return result;
+}
+
+void EffectBufferHalHidl::setExternalData(void* external) {
+    mExternalData = external;
+}
+
+void EffectBufferHalHidl::update() {
+    update(mBufferSize);
+}
+
+void EffectBufferHalHidl::commit() {
+    commit(mBufferSize);
+}
+
+void EffectBufferHalHidl::update(size_t size) {
+    if (mExternalData == nullptr) return;
+    mMemory->update();
+    if (size > mBufferSize) size = mBufferSize;
+    memcpy(mAudioBuffer.raw, mExternalData, size);
+    mMemory->commit();
+}
+
+void EffectBufferHalHidl::commit(size_t size) {
+    if (mExternalData == nullptr) return;
+    if (size > mBufferSize) size = mBufferSize;
+    memcpy(mExternalData, mAudioBuffer.raw, size);
+}
+
+} // namespace V4_0
+} // namespace android
diff --git a/media/libaudiohal/4.0/EffectBufferHalHidl.h b/media/libaudiohal/4.0/EffectBufferHalHidl.h
new file mode 100644
index 0000000..6d578c6
--- /dev/null
+++ b/media/libaudiohal/4.0/EffectBufferHalHidl.h
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_HARDWARE_EFFECT_BUFFER_HAL_HIDL_4_0_H
+#define ANDROID_HARDWARE_EFFECT_BUFFER_HAL_HIDL_4_0_H
+
+#include <android/hardware/audio/effect/4.0/types.h>
+#include <android/hidl/memory/1.0/IMemory.h>
+#include <hidl/HidlSupport.h>
+#include <media/audiohal/EffectBufferHalInterface.h>
+#include <system/audio_effect.h>
+
+using android::hardware::audio::effect::V4_0::AudioBuffer;
+using android::hardware::hidl_memory;
+using android::hidl::memory::V1_0::IMemory;
+
+namespace android {
+namespace V4_0 {
+
+class EffectBufferHalHidl : public EffectBufferHalInterface
+{
+  public:
+    static status_t allocate(size_t size, sp<EffectBufferHalInterface>* buffer);
+    static status_t mirror(void* external, size_t size, sp<EffectBufferHalInterface>* buffer);
+
+    virtual audio_buffer_t* audioBuffer();
+    virtual void* externalData() const;
+
+    virtual size_t getSize() const override { return mBufferSize; }
+
+    virtual void setExternalData(void* external);
+    virtual void setFrameCount(size_t frameCount);
+    virtual bool checkFrameCountChange();
+
+    virtual void update();
+    virtual void commit();
+    virtual void update(size_t size);
+    virtual void commit(size_t size);
+
+    const AudioBuffer& hidlBuffer() const { return mHidlBuffer; }
+
+  private:
+    friend class EffectBufferHalInterface;
+
+    static uint64_t makeUniqueId();
+
+    const size_t mBufferSize;
+    bool mFrameCountChanged;
+    void* mExternalData;
+    AudioBuffer mHidlBuffer;
+    sp<IMemory> mMemory;
+    audio_buffer_t mAudioBuffer;
+
+    // Can not be constructed directly by clients.
+    explicit EffectBufferHalHidl(size_t size);
+
+    virtual ~EffectBufferHalHidl();
+
+    status_t init();
+};
+
+} // namespace V4_0
+} // namespace android
+
+#endif // ANDROID_HARDWARE_EFFECT_BUFFER_HAL_HIDL_4_0_H
diff --git a/media/libaudiohal/4.0/EffectHalHidl.cpp b/media/libaudiohal/4.0/EffectHalHidl.cpp
new file mode 100644
index 0000000..c99c4c8
--- /dev/null
+++ b/media/libaudiohal/4.0/EffectHalHidl.cpp
@@ -0,0 +1,342 @@
+/*
+ * Copyright (C) 2016 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_TAG "EffectHalHidl"
+//#define LOG_NDEBUG 0
+
+#include <common/all-versions/VersionUtils.h>
+#include <hwbinder/IPCThreadState.h>
+#include <media/EffectsFactoryApi.h>
+#include <utils/Log.h>
+
+#include "ConversionHelperHidl.h"
+#include "EffectBufferHalHidl.h"
+#include "EffectHalHidl.h"
+#include "HidlUtils.h"
+
+using ::android::hardware::audio::effect::V4_0::AudioBuffer;
+using ::android::hardware::audio::effect::V4_0::EffectBufferAccess;
+using ::android::hardware::audio::effect::V4_0::EffectConfigParameters;
+using ::android::hardware::audio::effect::V4_0::MessageQueueFlagBits;
+using ::android::hardware::audio::effect::V4_0::Result;
+using ::android::hardware::audio::common::V4_0::HidlUtils;
+using ::android::hardware::audio::common::V4_0::AudioChannelMask;
+using ::android::hardware::audio::common::V4_0::AudioFormat;
+using ::android::hardware::audio::common::utils::mkEnumConverter;
+using ::android::hardware::hidl_vec;
+using ::android::hardware::MQDescriptorSync;
+using ::android::hardware::Return;
+
+namespace android {
+namespace V4_0 {
+
+EffectHalHidl::EffectHalHidl(const sp<IEffect>& effect, uint64_t effectId)
+        : mEffect(effect), mEffectId(effectId), mBuffersChanged(true), mEfGroup(nullptr) {
+}
+
+EffectHalHidl::~EffectHalHidl() {
+    if (mEffect != 0) {
+        close();
+        mEffect.clear();
+        hardware::IPCThreadState::self()->flushCommands();
+    }
+    if (mEfGroup) {
+        EventFlag::deleteEventFlag(&mEfGroup);
+    }
+}
+
+// static
+void EffectHalHidl::effectDescriptorToHal(
+        const EffectDescriptor& descriptor, effect_descriptor_t* halDescriptor) {
+    HidlUtils::uuidToHal(descriptor.type, &halDescriptor->type);
+    HidlUtils::uuidToHal(descriptor.uuid, &halDescriptor->uuid);
+    halDescriptor->flags = static_cast<uint32_t>(descriptor.flags);
+    halDescriptor->cpuLoad = descriptor.cpuLoad;
+    halDescriptor->memoryUsage = descriptor.memoryUsage;
+    memcpy(halDescriptor->name, descriptor.name.data(), descriptor.name.size());
+    memcpy(halDescriptor->implementor,
+            descriptor.implementor.data(), descriptor.implementor.size());
+}
+
+// TODO(mnaganov): These buffer conversion functions should be shared with Effect wrapper
+// via HidlUtils. Move them there when hardware/interfaces will get un-frozen again.
+
+// static
+void EffectHalHidl::effectBufferConfigFromHal(
+        const buffer_config_t& halConfig, EffectBufferConfig* config) {
+    config->samplingRateHz = halConfig.samplingRate;
+    config->channels = mkEnumConverter<AudioChannelMask>(halConfig.channels);
+    config->format = AudioFormat(halConfig.format);
+    config->accessMode = EffectBufferAccess(halConfig.accessMode);
+    config->mask = mkEnumConverter<EffectConfigParameters>(halConfig.mask);
+}
+
+// static
+void EffectHalHidl::effectBufferConfigToHal(
+        const EffectBufferConfig& config, buffer_config_t* halConfig) {
+    halConfig->buffer.frameCount = 0;
+    halConfig->buffer.raw = NULL;
+    halConfig->samplingRate = config.samplingRateHz;
+    halConfig->channels = static_cast<uint32_t>(config.channels);
+    halConfig->bufferProvider.cookie = NULL;
+    halConfig->bufferProvider.getBuffer = NULL;
+    halConfig->bufferProvider.releaseBuffer = NULL;
+    halConfig->format = static_cast<uint8_t>(config.format);
+    halConfig->accessMode = static_cast<uint8_t>(config.accessMode);
+    halConfig->mask = static_cast<uint8_t>(config.mask);
+}
+
+// static
+void EffectHalHidl::effectConfigFromHal(const effect_config_t& halConfig, EffectConfig* config) {
+    effectBufferConfigFromHal(halConfig.inputCfg, &config->inputCfg);
+    effectBufferConfigFromHal(halConfig.outputCfg, &config->outputCfg);
+}
+
+// static
+void EffectHalHidl::effectConfigToHal(const EffectConfig& config, effect_config_t* halConfig) {
+    effectBufferConfigToHal(config.inputCfg, &halConfig->inputCfg);
+    effectBufferConfigToHal(config.outputCfg, &halConfig->outputCfg);
+}
+
+// static
+status_t EffectHalHidl::analyzeResult(const Result& result) {
+    switch (result) {
+        case Result::OK: return OK;
+        case Result::INVALID_ARGUMENTS: return BAD_VALUE;
+        case Result::INVALID_STATE: return NOT_ENOUGH_DATA;
+        case Result::NOT_INITIALIZED: return NO_INIT;
+        case Result::NOT_SUPPORTED: return INVALID_OPERATION;
+        case Result::RESULT_TOO_BIG: return NO_MEMORY;
+        default: return NO_INIT;
+    }
+}
+
+status_t EffectHalHidl::setInBuffer(const sp<EffectBufferHalInterface>& buffer) {
+    if (!mBuffersChanged) {
+        if (buffer.get() == nullptr || mInBuffer.get() == nullptr) {
+            mBuffersChanged = buffer.get() != mInBuffer.get();
+        } else {
+            mBuffersChanged = buffer->audioBuffer() != mInBuffer->audioBuffer();
+        }
+    }
+    mInBuffer = buffer;
+    return OK;
+}
+
+status_t EffectHalHidl::setOutBuffer(const sp<EffectBufferHalInterface>& buffer) {
+    if (!mBuffersChanged) {
+        if (buffer.get() == nullptr || mOutBuffer.get() == nullptr) {
+            mBuffersChanged = buffer.get() != mOutBuffer.get();
+        } else {
+            mBuffersChanged = buffer->audioBuffer() != mOutBuffer->audioBuffer();
+        }
+    }
+    mOutBuffer = buffer;
+    return OK;
+}
+
+status_t EffectHalHidl::process() {
+    return processImpl(static_cast<uint32_t>(MessageQueueFlagBits::REQUEST_PROCESS));
+}
+
+status_t EffectHalHidl::processReverse() {
+    return processImpl(static_cast<uint32_t>(MessageQueueFlagBits::REQUEST_PROCESS_REVERSE));
+}
+
+status_t EffectHalHidl::prepareForProcessing() {
+    std::unique_ptr<StatusMQ> tempStatusMQ;
+    Result retval;
+    Return<void> ret = mEffect->prepareForProcessing(
+            [&](Result r, const MQDescriptorSync<Result>& statusMQ) {
+                retval = r;
+                if (retval == Result::OK) {
+                    tempStatusMQ.reset(new StatusMQ(statusMQ));
+                    if (tempStatusMQ->isValid() && tempStatusMQ->getEventFlagWord()) {
+                        EventFlag::createEventFlag(tempStatusMQ->getEventFlagWord(), &mEfGroup);
+                    }
+                }
+            });
+    if (!ret.isOk() || retval != Result::OK) {
+        return ret.isOk() ? analyzeResult(retval) : FAILED_TRANSACTION;
+    }
+    if (!tempStatusMQ || !tempStatusMQ->isValid() || !mEfGroup) {
+        ALOGE_IF(!tempStatusMQ, "Failed to obtain status message queue for effects");
+        ALOGE_IF(tempStatusMQ && !tempStatusMQ->isValid(),
+                "Status message queue for effects is invalid");
+        ALOGE_IF(!mEfGroup, "Event flag creation for effects failed");
+        return NO_INIT;
+    }
+    mStatusMQ = std::move(tempStatusMQ);
+    return OK;
+}
+
+bool EffectHalHidl::needToResetBuffers() {
+    if (mBuffersChanged) return true;
+    bool inBufferFrameCountUpdated = mInBuffer->checkFrameCountChange();
+    bool outBufferFrameCountUpdated = mOutBuffer->checkFrameCountChange();
+    return inBufferFrameCountUpdated || outBufferFrameCountUpdated;
+}
+
+status_t EffectHalHidl::processImpl(uint32_t mqFlag) {
+    if (mEffect == 0 || mInBuffer == 0 || mOutBuffer == 0) return NO_INIT;
+    status_t status;
+    if (!mStatusMQ && (status = prepareForProcessing()) != OK) {
+        return status;
+    }
+    if (needToResetBuffers() && (status = setProcessBuffers()) != OK) {
+        return status;
+    }
+    // The data is already in the buffers, just need to flush it and wake up the server side.
+    std::atomic_thread_fence(std::memory_order_release);
+    mEfGroup->wake(mqFlag);
+    uint32_t efState = 0;
+retry:
+    status_t ret = mEfGroup->wait(
+            static_cast<uint32_t>(MessageQueueFlagBits::DONE_PROCESSING), &efState);
+    if (efState & static_cast<uint32_t>(MessageQueueFlagBits::DONE_PROCESSING)) {
+        Result retval = Result::NOT_INITIALIZED;
+        mStatusMQ->read(&retval);
+        if (retval == Result::OK || retval == Result::INVALID_STATE) {
+            // Sync back the changed contents of the buffer.
+            std::atomic_thread_fence(std::memory_order_acquire);
+        }
+        return analyzeResult(retval);
+    }
+    if (ret == -EAGAIN || ret == -EINTR) {
+        // Spurious wakeup. This normally retries no more than once.
+        goto retry;
+    }
+    return ret;
+}
+
+status_t EffectHalHidl::setProcessBuffers() {
+    Return<Result> ret = mEffect->setProcessBuffers(
+            static_cast<EffectBufferHalHidl*>(mInBuffer.get())->hidlBuffer(),
+            static_cast<EffectBufferHalHidl*>(mOutBuffer.get())->hidlBuffer());
+    if (ret.isOk() && ret == Result::OK) {
+        mBuffersChanged = false;
+        return OK;
+    }
+    return ret.isOk() ? analyzeResult(ret) : FAILED_TRANSACTION;
+}
+
+status_t EffectHalHidl::command(uint32_t cmdCode, uint32_t cmdSize, void *pCmdData,
+        uint32_t *replySize, void *pReplyData) {
+    if (mEffect == 0) return NO_INIT;
+
+    // Special cases.
+    if (cmdCode == EFFECT_CMD_SET_CONFIG || cmdCode == EFFECT_CMD_SET_CONFIG_REVERSE) {
+        return setConfigImpl(cmdCode, cmdSize, pCmdData, replySize, pReplyData);
+    } else if (cmdCode == EFFECT_CMD_GET_CONFIG || cmdCode == EFFECT_CMD_GET_CONFIG_REVERSE) {
+        return getConfigImpl(cmdCode, replySize, pReplyData);
+    }
+
+    // Common case.
+    hidl_vec<uint8_t> hidlData;
+    if (pCmdData != nullptr && cmdSize > 0) {
+        hidlData.setToExternal(reinterpret_cast<uint8_t*>(pCmdData), cmdSize);
+    }
+    status_t status;
+    uint32_t replySizeStub = 0;
+    if (replySize == nullptr || pReplyData == nullptr) replySize = &replySizeStub;
+    Return<void> ret = mEffect->command(cmdCode, hidlData, *replySize,
+            [&](int32_t s, const hidl_vec<uint8_t>& result) {
+                status = s;
+                if (status == 0) {
+                    if (*replySize > result.size()) *replySize = result.size();
+                    if (pReplyData != nullptr && *replySize > 0) {
+                        memcpy(pReplyData, &result[0], *replySize);
+                    }
+                }
+            });
+    return ret.isOk() ? status : FAILED_TRANSACTION;
+}
+
+status_t EffectHalHidl::getDescriptor(effect_descriptor_t *pDescriptor) {
+    if (mEffect == 0) return NO_INIT;
+    Result retval = Result::NOT_INITIALIZED;
+    Return<void> ret = mEffect->getDescriptor(
+            [&](Result r, const EffectDescriptor& result) {
+                retval = r;
+                if (retval == Result::OK) {
+                    effectDescriptorToHal(result, pDescriptor);
+                }
+            });
+    return ret.isOk() ? analyzeResult(retval) : FAILED_TRANSACTION;
+}
+
+status_t EffectHalHidl::close() {
+    if (mEffect == 0) return NO_INIT;
+    Return<Result> ret = mEffect->close();
+    return ret.isOk() ? analyzeResult(ret) : FAILED_TRANSACTION;
+}
+
+status_t EffectHalHidl::getConfigImpl(
+        uint32_t cmdCode, uint32_t *replySize, void *pReplyData) {
+    if (replySize == NULL || *replySize != sizeof(effect_config_t) || pReplyData == NULL) {
+        return BAD_VALUE;
+    }
+    status_t result = FAILED_TRANSACTION;
+    Return<void> ret;
+    if (cmdCode == EFFECT_CMD_GET_CONFIG) {
+        ret = mEffect->getConfig([&] (Result r, const EffectConfig &hidlConfig) {
+            result = analyzeResult(r);
+            if (r == Result::OK) {
+                effectConfigToHal(hidlConfig, static_cast<effect_config_t*>(pReplyData));
+            }
+        });
+    } else {
+        ret = mEffect->getConfigReverse([&] (Result r, const EffectConfig &hidlConfig) {
+            result = analyzeResult(r);
+            if (r == Result::OK) {
+                effectConfigToHal(hidlConfig, static_cast<effect_config_t*>(pReplyData));
+            }
+        });
+    }
+    if (!ret.isOk()) {
+        result = FAILED_TRANSACTION;
+    }
+    return result;
+}
+
+status_t EffectHalHidl::setConfigImpl(
+        uint32_t cmdCode, uint32_t cmdSize, void *pCmdData, uint32_t *replySize, void *pReplyData) {
+    if (pCmdData == NULL || cmdSize != sizeof(effect_config_t) ||
+            replySize == NULL || *replySize != sizeof(int32_t) || pReplyData == NULL) {
+        return BAD_VALUE;
+    }
+    const effect_config_t *halConfig = static_cast<effect_config_t*>(pCmdData);
+    if (halConfig->inputCfg.bufferProvider.getBuffer != NULL ||
+            halConfig->inputCfg.bufferProvider.releaseBuffer != NULL ||
+            halConfig->outputCfg.bufferProvider.getBuffer != NULL ||
+            halConfig->outputCfg.bufferProvider.releaseBuffer != NULL) {
+        ALOGE("Buffer provider callbacks are not supported");
+    }
+    EffectConfig hidlConfig;
+    effectConfigFromHal(*halConfig, &hidlConfig);
+    Return<Result> ret = cmdCode == EFFECT_CMD_SET_CONFIG ?
+            mEffect->setConfig(hidlConfig, nullptr, nullptr) :
+            mEffect->setConfigReverse(hidlConfig, nullptr, nullptr);
+    status_t result = FAILED_TRANSACTION;
+    if (ret.isOk()) {
+        result = analyzeResult(ret);
+        *static_cast<int32_t*>(pReplyData) = result;
+    }
+    return result;
+}
+
+} // namespace V4_0
+} // namespace android
diff --git a/media/libaudiohal/4.0/EffectHalHidl.h b/media/libaudiohal/4.0/EffectHalHidl.h
new file mode 100644
index 0000000..5a4dab1
--- /dev/null
+++ b/media/libaudiohal/4.0/EffectHalHidl.h
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2016 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_EFFECT_HAL_HIDL_4_0_H
+#define ANDROID_HARDWARE_EFFECT_HAL_HIDL_4_0_H
+
+#include <android/hardware/audio/effect/4.0/IEffect.h>
+#include <media/audiohal/EffectHalInterface.h>
+#include <fmq/EventFlag.h>
+#include <fmq/MessageQueue.h>
+#include <system/audio_effect.h>
+
+using ::android::hardware::audio::effect::V4_0::EffectBufferConfig;
+using ::android::hardware::audio::effect::V4_0::EffectConfig;
+using ::android::hardware::audio::effect::V4_0::EffectDescriptor;
+using ::android::hardware::audio::effect::V4_0::IEffect;
+using ::android::hardware::EventFlag;
+using ::android::hardware::MessageQueue;
+
+namespace android {
+namespace V4_0 {
+
+class EffectHalHidl : public EffectHalInterface
+{
+  public:
+    // Set the input buffer.
+    virtual status_t setInBuffer(const sp<EffectBufferHalInterface>& buffer);
+
+    // Set the output buffer.
+    virtual status_t setOutBuffer(const sp<EffectBufferHalInterface>& buffer);
+
+    // Effect process function.
+    virtual status_t process();
+
+    // Process reverse stream function. This function is used to pass
+    // a reference stream to the effect engine.
+    virtual status_t processReverse();
+
+    // Send a command and receive a response to/from effect engine.
+    virtual status_t command(uint32_t cmdCode, uint32_t cmdSize, void *pCmdData,
+            uint32_t *replySize, void *pReplyData);
+
+    // Returns the effect descriptor.
+    virtual status_t getDescriptor(effect_descriptor_t *pDescriptor);
+
+    // Free resources on the remote side.
+    virtual status_t close();
+
+    // Whether it's a local implementation.
+    virtual bool isLocal() const { return false; }
+
+    uint64_t effectId() const { return mEffectId; }
+
+    static void effectDescriptorToHal(
+            const EffectDescriptor& descriptor, effect_descriptor_t* halDescriptor);
+
+  private:
+    friend class EffectsFactoryHalHidl;
+    typedef MessageQueue<
+        hardware::audio::effect::V4_0::Result, hardware::kSynchronizedReadWrite> StatusMQ;
+
+    sp<IEffect> mEffect;
+    const uint64_t mEffectId;
+    sp<EffectBufferHalInterface> mInBuffer;
+    sp<EffectBufferHalInterface> mOutBuffer;
+    bool mBuffersChanged;
+    std::unique_ptr<StatusMQ> mStatusMQ;
+    EventFlag* mEfGroup;
+
+    static status_t analyzeResult(const hardware::audio::effect::V4_0::Result& result);
+    static void effectBufferConfigFromHal(
+            const buffer_config_t& halConfig, EffectBufferConfig* config);
+    static void effectBufferConfigToHal(
+            const EffectBufferConfig& config, buffer_config_t* halConfig);
+    static void effectConfigFromHal(const effect_config_t& halConfig, EffectConfig* config);
+    static void effectConfigToHal(const EffectConfig& config, effect_config_t* halConfig);
+
+    // Can not be constructed directly by clients.
+    EffectHalHidl(const sp<IEffect>& effect, uint64_t effectId);
+
+    // The destructor automatically releases the effect.
+    virtual ~EffectHalHidl();
+
+    status_t getConfigImpl(uint32_t cmdCode, uint32_t *replySize, void *pReplyData);
+    status_t prepareForProcessing();
+    bool needToResetBuffers();
+    status_t processImpl(uint32_t mqFlag);
+    status_t setConfigImpl(
+            uint32_t cmdCode, uint32_t cmdSize, void *pCmdData,
+            uint32_t *replySize, void *pReplyData);
+    status_t setProcessBuffers();
+};
+
+} // namespace V4_0
+} // namespace android
+
+#endif // ANDROID_HARDWARE_EFFECT_HAL_HIDL_4_0_H
diff --git a/media/libaudiohal/4.0/EffectsFactoryHalHidl.cpp b/media/libaudiohal/4.0/EffectsFactoryHalHidl.cpp
new file mode 100644
index 0000000..dfed784
--- /dev/null
+++ b/media/libaudiohal/4.0/EffectsFactoryHalHidl.cpp
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2016 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_TAG "EffectsFactoryHalHidl"
+//#define LOG_NDEBUG 0
+
+#include <cutils/native_handle.h>
+#include <libaudiohal/4.0/EffectsFactoryHalHidl.h>
+
+#include "ConversionHelperHidl.h"
+#include "EffectBufferHalHidl.h"
+#include "EffectHalHidl.h"
+#include "HidlUtils.h"
+
+using ::android::hardware::audio::common::V4_0::HidlUtils;
+using ::android::hardware::audio::common::V4_0::Uuid;
+using ::android::hardware::audio::effect::V4_0::IEffect;
+using ::android::hardware::audio::effect::V4_0::Result;
+using ::android::hardware::Return;
+
+namespace android {
+namespace V4_0 {
+
+EffectsFactoryHalHidl::EffectsFactoryHalHidl() : ConversionHelperHidl("EffectsFactory") {
+    mEffectsFactory = IEffectsFactory::getService();
+    if (mEffectsFactory == 0) {
+        ALOGE("Failed to obtain IEffectsFactory service, terminating process.");
+        exit(1);
+    }
+}
+
+EffectsFactoryHalHidl::~EffectsFactoryHalHidl() {
+}
+
+status_t EffectsFactoryHalHidl::queryAllDescriptors() {
+    if (mEffectsFactory == 0) return NO_INIT;
+    Result retval = Result::NOT_INITIALIZED;
+    Return<void> ret = mEffectsFactory->getAllDescriptors(
+            [&](Result r, const hidl_vec<EffectDescriptor>& result) {
+                retval = r;
+                if (retval == Result::OK) {
+                    mLastDescriptors = result;
+                }
+            });
+    if (ret.isOk()) {
+        return retval == Result::OK ? OK : NO_INIT;
+    }
+    mLastDescriptors.resize(0);
+    return processReturn(__FUNCTION__, ret);
+}
+
+status_t EffectsFactoryHalHidl::queryNumberEffects(uint32_t *pNumEffects) {
+    status_t queryResult = queryAllDescriptors();
+    if (queryResult == OK) {
+        *pNumEffects = mLastDescriptors.size();
+    }
+    return queryResult;
+}
+
+status_t EffectsFactoryHalHidl::getDescriptor(
+        uint32_t index, effect_descriptor_t *pDescriptor) {
+    // TODO: We need somehow to track the changes on the server side
+    // or figure out how to convert everybody to query all the descriptors at once.
+    // TODO: check for nullptr
+    if (mLastDescriptors.size() == 0) {
+        status_t queryResult = queryAllDescriptors();
+        if (queryResult != OK) return queryResult;
+    }
+    if (index >= mLastDescriptors.size()) return NAME_NOT_FOUND;
+    EffectHalHidl::effectDescriptorToHal(mLastDescriptors[index], pDescriptor);
+    return OK;
+}
+
+status_t EffectsFactoryHalHidl::getDescriptor(
+        const effect_uuid_t *pEffectUuid, effect_descriptor_t *pDescriptor) {
+    // TODO: check for nullptr
+    if (mEffectsFactory == 0) return NO_INIT;
+    Uuid hidlUuid;
+    HidlUtils::uuidFromHal(*pEffectUuid, &hidlUuid);
+    Result retval = Result::NOT_INITIALIZED;
+    Return<void> ret = mEffectsFactory->getDescriptor(hidlUuid,
+            [&](Result r, const EffectDescriptor& result) {
+                retval = r;
+                if (retval == Result::OK) {
+                    EffectHalHidl::effectDescriptorToHal(result, pDescriptor);
+                }
+            });
+    if (ret.isOk()) {
+        if (retval == Result::OK) return OK;
+        else if (retval == Result::INVALID_ARGUMENTS) return NAME_NOT_FOUND;
+        else return NO_INIT;
+    }
+    return processReturn(__FUNCTION__, ret);
+}
+
+status_t EffectsFactoryHalHidl::createEffect(
+        const effect_uuid_t *pEffectUuid, int32_t sessionId, int32_t ioId,
+        sp<EffectHalInterface> *effect) {
+    if (mEffectsFactory == 0) return NO_INIT;
+    Uuid hidlUuid;
+    HidlUtils::uuidFromHal(*pEffectUuid, &hidlUuid);
+    Result retval = Result::NOT_INITIALIZED;
+    Return<void> ret = mEffectsFactory->createEffect(
+            hidlUuid, sessionId, ioId,
+            [&](Result r, const sp<IEffect>& result, uint64_t effectId) {
+                retval = r;
+                if (retval == Result::OK) {
+                    *effect = new EffectHalHidl(result, effectId);
+                }
+            });
+    if (ret.isOk()) {
+        if (retval == Result::OK) return OK;
+        else if (retval == Result::INVALID_ARGUMENTS) return NAME_NOT_FOUND;
+        else return NO_INIT;
+    }
+    return processReturn(__FUNCTION__, ret);
+}
+
+status_t EffectsFactoryHalHidl::dumpEffects(int fd) {
+    if (mEffectsFactory == 0) return NO_INIT;
+    native_handle_t* hidlHandle = native_handle_create(1, 0);
+    hidlHandle->data[0] = fd;
+    Return<void> ret = mEffectsFactory->debug(hidlHandle, {} /* options */);
+    native_handle_delete(hidlHandle);
+    return processReturn(__FUNCTION__, ret);
+}
+
+status_t EffectsFactoryHalHidl::allocateBuffer(size_t size, sp<EffectBufferHalInterface>* buffer) {
+    return EffectBufferHalHidl::allocate(size, buffer);
+}
+
+status_t EffectsFactoryHalHidl::mirrorBuffer(void* external, size_t size,
+                          sp<EffectBufferHalInterface>* buffer) {
+    return EffectBufferHalHidl::mirror(external, size, buffer);
+}
+
+
+} // namespace V4_0
+} // namespace android
diff --git a/media/libaudiohal/4.0/StreamHalHidl.cpp b/media/libaudiohal/4.0/StreamHalHidl.cpp
new file mode 100644
index 0000000..de16e98
--- /dev/null
+++ b/media/libaudiohal/4.0/StreamHalHidl.cpp
@@ -0,0 +1,758 @@
+/*
+ * Copyright (C) 2016 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_TAG "StreamHalHidl"
+//#define LOG_NDEBUG 0
+
+#include <android/hardware/audio/4.0/IStreamOutCallback.h>
+#include <hwbinder/IPCThreadState.h>
+#include <mediautils/SchedulingPolicyService.h>
+#include <utils/Log.h>
+
+#include "DeviceHalHidl.h"
+#include "EffectHalHidl.h"
+#include "StreamHalHidl.h"
+#include "VersionUtils.h"
+
+using ::android::hardware::audio::common::V4_0::AudioChannelMask;
+using ::android::hardware::audio::common::V4_0::AudioFormat;
+using ::android::hardware::audio::common::V4_0::ThreadInfo;
+using ::android::hardware::audio::V4_0::AudioDrain;
+using ::android::hardware::audio::V4_0::IStreamOutCallback;
+using ::android::hardware::audio::V4_0::MessageQueueFlagBits;
+using ::android::hardware::audio::V4_0::MmapBufferInfo;
+using ::android::hardware::audio::V4_0::MmapPosition;
+using ::android::hardware::audio::V4_0::ParameterValue;
+using ::android::hardware::audio::V4_0::Result;
+using ::android::hardware::audio::V4_0::TimeSpec;
+using ::android::hardware::MQDescriptorSync;
+using ::android::hardware::Return;
+using ::android::hardware::Void;
+using ReadCommand = ::android::hardware::audio::V4_0::IStreamIn::ReadCommand;
+
+namespace android {
+namespace V4_0 {
+
+StreamHalHidl::StreamHalHidl(IStream *stream)
+        : ConversionHelperHidl("Stream"),
+          mStream(stream),
+          mHalThreadPriority(HAL_THREAD_PRIORITY_DEFAULT),
+          mCachedBufferSize(0){
+
+    // Instrument audio signal power logging.
+    // Note: This assumes channel mask, format, and sample rate do not change after creation.
+    if (mStream != nullptr && mStreamPowerLog.isUserDebugOrEngBuild()) {
+        // Obtain audio properties (see StreamHalHidl::getAudioProperties() below).
+        Return<void> ret = mStream->getAudioProperties(
+                [&](auto sr, auto m, auto f) {
+                mStreamPowerLog.init(sr,
+                        static_cast<audio_channel_mask_t>(m),
+                        static_cast<audio_format_t>(f));
+            });
+    }
+}
+
+StreamHalHidl::~StreamHalHidl() {
+    mStream = nullptr;
+}
+
+status_t StreamHalHidl::getSampleRate(uint32_t *rate) {
+    if (!mStream) return NO_INIT;
+    return processReturn("getSampleRate", mStream->getSampleRate(), rate);
+}
+
+status_t StreamHalHidl::getBufferSize(size_t *size) {
+    if (!mStream) return NO_INIT;
+    status_t status = processReturn("getBufferSize", mStream->getBufferSize(), size);
+    if (status == OK) {
+        mCachedBufferSize = *size;
+    }
+    return status;
+}
+
+status_t StreamHalHidl::getChannelMask(audio_channel_mask_t *mask) {
+    if (!mStream) return NO_INIT;
+    return processReturn("getChannelMask", mStream->getChannelMask(), mask);
+}
+
+status_t StreamHalHidl::getFormat(audio_format_t *format) {
+    if (!mStream) return NO_INIT;
+    return processReturn("getFormat", mStream->getFormat(), format);
+}
+
+status_t StreamHalHidl::getAudioProperties(
+        uint32_t *sampleRate, audio_channel_mask_t *mask, audio_format_t *format) {
+    if (!mStream) return NO_INIT;
+    Return<void> ret = mStream->getAudioProperties(
+            [&](uint32_t sr, auto m, auto f) {
+                *sampleRate = sr;
+                *mask = static_cast<audio_channel_mask_t>(m);
+                *format = static_cast<audio_format_t>(f);
+            });
+    return processReturn("getAudioProperties", ret);
+}
+
+status_t StreamHalHidl::setParameters(const String8& kvPairs) {
+    if (!mStream) return NO_INIT;
+    hidl_vec<ParameterValue> hidlParams;
+    status_t status = parametersFromHal(kvPairs, &hidlParams);
+    if (status != OK) return status;
+    return processReturn("setParameters",
+                         utils::setParameters(mStream, hidlParams, {} /* options */));
+}
+
+status_t StreamHalHidl::getParameters(const String8& keys, String8 *values) {
+    values->clear();
+    if (!mStream) return NO_INIT;
+    hidl_vec<hidl_string> hidlKeys;
+    status_t status = keysFromHal(keys, &hidlKeys);
+    if (status != OK) return status;
+    Result retval;
+    Return<void> ret = utils::getParameters(
+            mStream,
+            {} /* context */,
+            hidlKeys,
+            [&](Result r, const hidl_vec<ParameterValue>& parameters) {
+                retval = r;
+                if (retval == Result::OK) {
+                    parametersToHal(parameters, values);
+                }
+            });
+    return processReturn("getParameters", ret, retval);
+}
+
+status_t StreamHalHidl::addEffect(sp<EffectHalInterface> effect) {
+    if (!mStream) return NO_INIT;
+    return processReturn("addEffect", mStream->addEffect(
+                    static_cast<EffectHalHidl*>(effect.get())->effectId()));
+}
+
+status_t StreamHalHidl::removeEffect(sp<EffectHalInterface> effect) {
+    if (!mStream) return NO_INIT;
+    return processReturn("removeEffect", mStream->removeEffect(
+                    static_cast<EffectHalHidl*>(effect.get())->effectId()));
+}
+
+status_t StreamHalHidl::standby() {
+    if (!mStream) return NO_INIT;
+    return processReturn("standby", mStream->standby());
+}
+
+status_t StreamHalHidl::dump(int fd) {
+    if (!mStream) return NO_INIT;
+    native_handle_t* hidlHandle = native_handle_create(1, 0);
+    hidlHandle->data[0] = fd;
+    Return<void> ret = mStream->debug(hidlHandle, {} /* options */);
+    native_handle_delete(hidlHandle);
+    mStreamPowerLog.dump(fd);
+    return processReturn("dump", ret);
+}
+
+status_t StreamHalHidl::start() {
+    if (!mStream) return NO_INIT;
+    return processReturn("start", mStream->start());
+}
+
+status_t StreamHalHidl::stop() {
+    if (!mStream) return NO_INIT;
+    return processReturn("stop", mStream->stop());
+}
+
+status_t StreamHalHidl::createMmapBuffer(int32_t minSizeFrames,
+                                  struct audio_mmap_buffer_info *info) {
+    Result retval;
+    Return<void> ret = mStream->createMmapBuffer(
+            minSizeFrames,
+            [&](Result r, const MmapBufferInfo& hidlInfo) {
+                retval = r;
+                if (retval == Result::OK) {
+                    const native_handle *handle = hidlInfo.sharedMemory.handle();
+                    if (handle->numFds > 0) {
+                        info->shared_memory_fd = handle->data[0];
+                        info->buffer_size_frames = hidlInfo.bufferSizeFrames;
+                        info->burst_size_frames = hidlInfo.burstSizeFrames;
+                        // info->shared_memory_address is not needed in HIDL context
+                        info->shared_memory_address = NULL;
+                    } else {
+                        retval = Result::NOT_INITIALIZED;
+                    }
+                }
+            });
+    return processReturn("createMmapBuffer", ret, retval);
+}
+
+status_t StreamHalHidl::getMmapPosition(struct audio_mmap_position *position) {
+    Result retval;
+    Return<void> ret = mStream->getMmapPosition(
+            [&](Result r, const MmapPosition& hidlPosition) {
+                retval = r;
+                if (retval == Result::OK) {
+                    position->time_nanoseconds = hidlPosition.timeNanoseconds;
+                    position->position_frames = hidlPosition.positionFrames;
+                }
+            });
+    return processReturn("getMmapPosition", ret, retval);
+}
+
+status_t StreamHalHidl::setHalThreadPriority(int priority) {
+    mHalThreadPriority = priority;
+    return OK;
+}
+
+status_t StreamHalHidl::getCachedBufferSize(size_t *size) {
+    if (mCachedBufferSize != 0) {
+        *size = mCachedBufferSize;
+        return OK;
+    }
+    return getBufferSize(size);
+}
+
+bool StreamHalHidl::requestHalThreadPriority(pid_t threadPid, pid_t threadId) {
+    if (mHalThreadPriority == HAL_THREAD_PRIORITY_DEFAULT) {
+        return true;
+    }
+    int err = requestPriority(
+            threadPid, threadId,
+            mHalThreadPriority, false /*isForApp*/, true /*asynchronous*/);
+    ALOGE_IF(err, "failed to set priority %d for pid %d tid %d; error %d",
+            mHalThreadPriority, threadPid, threadId, err);
+    // Audio will still work, but latency will be higher and sometimes unacceptable.
+    return err == 0;
+}
+
+namespace {
+
+/* Notes on callback ownership.
+
+This is how (Hw)Binder ownership model looks like. The server implementation
+is owned by Binder framework (via sp<>). Proxies are owned by clients.
+When the last proxy disappears, Binder framework releases the server impl.
+
+Thus, it is not needed to keep any references to StreamOutCallback (this is
+the server impl) -- it will live as long as HAL server holds a strong ref to
+IStreamOutCallback proxy. We clear that reference by calling 'clearCallback'
+from the destructor of StreamOutHalHidl.
+
+The callback only keeps a weak reference to the stream. The stream is owned
+by AudioFlinger.
+
+*/
+
+struct StreamOutCallback : public IStreamOutCallback {
+    StreamOutCallback(const wp<StreamOutHalHidl>& stream) : mStream(stream) {}
+
+    // IStreamOutCallback implementation
+    Return<void> onWriteReady()  override {
+        sp<StreamOutHalHidl> stream = mStream.promote();
+        if (stream != 0) {
+            stream->onWriteReady();
+        }
+        return Void();
+    }
+
+    Return<void> onDrainReady()  override {
+        sp<StreamOutHalHidl> stream = mStream.promote();
+        if (stream != 0) {
+            stream->onDrainReady();
+        }
+        return Void();
+    }
+
+    Return<void> onError()  override {
+        sp<StreamOutHalHidl> stream = mStream.promote();
+        if (stream != 0) {
+            stream->onError();
+        }
+        return Void();
+    }
+
+  private:
+    wp<StreamOutHalHidl> mStream;
+};
+
+}  // namespace
+
+StreamOutHalHidl::StreamOutHalHidl(const sp<IStreamOut>& stream)
+        : StreamHalHidl(stream.get()), mStream(stream), mWriterClient(0), mEfGroup(nullptr) {
+}
+
+StreamOutHalHidl::~StreamOutHalHidl() {
+    if (mStream != 0) {
+        if (mCallback.unsafe_get()) {
+            processReturn("clearCallback", mStream->clearCallback());
+        }
+        processReturn("close", mStream->close());
+        mStream.clear();
+    }
+    mCallback.clear();
+    hardware::IPCThreadState::self()->flushCommands();
+    if (mEfGroup) {
+        EventFlag::deleteEventFlag(&mEfGroup);
+    }
+}
+
+status_t StreamOutHalHidl::getFrameSize(size_t *size) {
+    if (mStream == 0) return NO_INIT;
+    return processReturn("getFrameSize", mStream->getFrameSize(), size);
+}
+
+status_t StreamOutHalHidl::getLatency(uint32_t *latency) {
+    if (mStream == 0) return NO_INIT;
+    if (mWriterClient == gettid() && mCommandMQ) {
+        return callWriterThread(
+                WriteCommand::GET_LATENCY, "getLatency", nullptr, 0,
+                [&](const WriteStatus& writeStatus) {
+                    *latency = writeStatus.reply.latencyMs;
+                });
+    } else {
+        return processReturn("getLatency", mStream->getLatency(), latency);
+    }
+}
+
+status_t StreamOutHalHidl::setVolume(float left, float right) {
+    if (mStream == 0) return NO_INIT;
+    return processReturn("setVolume", mStream->setVolume(left, right));
+}
+
+status_t StreamOutHalHidl::write(const void *buffer, size_t bytes, size_t *written) {
+    if (mStream == 0) return NO_INIT;
+    *written = 0;
+
+    if (bytes == 0 && !mDataMQ) {
+        // Can't determine the size for the MQ buffer. Wait for a non-empty write request.
+        ALOGW_IF(mCallback.unsafe_get(), "First call to async write with 0 bytes");
+        return OK;
+    }
+
+    status_t status;
+    if (!mDataMQ) {
+        // In case if playback starts close to the end of a compressed track, the bytes
+        // that need to be written is less than the actual buffer size. Need to use
+        // full buffer size for the MQ since otherwise after seeking back to the middle
+        // data will be truncated.
+        size_t bufferSize;
+        if ((status = getCachedBufferSize(&bufferSize)) != OK) {
+            return status;
+        }
+        if (bytes > bufferSize) bufferSize = bytes;
+        if ((status = prepareForWriting(bufferSize)) != OK) {
+            return status;
+        }
+    }
+
+    status = callWriterThread(
+            WriteCommand::WRITE, "write", static_cast<const uint8_t*>(buffer), bytes,
+            [&] (const WriteStatus& writeStatus) {
+                *written = writeStatus.reply.written;
+                // Diagnostics of the cause of b/35813113.
+                ALOGE_IF(*written > bytes,
+                        "hal reports more bytes written than asked for: %lld > %lld",
+                        (long long)*written, (long long)bytes);
+            });
+    mStreamPowerLog.log(buffer, *written);
+    return status;
+}
+
+status_t StreamOutHalHidl::callWriterThread(
+        WriteCommand cmd, const char* cmdName,
+        const uint8_t* data, size_t dataSize, StreamOutHalHidl::WriterCallback callback) {
+    if (!mCommandMQ->write(&cmd)) {
+        ALOGE("command message queue write failed for \"%s\"", cmdName);
+        return -EAGAIN;
+    }
+    if (data != nullptr) {
+        size_t availableToWrite = mDataMQ->availableToWrite();
+        if (dataSize > availableToWrite) {
+            ALOGW("truncating write data from %lld to %lld due to insufficient data queue space",
+                    (long long)dataSize, (long long)availableToWrite);
+            dataSize = availableToWrite;
+        }
+        if (!mDataMQ->write(data, dataSize)) {
+            ALOGE("data message queue write failed for \"%s\"", cmdName);
+        }
+    }
+    mEfGroup->wake(static_cast<uint32_t>(MessageQueueFlagBits::NOT_EMPTY));
+
+    // TODO: Remove manual event flag handling once blocking MQ is implemented. b/33815422
+    uint32_t efState = 0;
+retry:
+    status_t ret = mEfGroup->wait(static_cast<uint32_t>(MessageQueueFlagBits::NOT_FULL), &efState);
+    if (efState & static_cast<uint32_t>(MessageQueueFlagBits::NOT_FULL)) {
+        WriteStatus writeStatus;
+        writeStatus.retval = Result::NOT_INITIALIZED;
+        if (!mStatusMQ->read(&writeStatus)) {
+            ALOGE("status message read failed for \"%s\"", cmdName);
+        }
+        if (writeStatus.retval == Result::OK) {
+            ret = OK;
+            callback(writeStatus);
+        } else {
+            ret = processReturn(cmdName, writeStatus.retval);
+        }
+        return ret;
+    }
+    if (ret == -EAGAIN || ret == -EINTR) {
+        // Spurious wakeup. This normally retries no more than once.
+        goto retry;
+    }
+    return ret;
+}
+
+status_t StreamOutHalHidl::prepareForWriting(size_t bufferSize) {
+    std::unique_ptr<CommandMQ> tempCommandMQ;
+    std::unique_ptr<DataMQ> tempDataMQ;
+    std::unique_ptr<StatusMQ> tempStatusMQ;
+    Result retval;
+    pid_t halThreadPid, halThreadTid;
+    Return<void> ret = mStream->prepareForWriting(
+            1, bufferSize,
+            [&](Result r,
+                    const CommandMQ::Descriptor& commandMQ,
+                    const DataMQ::Descriptor& dataMQ,
+                    const StatusMQ::Descriptor& statusMQ,
+                    const ThreadInfo& halThreadInfo) {
+                retval = r;
+                if (retval == Result::OK) {
+                    tempCommandMQ.reset(new CommandMQ(commandMQ));
+                    tempDataMQ.reset(new DataMQ(dataMQ));
+                    tempStatusMQ.reset(new StatusMQ(statusMQ));
+                    if (tempDataMQ->isValid() && tempDataMQ->getEventFlagWord()) {
+                        EventFlag::createEventFlag(tempDataMQ->getEventFlagWord(), &mEfGroup);
+                    }
+                    halThreadPid = halThreadInfo.pid;
+                    halThreadTid = halThreadInfo.tid;
+                }
+            });
+    if (!ret.isOk() || retval != Result::OK) {
+        return processReturn("prepareForWriting", ret, retval);
+    }
+    if (!tempCommandMQ || !tempCommandMQ->isValid() ||
+            !tempDataMQ || !tempDataMQ->isValid() ||
+            !tempStatusMQ || !tempStatusMQ->isValid() ||
+            !mEfGroup) {
+        ALOGE_IF(!tempCommandMQ, "Failed to obtain command message queue for writing");
+        ALOGE_IF(tempCommandMQ && !tempCommandMQ->isValid(),
+                "Command message queue for writing is invalid");
+        ALOGE_IF(!tempDataMQ, "Failed to obtain data message queue for writing");
+        ALOGE_IF(tempDataMQ && !tempDataMQ->isValid(), "Data message queue for writing is invalid");
+        ALOGE_IF(!tempStatusMQ, "Failed to obtain status message queue for writing");
+        ALOGE_IF(tempStatusMQ && !tempStatusMQ->isValid(),
+                "Status message queue for writing is invalid");
+        ALOGE_IF(!mEfGroup, "Event flag creation for writing failed");
+        return NO_INIT;
+    }
+    requestHalThreadPriority(halThreadPid, halThreadTid);
+
+    mCommandMQ = std::move(tempCommandMQ);
+    mDataMQ = std::move(tempDataMQ);
+    mStatusMQ = std::move(tempStatusMQ);
+    mWriterClient = gettid();
+    return OK;
+}
+
+status_t StreamOutHalHidl::getRenderPosition(uint32_t *dspFrames) {
+    if (mStream == 0) return NO_INIT;
+    Result retval;
+    Return<void> ret = mStream->getRenderPosition(
+            [&](Result r, uint32_t d) {
+                retval = r;
+                if (retval == Result::OK) {
+                    *dspFrames = d;
+                }
+            });
+    return processReturn("getRenderPosition", ret, retval);
+}
+
+status_t StreamOutHalHidl::getNextWriteTimestamp(int64_t *timestamp) {
+    if (mStream == 0) return NO_INIT;
+    Result retval;
+    Return<void> ret = mStream->getNextWriteTimestamp(
+            [&](Result r, int64_t t) {
+                retval = r;
+                if (retval == Result::OK) {
+                    *timestamp = t;
+                }
+            });
+    return processReturn("getRenderPosition", ret, retval);
+}
+
+status_t StreamOutHalHidl::setCallback(wp<StreamOutHalInterfaceCallback> callback) {
+    if (mStream == 0) return NO_INIT;
+    status_t status = processReturn(
+            "setCallback", mStream->setCallback(new StreamOutCallback(this)));
+    if (status == OK) {
+        mCallback = callback;
+    }
+    return status;
+}
+
+status_t StreamOutHalHidl::supportsPauseAndResume(bool *supportsPause, bool *supportsResume) {
+    if (mStream == 0) return NO_INIT;
+    Return<void> ret = mStream->supportsPauseAndResume(
+            [&](bool p, bool r) {
+                *supportsPause = p;
+                *supportsResume = r;
+            });
+    return processReturn("supportsPauseAndResume", ret);
+}
+
+status_t StreamOutHalHidl::pause() {
+    if (mStream == 0) return NO_INIT;
+    return processReturn("pause", mStream->pause());
+}
+
+status_t StreamOutHalHidl::resume() {
+    if (mStream == 0) return NO_INIT;
+    return processReturn("pause", mStream->resume());
+}
+
+status_t StreamOutHalHidl::supportsDrain(bool *supportsDrain) {
+    if (mStream == 0) return NO_INIT;
+    return processReturn("supportsDrain", mStream->supportsDrain(), supportsDrain);
+}
+
+status_t StreamOutHalHidl::drain(bool earlyNotify) {
+    if (mStream == 0) return NO_INIT;
+    return processReturn(
+            "drain", mStream->drain(earlyNotify ? AudioDrain::EARLY_NOTIFY : AudioDrain::ALL));
+}
+
+status_t StreamOutHalHidl::flush() {
+    if (mStream == 0) return NO_INIT;
+    return processReturn("pause", mStream->flush());
+}
+
+status_t StreamOutHalHidl::getPresentationPosition(uint64_t *frames, struct timespec *timestamp) {
+    if (mStream == 0) return NO_INIT;
+    if (mWriterClient == gettid() && mCommandMQ) {
+        return callWriterThread(
+                WriteCommand::GET_PRESENTATION_POSITION, "getPresentationPosition", nullptr, 0,
+                [&](const WriteStatus& writeStatus) {
+                    *frames = writeStatus.reply.presentationPosition.frames;
+                    timestamp->tv_sec = writeStatus.reply.presentationPosition.timeStamp.tvSec;
+                    timestamp->tv_nsec = writeStatus.reply.presentationPosition.timeStamp.tvNSec;
+                });
+    } else {
+        Result retval;
+        Return<void> ret = mStream->getPresentationPosition(
+                [&](Result r, uint64_t hidlFrames, const TimeSpec& hidlTimeStamp) {
+                    retval = r;
+                    if (retval == Result::OK) {
+                        *frames = hidlFrames;
+                        timestamp->tv_sec = hidlTimeStamp.tvSec;
+                        timestamp->tv_nsec = hidlTimeStamp.tvNSec;
+                    }
+                });
+        return processReturn("getPresentationPosition", ret, retval);
+    }
+}
+
+void StreamOutHalHidl::onWriteReady() {
+    sp<StreamOutHalInterfaceCallback> callback = mCallback.promote();
+    if (callback == 0) return;
+    ALOGV("asyncCallback onWriteReady");
+    callback->onWriteReady();
+}
+
+void StreamOutHalHidl::onDrainReady() {
+    sp<StreamOutHalInterfaceCallback> callback = mCallback.promote();
+    if (callback == 0) return;
+    ALOGV("asyncCallback onDrainReady");
+    callback->onDrainReady();
+}
+
+void StreamOutHalHidl::onError() {
+    sp<StreamOutHalInterfaceCallback> callback = mCallback.promote();
+    if (callback == 0) return;
+    ALOGV("asyncCallback onError");
+    callback->onError();
+}
+
+
+StreamInHalHidl::StreamInHalHidl(const sp<IStreamIn>& stream)
+        : StreamHalHidl(stream.get()), mStream(stream), mReaderClient(0), mEfGroup(nullptr) {
+}
+
+StreamInHalHidl::~StreamInHalHidl() {
+    if (mStream != 0) {
+        processReturn("close", mStream->close());
+        mStream.clear();
+        hardware::IPCThreadState::self()->flushCommands();
+    }
+    if (mEfGroup) {
+        EventFlag::deleteEventFlag(&mEfGroup);
+    }
+}
+
+status_t StreamInHalHidl::getFrameSize(size_t *size) {
+    if (mStream == 0) return NO_INIT;
+    return processReturn("getFrameSize", mStream->getFrameSize(), size);
+}
+
+status_t StreamInHalHidl::setGain(float gain) {
+    if (mStream == 0) return NO_INIT;
+    return processReturn("setGain", mStream->setGain(gain));
+}
+
+status_t StreamInHalHidl::read(void *buffer, size_t bytes, size_t *read) {
+    if (mStream == 0) return NO_INIT;
+    *read = 0;
+
+    if (bytes == 0 && !mDataMQ) {
+        // Can't determine the size for the MQ buffer. Wait for a non-empty read request.
+        return OK;
+    }
+
+    status_t status;
+    if (!mDataMQ && (status = prepareForReading(bytes)) != OK) {
+        return status;
+    }
+
+    ReadParameters params;
+    params.command = ReadCommand::READ;
+    params.params.read = bytes;
+    status = callReaderThread(params, "read",
+            [&](const ReadStatus& readStatus) {
+                const size_t availToRead = mDataMQ->availableToRead();
+                if (!mDataMQ->read(static_cast<uint8_t*>(buffer), std::min(bytes, availToRead))) {
+                    ALOGE("data message queue read failed for \"read\"");
+                }
+                ALOGW_IF(availToRead != readStatus.reply.read,
+                        "HAL read report inconsistent: mq = %d, status = %d",
+                        (int32_t)availToRead, (int32_t)readStatus.reply.read);
+                *read = readStatus.reply.read;
+            });
+    mStreamPowerLog.log(buffer, *read);
+    return status;
+}
+
+status_t StreamInHalHidl::callReaderThread(
+        const ReadParameters& params, const char* cmdName,
+        StreamInHalHidl::ReaderCallback callback) {
+    if (!mCommandMQ->write(&params)) {
+        ALOGW("command message queue write failed");
+        return -EAGAIN;
+    }
+    mEfGroup->wake(static_cast<uint32_t>(MessageQueueFlagBits::NOT_FULL));
+
+    // TODO: Remove manual event flag handling once blocking MQ is implemented. b/33815422
+    uint32_t efState = 0;
+retry:
+    status_t ret = mEfGroup->wait(static_cast<uint32_t>(MessageQueueFlagBits::NOT_EMPTY), &efState);
+    if (efState & static_cast<uint32_t>(MessageQueueFlagBits::NOT_EMPTY)) {
+        ReadStatus readStatus;
+        readStatus.retval = Result::NOT_INITIALIZED;
+        if (!mStatusMQ->read(&readStatus)) {
+            ALOGE("status message read failed for \"%s\"", cmdName);
+        }
+         if (readStatus.retval == Result::OK) {
+            ret = OK;
+            callback(readStatus);
+        } else {
+            ret = processReturn(cmdName, readStatus.retval);
+        }
+        return ret;
+    }
+    if (ret == -EAGAIN || ret == -EINTR) {
+        // Spurious wakeup. This normally retries no more than once.
+        goto retry;
+    }
+    return ret;
+}
+
+status_t StreamInHalHidl::prepareForReading(size_t bufferSize) {
+    std::unique_ptr<CommandMQ> tempCommandMQ;
+    std::unique_ptr<DataMQ> tempDataMQ;
+    std::unique_ptr<StatusMQ> tempStatusMQ;
+    Result retval;
+    pid_t halThreadPid, halThreadTid;
+    Return<void> ret = mStream->prepareForReading(
+            1, bufferSize,
+            [&](Result r,
+                    const CommandMQ::Descriptor& commandMQ,
+                    const DataMQ::Descriptor& dataMQ,
+                    const StatusMQ::Descriptor& statusMQ,
+                    const ThreadInfo& halThreadInfo) {
+                retval = r;
+                if (retval == Result::OK) {
+                    tempCommandMQ.reset(new CommandMQ(commandMQ));
+                    tempDataMQ.reset(new DataMQ(dataMQ));
+                    tempStatusMQ.reset(new StatusMQ(statusMQ));
+                    if (tempDataMQ->isValid() && tempDataMQ->getEventFlagWord()) {
+                        EventFlag::createEventFlag(tempDataMQ->getEventFlagWord(), &mEfGroup);
+                    }
+                    halThreadPid = halThreadInfo.pid;
+                    halThreadTid = halThreadInfo.tid;
+                }
+            });
+    if (!ret.isOk() || retval != Result::OK) {
+        return processReturn("prepareForReading", ret, retval);
+    }
+    if (!tempCommandMQ || !tempCommandMQ->isValid() ||
+            !tempDataMQ || !tempDataMQ->isValid() ||
+            !tempStatusMQ || !tempStatusMQ->isValid() ||
+            !mEfGroup) {
+        ALOGE_IF(!tempCommandMQ, "Failed to obtain command message queue for writing");
+        ALOGE_IF(tempCommandMQ && !tempCommandMQ->isValid(),
+                "Command message queue for writing is invalid");
+        ALOGE_IF(!tempDataMQ, "Failed to obtain data message queue for reading");
+        ALOGE_IF(tempDataMQ && !tempDataMQ->isValid(), "Data message queue for reading is invalid");
+        ALOGE_IF(!tempStatusMQ, "Failed to obtain status message queue for reading");
+        ALOGE_IF(tempStatusMQ && !tempStatusMQ->isValid(),
+                "Status message queue for reading is invalid");
+        ALOGE_IF(!mEfGroup, "Event flag creation for reading failed");
+        return NO_INIT;
+    }
+    requestHalThreadPriority(halThreadPid, halThreadTid);
+
+    mCommandMQ = std::move(tempCommandMQ);
+    mDataMQ = std::move(tempDataMQ);
+    mStatusMQ = std::move(tempStatusMQ);
+    mReaderClient = gettid();
+    return OK;
+}
+
+status_t StreamInHalHidl::getInputFramesLost(uint32_t *framesLost) {
+    if (mStream == 0) return NO_INIT;
+    return processReturn("getInputFramesLost", mStream->getInputFramesLost(), framesLost);
+}
+
+status_t StreamInHalHidl::getCapturePosition(int64_t *frames, int64_t *time) {
+    if (mStream == 0) return NO_INIT;
+    if (mReaderClient == gettid() && mCommandMQ) {
+        ReadParameters params;
+        params.command = ReadCommand::GET_CAPTURE_POSITION;
+        return callReaderThread(params, "getCapturePosition",
+                [&](const ReadStatus& readStatus) {
+                    *frames = readStatus.reply.capturePosition.frames;
+                    *time = readStatus.reply.capturePosition.time;
+                });
+    } else {
+        Result retval;
+        Return<void> ret = mStream->getCapturePosition(
+                [&](Result r, uint64_t hidlFrames, uint64_t hidlTime) {
+                    retval = r;
+                    if (retval == Result::OK) {
+                        *frames = hidlFrames;
+                        *time = hidlTime;
+                    }
+                });
+        return processReturn("getCapturePosition", ret, retval);
+    }
+}
+
+} // namespace V4_0
+} // namespace android
diff --git a/media/libaudiohal/4.0/StreamHalHidl.h b/media/libaudiohal/4.0/StreamHalHidl.h
new file mode 100644
index 0000000..8d4dc8c
--- /dev/null
+++ b/media/libaudiohal/4.0/StreamHalHidl.h
@@ -0,0 +1,241 @@
+/*
+ * Copyright (C) 2016 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_STREAM_HAL_HIDL_4_0_H
+#define ANDROID_HARDWARE_STREAM_HAL_HIDL_4_0_H
+
+#include <atomic>
+
+#include <android/hardware/audio/4.0/IStream.h>
+#include <android/hardware/audio/4.0/IStreamIn.h>
+#include <android/hardware/audio/4.0/IStreamOut.h>
+#include <fmq/EventFlag.h>
+#include <fmq/MessageQueue.h>
+#include <media/audiohal/StreamHalInterface.h>
+
+#include "ConversionHelperHidl.h"
+#include "StreamPowerLog.h"
+
+using ::android::hardware::audio::V4_0::IStream;
+using ::android::hardware::audio::V4_0::IStreamIn;
+using ::android::hardware::audio::V4_0::IStreamOut;
+using ::android::hardware::EventFlag;
+using ::android::hardware::MessageQueue;
+using ::android::hardware::Return;
+using ReadParameters = ::android::hardware::audio::V4_0::IStreamIn::ReadParameters;
+using ReadStatus = ::android::hardware::audio::V4_0::IStreamIn::ReadStatus;
+using WriteCommand = ::android::hardware::audio::V4_0::IStreamOut::WriteCommand;
+using WriteStatus = ::android::hardware::audio::V4_0::IStreamOut::WriteStatus;
+
+namespace android {
+namespace V4_0 {
+
+class DeviceHalHidl;
+
+class StreamHalHidl : public virtual StreamHalInterface, public ConversionHelperHidl
+{
+  public:
+    // Return the sampling rate in Hz - eg. 44100.
+    virtual status_t getSampleRate(uint32_t *rate);
+
+    // Return size of input/output buffer in bytes for this stream - eg. 4800.
+    virtual status_t getBufferSize(size_t *size);
+
+    // Return the channel mask.
+    virtual status_t getChannelMask(audio_channel_mask_t *mask);
+
+    // Return the audio format - e.g. AUDIO_FORMAT_PCM_16_BIT.
+    virtual status_t getFormat(audio_format_t *format);
+
+    // Convenience method.
+    virtual status_t getAudioProperties(
+            uint32_t *sampleRate, audio_channel_mask_t *mask, audio_format_t *format);
+
+    // Set audio stream parameters.
+    virtual status_t setParameters(const String8& kvPairs);
+
+    // Get audio stream parameters.
+    virtual status_t getParameters(const String8& keys, String8 *values);
+
+    // Add or remove the effect on the stream.
+    virtual status_t addEffect(sp<EffectHalInterface> effect);
+    virtual status_t removeEffect(sp<EffectHalInterface> effect);
+
+    // Put the audio hardware input/output into standby mode.
+    virtual status_t standby();
+
+    virtual status_t dump(int fd);
+
+    // Start a stream operating in mmap mode.
+    virtual status_t start();
+
+    // Stop a stream operating in mmap mode.
+    virtual status_t stop();
+
+    // Retrieve information on the data buffer in mmap mode.
+    virtual status_t createMmapBuffer(int32_t minSizeFrames,
+                                      struct audio_mmap_buffer_info *info);
+
+    // Get current read/write position in the mmap buffer
+    virtual status_t getMmapPosition(struct audio_mmap_position *position);
+
+    // Set the priority of the thread that interacts with the HAL
+    // (must match the priority of the audioflinger's thread that calls 'read' / 'write')
+    virtual status_t setHalThreadPriority(int priority);
+
+  protected:
+    // Subclasses can not be constructed directly by clients.
+    explicit StreamHalHidl(IStream *stream);
+
+    // The destructor automatically closes the stream.
+    virtual ~StreamHalHidl();
+
+    status_t getCachedBufferSize(size_t *size);
+
+    bool requestHalThreadPriority(pid_t threadPid, pid_t threadId);
+
+    // mStreamPowerLog is used for audio signal power logging.
+    StreamPowerLog mStreamPowerLog;
+
+  private:
+    const int HAL_THREAD_PRIORITY_DEFAULT = -1;
+    IStream *mStream;
+    int mHalThreadPriority;
+    size_t mCachedBufferSize;
+};
+
+class StreamOutHalHidl : public StreamOutHalInterface, public StreamHalHidl {
+  public:
+    // Return the frame size (number of bytes per sample) of a stream.
+    virtual status_t getFrameSize(size_t *size);
+
+    // Return the audio hardware driver estimated latency in milliseconds.
+    virtual status_t getLatency(uint32_t *latency);
+
+    // Use this method in situations where audio mixing is done in the hardware.
+    virtual status_t setVolume(float left, float right);
+
+    // Write audio buffer to driver.
+    virtual status_t write(const void *buffer, size_t bytes, size_t *written);
+
+    // Return the number of audio frames written by the audio dsp to DAC since
+    // the output has exited standby.
+    virtual status_t getRenderPosition(uint32_t *dspFrames);
+
+    // Get the local time at which the next write to the audio driver will be presented.
+    virtual status_t getNextWriteTimestamp(int64_t *timestamp);
+
+    // Set the callback for notifying completion of non-blocking write and drain.
+    virtual status_t setCallback(wp<StreamOutHalInterfaceCallback> callback);
+
+    // Returns whether pause and resume operations are supported.
+    virtual status_t supportsPauseAndResume(bool *supportsPause, bool *supportsResume);
+
+    // Notifies to the audio driver to resume playback following a pause.
+    virtual status_t pause();
+
+    // Notifies to the audio driver to resume playback following a pause.
+    virtual status_t resume();
+
+    // Returns whether drain operation is supported.
+    virtual status_t supportsDrain(bool *supportsDrain);
+
+    // Requests notification when data buffered by the driver/hardware has been played.
+    virtual status_t drain(bool earlyNotify);
+
+    // Notifies to the audio driver to flush the queued data.
+    virtual status_t flush();
+
+    // Return a recent count of the number of audio frames presented to an external observer.
+    virtual status_t getPresentationPosition(uint64_t *frames, struct timespec *timestamp);
+
+    // Methods used by StreamOutCallback (HIDL).
+    void onWriteReady();
+    void onDrainReady();
+    void onError();
+
+  private:
+    friend class DeviceHalHidl;
+    typedef MessageQueue<WriteCommand, hardware::kSynchronizedReadWrite> CommandMQ;
+    typedef MessageQueue<uint8_t, hardware::kSynchronizedReadWrite> DataMQ;
+    typedef MessageQueue<WriteStatus, hardware::kSynchronizedReadWrite> StatusMQ;
+
+    wp<StreamOutHalInterfaceCallback> mCallback;
+    sp<IStreamOut> mStream;
+    std::unique_ptr<CommandMQ> mCommandMQ;
+    std::unique_ptr<DataMQ> mDataMQ;
+    std::unique_ptr<StatusMQ> mStatusMQ;
+    std::atomic<pid_t> mWriterClient;
+    EventFlag* mEfGroup;
+
+    // Can not be constructed directly by clients.
+    StreamOutHalHidl(const sp<IStreamOut>& stream);
+
+    virtual ~StreamOutHalHidl();
+
+    using WriterCallback = std::function<void(const WriteStatus& writeStatus)>;
+    status_t callWriterThread(
+            WriteCommand cmd, const char* cmdName,
+            const uint8_t* data, size_t dataSize, WriterCallback callback);
+    status_t prepareForWriting(size_t bufferSize);
+};
+
+class StreamInHalHidl : public StreamInHalInterface, public StreamHalHidl {
+  public:
+    // Return the frame size (number of bytes per sample) of a stream.
+    virtual status_t getFrameSize(size_t *size);
+
+    // Set the input gain for the audio driver.
+    virtual status_t setGain(float gain);
+
+    // Read audio buffer in from driver.
+    virtual status_t read(void *buffer, size_t bytes, size_t *read);
+
+    // Return the amount of input frames lost in the audio driver.
+    virtual status_t getInputFramesLost(uint32_t *framesLost);
+
+    // Return a recent count of the number of audio frames received and
+    // the clock time associated with that frame count.
+    virtual status_t getCapturePosition(int64_t *frames, int64_t *time);
+
+  private:
+    friend class DeviceHalHidl;
+    typedef MessageQueue<ReadParameters, hardware::kSynchronizedReadWrite> CommandMQ;
+    typedef MessageQueue<uint8_t, hardware::kSynchronizedReadWrite> DataMQ;
+    typedef MessageQueue<ReadStatus, hardware::kSynchronizedReadWrite> StatusMQ;
+
+    sp<IStreamIn> mStream;
+    std::unique_ptr<CommandMQ> mCommandMQ;
+    std::unique_ptr<DataMQ> mDataMQ;
+    std::unique_ptr<StatusMQ> mStatusMQ;
+    std::atomic<pid_t> mReaderClient;
+    EventFlag* mEfGroup;
+
+    // Can not be constructed directly by clients.
+    StreamInHalHidl(const sp<IStreamIn>& stream);
+
+    virtual ~StreamInHalHidl();
+
+    using ReaderCallback = std::function<void(const ReadStatus& readStatus)>;
+    status_t callReaderThread(
+            const ReadParameters& params, const char* cmdName, ReaderCallback callback);
+    status_t prepareForReading(size_t bufferSize);
+};
+
+} // namespace V4_0
+} // namespace android
+
+#endif // ANDROID_HARDWARE_STREAM_HAL_HIDL_4_0_H
diff --git a/media/libaudiohal/4.0/StreamHalLocal.cpp b/media/libaudiohal/4.0/StreamHalLocal.cpp
new file mode 100644
index 0000000..592a931
--- /dev/null
+++ b/media/libaudiohal/4.0/StreamHalLocal.cpp
@@ -0,0 +1,319 @@
+/*
+ * Copyright (C) 2016 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_TAG "StreamHalLocal"
+//#define LOG_NDEBUG 0
+
+#include <hardware/audio.h>
+#include <utils/Log.h>
+
+#include "DeviceHalLocal.h"
+#include "StreamHalLocal.h"
+#include "VersionUtils.h"
+
+namespace android {
+namespace V4_0 {
+
+StreamHalLocal::StreamHalLocal(audio_stream_t *stream, sp<DeviceHalLocal> device)
+        : mDevice(device),
+          mStream(stream) {
+    // Instrument audio signal power logging.
+    // Note: This assumes channel mask, format, and sample rate do not change after creation.
+    if (mStream != nullptr && mStreamPowerLog.isUserDebugOrEngBuild()) {
+        mStreamPowerLog.init(mStream->get_sample_rate(mStream),
+                mStream->get_channels(mStream),
+                mStream->get_format(mStream));
+    }
+}
+
+StreamHalLocal::~StreamHalLocal() {
+    mStream = 0;
+    mDevice.clear();
+}
+
+status_t StreamHalLocal::getSampleRate(uint32_t *rate) {
+    *rate = mStream->get_sample_rate(mStream);
+    return OK;
+}
+
+status_t StreamHalLocal::getBufferSize(size_t *size) {
+    *size = mStream->get_buffer_size(mStream);
+    return OK;
+}
+
+status_t StreamHalLocal::getChannelMask(audio_channel_mask_t *mask) {
+    *mask = mStream->get_channels(mStream);
+    return OK;
+}
+
+status_t StreamHalLocal::getFormat(audio_format_t *format) {
+    *format = mStream->get_format(mStream);
+    return OK;
+}
+
+status_t StreamHalLocal::getAudioProperties(
+        uint32_t *sampleRate, audio_channel_mask_t *mask, audio_format_t *format) {
+    *sampleRate = mStream->get_sample_rate(mStream);
+    *mask = mStream->get_channels(mStream);
+    *format = mStream->get_format(mStream);
+    return OK;
+}
+
+status_t StreamHalLocal::setParameters(const String8& kvPairs) {
+    return mStream->set_parameters(mStream, kvPairs.string());
+}
+
+status_t StreamHalLocal::getParameters(const String8& keys, String8 *values) {
+    char *halValues = mStream->get_parameters(mStream, keys.string());
+    if (halValues != NULL) {
+        values->setTo(halValues);
+        free(halValues);
+    } else {
+        values->clear();
+    }
+    return OK;
+}
+
+status_t StreamHalLocal::addEffect(sp<EffectHalInterface>) {
+    LOG_ALWAYS_FATAL("Local streams can not have effects");
+    return INVALID_OPERATION;
+}
+
+status_t StreamHalLocal::removeEffect(sp<EffectHalInterface>) {
+    LOG_ALWAYS_FATAL("Local streams can not have effects");
+    return INVALID_OPERATION;
+}
+
+status_t StreamHalLocal::standby() {
+    return mStream->standby(mStream);
+}
+
+status_t StreamHalLocal::dump(int fd) {
+    status_t status = mStream->dump(mStream, fd);
+    mStreamPowerLog.dump(fd);
+    return status;
+}
+
+status_t StreamHalLocal::setHalThreadPriority(int) {
+    // Don't need to do anything as local hal is executed by audioflinger directly
+    // on the same thread.
+    return OK;
+}
+
+StreamOutHalLocal::StreamOutHalLocal(audio_stream_out_t *stream, sp<DeviceHalLocal> device)
+        : StreamHalLocal(&stream->common, device), mStream(stream) {
+}
+
+StreamOutHalLocal::~StreamOutHalLocal() {
+    mCallback.clear();
+    mDevice->closeOutputStream(mStream);
+    mStream = 0;
+}
+
+status_t StreamOutHalLocal::getFrameSize(size_t *size) {
+    *size = audio_stream_out_frame_size(mStream);
+    return OK;
+}
+
+status_t StreamOutHalLocal::getLatency(uint32_t *latency) {
+    *latency = mStream->get_latency(mStream);
+    return OK;
+}
+
+status_t StreamOutHalLocal::setVolume(float left, float right) {
+    if (mStream->set_volume == NULL) return INVALID_OPERATION;
+    return mStream->set_volume(mStream, left, right);
+}
+
+status_t StreamOutHalLocal::write(const void *buffer, size_t bytes, size_t *written) {
+    ssize_t writeResult = mStream->write(mStream, buffer, bytes);
+    if (writeResult > 0) {
+        *written = writeResult;
+        mStreamPowerLog.log(buffer, *written);
+        return OK;
+    } else {
+        *written = 0;
+        return writeResult;
+    }
+}
+
+status_t StreamOutHalLocal::getRenderPosition(uint32_t *dspFrames) {
+    return mStream->get_render_position(mStream, dspFrames);
+}
+
+status_t StreamOutHalLocal::getNextWriteTimestamp(int64_t *timestamp) {
+    if (mStream->get_next_write_timestamp == NULL) return INVALID_OPERATION;
+    return mStream->get_next_write_timestamp(mStream, timestamp);
+}
+
+status_t StreamOutHalLocal::setCallback(wp<StreamOutHalInterfaceCallback> callback) {
+    if (mStream->set_callback == NULL) return INVALID_OPERATION;
+    status_t result = mStream->set_callback(mStream, StreamOutHalLocal::asyncCallback, this);
+    if (result == OK) {
+        mCallback = callback;
+    }
+    return result;
+}
+
+// static
+int StreamOutHalLocal::asyncCallback(stream_callback_event_t event, void*, void *cookie) {
+    // We act as if we gave a wp<StreamOutHalLocal> to HAL. This way we should handle
+    // correctly the case when the callback is invoked while StreamOutHalLocal's destructor is
+    // already running, because the destructor is invoked after the refcount has been atomically
+    // decremented.
+    wp<StreamOutHalLocal> weakSelf(static_cast<StreamOutHalLocal*>(cookie));
+    sp<StreamOutHalLocal> self = weakSelf.promote();
+    if (self == 0) return 0;
+    sp<StreamOutHalInterfaceCallback> callback = self->mCallback.promote();
+    if (callback == 0) return 0;
+    ALOGV("asyncCallback() event %d", event);
+    switch (event) {
+        case STREAM_CBK_EVENT_WRITE_READY:
+            callback->onWriteReady();
+            break;
+        case STREAM_CBK_EVENT_DRAIN_READY:
+            callback->onDrainReady();
+            break;
+        case STREAM_CBK_EVENT_ERROR:
+            callback->onError();
+            break;
+        default:
+            ALOGW("asyncCallback() unknown event %d", event);
+            break;
+    }
+    return 0;
+}
+
+status_t StreamOutHalLocal::supportsPauseAndResume(bool *supportsPause, bool *supportsResume) {
+    *supportsPause = mStream->pause != NULL;
+    *supportsResume = mStream->resume != NULL;
+    return OK;
+}
+
+status_t StreamOutHalLocal::pause() {
+    if (mStream->pause == NULL) return INVALID_OPERATION;
+    return mStream->pause(mStream);
+}
+
+status_t StreamOutHalLocal::resume() {
+    if (mStream->resume == NULL) return INVALID_OPERATION;
+    return mStream->resume(mStream);
+}
+
+status_t StreamOutHalLocal::supportsDrain(bool *supportsDrain) {
+    *supportsDrain = mStream->drain != NULL;
+    return OK;
+}
+
+status_t StreamOutHalLocal::drain(bool earlyNotify) {
+    if (mStream->drain == NULL) return INVALID_OPERATION;
+    return mStream->drain(mStream, earlyNotify ? AUDIO_DRAIN_EARLY_NOTIFY : AUDIO_DRAIN_ALL);
+}
+
+status_t StreamOutHalLocal::flush() {
+    if (mStream->flush == NULL) return INVALID_OPERATION;
+    return mStream->flush(mStream);
+}
+
+status_t StreamOutHalLocal::getPresentationPosition(uint64_t *frames, struct timespec *timestamp) {
+    if (mStream->get_presentation_position == NULL) return INVALID_OPERATION;
+    return mStream->get_presentation_position(mStream, frames, timestamp);
+}
+
+status_t StreamOutHalLocal::start() {
+    if (mStream->start == NULL) return INVALID_OPERATION;
+    return mStream->start(mStream);
+}
+
+status_t StreamOutHalLocal::stop() {
+    if (mStream->stop == NULL) return INVALID_OPERATION;
+    return mStream->stop(mStream);
+}
+
+status_t StreamOutHalLocal::createMmapBuffer(int32_t minSizeFrames,
+                                  struct audio_mmap_buffer_info *info) {
+    if (mStream->create_mmap_buffer == NULL) return INVALID_OPERATION;
+    return mStream->create_mmap_buffer(mStream, minSizeFrames, info);
+}
+
+status_t StreamOutHalLocal::getMmapPosition(struct audio_mmap_position *position) {
+    if (mStream->get_mmap_position == NULL) return INVALID_OPERATION;
+    return mStream->get_mmap_position(mStream, position);
+}
+
+StreamInHalLocal::StreamInHalLocal(audio_stream_in_t *stream, sp<DeviceHalLocal> device)
+        : StreamHalLocal(&stream->common, device), mStream(stream) {
+}
+
+StreamInHalLocal::~StreamInHalLocal() {
+    mDevice->closeInputStream(mStream);
+    mStream = 0;
+}
+
+status_t StreamInHalLocal::getFrameSize(size_t *size) {
+    *size = audio_stream_in_frame_size(mStream);
+    return OK;
+}
+
+status_t StreamInHalLocal::setGain(float gain) {
+    return mStream->set_gain(mStream, gain);
+}
+
+status_t StreamInHalLocal::read(void *buffer, size_t bytes, size_t *read) {
+    ssize_t readResult = mStream->read(mStream, buffer, bytes);
+    if (readResult > 0) {
+        *read = readResult;
+        mStreamPowerLog.log( buffer, *read);
+        return OK;
+    } else {
+        *read = 0;
+        return readResult;
+    }
+}
+
+status_t StreamInHalLocal::getInputFramesLost(uint32_t *framesLost) {
+    *framesLost = mStream->get_input_frames_lost(mStream);
+    return OK;
+}
+
+status_t StreamInHalLocal::getCapturePosition(int64_t *frames, int64_t *time) {
+    if (mStream->get_capture_position == NULL) return INVALID_OPERATION;
+    return mStream->get_capture_position(mStream, frames, time);
+}
+
+status_t StreamInHalLocal::start() {
+    if (mStream->start == NULL) return INVALID_OPERATION;
+    return mStream->start(mStream);
+}
+
+status_t StreamInHalLocal::stop() {
+    if (mStream->stop == NULL) return INVALID_OPERATION;
+    return mStream->stop(mStream);
+}
+
+status_t StreamInHalLocal::createMmapBuffer(int32_t minSizeFrames,
+                                  struct audio_mmap_buffer_info *info) {
+    if (mStream->create_mmap_buffer == NULL) return INVALID_OPERATION;
+    return mStream->create_mmap_buffer(mStream, minSizeFrames, info);
+}
+
+status_t StreamInHalLocal::getMmapPosition(struct audio_mmap_position *position) {
+    if (mStream->get_mmap_position == NULL) return INVALID_OPERATION;
+    return mStream->get_mmap_position(mStream, position);
+}
+
+} // namespace V4_0
+} // namespace android
diff --git a/media/libaudiohal/4.0/StreamHalLocal.h b/media/libaudiohal/4.0/StreamHalLocal.h
new file mode 100644
index 0000000..076bc4c
--- /dev/null
+++ b/media/libaudiohal/4.0/StreamHalLocal.h
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2016 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_STREAM_HAL_LOCAL_4_0_H
+#define ANDROID_HARDWARE_STREAM_HAL_LOCAL_4_0_H
+
+#include <media/audiohal/StreamHalInterface.h>
+#include "StreamPowerLog.h"
+
+namespace android {
+namespace V4_0 {
+
+class DeviceHalLocal;
+
+class StreamHalLocal : public virtual StreamHalInterface
+{
+  public:
+    // Return the sampling rate in Hz - eg. 44100.
+    virtual status_t getSampleRate(uint32_t *rate);
+
+    // Return size of input/output buffer in bytes for this stream - eg. 4800.
+    virtual status_t getBufferSize(size_t *size);
+
+    // Return the channel mask.
+    virtual status_t getChannelMask(audio_channel_mask_t *mask);
+
+    // Return the audio format - e.g. AUDIO_FORMAT_PCM_16_BIT.
+    virtual status_t getFormat(audio_format_t *format);
+
+    // Convenience method.
+    virtual status_t getAudioProperties(
+            uint32_t *sampleRate, audio_channel_mask_t *mask, audio_format_t *format);
+
+    // Set audio stream parameters.
+    virtual status_t setParameters(const String8& kvPairs);
+
+    // Get audio stream parameters.
+    virtual status_t getParameters(const String8& keys, String8 *values);
+
+    // Add or remove the effect on the stream.
+    virtual status_t addEffect(sp<EffectHalInterface> effect);
+    virtual status_t removeEffect(sp<EffectHalInterface> effect);
+
+    // Put the audio hardware input/output into standby mode.
+    virtual status_t standby();
+
+    virtual status_t dump(int fd);
+
+    // Start a stream operating in mmap mode.
+    virtual status_t start() = 0;
+
+    // Stop a stream operating in mmap mode.
+    virtual status_t stop() = 0;
+
+    // Retrieve information on the data buffer in mmap mode.
+    virtual status_t createMmapBuffer(int32_t minSizeFrames,
+                                      struct audio_mmap_buffer_info *info) = 0;
+
+    // Get current read/write position in the mmap buffer
+    virtual status_t getMmapPosition(struct audio_mmap_position *position) = 0;
+
+    // Set the priority of the thread that interacts with the HAL
+    // (must match the priority of the audioflinger's thread that calls 'read' / 'write')
+    virtual status_t setHalThreadPriority(int priority);
+
+  protected:
+    // Subclasses can not be constructed directly by clients.
+    StreamHalLocal(audio_stream_t *stream, sp<DeviceHalLocal> device);
+
+    // The destructor automatically closes the stream.
+    virtual ~StreamHalLocal();
+
+    sp<DeviceHalLocal> mDevice;
+
+    // mStreamPowerLog is used for audio signal power logging.
+    StreamPowerLog mStreamPowerLog;
+
+  private:
+    audio_stream_t *mStream;
+};
+
+class StreamOutHalLocal : public StreamOutHalInterface, public StreamHalLocal {
+  public:
+    // Return the frame size (number of bytes per sample) of a stream.
+    virtual status_t getFrameSize(size_t *size);
+
+    // Return the audio hardware driver estimated latency in milliseconds.
+    virtual status_t getLatency(uint32_t *latency);
+
+    // Use this method in situations where audio mixing is done in the hardware.
+    virtual status_t setVolume(float left, float right);
+
+    // Write audio buffer to driver.
+    virtual status_t write(const void *buffer, size_t bytes, size_t *written);
+
+    // Return the number of audio frames written by the audio dsp to DAC since
+    // the output has exited standby.
+    virtual status_t getRenderPosition(uint32_t *dspFrames);
+
+    // Get the local time at which the next write to the audio driver will be presented.
+    virtual status_t getNextWriteTimestamp(int64_t *timestamp);
+
+    // Set the callback for notifying completion of non-blocking write and drain.
+    virtual status_t setCallback(wp<StreamOutHalInterfaceCallback> callback);
+
+    // Returns whether pause and resume operations are supported.
+    virtual status_t supportsPauseAndResume(bool *supportsPause, bool *supportsResume);
+
+    // Notifies to the audio driver to resume playback following a pause.
+    virtual status_t pause();
+
+    // Notifies to the audio driver to resume playback following a pause.
+    virtual status_t resume();
+
+    // Returns whether drain operation is supported.
+    virtual status_t supportsDrain(bool *supportsDrain);
+
+    // Requests notification when data buffered by the driver/hardware has been played.
+    virtual status_t drain(bool earlyNotify);
+
+    // Notifies to the audio driver to flush the queued data.
+    virtual status_t flush();
+
+    // Return a recent count of the number of audio frames presented to an external observer.
+    virtual status_t getPresentationPosition(uint64_t *frames, struct timespec *timestamp);
+
+    // Start a stream operating in mmap mode.
+    virtual status_t start();
+
+    // Stop a stream operating in mmap mode.
+    virtual status_t stop();
+
+    // Retrieve information on the data buffer in mmap mode.
+    virtual status_t createMmapBuffer(int32_t minSizeFrames,
+                                      struct audio_mmap_buffer_info *info);
+
+    // Get current read/write position in the mmap buffer
+    virtual status_t getMmapPosition(struct audio_mmap_position *position);
+
+  private:
+    audio_stream_out_t *mStream;
+    wp<StreamOutHalInterfaceCallback> mCallback;
+
+    friend class DeviceHalLocal;
+
+    // Can not be constructed directly by clients.
+    StreamOutHalLocal(audio_stream_out_t *stream, sp<DeviceHalLocal> device);
+
+    virtual ~StreamOutHalLocal();
+
+    static int asyncCallback(stream_callback_event_t event, void *param, void *cookie);
+};
+
+class StreamInHalLocal : public StreamInHalInterface, public StreamHalLocal {
+  public:
+    // Return the frame size (number of bytes per sample) of a stream.
+    virtual status_t getFrameSize(size_t *size);
+
+    // Set the input gain for the audio driver.
+    virtual status_t setGain(float gain);
+
+    // Read audio buffer in from driver.
+    virtual status_t read(void *buffer, size_t bytes, size_t *read);
+
+    // Return the amount of input frames lost in the audio driver.
+    virtual status_t getInputFramesLost(uint32_t *framesLost);
+
+    // Return a recent count of the number of audio frames received and
+    // the clock time associated with that frame count.
+    virtual status_t getCapturePosition(int64_t *frames, int64_t *time);
+
+    // Start a stream operating in mmap mode.
+    virtual status_t start();
+
+    // Stop a stream operating in mmap mode.
+    virtual status_t stop();
+
+    // Retrieve information on the data buffer in mmap mode.
+    virtual status_t createMmapBuffer(int32_t minSizeFrames,
+                                      struct audio_mmap_buffer_info *info);
+
+    // Get current read/write position in the mmap buffer
+    virtual status_t getMmapPosition(struct audio_mmap_position *position);
+
+  private:
+    audio_stream_in_t *mStream;
+
+    friend class DeviceHalLocal;
+
+    // Can not be constructed directly by clients.
+    StreamInHalLocal(audio_stream_in_t *stream, sp<DeviceHalLocal> device);
+
+    virtual ~StreamInHalLocal();
+};
+
+} // namespace V4_0
+} // namespace android
+
+#endif // ANDROID_HARDWARE_STREAM_HAL_LOCAL_4_0_H
diff --git a/media/libaudiohal/4.0/StreamPowerLog.h b/media/libaudiohal/4.0/StreamPowerLog.h
new file mode 100644
index 0000000..57b7201
--- /dev/null
+++ b/media/libaudiohal/4.0/StreamPowerLog.h
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_HARDWARE_STREAM_POWER_LOG_4_0_H
+#define ANDROID_HARDWARE_STREAM_POWER_LOG_4_0_H
+
+#include <audio_utils/clock.h>
+#include <audio_utils/PowerLog.h>
+#include <cutils/properties.h>
+#include <system/audio.h>
+
+namespace android {
+namespace V4_0 {
+
+class StreamPowerLog {
+public:
+    StreamPowerLog() :
+        mIsUserDebugOrEngBuild(is_userdebug_or_eng_build()),
+        mPowerLog(nullptr),
+        mFrameSize(0) {
+        // use init() to set up the power log.
+    }
+
+    ~StreamPowerLog() {
+        power_log_destroy(mPowerLog); // OK for null mPowerLog
+        mPowerLog = nullptr;
+    }
+
+    // A one-time initialization (do not call twice) before using StreamPowerLog.
+    void init(uint32_t sampleRate, audio_channel_mask_t channelMask, audio_format_t format) {
+        if (mPowerLog == nullptr) {
+            // Note: A way to get channel count for both input and output channel masks
+            // but does not check validity of the channel mask.
+            const uint32_t channelCount = popcount(audio_channel_mask_get_bits(channelMask));
+            mFrameSize = channelCount * audio_bytes_per_sample(format);
+            if (mFrameSize > 0) {
+                const size_t kPowerLogFramesPerEntry =
+                        (long long)sampleRate * kPowerLogSamplingIntervalMs / 1000;
+                mPowerLog = power_log_create(
+                        sampleRate,
+                        channelCount,
+                        format,
+                        kPowerLogEntries,
+                        kPowerLogFramesPerEntry);
+            }
+        }
+        // mPowerLog may be NULL (not the right build, format not accepted, etc.).
+    }
+
+    // Dump the power log to fd.
+    void dump(int fd) const {
+        // OK for null mPowerLog
+        (void)power_log_dump(
+                mPowerLog, fd, "      " /* prefix */, kPowerLogLines, 0 /* limit_ns */);
+    }
+
+    // Log the audio data contained in buffer.
+    void log(const void *buffer, size_t sizeInBytes) const {
+        if (mPowerLog != nullptr) { // mFrameSize is always nonzero if mPowerLog exists.
+            power_log_log(
+                    mPowerLog, buffer, sizeInBytes / mFrameSize, audio_utils_get_real_time_ns());
+        }
+    }
+
+    bool isUserDebugOrEngBuild() const {
+        return mIsUserDebugOrEngBuild;
+    }
+
+private:
+
+    static inline bool is_userdebug_or_eng_build() {
+        char value[PROPERTY_VALUE_MAX];
+        (void)property_get("ro.build.type", value, "unknown"); // ignore actual length
+        return strcmp(value, "userdebug") == 0 || strcmp(value, "eng") == 0;
+    }
+
+    // Audio signal power log configuration.
+    static const size_t kPowerLogLines = 40;
+    static const size_t kPowerLogSamplingIntervalMs = 50;
+    static const size_t kPowerLogEntries = (1 /* minutes */ * 60 /* seconds */ * 1000 /* msec */
+            / kPowerLogSamplingIntervalMs);
+
+    const bool mIsUserDebugOrEngBuild;
+    power_log_t *mPowerLog;
+    size_t mFrameSize;
+};
+
+} // namespace V4_0
+} // namespace android
+
+#endif // ANDROID_HARDWARE_STREAM_POWER_LOG_4_0_H
diff --git a/media/libaudiohal/4.0/VersionUtils.h b/media/libaudiohal/4.0/VersionUtils.h
new file mode 100644
index 0000000..1246c2e
--- /dev/null
+++ b/media/libaudiohal/4.0/VersionUtils.h
@@ -0,0 +1,49 @@
+/*
+ * 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 ANDROID_HARDWARE_VERSION_UTILS_4_0_H
+#define ANDROID_HARDWARE_VERSION_UTILS_4_0_H
+
+#include <android/hardware/audio/4.0/types.h>
+#include <hidl/HidlSupport.h>
+
+using ::android::hardware::audio::V4_0::ParameterValue;
+using ::android::hardware::audio::V4_0::Result;
+using ::android::hardware::Return;
+using ::android::hardware::hidl_vec;
+using ::android::hardware::hidl_string;
+
+namespace android {
+namespace V4_0 {
+namespace utils {
+
+template <class T, class Callback>
+Return<void> getParameters(T& object, hidl_vec<ParameterValue> context,
+                           hidl_vec<hidl_string> keys, Callback callback) {
+    return object->getParameters(context, keys, callback);
+}
+
+template <class T>
+Return<Result> setParameters(T& object, hidl_vec<ParameterValue> context,
+                             hidl_vec<ParameterValue> keys) {
+    return object->setParameters(context, keys);
+}
+
+} // namespace utils
+} // namespace V4_0
+} // namespace android
+
+#endif // ANDROID_HARDWARE_VERSION_UTILS_4_0_H
diff --git a/media/libaudiohal/4.0/include/libaudiohal/4.0/DevicesFactoryHalHybrid.h b/media/libaudiohal/4.0/include/libaudiohal/4.0/DevicesFactoryHalHybrid.h
new file mode 100644
index 0000000..abf6de0
--- /dev/null
+++ b/media/libaudiohal/4.0/include/libaudiohal/4.0/DevicesFactoryHalHybrid.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_HARDWARE_DEVICES_FACTORY_HAL_HYBRID_4_0_H
+#define ANDROID_HARDWARE_DEVICES_FACTORY_HAL_HYBRID_4_0_H
+
+#include <media/audiohal/DevicesFactoryHalInterface.h>
+#include <utils/Errors.h>
+#include <utils/RefBase.h>
+
+namespace android {
+namespace V4_0 {
+
+class DevicesFactoryHalHybrid : public DevicesFactoryHalInterface
+{
+  public:
+    // Opens a device with the specified name. To close the device, it is
+    // necessary to release references to the returned object.
+    virtual status_t openDevice(const char *name, sp<DeviceHalInterface> *device);
+
+  private:
+    friend class DevicesFactoryHalInterface;
+
+    // Can not be constructed directly by clients.
+    DevicesFactoryHalHybrid();
+
+    virtual ~DevicesFactoryHalHybrid();
+
+    sp<DevicesFactoryHalInterface> mLocalFactory;
+    sp<DevicesFactoryHalInterface> mHidlFactory;
+};
+
+} // namespace V4_0
+} // namespace android
+
+#endif // ANDROID_HARDWARE_DEVICES_FACTORY_HAL_HYBRID_4_0_H
diff --git a/media/libaudiohal/4.0/include/libaudiohal/4.0/EffectsFactoryHalHidl.h b/media/libaudiohal/4.0/include/libaudiohal/4.0/EffectsFactoryHalHidl.h
new file mode 100644
index 0000000..680b7a1
--- /dev/null
+++ b/media/libaudiohal/4.0/include/libaudiohal/4.0/EffectsFactoryHalHidl.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2016 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_EFFECTS_FACTORY_HAL_HIDL_4_0_H
+#define ANDROID_HARDWARE_EFFECTS_FACTORY_HAL_HIDL_4_0_H
+
+#include <android/hardware/audio/effect/4.0/IEffectsFactory.h>
+#include <android/hardware/audio/effect/4.0/types.h>
+#include <media/audiohal/EffectsFactoryHalInterface.h>
+
+#include "ConversionHelperHidl.h"
+
+namespace android {
+namespace V4_0 {
+
+using ::android::hardware::audio::effect::V4_0::EffectDescriptor;
+using ::android::hardware::audio::effect::V4_0::IEffectsFactory;
+using ::android::hardware::hidl_vec;
+
+class EffectsFactoryHalHidl : public EffectsFactoryHalInterface, public ConversionHelperHidl
+{
+  public:
+    // Returns the number of different effects in all loaded libraries.
+    virtual status_t queryNumberEffects(uint32_t *pNumEffects);
+
+    // Returns a descriptor of the next available effect.
+    virtual status_t getDescriptor(uint32_t index,
+            effect_descriptor_t *pDescriptor);
+
+    virtual status_t getDescriptor(const effect_uuid_t *pEffectUuid,
+            effect_descriptor_t *pDescriptor);
+
+    // Creates an effect engine of the specified type.
+    // To release the effect engine, it is necessary to release references
+    // to the returned effect object.
+    virtual status_t createEffect(const effect_uuid_t *pEffectUuid,
+            int32_t sessionId, int32_t ioId,
+            sp<EffectHalInterface> *effect);
+
+    virtual status_t dumpEffects(int fd);
+
+    status_t allocateBuffer(size_t size, sp<EffectBufferHalInterface>* buffer) override;
+    status_t mirrorBuffer(void* external, size_t size,
+                          sp<EffectBufferHalInterface>* buffer) override;
+
+  private:
+    friend class EffectsFactoryHalInterface;
+
+    sp<IEffectsFactory> mEffectsFactory;
+    hidl_vec<EffectDescriptor> mLastDescriptors;
+
+    // Can not be constructed directly by clients.
+    EffectsFactoryHalHidl();
+    virtual ~EffectsFactoryHalHidl();
+
+    status_t queryAllDescriptors();
+};
+
+} // namespace V4_0
+} // namespace android
+
+#endif // ANDROID_HARDWARE_EFFECTS_FACTORY_HAL_HIDL_4_0_H
diff --git a/media/libaudiohal/Android.bp b/media/libaudiohal/Android.bp
index 7ecb9d8..3a5df27 100644
--- a/media/libaudiohal/Android.bp
+++ b/media/libaudiohal/Android.bp
@@ -12,9 +12,12 @@
     ],
 
     shared_libs: [
-        "android.hardware.audio@2.0",
         "android.hardware.audio.effect@2.0",
+        "android.hardware.audio.effect@4.0",
+        "android.hardware.audio@2.0",
+        "android.hardware.audio@4.0",
         "libaudiohal@2.0",
+        "libaudiohal@4.0",
         "libutils",
     ],
 
diff --git a/media/libaudiohal/DevicesFactoryHalInterface.cpp b/media/libaudiohal/DevicesFactoryHalInterface.cpp
index cfec3d6..4c8eaf6 100644
--- a/media/libaudiohal/DevicesFactoryHalInterface.cpp
+++ b/media/libaudiohal/DevicesFactoryHalInterface.cpp
@@ -14,16 +14,20 @@
  * limitations under the License.
  */
 
-#include "DevicesFactoryHalHybrid.h"
 #include <android/hardware/audio/2.0/IDevicesFactory.h>
+#include <android/hardware/audio/4.0/IDevicesFactory.h>
 
-using namespace ::android::hardware::audio;
+#include <DevicesFactoryHalHybrid.h>
+#include <libaudiohal/4.0/DevicesFactoryHalHybrid.h>
 
 namespace android {
 
 // static
 sp<DevicesFactoryHalInterface> DevicesFactoryHalInterface::create() {
-    if (V2_0::IDevicesFactory::getService() != nullptr) {
+    if (hardware::audio::V4_0::IDevicesFactory::getService() != nullptr) {
+        return new V4_0::DevicesFactoryHalHybrid();
+    }
+    if (hardware::audio::V2_0::IDevicesFactory::getService() != nullptr) {
         return new DevicesFactoryHalHybrid();
     }
     return nullptr;
diff --git a/media/libaudiohal/EffectsFactoryHalInterface.cpp b/media/libaudiohal/EffectsFactoryHalInterface.cpp
index 01a171e..ead1fa2 100644
--- a/media/libaudiohal/EffectsFactoryHalInterface.cpp
+++ b/media/libaudiohal/EffectsFactoryHalInterface.cpp
@@ -14,21 +14,21 @@
  * limitations under the License.
  */
 
-#define LOG_TAG "EffectsFactoryHalHidl"
-//#define LOG_NDEBUG 0
+#include <android/hardware/audio/effect/2.0/IEffectsFactory.h>
+#include <android/hardware/audio/effect/4.0/IEffectsFactory.h>
 
-#include "EffectHalHidl.h"
-#include "EffectsFactoryHalHidl.h"
+#include <EffectsFactoryHalHidl.h>
+#include <libaudiohal/4.0/EffectsFactoryHalHidl.h>
 
-using ::android::hardware::Return;
-
-using namespace ::android::hardware::audio::effect;
 
 namespace android {
 
 // static
 sp<EffectsFactoryHalInterface> EffectsFactoryHalInterface::create() {
-    if (V2_0::IEffectsFactory::getService() != nullptr) {
+    if (hardware::audio::effect::V4_0::IEffectsFactory::getService() != nullptr) {
+        return new V4_0::EffectsFactoryHalHidl();
+    }
+    if (hardware::audio::effect::V2_0::IEffectsFactory::getService() != nullptr) {
         return new EffectsFactoryHalHidl();
     }
     return nullptr;
diff --git a/media/libheif/HeifDecoderImpl.cpp b/media/libheif/HeifDecoderImpl.cpp
index 2dfbdca..4b17a9b 100644
--- a/media/libheif/HeifDecoderImpl.cpp
+++ b/media/libheif/HeifDecoderImpl.cpp
@@ -332,8 +332,8 @@
 
     if (frameInfo != nullptr) {
         frameInfo->set(
-                videoFrame->mDisplayWidth,
-                videoFrame->mDisplayHeight,
+                videoFrame->mWidth,
+                videoFrame->mHeight,
                 videoFrame->mRotationAngle,
                 videoFrame->mBytesPerPixel,
                 videoFrame->mIccSize,
@@ -410,8 +410,8 @@
 
     if (frameInfo != nullptr) {
         frameInfo->set(
-                videoFrame->mDisplayWidth,
-                videoFrame->mDisplayHeight,
+                videoFrame->mWidth,
+                videoFrame->mHeight,
                 videoFrame->mRotationAngle,
                 videoFrame->mBytesPerPixel,
                 videoFrame->mIccSize,
@@ -430,12 +430,12 @@
         return false;
     }
     VideoFrame* videoFrame = static_cast<VideoFrame*>(mFrameMemory->pointer());
-    if (mCurScanline >= videoFrame->mDisplayHeight) {
+    if (mCurScanline >= videoFrame->mHeight) {
         ALOGE("no more scanline available");
         return false;
     }
     uint8_t* src = videoFrame->getFlattenedData() + videoFrame->mRowBytes * mCurScanline++;
-    memcpy(dst, src, videoFrame->mBytesPerPixel * videoFrame->mDisplayWidth);
+    memcpy(dst, src, videoFrame->mBytesPerPixel * videoFrame->mWidth);
     return true;
 }
 
@@ -447,8 +447,8 @@
 
     uint32_t oldScanline = mCurScanline;
     mCurScanline += count;
-    if (mCurScanline > videoFrame->mDisplayHeight) {
-        mCurScanline = videoFrame->mDisplayHeight;
+    if (mCurScanline > videoFrame->mHeight) {
+        mCurScanline = videoFrame->mHeight;
     }
     return (mCurScanline > oldScanline) ? (mCurScanline - oldScanline) : 0;
 }
diff --git a/media/libmedia/NdkWrapper.cpp b/media/libmedia/NdkWrapper.cpp
index 5e47b48..5418af9 100644
--- a/media/libmedia/NdkWrapper.cpp
+++ b/media/libmedia/NdkWrapper.cpp
@@ -81,11 +81,16 @@
     AMEDIAFORMAT_KEY_STRIDE,
     AMEDIAFORMAT_KEY_TRACK_ID,
     AMEDIAFORMAT_KEY_WIDTH,
+    AMEDIAFORMAT_KEY_DISPLAY_HEIGHT,
+    AMEDIAFORMAT_KEY_DISPLAY_WIDTH,
+    AMEDIAFORMAT_KEY_TEMPORAL_LAYER_ID,
+    AMEDIAFORMAT_KEY_TRACK_INDEX,
 };
 
 static const char *AMediaFormatKeyGroupInt64[] = {
     AMEDIAFORMAT_KEY_DURATION,
     AMEDIAFORMAT_KEY_REPEAT_PREVIOUS_FRAME_AFTER,
+    AMEDIAFORMAT_KEY_TIME_US,
 };
 
 static const char *AMediaFormatKeyGroupString[] = {
@@ -96,6 +101,14 @@
 
 static const char *AMediaFormatKeyGroupBuffer[] = {
     AMEDIAFORMAT_KEY_HDR_STATIC_INFO,
+    AMEDIAFORMAT_KEY_SEI,
+    AMEDIAFORMAT_KEY_MPEG_USER_DATA,
+};
+
+static const char *AMediaFormatKeyGroupCsd[] = {
+    AMEDIAFORMAT_KEY_CSD_0,
+    AMEDIAFORMAT_KEY_CSD_1,
+    AMEDIAFORMAT_KEY_CSD_2,
 };
 
 static const char *AMediaFormatKeyGroupRect[] = {
@@ -111,6 +124,10 @@
 static status_t translateErrorCode(media_status_t err) {
     if (err == AMEDIA_OK) {
         return OK;
+    } else if (err == AMEDIA_ERROR_END_OF_STREAM) {
+        return ERROR_END_OF_STREAM;
+    } else if (err == AMEDIA_ERROR_IO) {
+        return ERROR_IO;
     } else if (err == AMEDIACODEC_INFO_TRY_AGAIN_LATER) {
         return -EAGAIN;
     }
@@ -303,11 +320,19 @@
 }
 
 sp<AMessage> AMediaFormatWrapper::toAMessage() const {
+  sp<AMessage> msg;
+  writeToAMessage(msg);
+  return msg;
+}
+
+void AMediaFormatWrapper::writeToAMessage(sp<AMessage> &msg) const {
     if (mAMediaFormat == NULL) {
-        return NULL;
+        msg = NULL;
     }
 
-    sp<AMessage> msg = new AMessage;
+    if (msg == NULL) {
+        msg = new AMessage;
+    }
     for (auto& key : AMediaFormatKeyGroupInt32) {
         int32_t val;
         if (getInt32(key, &val)) {
@@ -334,6 +359,16 @@
             msg->setBuffer(key, buffer);
         }
     }
+    for (auto& key : AMediaFormatKeyGroupCsd) {
+        void *data;
+        size_t size;
+        if (getBuffer(key, &data, &size)) {
+            sp<ABuffer> buffer = ABuffer::CreateAsCopy(data, size);
+            buffer->meta()->setInt32(AMEDIAFORMAT_KEY_CSD, 1);
+            buffer->meta()->setInt64(AMEDIAFORMAT_KEY_TIME_US, 0);
+            msg->setBuffer(key, buffer);
+        }
+    }
     for (auto& key : AMediaFormatKeyGroupRect) {
         int32_t left, top, right, bottom;
         if (getRect(key, &left, &top, &right, &bottom)) {
@@ -351,7 +386,6 @@
             }
         }
     }
-    return msg;
 }
 
 const char* AMediaFormatWrapper::toString() const {
@@ -1150,6 +1184,15 @@
     return AMediaExtractor_getSampleTime(mAMediaExtractor);
 }
 
+status_t AMediaExtractorWrapper::getSampleFormat(sp<AMediaFormatWrapper> &formatWrapper) {
+    if (mAMediaExtractor == NULL) {
+        return DEAD_OBJECT;
+    }
+    AMediaFormat *format = AMediaFormat_new();
+    formatWrapper = new AMediaFormatWrapper(format);
+    return translateErrorCode(AMediaExtractor_getSampleFormat(mAMediaExtractor, format));
+}
+
 int64_t AMediaExtractorWrapper::getCachedDuration() {
     if (mAMediaExtractor == NULL) {
         return -1;
diff --git a/media/libmedia/TypeConverter.cpp b/media/libmedia/TypeConverter.cpp
index 4cadeb1..96dd004 100644
--- a/media/libmedia/TypeConverter.cpp
+++ b/media/libmedia/TypeConverter.cpp
@@ -55,6 +55,8 @@
     MAKE_STRING_FROM_ENUM(AUDIO_DEVICE_OUT_BUS),
     MAKE_STRING_FROM_ENUM(AUDIO_DEVICE_OUT_PROXY),
     MAKE_STRING_FROM_ENUM(AUDIO_DEVICE_OUT_USB_HEADSET),
+    MAKE_STRING_FROM_ENUM(AUDIO_DEVICE_OUT_HEARING_AID),
+    MAKE_STRING_FROM_ENUM(AUDIO_DEVICE_OUT_ECHO_CANCELLER),
     MAKE_STRING_FROM_ENUM(AUDIO_DEVICE_OUT_DEFAULT),
     // STUB must be after DEFAULT, so the latter is picked up by toString first.
     MAKE_STRING_FROM_ENUM(AUDIO_DEVICE_OUT_STUB),
@@ -115,9 +117,7 @@
     MAKE_STRING_FROM_ENUM(AUDIO_OUTPUT_FLAG_DIRECT_PCM),
     MAKE_STRING_FROM_ENUM(AUDIO_OUTPUT_FLAG_MMAP_NOIRQ),
     MAKE_STRING_FROM_ENUM(AUDIO_OUTPUT_FLAG_VOIP_RX),
-    // FIXME: this cast will be removed when the flag will be
-    // declared in types.hal for audio HAL V4.0 and auto imported to audio-base.h
-    {"AUDIO_OUTPUT_FLAG_INCALL_MUSIC", static_cast<audio_output_flags_t>(AUDIO_OUTPUT_FLAG_INCALL_MUSIC)},
+    MAKE_STRING_FROM_ENUM(AUDIO_OUTPUT_FLAG_INCALL_MUSIC),
     TERMINATOR
 };
 
@@ -131,6 +131,7 @@
     MAKE_STRING_FROM_ENUM(AUDIO_INPUT_FLAG_SYNC),
     MAKE_STRING_FROM_ENUM(AUDIO_INPUT_FLAG_MMAP_NOIRQ),
     MAKE_STRING_FROM_ENUM(AUDIO_INPUT_FLAG_VOIP_TX),
+    MAKE_STRING_FROM_ENUM(AUDIO_INPUT_FLAG_HW_AV_SYNC),
     TERMINATOR
 };
 
@@ -167,6 +168,7 @@
     MAKE_STRING_FROM_ENUM(AUDIO_FORMAT_AAC_ADTS_LD),
     MAKE_STRING_FROM_ENUM(AUDIO_FORMAT_AAC_ADTS_HE_V2),
     MAKE_STRING_FROM_ENUM(AUDIO_FORMAT_AAC_ADTS_ELD),
+    MAKE_STRING_FROM_ENUM(AUDIO_FORMAT_AAC_ADTS_XHE),
     MAKE_STRING_FROM_ENUM(AUDIO_FORMAT_VORBIS),
     MAKE_STRING_FROM_ENUM(AUDIO_FORMAT_HE_AAC_V1),
     MAKE_STRING_FROM_ENUM(AUDIO_FORMAT_HE_AAC_V2),
@@ -197,6 +199,11 @@
     MAKE_STRING_FROM_ENUM(AUDIO_FORMAT_APTX_HD),
     MAKE_STRING_FROM_ENUM(AUDIO_FORMAT_AC4),
     MAKE_STRING_FROM_ENUM(AUDIO_FORMAT_LDAC),
+    MAKE_STRING_FROM_ENUM(AUDIO_FORMAT_MAT),
+    MAKE_STRING_FROM_ENUM(AUDIO_FORMAT_E_AC3_JOC),
+    MAKE_STRING_FROM_ENUM(AUDIO_FORMAT_MAT_1_0),
+    MAKE_STRING_FROM_ENUM(AUDIO_FORMAT_MAT_2_0),
+    MAKE_STRING_FROM_ENUM(AUDIO_FORMAT_MAT_2_1),
     TERMINATOR
 };
 
@@ -206,10 +213,18 @@
     MAKE_STRING_FROM_ENUM(AUDIO_CHANNEL_OUT_MONO),
     MAKE_STRING_FROM_ENUM(AUDIO_CHANNEL_OUT_STEREO),
     MAKE_STRING_FROM_ENUM(AUDIO_CHANNEL_OUT_2POINT1),
+    MAKE_STRING_FROM_ENUM(AUDIO_CHANNEL_OUT_2POINT0POINT2),
+    MAKE_STRING_FROM_ENUM(AUDIO_CHANNEL_OUT_2POINT1POINT2),
+    MAKE_STRING_FROM_ENUM(AUDIO_CHANNEL_OUT_3POINT0POINT2),
+    MAKE_STRING_FROM_ENUM(AUDIO_CHANNEL_OUT_3POINT1POINT2),
     MAKE_STRING_FROM_ENUM(AUDIO_CHANNEL_OUT_QUAD),
+    MAKE_STRING_FROM_ENUM(AUDIO_CHANNEL_OUT_QUAD_BACK),
+    MAKE_STRING_FROM_ENUM(AUDIO_CHANNEL_OUT_QUAD_SIDE),
     MAKE_STRING_FROM_ENUM(AUDIO_CHANNEL_OUT_SURROUND),
     MAKE_STRING_FROM_ENUM(AUDIO_CHANNEL_OUT_PENTA),
     MAKE_STRING_FROM_ENUM(AUDIO_CHANNEL_OUT_5POINT1),
+    MAKE_STRING_FROM_ENUM(AUDIO_CHANNEL_OUT_5POINT1_BACK),
+    MAKE_STRING_FROM_ENUM(AUDIO_CHANNEL_OUT_5POINT1_SIDE),
     MAKE_STRING_FROM_ENUM(AUDIO_CHANNEL_OUT_6POINT1),
     MAKE_STRING_FROM_ENUM(AUDIO_CHANNEL_OUT_7POINT1),
     TERMINATOR
@@ -222,6 +237,11 @@
     MAKE_STRING_FROM_ENUM(AUDIO_CHANNEL_IN_STEREO),
     MAKE_STRING_FROM_ENUM(AUDIO_CHANNEL_IN_FRONT_BACK),
     MAKE_STRING_FROM_ENUM(AUDIO_CHANNEL_IN_6),
+    MAKE_STRING_FROM_ENUM(AUDIO_CHANNEL_IN_2POINT0POINT2),
+    MAKE_STRING_FROM_ENUM(AUDIO_CHANNEL_IN_2POINT1POINT2),
+    MAKE_STRING_FROM_ENUM(AUDIO_CHANNEL_IN_3POINT0POINT2),
+    MAKE_STRING_FROM_ENUM(AUDIO_CHANNEL_IN_3POINT1POINT2),
+    MAKE_STRING_FROM_ENUM(AUDIO_CHANNEL_IN_5POINT1),
     MAKE_STRING_FROM_ENUM(AUDIO_CHANNEL_IN_VOICE_UPLINK_MONO),
     MAKE_STRING_FROM_ENUM(AUDIO_CHANNEL_IN_VOICE_DNLINK_MONO),
     MAKE_STRING_FROM_ENUM(AUDIO_CHANNEL_IN_VOICE_CALL_MONO),
@@ -308,8 +328,6 @@
     MAKE_STRING_FROM_ENUM(AUDIO_USAGE_ASSISTANCE_SONIFICATION),
     MAKE_STRING_FROM_ENUM(AUDIO_USAGE_GAME),
     MAKE_STRING_FROM_ENUM(AUDIO_USAGE_VIRTUAL_SOURCE),
-    MAKE_STRING_FROM_ENUM(AUDIO_USAGE_CNT),
-    MAKE_STRING_FROM_ENUM(AUDIO_USAGE_MAX),
     TERMINATOR
 };
 
@@ -325,8 +343,6 @@
     MAKE_STRING_FROM_ENUM(AUDIO_SOURCE_VOICE_COMMUNICATION),
     MAKE_STRING_FROM_ENUM(AUDIO_SOURCE_REMOTE_SUBMIX),
     MAKE_STRING_FROM_ENUM(AUDIO_SOURCE_UNPROCESSED),
-    MAKE_STRING_FROM_ENUM(AUDIO_SOURCE_CNT),
-    MAKE_STRING_FROM_ENUM(AUDIO_SOURCE_MAX),
     MAKE_STRING_FROM_ENUM(AUDIO_SOURCE_FM_TUNER),
     MAKE_STRING_FROM_ENUM(AUDIO_SOURCE_HOTWORD),
     TERMINATOR
diff --git a/media/libmedia/include/media/NdkWrapper.h b/media/libmedia/include/media/NdkWrapper.h
index b71b758..191665a 100644
--- a/media/libmedia/include/media/NdkWrapper.h
+++ b/media/libmedia/include/media/NdkWrapper.h
@@ -45,6 +45,7 @@
 class MetaData;
 
 struct AMediaFormatWrapper : public RefBase {
+
     static sp<AMediaFormatWrapper> Create(const sp<AMessage> &message);
 
     AMediaFormatWrapper();
@@ -54,6 +55,7 @@
     AMediaFormat *getAMediaFormat() const;
 
     sp<AMessage> toAMessage() const ;
+    void writeToAMessage(sp<AMessage>&) const ;
     const char* toString() const ;
 
     status_t release();
@@ -313,6 +315,8 @@
 
     int64_t getSampleTime();
 
+    status_t getSampleFormat(sp<AMediaFormatWrapper> &formatWrapper);
+
     int64_t getCachedDuration();
 
     bool advance();
diff --git a/media/libmediaextractor/include/media/ExtractorUtils.h b/media/libmediaextractor/include/media/ExtractorUtils.h
new file mode 100644
index 0000000..22f9349
--- /dev/null
+++ b/media/libmediaextractor/include/media/ExtractorUtils.h
@@ -0,0 +1,32 @@
+/*
+ * 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 EXTRACTOR_UTILS_H_
+
+#define EXTRACTOR_UTILS_H_
+
+#include <memory>
+
+namespace android {
+
+template <class T>
+std::unique_ptr<T[]> heapbuffer(size_t size) {
+    return std::unique_ptr<T[]>(new (std::nothrow) T[size]);
+}
+
+}  // namespace android
+
+#endif  // UTILS_H_
diff --git a/media/libmediaplayer2/include/mediaplayer2/MediaPlayer2Types.h b/media/libmediaplayer2/include/mediaplayer2/MediaPlayer2Types.h
index 260c7ed..3905b55 100644
--- a/media/libmediaplayer2/include/mediaplayer2/MediaPlayer2Types.h
+++ b/media/libmediaplayer2/include/mediaplayer2/MediaPlayer2Types.h
@@ -148,7 +148,16 @@
     MEDIA2_INFO_TIMED_TEXT_ERROR = 900,
 };
 
-enum media_player2_states {
+// Do not change these values without updating their counterparts in MediaPlayer2.java
+enum mediaplayer2_states {
+    MEDIAPLAYER2_STATE_IDLE         = 1,
+    MEDIAPLAYER2_STATE_PREPARED     = 2,
+    MEDIAPLAYER2_STATE_PLAYING      = 3,
+    MEDIAPLAYER2_STATE_PAUSED       = 4,
+    MEDIAPLAYER2_STATE_ERROR        = 5,
+};
+
+enum media_player2_internal_states {
     MEDIA_PLAYER2_STATE_ERROR        = 0,
     MEDIA_PLAYER2_IDLE               = 1 << 0,
     MEDIA_PLAYER2_INITIALIZED        = 1 << 1,
diff --git a/media/libmediaplayer2/include/mediaplayer2/mediaplayer2.h b/media/libmediaplayer2/include/mediaplayer2/mediaplayer2.h
index 3433cb1..d586192 100644
--- a/media/libmediaplayer2/include/mediaplayer2/mediaplayer2.h
+++ b/media/libmediaplayer2/include/mediaplayer2/mediaplayer2.h
@@ -68,6 +68,7 @@
             status_t        stop();
             status_t        pause();
             bool            isPlaying();
+            mediaplayer2_states getMediaPlayer2State();
             status_t        setPlaybackSettings(const AudioPlaybackRate& rate);
             status_t        getPlaybackSettings(AudioPlaybackRate* rate /* nonnull */);
             status_t        setSyncSettings(const AVSyncSettings& sync, float videoFpsHint);
@@ -145,7 +146,7 @@
     mutable Mutex               mLock;
     Mutex                       mNotifyLock;
     sp<MediaPlayer2Listener>    mListener;
-    media_player2_states        mCurrentState;
+    media_player2_internal_states mCurrentState;
     int64_t                     mCurrentPosition;
     MediaPlayer2SeekMode        mCurrentSeekMode;
     int64_t                     mSeekPosition;
diff --git a/media/libmediaplayer2/mediaplayer2.cpp b/media/libmediaplayer2/mediaplayer2.cpp
index c465caa..9e96a2b 100644
--- a/media/libmediaplayer2/mediaplayer2.cpp
+++ b/media/libmediaplayer2/mediaplayer2.cpp
@@ -860,6 +860,27 @@
     return false;
 }
 
+mediaplayer2_states MediaPlayer2::getMediaPlayer2State() {
+    Mutex::Autolock _l(mLock);
+    if (mCurrentState & MEDIA_PLAYER2_STATE_ERROR) {
+        return MEDIAPLAYER2_STATE_ERROR;
+    }
+    if (mPlayer == 0
+        || (mCurrentState &
+            (MEDIA_PLAYER2_IDLE | MEDIA_PLAYER2_INITIALIZED | MEDIA_PLAYER2_PREPARING))) {
+        return MEDIAPLAYER2_STATE_IDLE;
+    }
+    if (mCurrentState & MEDIA_PLAYER2_STARTED) {
+        return MEDIAPLAYER2_STATE_PLAYING;
+    }
+    if (mCurrentState
+        & (MEDIA_PLAYER2_PAUSED | MEDIA_PLAYER2_STOPPED | MEDIA_PLAYER2_PLAYBACK_COMPLETE)) {
+        return MEDIAPLAYER2_STATE_PAUSED;
+    }
+    // now only mCurrentState & MEDIA_PLAYER2_PREPARED is true
+    return MEDIAPLAYER2_STATE_PREPARED;
+}
+
 status_t MediaPlayer2::setPlaybackSettings(const AudioPlaybackRate& rate) {
     ALOGV("setPlaybackSettings: %f %f %d %d",
             rate.mSpeed, rate.mPitch, rate.mFallbackMode, rate.mStretchMode);
diff --git a/media/libmediaplayer2/nuplayer2/GenericSource2.cpp b/media/libmediaplayer2/nuplayer2/GenericSource2.cpp
index 790581a..196b103 100644
--- a/media/libmediaplayer2/nuplayer2/GenericSource2.cpp
+++ b/media/libmediaplayer2/nuplayer2/GenericSource2.cpp
@@ -35,13 +35,13 @@
 #include <media/stagefright/foundation/ADebug.h>
 #include <media/stagefright/foundation/AMessage.h>
 #include <media/stagefright/DataSourceFactory.h>
-#include <media/stagefright/FileSource.h>
 #include <media/stagefright/InterfaceUtils.h>
 #include <media/stagefright/MediaBuffer.h>
 #include <media/stagefright/MediaClock.h>
 #include <media/stagefright/MediaDefs.h>
 #include <media/stagefright/MediaExtractorFactory.h>
 #include <media/stagefright/MetaData.h>
+#include <media/stagefright/NdkUtils.h>
 #include <media/stagefright/Utils.h>
 #include "../../libstagefright/include/NuCachedSource2.h"
 #include "../../libstagefright/include/HTTPBase.h"
@@ -161,29 +161,40 @@
 }
 
 status_t NuPlayer2::GenericSource2::initFromDataSource() {
-    sp<IMediaExtractor> extractor;
-    CHECK(mDataSource != NULL);
+    mExtractor = new AMediaExtractorWrapper(AMediaExtractor_new());
+    CHECK(mDataSource != NULL || mFd != -1);
     sp<DataSource> dataSource = mDataSource;
+    const int fd = mFd;
+    const int64_t offset = mOffset;
+    const int64_t length = mLength;
 
     mLock.unlock();
     // This might take long time if data source is not reliable.
-    extractor = MediaExtractorFactory::Create(dataSource, NULL);
+    status_t err;
+    if (dataSource != nullptr) {
+        mDataSourceWrapper = new AMediaDataSourceWrapper(dataSource);
+        err = mExtractor->setDataSource(mDataSourceWrapper->getAMediaDataSource());
+    } else {
+        err = mExtractor->setDataSource(fd, offset, length);
+    }
 
-    if (extractor == NULL) {
-        ALOGE("initFromDataSource, cannot create extractor!");
+    if (err != OK) {
+        ALOGE("initFromDataSource, failed to create data source!");
+        mLock.lock();
         return UNKNOWN_ERROR;
     }
 
-    sp<MetaData> fileMeta = extractor->getMetaData();
-
-    size_t numtracks = extractor->countTracks();
+    size_t numtracks = mExtractor->getTrackCount();
     if (numtracks == 0) {
         ALOGE("initFromDataSource, source has no track!");
+        mLock.lock();
         return UNKNOWN_ERROR;
     }
 
     mLock.lock();
-    mFileMeta = fileMeta;
+    mFd = -1;
+    mDataSource = dataSource;
+    mFileMeta = convertMediaFormatWrapperToMetaData(mExtractor->getFormat());
     if (mFileMeta != NULL) {
         int64_t duration;
         if (mFileMeta->findInt64(kKeyDuration, &duration)) {
@@ -196,18 +207,22 @@
     mMimes.clear();
 
     for (size_t i = 0; i < numtracks; ++i) {
-        sp<IMediaSource> track = extractor->getTrack(i);
-        if (track == NULL) {
-            continue;
-        }
 
-        sp<MetaData> meta = extractor->getTrackMetaData(i);
-        if (meta == NULL) {
+        sp<AMediaFormatWrapper> trackFormat = mExtractor->getTrackFormat(i);
+        if (trackFormat == NULL) {
             ALOGE("no metadata for track %zu", i);
             return UNKNOWN_ERROR;
         }
 
+        sp<AMediaExtractorWrapper> trackExtractor = new AMediaExtractorWrapper(AMediaExtractor_new());
+        if (mDataSourceWrapper != nullptr) {
+            err = trackExtractor->setDataSource(mDataSourceWrapper->getAMediaDataSource());
+        } else {
+            err = trackExtractor->setDataSource(fd, offset, length);
+        }
+
         const char *mime;
+        sp<MetaData> meta = convertMediaFormatWrapperToMetaData(trackFormat);
         CHECK(meta->findCString(kKeyMIMEType, &mime));
 
         ALOGV("initFromDataSource track[%zu]: %s", i, mime);
@@ -217,11 +232,11 @@
         // extractor operation, some extractors might modify meta
         // during getTrack() and make it invalid.
         if (!strncasecmp(mime, "audio/", 6)) {
-            if (mAudioTrack.mSource == NULL) {
+            if (mAudioTrack.mExtractor == NULL) {
                 mAudioTrack.mIndex = i;
-                mAudioTrack.mSource = track;
-                mAudioTrack.mPackets =
-                    new AnotherPacketSource(mAudioTrack.mSource->getFormat());
+                mAudioTrack.mExtractor = trackExtractor;
+                mAudioTrack.mExtractor->selectTrack(i);
+                mAudioTrack.mPackets = new AnotherPacketSource(meta);
 
                 if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_VORBIS)) {
                     mAudioIsVorbis = true;
@@ -232,18 +247,18 @@
                 mMimes.add(String8(mime));
             }
         } else if (!strncasecmp(mime, "video/", 6)) {
-            if (mVideoTrack.mSource == NULL) {
+            if (mVideoTrack.mExtractor == NULL) {
                 mVideoTrack.mIndex = i;
-                mVideoTrack.mSource = track;
-                mVideoTrack.mPackets =
-                    new AnotherPacketSource(mVideoTrack.mSource->getFormat());
+                mVideoTrack.mExtractor = trackExtractor;
+                mVideoTrack.mExtractor->selectTrack(i);
+                mVideoTrack.mPackets = new AnotherPacketSource(meta);
 
                 // video always at the beginning
                 mMimes.insertAt(String8(mime), 0);
             }
         }
 
-        mSources.push(track);
+        mExtractors.push(trackExtractor);
         int64_t durationUs;
         if (meta->findInt64(kKeyDuration, &durationUs)) {
             if (durationUs > mDurationUs) {
@@ -259,10 +274,10 @@
         }
     }
 
-    ALOGV("initFromDataSource mSources.size(): %zu  mIsSecure: %d  mime[0]: %s", mSources.size(),
+    ALOGV("initFromDataSource mExtractors.size(): %zu  mIsSecure: %d  mime[0]: %s", mExtractors.size(),
             mIsSecure, (mMimes.isEmpty() ? "NONE" : mMimes[0].string()));
 
-    if (mSources.size() == 0) {
+    if (mExtractors.size() == 0) {
         ALOGE("b/23705695");
         return UNKNOWN_ERROR;
     }
@@ -294,31 +309,10 @@
     return OK;
 }
 
-status_t NuPlayer2::GenericSource2::startSources() {
-    // Start the selected A/V tracks now before we start buffering.
-    // Widevine sources might re-initialize crypto when starting, if we delay
-    // this to start(), all data buffered during prepare would be wasted.
-    // (We don't actually start reading until start().)
-    //
-    // TODO: this logic may no longer be relevant after the removal of widevine
-    // support
-    if (mAudioTrack.mSource != NULL && mAudioTrack.mSource->start() != OK) {
-        ALOGE("failed to start audio track!");
-        return UNKNOWN_ERROR;
-    }
-
-    if (mVideoTrack.mSource != NULL && mVideoTrack.mSource->start() != OK) {
-        ALOGE("failed to start video track!");
-        return UNKNOWN_ERROR;
-    }
-
-    return OK;
-}
-
 int64_t NuPlayer2::GenericSource2::getLastReadPosition() {
-    if (mAudioTrack.mSource != NULL) {
+    if (mAudioTrack.mExtractor != NULL) {
         return mAudioTimeUs;
-    } else if (mVideoTrack.mSource != NULL) {
+    } else if (mVideoTrack.mExtractor != NULL) {
         return mVideoTimeUs;
     } else {
         return 0;
@@ -391,51 +385,16 @@
             if (!mDisconnected) {
                 mDataSource = dataSource;
             }
-        } else {
-            if (property_get_bool("media.stagefright.extractremote", true) &&
-                    !FileSource::requiresDrm(mFd, mOffset, mLength, nullptr /* mime */)) {
-                sp<IBinder> binder =
-                        defaultServiceManager()->getService(String16("media.extractor"));
-                if (binder != nullptr) {
-                    ALOGD("FileSource remote");
-                    sp<IMediaExtractorService> mediaExService(
-                            interface_cast<IMediaExtractorService>(binder));
-                    sp<IDataSource> source =
-                            mediaExService->makeIDataSource(mFd, mOffset, mLength);
-                    ALOGV("IDataSource(FileSource): %p %d %lld %lld",
-                            source.get(), mFd, (long long)mOffset, (long long)mLength);
-                    if (source.get() != nullptr) {
-                        mDataSource = CreateDataSourceFromIDataSource(source);
-                        if (mDataSource != nullptr) {
-                            // Close the local file descriptor as it is not needed anymore.
-                            close(mFd);
-                            mFd = -1;
-                        }
-                    } else {
-                        ALOGW("extractor service cannot make data source");
-                    }
-                } else {
-                    ALOGW("extractor service not running");
-                }
-            }
-            if (mDataSource == nullptr) {
-                ALOGD("FileSource local");
-                mDataSource = new FileSource(mFd, mOffset, mLength);
-            }
-            // TODO: close should always be done on mFd, see the lines following
-            // CreateDataSourceFromIDataSource above,
-            // and the FileSource constructor should dup the mFd argument as needed.
-            mFd = -1;
         }
 
-        if (mDataSource == NULL) {
+        if (mFd == -1 && mDataSource == NULL) {
             ALOGE("Failed to create data source!");
             notifyPreparedAndCleanup(UNKNOWN_ERROR);
             return;
         }
     }
 
-    if (mDataSource->flags() & DataSource::kIsCachingDataSource) {
+    if (mDataSource != nullptr && mDataSource->flags() & DataSource::kIsCachingDataSource) {
         mCachedSource = static_cast<NuCachedSource2 *>(mDataSource.get());
     }
 
@@ -452,7 +411,7 @@
         return;
     }
 
-    if (mVideoTrack.mSource != NULL) {
+    if (mVideoTrack.mExtractor != NULL) {
         sp<MetaData> meta = getFormatMeta_l(false /* audio */);
         sp<AMessage> msg = new AMessage;
         err = convertMetaDataToMessage(meta, &msg);
@@ -479,13 +438,6 @@
 void NuPlayer2::GenericSource2::finishPrepareAsync() {
     ALOGV("finishPrepareAsync");
 
-    status_t err = startSources();
-    if (err != OK) {
-        ALOGE("Failed to init start data source!");
-        notifyPreparedAndCleanup(err);
-        return;
-    }
-
     if (mIsStreaming) {
         mCachedSource->resumeFetchingIfNecessary();
         mPreparing = true;
@@ -494,11 +446,11 @@
         notifyPrepared();
     }
 
-    if (mAudioTrack.mSource != NULL) {
+    if (mAudioTrack.mExtractor != NULL) {
         postReadBuffer(MEDIA_TRACK_TYPE_AUDIO);
     }
 
-    if (mVideoTrack.mSource != NULL) {
+    if (mVideoTrack.mExtractor != NULL) {
         postReadBuffer(MEDIA_TRACK_TYPE_VIDEO);
     }
 }
@@ -520,11 +472,11 @@
     Mutex::Autolock _l(mLock);
     ALOGI("start");
 
-    if (mAudioTrack.mSource != NULL) {
+    if (mAudioTrack.mExtractor != NULL) {
         postReadBuffer(MEDIA_TRACK_TYPE_AUDIO);
     }
 
-    if (mVideoTrack.mSource != NULL) {
+    if (mVideoTrack.mExtractor != NULL) {
         postReadBuffer(MEDIA_TRACK_TYPE_VIDEO);
     }
 
@@ -563,6 +515,9 @@
     } else if (httpSource != NULL) {
         static_cast<HTTPBase *>(httpSource.get())->disconnect();
     }
+
+    mDataSourceWrapper = NULL;
+
 }
 
 status_t NuPlayer2::GenericSource2::feedMoreTSData() {
@@ -630,30 +585,27 @@
       {
           int32_t trackIndex;
           CHECK(msg->findInt32("trackIndex", &trackIndex));
-          const sp<IMediaSource> source = mSources.itemAt(trackIndex);
+          const sp<AMediaExtractorWrapper> extractor = mExtractors.itemAt(trackIndex);
 
           Track* track;
-          const char *mime;
+          AString mime;
           media_track_type trackType, counterpartType;
-          sp<MetaData> meta = source->getFormat();
-          meta->findCString(kKeyMIMEType, &mime);
-          if (!strncasecmp(mime, "audio/", 6)) {
+          sp<AMediaFormatWrapper> format = extractor->getTrackFormat(trackIndex);
+          format->getString(AMEDIAFORMAT_KEY_MIME, &mime);
+          if (!strncasecmp(mime.c_str(), "audio/", 6)) {
               track = &mAudioTrack;
               trackType = MEDIA_TRACK_TYPE_AUDIO;
               counterpartType = MEDIA_TRACK_TYPE_VIDEO;;
           } else {
-              CHECK(!strncasecmp(mime, "video/", 6));
+              CHECK(!strncasecmp(mime.c_str(), "video/", 6));
               track = &mVideoTrack;
               trackType = MEDIA_TRACK_TYPE_VIDEO;
               counterpartType = MEDIA_TRACK_TYPE_AUDIO;;
           }
 
 
-          if (track->mSource != NULL) {
-              track->mSource->stop();
-          }
-          track->mSource = source;
-          track->mSource->start();
+          track->mExtractor = extractor;
+          track->mExtractor->selectSingleTrack(trackIndex);
           track->mIndex = trackIndex;
           ++mAudioDataGeneration;
           ++mVideoDataGeneration;
@@ -786,11 +738,10 @@
         return;
     }
 
-    uint32_t textType;
-    const void *data;
+    void *data = NULL;
     size_t size = 0;
-    if (mTimedTextTrack.mSource->getFormat()->findData(
-                    kKeyTextFormatData, &textType, &data, &size)) {
+    if (mTimedTextTrack.mExtractor->getTrackFormat(mTimedTextTrack.mIndex)->getBuffer(
+                    "text", &data, &size)) {
         mGlobalTimedText = new ABuffer(size);
         if (mGlobalTimedText->data()) {
             memcpy(mGlobalTimedText->data(), data, size);
@@ -806,19 +757,36 @@
     }
 }
 
+sp<AMessage> NuPlayer2::GenericSource2::getFormat(bool audio) {
+    Mutex::Autolock _l(mLock);
+    return getFormat_l(audio);
+}
+
 sp<MetaData> NuPlayer2::GenericSource2::getFormatMeta(bool audio) {
     Mutex::Autolock _l(mLock);
     return getFormatMeta_l(audio);
 }
 
-sp<MetaData> NuPlayer2::GenericSource2::getFormatMeta_l(bool audio) {
-    sp<IMediaSource> source = audio ? mAudioTrack.mSource : mVideoTrack.mSource;
+sp<AMessage> NuPlayer2::GenericSource2::getFormat_l(bool audio) {
+    sp<AMediaExtractorWrapper> extractor = audio ? mAudioTrack.mExtractor : mVideoTrack.mExtractor;
+    size_t trackIndex = audio ? mAudioTrack.mIndex : mVideoTrack.mIndex;
 
-    if (source == NULL) {
+    if (extractor == NULL) {
         return NULL;
     }
 
-    return source->getFormat();
+    return extractor->getTrackFormat(trackIndex)->toAMessage();
+}
+
+sp<MetaData> NuPlayer2::GenericSource2::getFormatMeta_l(bool audio) {
+    sp<AMediaExtractorWrapper> extractor = audio ? mAudioTrack.mExtractor : mVideoTrack.mExtractor;
+    size_t trackIndex = audio ? mAudioTrack.mIndex : mVideoTrack.mIndex;
+
+    if (extractor == NULL) {
+        return NULL;
+    }
+
+    return convertMediaFormatWrapperToMetaData(extractor->getTrackFormat(trackIndex));
 }
 
 status_t NuPlayer2::GenericSource2::dequeueAccessUnit(
@@ -833,7 +801,7 @@
 
     Track *track = audio ? &mAudioTrack : &mVideoTrack;
 
-    if (track->mSource == NULL) {
+    if (track->mExtractor == NULL) {
         return -EWOULDBLOCK;
     }
 
@@ -878,11 +846,11 @@
     }
 
     if (result != OK) {
-        if (mSubtitleTrack.mSource != NULL) {
+        if (mSubtitleTrack.mExtractor != NULL) {
             mSubtitleTrack.mPackets->clear();
             mFetchSubtitleDataGeneration++;
         }
-        if (mTimedTextTrack.mSource != NULL) {
+        if (mTimedTextTrack.mExtractor != NULL) {
             mTimedTextTrack.mPackets->clear();
             mFetchTimedTextDataGeneration++;
         }
@@ -898,7 +866,7 @@
         mVideoLastDequeueTimeUs = timeUs;
     }
 
-    if (mSubtitleTrack.mSource != NULL
+    if (mSubtitleTrack.mExtractor != NULL
             && !mSubtitleTrack.mPackets->hasBufferAvailable(&eosResult)) {
         sp<AMessage> msg = new AMessage(kWhatFetchSubtitleData, this);
         msg->setInt64("timeUs", timeUs);
@@ -906,7 +874,7 @@
         msg->post();
     }
 
-    if (mTimedTextTrack.mSource != NULL
+    if (mTimedTextTrack.mExtractor != NULL
             && !mTimedTextTrack.mPackets->hasBufferAvailable(&eosResult)) {
         sp<AMessage> msg = new AMessage(kWhatFetchTimedTextData, this);
         msg->setInt64("timeUs", timeUs);
@@ -925,50 +893,47 @@
 
 size_t NuPlayer2::GenericSource2::getTrackCount() const {
     Mutex::Autolock _l(mLock);
-    return mSources.size();
+    return mExtractors.size();
 }
 
 sp<AMessage> NuPlayer2::GenericSource2::getTrackInfo(size_t trackIndex) const {
     Mutex::Autolock _l(mLock);
-    size_t trackCount = mSources.size();
+    size_t trackCount = mExtractors.size();
     if (trackIndex >= trackCount) {
         return NULL;
     }
 
-    sp<AMessage> format = new AMessage();
-    sp<MetaData> meta = mSources.itemAt(trackIndex)->getFormat();
-    if (meta == NULL) {
+    sp<AMessage> format = mExtractors.itemAt(trackIndex)->getTrackFormat(trackIndex)->toAMessage();
+    if (format == NULL) {
         ALOGE("no metadata for track %zu", trackIndex);
         return NULL;
     }
 
-    const char *mime;
-    CHECK(meta->findCString(kKeyMIMEType, &mime));
-    format->setString("mime", mime);
+    AString mime;
+    CHECK(format->findString(AMEDIAFORMAT_KEY_MIME, &mime));
 
     int32_t trackType;
-    if (!strncasecmp(mime, "video/", 6)) {
+    if (!strncasecmp(mime.c_str(), "video/", 6)) {
         trackType = MEDIA_TRACK_TYPE_VIDEO;
-    } else if (!strncasecmp(mime, "audio/", 6)) {
+    } else if (!strncasecmp(mime.c_str(), "audio/", 6)) {
         trackType = MEDIA_TRACK_TYPE_AUDIO;
-    } else if (!strcasecmp(mime, MEDIA_MIMETYPE_TEXT_3GPP)) {
+    } else if (!strcasecmp(mime.c_str(), MEDIA_MIMETYPE_TEXT_3GPP)) {
         trackType = MEDIA_TRACK_TYPE_TIMEDTEXT;
     } else {
         trackType = MEDIA_TRACK_TYPE_UNKNOWN;
     }
     format->setInt32("type", trackType);
 
-    const char *lang;
-    if (!meta->findCString(kKeyMediaLanguage, &lang)) {
-        lang = "und";
+    AString lang;
+    if (!format->findString("language", &lang)) {
+        format->setString("language", "und");
     }
-    format->setString("language", lang);
 
     if (trackType == MEDIA_TRACK_TYPE_SUBTITLE) {
         int32_t isAutoselect = 1, isDefault = 0, isForced = 0;
-        meta->findInt32(kKeyTrackIsAutoselect, &isAutoselect);
-        meta->findInt32(kKeyTrackIsDefault, &isDefault);
-        meta->findInt32(kKeyTrackIsForced, &isForced);
+        format->findInt32(AMEDIAFORMAT_KEY_IS_AUTOSELECT, &isAutoselect);
+        format->findInt32(AMEDIAFORMAT_KEY_IS_DEFAULT, &isDefault);
+        format->findInt32(AMEDIAFORMAT_KEY_IS_FORCED_SUBTITLE, &isForced);
 
         format->setInt32("auto", !!isAutoselect);
         format->setInt32("default", !!isDefault);
@@ -998,7 +963,7 @@
         break;
     }
 
-    if (track != NULL && track->mSource != NULL) {
+    if (track != NULL && track->mExtractor != NULL) {
         return track->mIndex;
     }
 
@@ -1009,49 +974,45 @@
     Mutex::Autolock _l(mLock);
     ALOGV("%s track: %zu", select ? "select" : "deselect", trackIndex);
 
-    if (trackIndex >= mSources.size()) {
+    if (trackIndex >= mExtractors.size()) {
         return BAD_INDEX;
     }
 
     if (!select) {
         Track* track = NULL;
-        if (mSubtitleTrack.mSource != NULL && trackIndex == mSubtitleTrack.mIndex) {
+        if (mSubtitleTrack.mExtractor != NULL && trackIndex == mSubtitleTrack.mIndex) {
             track = &mSubtitleTrack;
             mFetchSubtitleDataGeneration++;
-        } else if (mTimedTextTrack.mSource != NULL && trackIndex == mTimedTextTrack.mIndex) {
+        } else if (mTimedTextTrack.mExtractor != NULL && trackIndex == mTimedTextTrack.mIndex) {
             track = &mTimedTextTrack;
             mFetchTimedTextDataGeneration++;
         }
         if (track == NULL) {
             return INVALID_OPERATION;
         }
-        track->mSource->stop();
-        track->mSource = NULL;
+        track->mExtractor = NULL;
         track->mPackets->clear();
         return OK;
     }
 
-    const sp<IMediaSource> source = mSources.itemAt(trackIndex);
-    sp<MetaData> meta = source->getFormat();
+    const sp<AMediaExtractorWrapper> extractor = mExtractors.itemAt(trackIndex);
+    sp<MetaData> meta = convertMediaFormatWrapperToMetaData(extractor->getTrackFormat(trackIndex));
     const char *mime;
     CHECK(meta->findCString(kKeyMIMEType, &mime));
     if (!strncasecmp(mime, "text/", 5)) {
         bool isSubtitle = strcasecmp(mime, MEDIA_MIMETYPE_TEXT_3GPP);
         Track *track = isSubtitle ? &mSubtitleTrack : &mTimedTextTrack;
-        if (track->mSource != NULL && track->mIndex == trackIndex) {
+        if (track->mExtractor != NULL && track->mIndex == trackIndex) {
             return OK;
         }
         track->mIndex = trackIndex;
-        if (track->mSource != NULL) {
-            track->mSource->stop();
-        }
-        track->mSource = mSources.itemAt(trackIndex);
-        track->mSource->start();
+        track->mExtractor = mExtractors.itemAt(trackIndex);
+        track->mExtractor->selectSingleTrack(trackIndex);
         if (track->mPackets == NULL) {
-            track->mPackets = new AnotherPacketSource(track->mSource->getFormat());
+            track->mPackets = new AnotherPacketSource(meta);
         } else {
             track->mPackets->clear();
-            track->mPackets->setFormat(track->mSource->getFormat());
+            track->mPackets->setFormat(meta);
 
         }
 
@@ -1062,7 +1023,7 @@
         }
 
         status_t eosResult; // ignored
-        if (mSubtitleTrack.mSource != NULL
+        if (mSubtitleTrack.mExtractor != NULL
                 && !mSubtitleTrack.mPackets->hasBufferAvailable(&eosResult)) {
             sp<AMessage> msg = new AMessage(kWhatFetchSubtitleData, this);
             msg->setInt64("timeUs", timeUs);
@@ -1074,7 +1035,7 @@
         msg2->setInt32("generation", mFetchTimedTextDataGeneration);
         msg2->post();
 
-        if (mTimedTextTrack.mSource != NULL
+        if (mTimedTextTrack.mExtractor != NULL
                 && !mTimedTextTrack.mPackets->hasBufferAvailable(&eosResult)) {
             sp<AMessage> msg = new AMessage(kWhatFetchTimedTextData, this);
             msg->setInt64("timeUs", timeUs);
@@ -1086,7 +1047,7 @@
     } else if (!strncasecmp(mime, "audio/", 6) || !strncasecmp(mime, "video/", 6)) {
         bool audio = !strncasecmp(mime, "audio/", 6);
         Track *track = audio ? &mAudioTrack : &mVideoTrack;
-        if (track->mSource != NULL && track->mIndex == trackIndex) {
+        if (track->mExtractor != NULL && track->mIndex == trackIndex) {
             return OK;
         }
 
@@ -1133,7 +1094,7 @@
 }
 
 status_t NuPlayer2::GenericSource2::doSeek(int64_t seekTimeUs, MediaPlayer2SeekMode mode) {
-    if (mVideoTrack.mSource != NULL) {
+    if (mVideoTrack.mExtractor != NULL) {
         ++mVideoDataGeneration;
 
         int64_t actualTimeUs;
@@ -1145,18 +1106,18 @@
         mVideoLastDequeueTimeUs = actualTimeUs;
     }
 
-    if (mAudioTrack.mSource != NULL) {
+    if (mAudioTrack.mExtractor != NULL) {
         ++mAudioDataGeneration;
         readBuffer(MEDIA_TRACK_TYPE_AUDIO, seekTimeUs, MediaPlayer2SeekMode::SEEK_CLOSEST);
         mAudioLastDequeueTimeUs = seekTimeUs;
     }
 
-    if (mSubtitleTrack.mSource != NULL) {
+    if (mSubtitleTrack.mExtractor != NULL) {
         mSubtitleTrack.mPackets->clear();
         mFetchSubtitleDataGeneration++;
     }
 
-    if (mTimedTextTrack.mSource != NULL) {
+    if (mTimedTextTrack.mExtractor != NULL) {
         mTimedTextTrack.mPackets->clear();
         mFetchTimedTextDataGeneration++;
     }
@@ -1227,10 +1188,12 @@
     }
 
     if (trackType == MEDIA_TRACK_TYPE_TIMEDTEXT) {
-        const char *mime;
-        CHECK(mTimedTextTrack.mSource != NULL
-                && mTimedTextTrack.mSource->getFormat()->findCString(kKeyMIMEType, &mime));
-        meta->setString("mime", mime);
+        AString mime;
+        sp<AMediaExtractorWrapper> extractor = mTimedTextTrack.mExtractor;
+        size_t trackIndex = mTimedTextTrack.mIndex;
+        CHECK(extractor != NULL
+                && extractor->getTrackFormat(trackIndex)->getString(AMEDIAFORMAT_KEY_MIME, &mime));
+        meta->setString("mime", mime.c_str());
     }
 
     int64_t durationUs;
@@ -1239,7 +1202,7 @@
     }
 
     if (trackType == MEDIA_TRACK_TYPE_SUBTITLE) {
-        meta->setInt32("trackIndex", mSubtitleTrack.mIndex);
+        meta->setInt32(AMEDIAFORMAT_KEY_TRACK_INDEX, mSubtitleTrack.mIndex);
     }
 
     uint32_t dataType; // unused
@@ -1255,7 +1218,7 @@
     if (mb->meta_data().findData(
             kKeyMpegUserData, &dataType, &mpegUserDataPointer, &mpegUserDataLength)) {
         sp<ABuffer> mpegUserData = ABuffer::CreateAsCopy(mpegUserDataPointer, mpegUserDataLength);
-        meta->setBuffer("mpegUserData", mpegUserData);
+        meta->setBuffer(AMEDIAFORMAT_KEY_MPEG_USER_DATA, mpegUserData);
     }
 
     mb->release();
@@ -1327,7 +1290,7 @@
             TRESPASS();
     }
 
-    if (track->mSource == NULL) {
+    if (track->mExtractor == NULL) {
         return;
     }
 
@@ -1335,109 +1298,77 @@
         *actualTimeUs = seekTimeUs;
     }
 
-    MediaSource::ReadOptions options;
 
     bool seeking = false;
+    sp<AMediaExtractorWrapper> extractor = track->mExtractor;
     if (seekTimeUs >= 0) {
-        options.setSeekTo(seekTimeUs, mode);
+        extractor->seekTo(seekTimeUs, mode);
         seeking = true;
     }
 
-    const bool couldReadMultiple = (track->mSource->supportReadMultiple());
-
-    if (couldReadMultiple) {
-        options.setNonBlocking();
-    }
-
     int32_t generation = getDataGeneration(trackType);
     for (size_t numBuffers = 0; numBuffers < maxBuffers; ) {
-        Vector<MediaBufferBase *> mediaBuffers;
-        status_t err = NO_ERROR;
+        Vector<sp<ABuffer> > aBuffers;
 
-        sp<IMediaSource> source = track->mSource;
         mLock.unlock();
-        if (couldReadMultiple) {
-            err = source->readMultiple(
-                    &mediaBuffers, maxBuffers - numBuffers, &options);
-        } else {
-            MediaBufferBase *mbuf = NULL;
-            err = source->read(&mbuf, &options);
-            if (err == OK && mbuf != NULL) {
-                mediaBuffers.push_back(mbuf);
-            }
+
+        sp<AMediaFormatWrapper> format;
+        ssize_t sampleSize = -1;
+        status_t err = extractor->getSampleFormat(format);
+        if (err == OK) {
+            sampleSize = extractor->getSampleSize();
         }
+
+        if (err != OK || sampleSize < 0) {
+            mLock.lock();
+            track->mPackets->signalEOS(err != OK ? err : ERROR_END_OF_STREAM);
+            break;
+        }
+
+        sp<ABuffer> abuf = new ABuffer(sampleSize);
+        sampleSize = extractor->readSampleData(abuf);
         mLock.lock();
 
-        options.clearNonPersistent();
-
-        size_t id = 0;
-        size_t count = mediaBuffers.size();
-
         // in case track has been changed since we don't have lock for some time.
         if (generation != getDataGeneration(trackType)) {
-            for (; id < count; ++id) {
-                mediaBuffers[id]->release();
-            }
             break;
         }
 
-        for (; id < count; ++id) {
-            int64_t timeUs;
-            MediaBufferBase *mbuf = mediaBuffers[id];
-            if (!mbuf->meta_data().findInt64(kKeyTime, &timeUs)) {
-                mbuf->meta_data().dumpToLog();
-                track->mPackets->signalEOS(ERROR_MALFORMED);
-                break;
-            }
-            if (trackType == MEDIA_TRACK_TYPE_AUDIO) {
-                mAudioTimeUs = timeUs;
-            } else if (trackType == MEDIA_TRACK_TYPE_VIDEO) {
-                mVideoTimeUs = timeUs;
-            }
-
-            queueDiscontinuityIfNeeded(seeking, formatChange, trackType, track);
-
-            sp<ABuffer> buffer = mediaBufferToABuffer(mbuf, trackType);
-            if (numBuffers == 0 && actualTimeUs != nullptr) {
-                *actualTimeUs = timeUs;
-            }
-            if (seeking && buffer != nullptr) {
-                sp<AMessage> meta = buffer->meta();
-                if (meta != nullptr && mode == MediaPlayer2SeekMode::SEEK_CLOSEST
-                        && seekTimeUs > timeUs) {
-                    sp<AMessage> extra = new AMessage;
-                    extra->setInt64("resume-at-mediaTimeUs", seekTimeUs);
-                    meta->setMessage("extra", extra);
-                }
-            }
-
-            track->mPackets->queueAccessUnit(buffer);
-            formatChange = false;
-            seeking = false;
-            ++numBuffers;
-        }
-        if (id < count) {
-            // Error, some mediaBuffer doesn't have kKeyTime.
-            for (; id < count; ++id) {
-                mediaBuffers[id]->release();
-            }
+        int64_t timeUs = extractor->getSampleTime();
+        if (timeUs < 0) {
+            track->mPackets->signalEOS(ERROR_MALFORMED);
             break;
         }
 
-        if (err == WOULD_BLOCK) {
-            break;
-        } else if (err == INFO_FORMAT_CHANGED) {
-#if 0
-            track->mPackets->queueDiscontinuity(
-                    ATSParser::DISCONTINUITY_FORMATCHANGE,
-                    NULL,
-                    false /* discard */);
-#endif
-        } else if (err != OK) {
-            queueDiscontinuityIfNeeded(seeking, formatChange, trackType, track);
-            track->mPackets->signalEOS(err);
-            break;
+        sp<AMessage> meta = abuf->meta();
+        format->writeToAMessage(meta);
+        meta->setInt64("timeUs", timeUs);
+        if (trackType == MEDIA_TRACK_TYPE_AUDIO) {
+            mAudioTimeUs = timeUs;
+        } else if (trackType == MEDIA_TRACK_TYPE_VIDEO) {
+            mVideoTimeUs = timeUs;
         }
+
+        queueDiscontinuityIfNeeded(seeking, formatChange, trackType, track);
+
+        if (numBuffers == 0 && actualTimeUs != nullptr) {
+            *actualTimeUs = timeUs;
+        }
+        if (seeking) {
+            if (meta != nullptr && mode == MediaPlayer2SeekMode::SEEK_CLOSEST
+                    && seekTimeUs > timeUs) {
+                sp<AMessage> extra = new AMessage;
+                extra->setInt64("resume-at-mediaTimeUs", seekTimeUs);
+                meta->setMessage("extra", extra);
+            }
+        }
+
+        track->mPackets->queueAccessUnit(abuf);
+        formatChange = false;
+        seeking = false;
+        ++numBuffers;
+        extractor->advance();
+
     }
 
     if (mIsStreaming
@@ -1453,7 +1384,7 @@
             if (mPreparing || mSentPauseOnBuffering) {
                 Track *counterTrack =
                     (trackType == MEDIA_TRACK_TYPE_VIDEO ? &mAudioTrack : &mVideoTrack);
-                if (counterTrack->mSource != NULL) {
+                if (counterTrack->mExtractor != NULL) {
                     durationUs = counterTrack->mPackets->getBufferedDurationUs(&finalResult);
                 }
                 if (finalResult == ERROR_END_OF_STREAM || durationUs >= markUs) {
@@ -1649,26 +1580,22 @@
     // same source without being reset (called by prepareAsync/initFromDataSource)
     mIsDrmReleased = false;
 
-    if (mFileMeta == NULL) {
-        ALOGI("checkDrmInfo: No metadata");
+    if (mExtractor == NULL) {
+        ALOGV("checkDrmInfo: No extractor");
         return OK; // letting the caller responds accordingly
     }
 
-    uint32_t type;
-    const void *pssh;
-    size_t psshsize;
-
-    if (!mFileMeta->findData(kKeyPssh, &type, &pssh, &psshsize)) {
+    PsshInfo *psshInfo = mExtractor->getPsshInfo();
+    if (psshInfo == NULL) {
         ALOGV("checkDrmInfo: No PSSH");
         return OK; // source without DRM info
     }
 
-    sp<ABuffer> drmInfoBuffer = NuPlayer2Drm::retrieveDrmInfo(pssh, psshsize);
-    ALOGV("checkDrmInfo: MEDIA2_DRM_INFO PSSH size: %d drmInfoBuffer size: %d",
-          (int)psshsize, (int)drmInfoBuffer->size());
+    sp<ABuffer> drmInfoBuffer = NuPlayer2Drm::retrieveDrmInfo(psshInfo);
+    ALOGV("checkDrmInfo: MEDIA_DRM_INFO PSSH drm info size: %d", (int)drmInfoBuffer->size());
 
     if (drmInfoBuffer->size() == 0) {
-        ALOGE("checkDrmInfo: Unexpected drmInfoBuffer size: 0");
+        ALOGE("checkDrmInfo: Unexpected parcel size: 0");
         return UNKNOWN_ERROR;
     }
 
diff --git a/media/libmediaplayer2/nuplayer2/GenericSource2.h b/media/libmediaplayer2/nuplayer2/GenericSource2.h
index 896c397..9bc5182 100644
--- a/media/libmediaplayer2/nuplayer2/GenericSource2.h
+++ b/media/libmediaplayer2/nuplayer2/GenericSource2.h
@@ -25,6 +25,9 @@
 
 #include <media/stagefright/MediaBuffer.h>
 #include <mediaplayer2/mediaplayer2.h>
+#include <media/NdkMediaDataSource.h>
+#include <media/NdkMediaExtractor.h>
+#include <media/NdkWrapper.h>
 
 namespace android {
 
@@ -101,6 +104,7 @@
 
     virtual void onMessageReceived(const sp<AMessage> &msg);
 
+    virtual sp<AMessage> getFormat(bool audio);
     virtual sp<MetaData> getFormatMeta(bool audio);
 
 private:
@@ -122,19 +126,14 @@
 
     struct Track {
         size_t mIndex;
-        sp<IMediaSource> mSource;
+        sp<AMediaExtractorWrapper> mExtractor;
         sp<AnotherPacketSource> mPackets;
     };
 
-    Vector<sp<IMediaSource> > mSources;
-    Track mAudioTrack;
     int64_t mAudioTimeUs;
     int64_t mAudioLastDequeueTimeUs;
-    Track mVideoTrack;
     int64_t mVideoTimeUs;
     int64_t mVideoLastDequeueTimeUs;
-    Track mSubtitleTrack;
-    Track mTimedTextTrack;
 
     BufferingSettings mBufferingSettings;
     int32_t mPrevBufferPercentage;
@@ -164,12 +163,20 @@
     sp<NuCachedSource2> mCachedSource;
     sp<DataSource> mHttpSource;
     sp<MetaData> mFileMeta;
+    sp<AMediaDataSourceWrapper> mDataSourceWrapper;
+    sp<AMediaExtractorWrapper> mExtractor;
+    Vector<sp<AMediaExtractorWrapper> > mExtractors;
     bool mStarted;
     bool mPreparing;
     int64_t mBitrate;
     uint32_t mPendingReadBufferTypes;
     sp<ABuffer> mGlobalTimedText;
 
+    Track mVideoTrack;
+    Track mAudioTrack;
+    Track mSubtitleTrack;
+    Track mTimedTextTrack;
+
     mutable Mutex mLock;
 
     sp<ALooper> mLooper;
@@ -227,6 +234,7 @@
 
     void sendCacheStats();
 
+    sp<AMessage> getFormat_l(bool audio);
     sp<MetaData> getFormatMeta_l(bool audio);
     int32_t getDataGeneration(media_track_type type) const;
 
diff --git a/media/libmediaplayer2/nuplayer2/NuPlayer2.cpp b/media/libmediaplayer2/nuplayer2/NuPlayer2.cpp
index 5971a8b..060b698 100644
--- a/media/libmediaplayer2/nuplayer2/NuPlayer2.cpp
+++ b/media/libmediaplayer2/nuplayer2/NuPlayer2.cpp
@@ -2842,7 +2842,7 @@
 void NuPlayer2::sendSubtitleData(const sp<ABuffer> &buffer, int32_t baseIndex) {
     int32_t trackIndex;
     int64_t timeUs, durationUs;
-    CHECK(buffer->meta()->findInt32("trackIndex", &trackIndex));
+    CHECK(buffer->meta()->findInt32(AMEDIAFORMAT_KEY_TRACK_INDEX, &trackIndex));
     CHECK(buffer->meta()->findInt64("timeUs", &timeUs));
     CHECK(buffer->meta()->findInt64("durationUs", &durationUs));
 
diff --git a/media/libmediaplayer2/nuplayer2/NuPlayer2CCDecoder.cpp b/media/libmediaplayer2/nuplayer2/NuPlayer2CCDecoder.cpp
index e4afd5b..e48e388 100644
--- a/media/libmediaplayer2/nuplayer2/NuPlayer2CCDecoder.cpp
+++ b/media/libmediaplayer2/nuplayer2/NuPlayer2CCDecoder.cpp
@@ -21,6 +21,7 @@
 
 #include "NuPlayer2CCDecoder.h"
 
+#include <media/NdkMediaFormat.h>
 #include <media/stagefright/foundation/ABitReader.h>
 #include <media/stagefright/foundation/ABuffer.h>
 #include <media/stagefright/foundation/ADebug.h>
@@ -301,7 +302,7 @@
 // returns true if a new CC track is found
 bool NuPlayer2::CCDecoder::extractFromMPEGUserData(const sp<ABuffer> &accessUnit) {
     sp<ABuffer> mpegUserData;
-    if (!accessUnit->meta()->findBuffer("mpegUserData", &mpegUserData)
+    if (!accessUnit->meta()->findBuffer(AMEDIAFORMAT_KEY_MPEG_USER_DATA, &mpegUserData)
             || mpegUserData == NULL) {
         return false;
     }
@@ -538,7 +539,7 @@
         dumpBytePair(ccBuf);
 #endif
 
-        ccBuf->meta()->setInt32("trackIndex", mSelectedTrack);
+        ccBuf->meta()->setInt32(AMEDIAFORMAT_KEY_TRACK_INDEX, mSelectedTrack);
         ccBuf->meta()->setInt64("timeUs", timeUs);
         ccBuf->meta()->setInt64("durationUs", 0ll);
 
diff --git a/media/libmediaplayerservice/StagefrightRecorder.cpp b/media/libmediaplayerservice/StagefrightRecorder.cpp
index 5cbf976..30c0b1c 100644
--- a/media/libmediaplayerservice/StagefrightRecorder.cpp
+++ b/media/libmediaplayerservice/StagefrightRecorder.cpp
@@ -89,6 +89,13 @@
 static const char *kRecorderVideoTimescale = "android.media.mediarecorder.video-timescale";
 static const char *kRecorderWidth = "android.media.mediarecorder.width";
 
+// new fields, not yet frozen in the public Java API definitions
+static const char *kRecorderAudioMime = "android.media.mediarecorder.audio.mime";
+static const char *kRecorderVideoMime = "android.media.mediarecorder.video.mime";
+static const char *kRecorderDurationMs = "android.media.mediarecorder.durationMs";
+static const char *kRecorderPaused = "android.media.mediarecorder.pausedMs";
+static const char *kRecorderNumPauses = "android.media.mediarecorder.NPauses";
+
 
 // To collect the encoder usage for the battery app
 static void addBatteryData(uint32_t params) {
@@ -105,7 +112,7 @@
     : MediaRecorderBase(opPackageName),
       mWriter(NULL),
       mOutputFd(-1),
-      mAudioSource(AUDIO_SOURCE_CNT),
+      mAudioSource((audio_source_t)AUDIO_SOURCE_CNT), // initialize with invalid value
       mVideoSource(VIDEO_SOURCE_LIST_END),
       mStarted(false),
       mSelectedDeviceId(AUDIO_PORT_HANDLE_NONE),
@@ -126,21 +133,18 @@
     }
 
     // log the current record, provided it has some information worth recording
-    if (mAnalyticsDirty && mAnalyticsItem != NULL) {
-        updateMetrics();
-        if (mAnalyticsItem->count() > 0) {
-            mAnalyticsItem->selfrecord();
-        }
-        delete mAnalyticsItem;
-        mAnalyticsItem = NULL;
-    }
+    // NB: this also reclaims & clears mAnalyticsItem.
+    flushAndResetMetrics(false);
 }
 
 void StagefrightRecorder::updateMetrics() {
     ALOGV("updateMetrics");
 
-    // we'll populate the values from the raw fields.
-    // (NOT going to populate as we go through the various set* ops)
+    // we run as part of the media player service; what we really want to
+    // know is the app which requested the recording.
+    mAnalyticsItem->setUid(mClientUid);
+
+    // populate the values from the raw fields.
 
     // TBD mOutputFormat  = OUTPUT_FORMAT_THREE_GPP;
     // TBD mAudioEncoder  = AUDIO_ENCODER_AMR_NB;
@@ -168,7 +172,6 @@
     // TBD mTrackEveryTimeDurationUs = 0;
     mAnalyticsItem->setInt32(kRecorderCaptureFpsEnable, mCaptureFpsEnable);
     mAnalyticsItem->setDouble(kRecorderCaptureFps, mCaptureFps);
-    // TBD mCaptureFps = -1.0;
     // TBD mCameraSourceTimeLapse = NULL;
     // TBD mMetaDataStoredInVideoBuffers = kMetadataBufferTypeInvalid;
     // TBD mEncoderProfiles = MediaProfiles::getInstance();
@@ -177,14 +180,17 @@
     // PII mLongitudex10000 = -3600000;
     // TBD mTotalBitRate = 0;
 
-    // TBD: some duration information (capture, paused)
-    //
-
+    // duration information (recorded, paused, # of pauses)
+    mAnalyticsItem->setInt64(kRecorderDurationMs, (mDurationRecordedUs+500)/1000 );
+    if (mNPauses != 0) {
+        mAnalyticsItem->setInt64(kRecorderPaused, (mDurationPausedUs+500)/1000 );
+        mAnalyticsItem->setInt32(kRecorderNumPauses, mNPauses);
+    }
 }
 
-void StagefrightRecorder::resetMetrics() {
-    ALOGV("resetMetrics");
-    // flush anything we have, restart the record
+void StagefrightRecorder::flushAndResetMetrics(bool reinitialize) {
+    ALOGV("flushAndResetMetrics");
+    // flush anything we have, maybe setup a new record
     if (mAnalyticsDirty && mAnalyticsItem != NULL) {
         updateMetrics();
         if (mAnalyticsItem->count() > 0) {
@@ -193,8 +199,10 @@
         delete mAnalyticsItem;
         mAnalyticsItem = NULL;
     }
-    mAnalyticsItem = new MediaAnalyticsItem(kKeyRecorder);
     mAnalyticsDirty = false;
+    if (reinitialize) {
+        mAnalyticsItem = new MediaAnalyticsItem(kKeyRecorder);
+    }
 }
 
 status_t StagefrightRecorder::init() {
@@ -1030,6 +1038,8 @@
         mAnalyticsDirty = true;
         mStarted = true;
 
+        mStartedRecordingUs = systemTime() / 1000;
+
         uint32_t params = IMediaPlayerService::kBatteryDataCodecStarted;
         if (mAudioSource != AUDIO_SOURCE_CNT) {
             params |= IMediaPlayerService::kBatteryDataTrackAudio;
@@ -1109,6 +1119,14 @@
             return NULL;
     }
 
+    // log audio mime type for media metrics
+    if (mAnalyticsItem != NULL) {
+        AString audiomime;
+        if (format->findString("mime", &audiomime)) {
+            mAnalyticsItem->setCString(kRecorderAudioMime, audiomime.c_str());
+        }
+    }
+
     int32_t maxInputSize;
     CHECK(audioSource->getFormat()->findInt32(
                 kKeyMaxInputSize, &maxInputSize));
@@ -1655,6 +1673,14 @@
             break;
     }
 
+    // log video mime type for media metrics
+    if (mAnalyticsItem != NULL) {
+        AString videomime;
+        if (format->findString("mime", &videomime)) {
+            mAnalyticsItem->setCString(kRecorderVideoMime, videomime.c_str());
+        }
+    }
+
     if (cameraSource != NULL) {
         sp<MetaData> meta = cameraSource->getFormat();
 
@@ -1917,6 +1943,13 @@
     sp<MetaData> meta = new MetaData;
     meta->setInt64(kKeyTime, mPauseStartTimeUs);
 
+    if (mStartedRecordingUs != 0) {
+        // should always be true
+        int64_t recordingUs = mPauseStartTimeUs - mStartedRecordingUs;
+        mDurationRecordedUs += recordingUs;
+        mStartedRecordingUs = 0;
+    }
+
     if (mAudioEncoderSource != NULL) {
         mAudioEncoderSource->pause();
     }
@@ -1975,6 +2008,16 @@
         source->setInputBufferTimeOffset((int64_t)timeOffset);
         source->start(meta.get());
     }
+
+
+    // sum info on pause duration
+    // (ignore the 30msec of overlap adjustment factored into mTotalPausedDurationUs)
+    int64_t pausedUs = resumeStartTimeUs - mPauseStartTimeUs;
+    mDurationPausedUs += pausedUs;
+    mNPauses++;
+    // and a timestamp marking that we're back to recording....
+    mStartedRecordingUs = resumeStartTimeUs;
+
     mPauseStartTimeUs = 0;
 
     return OK;
@@ -2003,10 +2046,28 @@
         mWriter.clear();
     }
 
-    resetMetrics();
+    // account for the last 'segment' -- whether paused or recording
+    if (mPauseStartTimeUs != 0) {
+        // we were paused
+        int64_t additive = stopTimeUs - mPauseStartTimeUs;
+        mDurationPausedUs += additive;
+        mNPauses++;
+    } else if (mStartedRecordingUs != 0) {
+        // we were recording
+        int64_t additive = stopTimeUs - mStartedRecordingUs;
+        mDurationRecordedUs += additive;
+    } else {
+        ALOGW("stop while neither recording nor paused");
+    }
 
+    flushAndResetMetrics(true);
+
+    mDurationRecordedUs = 0;
+    mDurationPausedUs = 0;
+    mNPauses = 0;
     mTotalPausedDurationUs = 0;
     mPauseStartTimeUs = 0;
+    mStartedRecordingUs = 0;
 
     mGraphicBufferProducer.clear();
     mPersistentSurface.clear();
@@ -2047,7 +2108,7 @@
     stop();
 
     // No audio or video source by default
-    mAudioSource = AUDIO_SOURCE_CNT;
+    mAudioSource = (audio_source_t)AUDIO_SOURCE_CNT; // reset to invalid value
     mVideoSource = VIDEO_SOURCE_LIST_END;
 
     // Default parameters
@@ -2085,6 +2146,12 @@
     mLongitudex10000 = -3600000;
     mTotalBitRate = 0;
 
+    // tracking how long we recorded.
+    mDurationRecordedUs = 0;
+    mStartedRecordingUs = 0;
+    mDurationPausedUs = 0;
+    mNPauses = 0;
+
     mOutputFd = -1;
 
     return OK;
diff --git a/media/libmediaplayerservice/StagefrightRecorder.h b/media/libmediaplayerservice/StagefrightRecorder.h
index 18c9256..faa2e59 100644
--- a/media/libmediaplayerservice/StagefrightRecorder.h
+++ b/media/libmediaplayerservice/StagefrightRecorder.h
@@ -95,7 +95,7 @@
 
     MediaAnalyticsItem *mAnalyticsItem;
     bool mAnalyticsDirty;
-    void resetMetrics();
+    void flushAndResetMetrics(bool reinitialize);
     void updateMetrics();
 
     audio_source_t mAudioSource;
@@ -127,6 +127,11 @@
     int32_t mStartTimeOffsetMs;
     int32_t mTotalBitRate;
 
+    int64_t mDurationRecordedUs;
+    int64_t mStartedRecordingUs;
+    int64_t mDurationPausedUs;
+    int32_t mNPauses;
+
     bool mCaptureFpsEnable;
     double mCaptureFps;
     int64_t mTimeBetweenCaptureUs;
diff --git a/media/libmediaplayerservice/nuplayer/GenericSource.cpp b/media/libmediaplayerservice/nuplayer/GenericSource.cpp
index e7c3deb..cbc3015 100644
--- a/media/libmediaplayerservice/nuplayer/GenericSource.cpp
+++ b/media/libmediaplayerservice/nuplayer/GenericSource.cpp
@@ -1233,7 +1233,7 @@
     }
 
     if (trackType == MEDIA_TRACK_TYPE_SUBTITLE) {
-        meta->setInt32("trackIndex", mSubtitleTrack.mIndex);
+        meta->setInt32("track-index", mSubtitleTrack.mIndex);
     }
 
     uint32_t dataType; // unused
@@ -1249,7 +1249,7 @@
     if (mb->meta_data().findData(
             kKeyMpegUserData, &dataType, &mpegUserDataPointer, &mpegUserDataLength)) {
         sp<ABuffer> mpegUserData = ABuffer::CreateAsCopy(mpegUserDataPointer, mpegUserDataLength);
-        meta->setBuffer("mpegUserData", mpegUserData);
+        meta->setBuffer("mpeg-user-data", mpegUserData);
     }
 
     mb->release();
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayer.cpp b/media/libmediaplayerservice/nuplayer/NuPlayer.cpp
index d1e5d45..bd83c6d 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayer.cpp
+++ b/media/libmediaplayerservice/nuplayer/NuPlayer.cpp
@@ -183,6 +183,7 @@
       mVideoDecoderGeneration(0),
       mRendererGeneration(0),
       mLastStartedPlayingTimeNs(0),
+      mLastStartedRebufferingTimeNs(0),
       mPreviousSeekTimeUs(0),
       mAudioEOS(false),
       mVideoEOS(false),
@@ -1310,8 +1311,8 @@
             ALOGV("kWhatReset");
 
             mResetting = true;
-            stopPlaybackTimer("kWhatReset");
-            stopRebufferingTimer(true);
+            updatePlaybackTimer(true /* stopping */, "kWhatReset");
+            updateRebufferingTimer(true /* stopping */, true /* exiting */);
 
             mDeferredActions.push_back(
                     new FlushDecoderAction(
@@ -1585,23 +1586,28 @@
     }
 }
 
-void NuPlayer::stopPlaybackTimer(const char *where) {
+void NuPlayer::updatePlaybackTimer(bool stopping, const char *where) {
     Mutex::Autolock autoLock(mPlayingTimeLock);
 
-    ALOGV("stopPlaybackTimer()  time %20" PRId64 " (%s)", mLastStartedPlayingTimeNs, where);
+    ALOGV("updatePlaybackTimer(%s)  time %20" PRId64 " (%s)",
+	  stopping ? "stop" : "snap", mLastStartedPlayingTimeNs, where);
 
     if (mLastStartedPlayingTimeNs != 0) {
         sp<NuPlayerDriver> driver = mDriver.promote();
+        int64_t now = systemTime();
         if (driver != NULL) {
-            int64_t now = systemTime();
             int64_t played = now - mLastStartedPlayingTimeNs;
-            ALOGV("stopPlaybackTimer()  log  %20" PRId64 "", played);
+            ALOGV("updatePlaybackTimer()  log  %20" PRId64 "", played);
 
             if (played > 0) {
                 driver->notifyMorePlayingTimeUs((played+500)/1000);
             }
         }
-        mLastStartedPlayingTimeNs = 0;
+	if (stopping) {
+            mLastStartedPlayingTimeNs = 0;
+	} else {
+            mLastStartedPlayingTimeNs = now;
+	}
     }
 }
 
@@ -1613,17 +1619,18 @@
     }
 }
 
-void NuPlayer::stopRebufferingTimer(bool exitingPlayback) {
+void NuPlayer::updateRebufferingTimer(bool stopping, bool exitingPlayback) {
     Mutex::Autolock autoLock(mPlayingTimeLock);
 
-    ALOGV("stopRebufferTimer()  time %20" PRId64 " (exiting %d)", mLastStartedRebufferingTimeNs, exitingPlayback);
+    ALOGV("updateRebufferingTimer(%s)  time %20" PRId64 " (exiting %d)",
+	  stopping ? "stop" : "snap", mLastStartedRebufferingTimeNs, exitingPlayback);
 
     if (mLastStartedRebufferingTimeNs != 0) {
         sp<NuPlayerDriver> driver = mDriver.promote();
+        int64_t now = systemTime();
         if (driver != NULL) {
-            int64_t now = systemTime();
             int64_t rebuffered = now - mLastStartedRebufferingTimeNs;
-            ALOGV("stopRebufferingTimer()  log  %20" PRId64 "", rebuffered);
+            ALOGV("updateRebufferingTimer()  log  %20" PRId64 "", rebuffered);
 
             if (rebuffered > 0) {
                 driver->notifyMoreRebufferingTimeUs((rebuffered+500)/1000);
@@ -1632,13 +1639,24 @@
                 }
             }
         }
-        mLastStartedRebufferingTimeNs = 0;
+	if (stopping) {
+            mLastStartedRebufferingTimeNs = 0;
+	} else {
+            mLastStartedRebufferingTimeNs = now;
+	}
     }
 }
 
+void NuPlayer::updateInternalTimers() {
+    // update values, but ticking clocks keep ticking
+    ALOGV("updateInternalTimers()");
+    updatePlaybackTimer(false /* stopping */, "updateInternalTimers");
+    updateRebufferingTimer(false /* stopping */, false /* exiting */);
+}
+
 void NuPlayer::onPause() {
 
-    stopPlaybackTimer("onPause");
+    updatePlaybackTimer(true /* stopping */, "onPause");
 
     if (mPaused) {
         return;
@@ -2281,8 +2299,8 @@
     CHECK(mAudioDecoder == NULL);
     CHECK(mVideoDecoder == NULL);
 
-    stopPlaybackTimer("performReset");
-    stopRebufferingTimer(true);
+    updatePlaybackTimer(true /* stopping */, "performReset");
+    updateRebufferingTimer(true /* stopping */, true /* exiting */);
 
     cancelPollDuration();
 
@@ -2550,7 +2568,7 @@
             if (mStarted) {
                 ALOGI("buffer ready, resuming...");
 
-                stopRebufferingTimer(false);
+                updateRebufferingTimer(true /* stopping */, false /* exiting */);
                 mPausedForBuffering = false;
 
                 // do not resume yet if client didn't unpause
@@ -2684,7 +2702,7 @@
 void NuPlayer::sendSubtitleData(const sp<ABuffer> &buffer, int32_t baseIndex) {
     int32_t trackIndex;
     int64_t timeUs, durationUs;
-    CHECK(buffer->meta()->findInt32("trackIndex", &trackIndex));
+    CHECK(buffer->meta()->findInt32("track-index", &trackIndex));
     CHECK(buffer->meta()->findInt64("timeUs", &timeUs));
     CHECK(buffer->meta()->findInt64("durationUs", &durationUs));
 
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayer.h b/media/libmediaplayerservice/nuplayer/NuPlayer.h
index fda69e8..9481234 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayer.h
+++ b/media/libmediaplayerservice/nuplayer/NuPlayer.h
@@ -99,6 +99,8 @@
 
     const char *getDataSourceType();
 
+    void updateInternalTimers();
+
 protected:
     virtual ~NuPlayer();
 
@@ -180,12 +182,12 @@
 
     Mutex mPlayingTimeLock;
     int64_t mLastStartedPlayingTimeNs;
-    void stopPlaybackTimer(const char *where);
+    void updatePlaybackTimer(bool stopping, const char *where);
     void startPlaybackTimer(const char *where);
 
     int64_t mLastStartedRebufferingTimeNs;
     void startRebufferingTimer();
-    void stopRebufferingTimer(bool exitingPlayback);
+    void updateRebufferingTimer(bool stopping, bool exitingPlayback);
 
     int64_t mPreviousSeekTimeUs;
 
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerCCDecoder.cpp b/media/libmediaplayerservice/nuplayer/NuPlayerCCDecoder.cpp
index 0a8b97f..0402fca 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayerCCDecoder.cpp
+++ b/media/libmediaplayerservice/nuplayer/NuPlayerCCDecoder.cpp
@@ -301,7 +301,7 @@
 // returns true if a new CC track is found
 bool NuPlayer::CCDecoder::extractFromMPEGUserData(const sp<ABuffer> &accessUnit) {
     sp<ABuffer> mpegUserData;
-    if (!accessUnit->meta()->findBuffer("mpegUserData", &mpegUserData)
+    if (!accessUnit->meta()->findBuffer("mpeg-user-data", &mpegUserData)
             || mpegUserData == NULL) {
         return false;
     }
@@ -538,7 +538,7 @@
         dumpBytePair(ccBuf);
 #endif
 
-        ccBuf->meta()->setInt32("trackIndex", mSelectedTrack);
+        ccBuf->meta()->setInt32("track-index", mSelectedTrack);
         ccBuf->meta()->setInt64("timeUs", timeUs);
         ccBuf->meta()->setInt64("durationUs", 0ll);
 
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp b/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp
index c455951..731fdba 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp
+++ b/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp
@@ -535,6 +535,7 @@
 }
 
 void NuPlayerDriver::updateMetrics(const char *where) {
+
     if (where == NULL) {
         where = "unknown";
     }
@@ -592,6 +593,8 @@
     getDuration(&duration_ms);
     mAnalyticsItem->setInt64(kPlayerDuration, duration_ms);
 
+    mPlayer->updateInternalTimers();
+
     mAnalyticsItem->setInt64(kPlayerPlaying, (mPlayingTimeUs+500)/1000 );
 
     if (mRebufferingEvents != 0) {
@@ -630,7 +633,7 @@
             mAnalyticsItem->setUid(mClientUid);
         }
     } else {
-        ALOGV("did not have anything to record");
+        ALOGV("nothing to record (only %d fields)", mAnalyticsItem->count());
     }
 }
 
diff --git a/media/libstagefright/ACodec.cpp b/media/libstagefright/ACodec.cpp
index b296622..86d7c08 100644
--- a/media/libstagefright/ACodec.cpp
+++ b/media/libstagefright/ACodec.cpp
@@ -553,6 +553,7 @@
       mNativeWindowUsageBits(0),
       mLastNativeWindowDataSpace(HAL_DATASPACE_UNKNOWN),
       mIsVideo(false),
+      mIsImage(false),
       mIsEncoder(false),
       mFatalError(false),
       mShutdownInProgress(false),
@@ -1713,6 +1714,7 @@
 
     mIsEncoder = encoder;
     mIsVideo = !strncasecmp(mime, "video/", 6);
+    mIsImage = !strncasecmp(mime, "image/", 6);
 
     mPortMode[kPortIndexInput] = IOMX::kPortModePresetByteBuffer;
     mPortMode[kPortIndexOutput] = IOMX::kPortModePresetByteBuffer;
@@ -1728,10 +1730,11 @@
     // FLAC encoder or video encoder in constant quality mode doesn't need a
     // bitrate, other encoders do.
     if (encoder) {
-        if (mIsVideo && !findVideoBitrateControlInfo(
-                msg, &bitrateMode, &bitrate, &quality)) {
-            return INVALID_OPERATION;
-        } else if (!mIsVideo && strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_FLAC)
+        if (mIsVideo || mIsImage) {
+            if (!findVideoBitrateControlInfo(msg, &bitrateMode, &bitrate, &quality)) {
+                return INVALID_OPERATION;
+            }
+        } else if (strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_FLAC)
             && !msg->findInt32("bitrate", &bitrate)) {
             return INVALID_OPERATION;
         }
@@ -2010,7 +2013,7 @@
     (void)msg->findInt32("pcm-encoding", (int32_t*)&pcmEncoding);
     // invalid encodings will default to PCM-16bit in setupRawAudioFormat.
 
-    if (mIsVideo) {
+    if (mIsVideo || mIsImage) {
         // determine need for software renderer
         bool usingSwRenderer = false;
         if (haveNativeWindow && mComponentName.startsWith("OMX.google.")) {
@@ -2290,7 +2293,7 @@
     }
 
     // create data converters if needed
-    if (!mIsVideo && err == OK) {
+    if (!mIsVideo && !mIsImage && err == OK) {
         AudioEncoding codecPcmEncoding = kAudioEncodingPcm16bit;
         if (encoder) {
             (void)mInputFormat->findInt32("pcm-encoding", (int32_t*)&codecPcmEncoding);
@@ -3215,6 +3218,7 @@
     { MEDIA_MIMETYPE_VIDEO_VP8, OMX_VIDEO_CodingVP8 },
     { MEDIA_MIMETYPE_VIDEO_VP9, OMX_VIDEO_CodingVP9 },
     { MEDIA_MIMETYPE_VIDEO_DOLBY_VISION, OMX_VIDEO_CodingDolbyVision },
+    { MEDIA_MIMETYPE_IMAGE_ANDROID_HEIC, OMX_VIDEO_CodingImageHEIC },
 };
 
 static status_t GetVideoCodingTypeFromMime(
@@ -3872,7 +3876,8 @@
             break;
 
         case OMX_VIDEO_CodingHEVC:
-            err = setupHEVCEncoderParameters(msg);
+        case OMX_VIDEO_CodingImageHEIC:
+            err = setupHEVCEncoderParameters(msg, outputFormat);
             break;
 
         case OMX_VIDEO_CodingVP8:
@@ -4378,27 +4383,63 @@
     return configureBitrate(bitrateMode, bitrate);
 }
 
-status_t ACodec::setupHEVCEncoderParameters(const sp<AMessage> &msg) {
-    float iFrameInterval;
-    if (!msg->findAsFloat("i-frame-interval", &iFrameInterval)) {
-        return INVALID_OPERATION;
+status_t ACodec::configureImageGrid(
+        const sp<AMessage> &msg, sp<AMessage> &outputFormat) {
+    int32_t gridWidth, gridHeight, gridRows, gridCols;
+    if (!msg->findInt32("grid-width", &gridWidth) ||
+        !msg->findInt32("grid-height", &gridHeight) ||
+        !msg->findInt32("grid-rows", &gridRows) ||
+        !msg->findInt32("grid-cols", &gridCols)) {
+        return OK;
     }
 
+    OMX_VIDEO_PARAM_ANDROID_IMAGEGRIDTYPE gridType;
+    InitOMXParams(&gridType);
+    gridType.nPortIndex = kPortIndexOutput;
+    gridType.bEnabled = OMX_TRUE;
+    gridType.nGridWidth = gridWidth;
+    gridType.nGridHeight = gridHeight;
+    gridType.nGridRows = gridRows;
+    gridType.nGridCols = gridCols;
+
+    status_t err = mOMXNode->setParameter(
+            (OMX_INDEXTYPE)OMX_IndexParamVideoAndroidImageGrid,
+            &gridType, sizeof(gridType));
+
+    // for video encoders, grid config is only a hint.
+    if (!mIsImage) {
+        return OK;
+    }
+
+    // image encoders must support grid config.
+    if (err != OK) {
+        return err;
+    }
+
+    // query to get the image encoder's real grid config as it might be
+    // different from the requested, and transfer that to the output.
+    err = mOMXNode->getParameter(
+            (OMX_INDEXTYPE)OMX_IndexParamVideoAndroidImageGrid,
+            &gridType, sizeof(gridType));
+
+    if (err == OK && gridType.bEnabled) {
+        outputFormat->setInt32("grid-width", gridType.nGridWidth);
+        outputFormat->setInt32("grid-height", gridType.nGridHeight);
+        outputFormat->setInt32("grid-rows", gridType.nGridRows);
+        outputFormat->setInt32("grid-cols", gridType.nGridCols);
+    }
+
+    return err;
+}
+
+status_t ACodec::setupHEVCEncoderParameters(
+        const sp<AMessage> &msg, sp<AMessage> &outputFormat) {
     OMX_VIDEO_CONTROLRATETYPE bitrateMode;
     int32_t bitrate, quality;
     if (!findVideoBitrateControlInfo(msg, &bitrateMode, &bitrate, &quality)) {
         return INVALID_OPERATION;
     }
 
-    float frameRate;
-    if (!msg->findFloat("frame-rate", &frameRate)) {
-        int32_t tmp;
-        if (!msg->findInt32("frame-rate", &tmp)) {
-            return INVALID_OPERATION;
-        }
-        frameRate = (float)tmp;
-    }
-
     OMX_VIDEO_PARAM_HEVCTYPE hevcType;
     InitOMXParams(&hevcType);
     hevcType.nPortIndex = kPortIndexOutput;
@@ -4426,7 +4467,27 @@
         hevcType.eLevel = static_cast<OMX_VIDEO_HEVCLEVELTYPE>(level);
     }
     // TODO: finer control?
-    hevcType.nKeyFrameInterval = setPFramesSpacing(iFrameInterval, frameRate) + 1;
+    if (mIsImage) {
+        hevcType.nKeyFrameInterval = 1;
+    } else {
+        float iFrameInterval;
+        if (!msg->findAsFloat("i-frame-interval", &iFrameInterval)) {
+            return INVALID_OPERATION;
+        }
+
+        float frameRate;
+        if (!msg->findFloat("frame-rate", &frameRate)) {
+            int32_t tmp;
+            if (!msg->findInt32("frame-rate", &tmp)) {
+                return INVALID_OPERATION;
+            }
+            frameRate = (float)tmp;
+        }
+
+        hevcType.nKeyFrameInterval =
+                setPFramesSpacing(iFrameInterval, frameRate) + 1;
+    }
+
 
     err = mOMXNode->setParameter(
             (OMX_INDEXTYPE)OMX_IndexParamVideoHevc, &hevcType, sizeof(hevcType));
@@ -4434,6 +4495,12 @@
         return err;
     }
 
+    err = configureImageGrid(msg, outputFormat);
+
+    if (err != OK) {
+        return err;
+    }
+
     return configureBitrate(bitrateMode, bitrate, quality);
 }
 
@@ -4875,7 +4942,8 @@
                             (void)getHDRStaticInfoForVideoCodec(kPortIndexInput, notify);
                         }
                         uint32_t latency = 0;
-                        if (mIsEncoder && getLatency(&latency) == OK && latency > 0) {
+                        if (mIsEncoder && !mIsImage &&
+                                getLatency(&latency) == OK && latency > 0) {
                             notify->setInt32("latency", latency);
                         }
                     }
@@ -4931,7 +4999,8 @@
                         notify->setString("mime", mime.c_str());
                     }
                     uint32_t intraRefreshPeriod = 0;
-                    if (mIsEncoder && getIntraRefreshPeriod(&intraRefreshPeriod) == OK
+                    if (mIsEncoder && !mIsImage &&
+                            getIntraRefreshPeriod(&intraRefreshPeriod) == OK
                             && intraRefreshPeriod > 0) {
                         notify->setInt32("intra-refresh-period", intraRefreshPeriod);
                     }
@@ -6346,9 +6415,6 @@
 
     sp<AMessage> notify = new AMessage(kWhatOMXDied, mCodec);
 
-    Vector<AString> matchingCodecs;
-    Vector<AString> owners;
-
     AString componentName;
     CHECK(msg->findString("componentName", &componentName));
 
@@ -6424,7 +6490,7 @@
 
     mCodec->mOMX = omx;
     mCodec->mOMXNode = omxNode;
-    mCodec->mCallback->onComponentAllocated(mCodec->mComponentName.c_str());
+    mCodec->mCallback->onComponentAllocated(mCodec->mComponentName.c_str(), info);
     mCodec->changeState(mCodec->mLoadedState);
 
     return true;
@@ -8226,8 +8292,9 @@
     }
 
     bool isVideo = strncasecmp(mime, "video/", 6) == 0;
+    bool isImage = strncasecmp(mime, "image/", 6) == 0;
 
-    if (isVideo) {
+    if (isVideo || isImage) {
         OMX_VIDEO_PARAM_PROFILELEVELTYPE param;
         InitOMXParams(&param);
         param.nPortIndex = isEncoder ? kPortIndexOutput : kPortIndexInput;
diff --git a/media/libstagefright/Android.bp b/media/libstagefright/Android.bp
index 65348e5..13d80f5 100644
--- a/media/libstagefright/Android.bp
+++ b/media/libstagefright/Android.bp
@@ -137,7 +137,6 @@
         "libstagefright_omx",
         "libstagefright_omx_utils",
         "libstagefright_xmlparser",
-        "libdl",
         "libRScpp",
         "libhidlallocatorutils",
         "libhidlbase",
diff --git a/media/libstagefright/BufferImpl.cpp b/media/libstagefright/BufferImpl.cpp
index d2eee33..7b3fa02 100644
--- a/media/libstagefright/BufferImpl.cpp
+++ b/media/libstagefright/BufferImpl.cpp
@@ -70,6 +70,10 @@
     if (const_cast<Codec2Buffer *>(this)->base() == nullptr) {
         return false;
     }
+    if (!buffer) {
+        // Nothing to copy, so we can copy by doing nothing.
+        return true;
+    }
     if (buffer->data().type() != C2BufferData::LINEAR) {
         return false;
     }
@@ -89,7 +93,7 @@
 
 bool Codec2Buffer::copyLinear(const std::shared_ptr<C2Buffer> &buffer) {
     // We assume that all canCopyLinear() checks passed.
-    if (buffer->data().linearBlocks().size() == 0u) {
+    if (!buffer || buffer->data().linearBlocks().size() == 0u) {
         setRange(0, 0);
         return true;
     }
@@ -207,4 +211,445 @@
     return std::move(mBufferRef);
 }
 
+// GraphicView2MediaImageConverter
+
+namespace {
+
+class GraphicView2MediaImageConverter {
+public:
+    explicit GraphicView2MediaImageConverter(const C2GraphicView &view)
+        : mInitCheck(NO_INIT),
+          mView(view),
+          mWidth(view.width()),
+          mHeight(view.height()),
+          mAllocatedDepth(0),
+          mBackBufferSize(0),
+          mMediaImage(new ABuffer(sizeof(MediaImage2))) {
+        if (view.error() != C2_OK) {
+            ALOGD("Converter: view.error() = %d", view.error());
+            mInitCheck = BAD_VALUE;
+            return;
+        }
+        MediaImage2 *mediaImage = (MediaImage2 *)mMediaImage->base();
+        const C2PlanarLayout &layout = view.layout();
+        if (layout.numPlanes == 0) {
+            ALOGD("Converter: 0 planes");
+            mInitCheck = BAD_VALUE;
+            return;
+        }
+        mAllocatedDepth = layout.planes[0].allocatedDepth;
+        uint32_t bitDepth = layout.planes[0].bitDepth;
+
+        switch (layout.type) {
+            case C2PlanarLayout::TYPE_YUV:
+                mediaImage->mType = MediaImage2::MEDIA_IMAGE_TYPE_YUV; break;
+            case C2PlanarLayout::TYPE_YUVA:
+                mediaImage->mType = MediaImage2::MEDIA_IMAGE_TYPE_YUVA; break;
+            case C2PlanarLayout::TYPE_RGB:
+                mediaImage->mType = MediaImage2::MEDIA_IMAGE_TYPE_RGB; break;
+            case C2PlanarLayout::TYPE_RGBA:
+                mediaImage->mType = MediaImage2::MEDIA_IMAGE_TYPE_RGBA; break;
+            default:
+                mediaImage->mType = MediaImage2::MEDIA_IMAGE_TYPE_UNKNOWN; break;
+        }
+        mediaImage->mNumPlanes = layout.numPlanes;
+        mediaImage->mWidth = mWidth;
+        mediaImage->mHeight = mHeight;
+        mediaImage->mBitDepth = bitDepth;
+        mediaImage->mBitDepthAllocated = mAllocatedDepth;
+
+        uint32_t bufferSize = 0;
+        for (uint32_t i = 0; i < layout.numPlanes; ++i) {
+            const C2PlaneInfo &plane = layout.planes[i];
+            if (plane.rightShift != 0) {
+                ALOGV("rightShift value of %u unsupported", plane.rightShift);
+                mInitCheck = BAD_VALUE;
+                return;
+            }
+            if (plane.endianness != C2PlaneInfo::NATIVE) {
+                ALOGV("endianness value of %u unsupported", plane.endianness);
+                mInitCheck = BAD_VALUE;
+                return;
+            }
+            if (plane.allocatedDepth != mAllocatedDepth || plane.bitDepth != bitDepth) {
+                ALOGV("different allocatedDepth/bitDepth per plane unsupported");
+                mInitCheck = BAD_VALUE;
+                return;
+            }
+            bufferSize += mWidth * mHeight
+                    / plane.rowSampling / plane.colSampling * (plane.allocatedDepth / 8);
+        }
+
+        mBackBufferSize = bufferSize;
+        mInitCheck = OK;
+    }
+
+    status_t initCheck() const { return mInitCheck; }
+
+    uint32_t backBufferSize() const { return mBackBufferSize; }
+
+    /**
+     * Convert C2GraphicView to MediaImage2. Note that if not wrapped, the content
+     * is not copied over in this function --- the caller should use
+     * CopyGraphicView2MediaImage() function to do that explicitly.
+     *
+     * \param   view[in]          source C2GraphicView object.
+     * \param   alloc[in]         allocator function for ABuffer.
+     * \param   mediaImage[out]   destination MediaImage2 object.
+     * \param   buffer[out]       new buffer object.
+     * \param   wrapped[out]      whether we wrapped around existing map or
+     *                            allocated a new buffer
+     *
+     * \return  true              if conversion succeeds,
+     *          false             otherwise; all output params should be ignored.
+     */
+    sp<ABuffer> wrap() {
+        MediaImage2 *mediaImage = getMediaImage();
+        const C2PlanarLayout &layout = mView.layout();
+        if (layout.numPlanes == 1) {
+            const C2PlaneInfo &plane = layout.planes[0];
+            ssize_t offset = plane.minOffset(mWidth, mHeight);
+            mediaImage->mPlane[0].mOffset = -offset;
+            mediaImage->mPlane[0].mColInc = plane.colInc;
+            mediaImage->mPlane[0].mRowInc = plane.rowInc;
+            mediaImage->mPlane[0].mHorizSubsampling = plane.colSampling;
+            mediaImage->mPlane[0].mVertSubsampling = plane.rowSampling;
+            return new ABuffer(
+                    const_cast<uint8_t *>(mView.data()[0] + offset),
+                    plane.maxOffset(mWidth, mHeight) - offset + 1);
+        }
+        const uint8_t *minPtr = mView.data()[0];
+        const uint8_t *maxPtr = mView.data()[0];
+        int32_t planeSize = 0;
+        for (uint32_t i = 0; i < layout.numPlanes; ++i) {
+            const C2PlaneInfo &plane = layout.planes[i];
+            ssize_t minOffset = plane.minOffset(mWidth, mHeight);
+            ssize_t maxOffset = plane.maxOffset(mWidth, mHeight);
+            if (minPtr > mView.data()[i] + minOffset) {
+                minPtr = mView.data()[i] + minOffset;
+            }
+            if (maxPtr < mView.data()[i] + maxOffset) {
+                maxPtr = mView.data()[i] + maxOffset;
+            }
+            planeSize += std::abs(plane.rowInc) * mHeight
+                    / plane.rowSampling / plane.colSampling * (mAllocatedDepth / 8);
+        }
+
+        if ((maxPtr - minPtr + 1) <= planeSize) {
+            // FIXME: this is risky as reading/writing data out of bound results in
+            //        an undefined behavior.
+            for (uint32_t i = 0; i < layout.numPlanes; ++i) {
+                const C2PlaneInfo &plane = layout.planes[i];
+                mediaImage->mPlane[i].mOffset = mView.data()[i] - minPtr;
+                mediaImage->mPlane[i].mColInc = plane.colInc;
+                mediaImage->mPlane[i].mRowInc = plane.rowInc;
+                mediaImage->mPlane[i].mHorizSubsampling = plane.colSampling;
+                mediaImage->mPlane[i].mVertSubsampling = plane.rowSampling;
+            }
+            return new ABuffer(const_cast<uint8_t *>(minPtr), maxPtr - minPtr + 1);
+        }
+
+        return nullptr;
+    }
+
+    bool setBackBuffer(const sp<ABuffer> &backBuffer) {
+        if (backBuffer->capacity() < mBackBufferSize) {
+            return false;
+        }
+        backBuffer->setRange(0, mBackBufferSize);
+
+        const C2PlanarLayout &layout = mView.layout();
+        MediaImage2 *mediaImage = getMediaImage();
+        uint32_t offset = 0;
+        // TODO: keep interleaved planes together
+        for (uint32_t i = 0; i < layout.numPlanes; ++i) {
+            const C2PlaneInfo &plane = layout.planes[i];
+            mediaImage->mPlane[i].mOffset = offset;
+            mediaImage->mPlane[i].mColInc = mAllocatedDepth / 8;
+            mediaImage->mPlane[i].mRowInc =
+                mediaImage->mPlane[i].mColInc * mWidth / plane.colSampling;
+            mediaImage->mPlane[i].mHorizSubsampling = plane.colSampling;
+            mediaImage->mPlane[i].mVertSubsampling = plane.rowSampling;
+            offset += mediaImage->mPlane[i].mRowInc * mHeight / plane.rowSampling;
+        }
+        mBackBuffer = backBuffer;
+        return true;
+    }
+
+    /**
+     * Copy C2GraphicView to MediaImage2. This function assumes that |mediaImage| is
+     * an output from GraphicView2MediaImage(), so it mostly skips sanity check.
+     *
+     * \param   view[in]          source C2GraphicView object.
+     * \param   mediaImage[in]    destination MediaImage2 object.
+     * \param   buffer[out]       new buffer object.
+     */
+    void copy() {
+        // TODO: more efficient copying --- e.g. one row at a time, copying
+        //       interleaved planes together, etc.
+        const C2PlanarLayout &layout = mView.layout();
+        MediaImage2 *mediaImage = getMediaImage();
+        uint8_t *dst = mBackBuffer->base();
+        for (uint32_t i = 0; i < layout.numPlanes; ++i) {
+            const C2PlaneInfo &plane = layout.planes[i];
+            const uint8_t *src = mView.data()[i];
+            int32_t planeW = mWidth / plane.colSampling;
+            int32_t planeH = mHeight / plane.rowSampling;
+            for (int32_t row = 0; row < planeH; ++row) {
+                for(int32_t col = 0; col < planeW; ++col) {
+                    memcpy(dst, src, mAllocatedDepth / 8);
+                    dst += mediaImage->mPlane[i].mColInc;
+                    src += plane.colInc;
+                }
+                dst -= mediaImage->mPlane[i].mColInc * planeW;
+                dst += mediaImage->mPlane[i].mRowInc;
+                src -= plane.colInc * planeW;
+                src += plane.rowInc;
+            }
+        }
+    }
+
+    const sp<ABuffer> &imageData() const { return mMediaImage; }
+
+private:
+    status_t mInitCheck;
+
+    const C2GraphicView mView;
+    uint32_t mWidth;
+    uint32_t mHeight;
+    uint32_t mAllocatedDepth;
+    uint32_t mBackBufferSize;
+    sp<ABuffer> mMediaImage;
+    std::function<sp<ABuffer>(size_t)> mAlloc;
+
+    sp<ABuffer> mBackBuffer;
+
+    MediaImage2 *getMediaImage() {
+        return (MediaImage2 *)mMediaImage->base();
+    }
+};
+
+}  // namespace
+
+// GraphicBlockBuffer
+
+// static
+sp<GraphicBlockBuffer> GraphicBlockBuffer::Allocate(
+        const sp<AMessage> &format,
+        const std::shared_ptr<C2GraphicBlock> &block,
+        std::function<sp<ABuffer>(size_t)> alloc) {
+    C2GraphicView view(block->map().get());
+    if (view.error() != C2_OK) {
+        ALOGD("C2GraphicBlock::map failed: %d", view.error());
+        return nullptr;
+    }
+    GraphicView2MediaImageConverter converter(view);
+    if (converter.initCheck() != OK) {
+        ALOGD("Converter init failed: %d", converter.initCheck());
+        return nullptr;
+    }
+    bool wrapped = true;
+    sp<ABuffer> buffer = converter.wrap();
+    if (buffer == nullptr) {
+        buffer = alloc(converter.backBufferSize());
+        if (!converter.setBackBuffer(buffer)) {
+            ALOGD("Converter failed to set back buffer");
+            return nullptr;
+        }
+        wrapped = false;
+    }
+    return new GraphicBlockBuffer(
+            format,
+            buffer,
+            std::move(view),
+            block,
+            converter.imageData(),
+            wrapped);
+}
+
+GraphicBlockBuffer::GraphicBlockBuffer(
+        const sp<AMessage> &format,
+        const sp<ABuffer> &buffer,
+        C2GraphicView &&view,
+        const std::shared_ptr<C2GraphicBlock> &block,
+        const sp<ABuffer> &imageData,
+        bool wrapped)
+    : Codec2Buffer(format, buffer),
+      mView(view),
+      mBlock(block),
+      mImageData(imageData),
+      mWrapped(wrapped) {
+    meta()->setBuffer("image-data", imageData);
+}
+
+std::shared_ptr<C2Buffer> GraphicBlockBuffer::asC2Buffer() {
+    uint32_t width = mView.width();
+    uint32_t height = mView.height();
+    if (!mWrapped) {
+        MediaImage2 *mediaImage = imageData();
+        const C2PlanarLayout &layout = mView.layout();
+        for (uint32_t i = 0; i < mediaImage->mNumPlanes; ++i) {
+            const C2PlaneInfo &plane = layout.planes[i];
+            int32_t planeW = width / plane.colSampling;
+            int32_t planeH = height / plane.rowSampling;
+            const uint8_t *src = base() + mediaImage->mPlane[i].mOffset;
+            uint8_t *dst = mView.data()[i];
+            for (int32_t row = 0; row < planeH; ++row) {
+                for (int32_t col = 0; col < planeW; ++col) {
+                    memcpy(dst, src, mediaImage->mBitDepthAllocated / 8);
+                    src += mediaImage->mPlane[i].mColInc;
+                    dst += plane.colInc;
+                }
+                src -= mediaImage->mPlane[i].mColInc * planeW;
+                dst -= plane.colInc * planeW;
+                src += mediaImage->mPlane[i].mRowInc;
+                dst += plane.rowInc;
+            }
+        }
+    }
+    return C2Buffer::CreateGraphicBuffer(
+            mBlock->share(C2Rect(width, height), C2Fence()));
+}
+
+// ConstGraphicBlockBuffer
+
+// static
+sp<ConstGraphicBlockBuffer> ConstGraphicBlockBuffer::Allocate(
+        const sp<AMessage> &format,
+        const std::shared_ptr<C2Buffer> &buffer,
+        std::function<sp<ABuffer>(size_t)> alloc) {
+    if (!buffer
+            || buffer->data().type() != C2BufferData::GRAPHIC
+            || buffer->data().graphicBlocks().size() != 1u) {
+        ALOGD("C2Buffer precond fail");
+        return nullptr;
+    }
+    std::unique_ptr<const C2GraphicView> view(std::make_unique<const C2GraphicView>(
+            buffer->data().graphicBlocks()[0].map().get()));
+    std::unique_ptr<const C2GraphicView> holder;
+
+    GraphicView2MediaImageConverter converter(*view);
+    if (converter.initCheck() != OK) {
+        ALOGD("Converter init failed: %d", converter.initCheck());
+        return nullptr;
+    }
+    bool wrapped = true;
+    sp<ABuffer> aBuffer = converter.wrap();
+    if (aBuffer == nullptr) {
+        aBuffer = alloc(converter.backBufferSize());
+        if (!converter.setBackBuffer(aBuffer)) {
+            ALOGD("Converter failed to set back buffer");
+            return nullptr;
+        }
+        wrapped = false;
+        converter.copy();
+        // We don't need the view.
+        holder = std::move(view);
+    }
+    return new ConstGraphicBlockBuffer(
+            format,
+            aBuffer,
+            std::move(view),
+            buffer,
+            converter.imageData(),
+            wrapped);
+}
+
+// static
+sp<ConstGraphicBlockBuffer> ConstGraphicBlockBuffer::AllocateEmpty(
+        const sp<AMessage> &format,
+        std::function<sp<ABuffer>(size_t)> alloc) {
+    int32_t width, height;
+    if (!format->findInt32("width", &width)
+            || !format->findInt32("height", &height)) {
+        ALOGD("format had no width / height");
+        return nullptr;
+    }
+    sp<ABuffer> aBuffer(alloc(width * height * 4));
+    return new ConstGraphicBlockBuffer(
+            format,
+            aBuffer,
+            nullptr,
+            nullptr,
+            nullptr,
+            false);
+}
+
+ConstGraphicBlockBuffer::ConstGraphicBlockBuffer(
+        const sp<AMessage> &format,
+        const sp<ABuffer> &aBuffer,
+        std::unique_ptr<const C2GraphicView> &&view,
+        const std::shared_ptr<C2Buffer> &buffer,
+        const sp<ABuffer> &imageData,
+        bool wrapped)
+    : Codec2Buffer(format, aBuffer),
+      mView(std::move(view)),
+      mBufferRef(buffer),
+      mWrapped(wrapped) {
+    if (imageData != nullptr) {
+        meta()->setBuffer("image-data", imageData);
+    }
+}
+
+std::shared_ptr<C2Buffer> ConstGraphicBlockBuffer::asC2Buffer() {
+    mView.reset();
+    return std::move(mBufferRef);
+}
+
+bool ConstGraphicBlockBuffer::canCopy(const std::shared_ptr<C2Buffer> &buffer) const {
+    if (mWrapped || mBufferRef) {
+        ALOGD("ConstGraphicBlockBuffer::canCopy: %swrapped ; buffer ref %s",
+                mWrapped ? "" : "not ", mBufferRef ? "exists" : "doesn't exist");
+        return false;
+    }
+    if (!buffer) {
+        // Nothing to copy, so we can copy by doing nothing.
+        return true;
+    }
+    if (buffer->data().type() != C2BufferData::GRAPHIC) {
+        ALOGD("ConstGraphicBlockBuffer::canCopy: buffer precondition unsatisfied");
+        return false;
+    }
+    if (buffer->data().graphicBlocks().size() == 0) {
+        return true;
+    } else if (buffer->data().graphicBlocks().size() != 1u) {
+        ALOGD("ConstGraphicBlockBuffer::canCopy: too many blocks");
+        return false;
+    }
+    GraphicView2MediaImageConverter converter(
+            buffer->data().graphicBlocks()[0].map().get());
+    if (converter.initCheck() != OK) {
+        ALOGD("ConstGraphicBlockBuffer::canCopy: converter init failed: %d", converter.initCheck());
+        return false;
+    }
+    if (converter.backBufferSize() > capacity()) {
+        ALOGD("ConstGraphicBlockBuffer::canCopy: insufficient capacity: req %u has %zu",
+                converter.backBufferSize(), capacity());
+        return false;
+    }
+    return true;
+}
+
+bool ConstGraphicBlockBuffer::copy(const std::shared_ptr<C2Buffer> &buffer) {
+    if (!buffer || buffer->data().graphicBlocks().size() == 0) {
+        setRange(0, 0);
+        return true;
+    }
+    GraphicView2MediaImageConverter converter(
+            buffer->data().graphicBlocks()[0].map().get());
+    if (converter.initCheck() != OK) {
+        ALOGD("ConstGraphicBlockBuffer::copy: converter init failed: %d", converter.initCheck());
+        return false;
+    }
+    sp<ABuffer> aBuffer = new ABuffer(base(), capacity());
+    if (!converter.setBackBuffer(aBuffer)) {
+        ALOGD("ConstGraphicBlockBuffer::copy: set back buffer failed");
+        return false;
+    }
+    converter.copy();
+    meta()->setBuffer("image-data", converter.imageData());
+    mBufferRef = buffer;
+    return true;
+}
+
 }  // namespace android
diff --git a/media/libstagefright/CCodec.cpp b/media/libstagefright/CCodec.cpp
index f62ee41..0bdd808 100644
--- a/media/libstagefright/CCodec.cpp
+++ b/media/libstagefright/CCodec.cpp
@@ -238,14 +238,25 @@
     return mChannel;
 }
 
+status_t CCodec::tryAndReportOnError(std::function<status_t()> job) {
+    status_t err = job();
+    if (err != C2_OK) {
+        mCallback->onError(err, ACTION_CODE_FATAL);
+    }
+    return err;
+}
+
 void CCodec::initiateAllocateComponent(const sp<AMessage> &msg) {
-    {
+    auto setAllocating = [this] {
         Mutexed<State>::Locked state(mState);
         if (state->get() != RELEASED) {
-            mCallback->onError(INVALID_OPERATION, ACTION_CODE_FATAL);
-            return;
+            return INVALID_OPERATION;
         }
         state->set(ALLOCATING);
+        return OK;
+    };
+    if (tryAndReportOnError(setAllocating) != OK) {
+        return;
     }
 
     AString componentName;
@@ -259,7 +270,7 @@
 }
 
 void CCodec::allocate(const AString &componentName) {
-    // TODO: use C2ComponentStore to create component
+    ALOGV("allocate(%s)", componentName.c_str());
     mListener.reset(new CCodecListener(this));
 
     std::shared_ptr<C2Component> comp;
@@ -282,29 +293,30 @@
     }
     ALOGV("Success Create component: %s", componentName.c_str());
     comp->setListener_vb(mListener, C2_MAY_BLOCK);
-    {
+    mChannel->setComponent(comp);
+    auto setAllocated = [this, comp] {
         Mutexed<State>::Locked state(mState);
         if (state->get() != ALLOCATING) {
             state->set(RELEASED);
-            state.unlock();
-            mCallback->onError(UNKNOWN_ERROR, ACTION_CODE_FATAL);
-            state.lock();
-            return;
+            return UNKNOWN_ERROR;
         }
         state->set(ALLOCATED);
         state->comp = comp;
+        return OK;
+    };
+    if (tryAndReportOnError(setAllocated) != OK) {
+        return;
     }
-    mChannel->setComponent(comp);
     mCallback->onComponentAllocated(comp->intf()->getName().c_str());
 }
 
 void CCodec::initiateConfigureComponent(const sp<AMessage> &format) {
-    {
+    auto checkAllocated = [this] {
         Mutexed<State>::Locked state(mState);
-        if (state->get() != ALLOCATED) {
-            mCallback->onError(UNKNOWN_ERROR, ACTION_CODE_FATAL);
-            return;
-        }
+        return (state->get() != ALLOCATED) ? UNKNOWN_ERROR : OK;
+    };
+    if (tryAndReportOnError(checkAllocated) != OK) {
+        return;
     }
 
     sp<AMessage> msg(new AMessage(kWhatConfigure, this));
@@ -314,21 +326,22 @@
 
 void CCodec::configure(const sp<AMessage> &msg) {
     std::shared_ptr<C2ComponentInterface> intf;
-    {
+    auto checkAllocated = [this, &intf] {
         Mutexed<State>::Locked state(mState);
         if (state->get() != ALLOCATED) {
             state->set(RELEASED);
-            state.unlock();
-            mCallback->onError(UNKNOWN_ERROR, ACTION_CODE_FATAL);
-            state.lock();
-            return;
+            return UNKNOWN_ERROR;
         }
         intf = state->comp->intf();
+        return OK;
+    };
+    if (tryAndReportOnError(checkAllocated) != OK) {
+        return;
     }
 
     sp<AMessage> inputFormat(new AMessage);
     sp<AMessage> outputFormat(new AMessage);
-    if (status_t err = [=] {
+    auto doConfig = [=] {
         AString mime;
         if (!msg->findString("mime", &mime)) {
             return BAD_VALUE;
@@ -339,6 +352,11 @@
             encoder = false;
         }
 
+        // TODO: read from intf()
+        if ((!encoder) != (intf->getName().find("encoder") == std::string::npos)) {
+            return UNKNOWN_ERROR;
+        }
+
         sp<RefBase> obj;
         if (msg->findObject("native-window", &obj)) {
             sp<Surface> surface = static_cast<Surface *>(obj.get());
@@ -386,14 +404,22 @@
             if (audio) {
                 outputFormat->setInt32("channel-count", 2);
                 outputFormat->setInt32("sample-rate", 44100);
+            } else {
+                int32_t tmp;
+                if (msg->findInt32("width", &tmp)) {
+                    outputFormat->setInt32("width", tmp);
+                }
+                if (msg->findInt32("height", &tmp)) {
+                    outputFormat->setInt32("height", tmp);
+                }
             }
         }
 
         // TODO
 
         return OK;
-    }() != OK) {
-        mCallback->onError(err, ACTION_CODE_FATAL);
+    };
+    if (tryAndReportOnError(doConfig) != OK) {
         return;
     }
 
@@ -406,6 +432,22 @@
 }
 
 void CCodec::initiateCreateInputSurface() {
+    status_t err = [this] {
+        Mutexed<State>::Locked state(mState);
+        if (state->get() != ALLOCATED) {
+            return UNKNOWN_ERROR;
+        }
+        // TODO: read it from intf() properly.
+        if (state->comp->intf()->getName().find("encoder") == std::string::npos) {
+            return INVALID_OPERATION;
+        }
+        return OK;
+    }();
+    if (err != OK) {
+        mCallback->onInputSurfaceCreationFailed(err);
+        return;
+    }
+
     (new AMessage(kWhatCreateInputSurface, this))->post();
 }
 
@@ -477,15 +519,16 @@
 }
 
 void CCodec::initiateStart() {
-    {
+    auto setStarting = [this] {
         Mutexed<State>::Locked state(mState);
         if (state->get() != ALLOCATED) {
-            state.unlock();
-            mCallback->onError(UNKNOWN_ERROR, ACTION_CODE_FATAL);
-            state.lock();
-            return;
+            return UNKNOWN_ERROR;
         }
         state->set(STARTING);
+        return OK;
+    };
+    if (tryAndReportOnError(setStarting) != OK) {
+        return;
     }
 
     (new AMessage(kWhatStart, this))->post();
@@ -493,16 +536,18 @@
 
 void CCodec::start() {
     std::shared_ptr<C2Component> comp;
-    {
+    auto checkStarting = [this, &comp] {
         Mutexed<State>::Locked state(mState);
         if (state->get() != STARTING) {
-            state.unlock();
-            mCallback->onError(UNKNOWN_ERROR, ACTION_CODE_FATAL);
-            state.lock();
-            return;
+            return UNKNOWN_ERROR;
         }
         comp = state->comp;
+        return OK;
+    };
+    if (tryAndReportOnError(checkStarting) != OK) {
+        return;
     }
+
     c2_status_t err = comp->start();
     if (err != C2_OK) {
         // TODO: convert err into status_t
@@ -516,17 +561,22 @@
         inputFormat = formats->inputFormat;
         outputFormat = formats->outputFormat;
     }
-    mChannel->start(inputFormat, outputFormat);
+    status_t err2 = mChannel->start(inputFormat, outputFormat);
+    if (err2 != OK) {
+        mCallback->onError(err2, ACTION_CODE_FATAL);
+        return;
+    }
 
-    {
+    auto setRunning = [this] {
         Mutexed<State>::Locked state(mState);
         if (state->get() != STARTING) {
-            state.unlock();
-            mCallback->onError(UNKNOWN_ERROR, ACTION_CODE_FATAL);
-            state.lock();
-            return;
+            return UNKNOWN_ERROR;
         }
         state->set(RUNNING);
+        return OK;
+    };
+    if (tryAndReportOnError(setRunning) != OK) {
+        return;
     }
     mCallback->onStartCompleted();
 }
@@ -652,13 +702,26 @@
 }
 
 void CCodec::signalFlush() {
-    {
+    status_t err = [this] {
         Mutexed<State>::Locked state(mState);
+        if (state->get() == FLUSHED) {
+            return ALREADY_EXISTS;
+        }
         if (state->get() != RUNNING) {
-            mCallback->onError(UNKNOWN_ERROR, ACTION_CODE_FATAL);
-            return;
+            return UNKNOWN_ERROR;
         }
         state->set(FLUSHING);
+        return OK;
+    }();
+    switch (err) {
+        case ALREADY_EXISTS:
+            mCallback->onFlushCompleted();
+            return;
+        case OK:
+            break;
+        default:
+            mCallback->onError(err, ACTION_CODE_FATAL);
+            return;
     }
 
     (new AMessage(kWhatFlush, this))->post();
@@ -666,15 +729,16 @@
 
 void CCodec::flush() {
     std::shared_ptr<C2Component> comp;
-    {
+    auto checkFlushing = [this, &comp] {
         Mutexed<State>::Locked state(mState);
         if (state->get() != FLUSHING) {
-            state.unlock();
-            mCallback->onError(UNKNOWN_ERROR, ACTION_CODE_FATAL);
-            state.lock();
-            return;
+            return UNKNOWN_ERROR;
         }
         comp = state->comp;
+        return OK;
+    };
+    if (tryAndReportOnError(checkFlushing) != OK) {
+        return;
     }
 
     mChannel->stop();
@@ -696,18 +760,19 @@
 }
 
 void CCodec::signalResume() {
-    {
+    auto setResuming = [this] {
         Mutexed<State>::Locked state(mState);
         if (state->get() != FLUSHED) {
-            state.unlock();
-            mCallback->onError(UNKNOWN_ERROR, ACTION_CODE_FATAL);
-            state.lock();
-            return;
+            return UNKNOWN_ERROR;
         }
         state->set(RESUMING);
+        return OK;
+    };
+    if (tryAndReportOnError(setResuming) != OK) {
+        return;
     }
 
-    mChannel->start(nullptr, nullptr);
+    (void)mChannel->start(nullptr, nullptr);
 
     {
         Mutexed<State>::Locked state(mState);
@@ -727,6 +792,8 @@
 }
 
 void CCodec::signalEndOfInputStream() {
+    // TODO
+    mCallback->onSignaledInputEOS(INVALID_OPERATION);
 }
 
 void CCodec::signalRequestIDRFrame() {
@@ -735,9 +802,7 @@
 
 void CCodec::onWorkDone(std::list<std::unique_ptr<C2Work>> &workItems) {
     Mutexed<std::list<std::unique_ptr<C2Work>>>::Locked queue(mWorkDoneQueue);
-    for (std::unique_ptr<C2Work> &item : workItems) {
-        queue->push_back(std::move(item));
-    }
+    queue->splice(queue->end(), workItems);
     (new AMessage(kWhatWorkDone, this))->post();
 }
 
@@ -834,8 +899,8 @@
     }
 
     ALOGW("previous call to %s exceeded timeout", name.c_str());
-    mCallback->onError(UNKNOWN_ERROR, ACTION_CODE_FATAL);
     initiateRelease(false);
+    mCallback->onError(UNKNOWN_ERROR, ACTION_CODE_FATAL);
 }
 
 }  // namespace android
diff --git a/media/libstagefright/CCodecBufferChannel.cpp b/media/libstagefright/CCodecBufferChannel.cpp
index ac786cc..84962a9 100644
--- a/media/libstagefright/CCodecBufferChannel.cpp
+++ b/media/libstagefright/CCodecBufferChannel.cpp
@@ -60,7 +60,10 @@
     /**
      * Set format for MediaCodec-facing buffers.
      */
-    void setFormat(const sp<AMessage> &format) { mFormat = format; }
+    void setFormat(const sp<AMessage> &format) {
+        CHECK(format != nullptr);
+        mFormat = format;
+    }
 
     /**
      * Returns true if the buffers are operating under array mode.
@@ -179,21 +182,158 @@
 const static size_t kMinBufferArraySize = 16;
 const static size_t kLinearBufferSize = 524288;
 
-sp<LinearBlockBuffer> allocateLinearBuffer(
+/**
+ * Simple local buffer pool backed by std::vector.
+ */
+class LocalBufferPool : public std::enable_shared_from_this<LocalBufferPool> {
+public:
+    /**
+     * Create a new LocalBufferPool object.
+     *
+     * \param poolCapacity  max total size of buffers managed by this pool.
+     *
+     * \return  a newly created pool object.
+     */
+    static std::shared_ptr<LocalBufferPool> Create(size_t poolCapacity) {
+        return std::shared_ptr<LocalBufferPool>(new LocalBufferPool(poolCapacity));
+    }
+
+    /**
+     * Return an ABuffer object whose size is at least |capacity|.
+     *
+     * \param   capacity  requested capacity
+     * \return  nullptr if the pool capacity is reached
+     *          an ABuffer object otherwise.
+     */
+    sp<ABuffer> newBuffer(size_t capacity) {
+        Mutex::Autolock lock(mMutex);
+        auto it = std::find_if(
+                mPool.begin(), mPool.end(),
+                [capacity](const std::vector<uint8_t> &vec) {
+                    return vec.capacity() >= capacity;
+                });
+        if (it != mPool.end()) {
+            sp<ABuffer> buffer = new VectorBuffer(std::move(*it), shared_from_this());
+            mPool.erase(it);
+            return buffer;
+        }
+        if (mUsedSize + capacity > mPoolCapacity) {
+            while (!mPool.empty()) {
+                mUsedSize -= mPool.back().capacity();
+                mPool.pop_back();
+            }
+            if (mUsedSize + capacity > mPoolCapacity) {
+                ALOGD("mUsedSize = %zu, capacity = %zu, mPoolCapacity = %zu",
+                        mUsedSize, capacity, mPoolCapacity);
+                return nullptr;
+            }
+        }
+        std::vector<uint8_t> vec(capacity);
+        mUsedSize += vec.capacity();
+        return new VectorBuffer(std::move(vec), shared_from_this());
+    }
+
+private:
+    /**
+     * ABuffer backed by std::vector.
+     */
+    class VectorBuffer : public ::android::ABuffer {
+    public:
+        /**
+         * Construct a VectorBuffer by taking the ownership of supplied vector.
+         *
+         * \param vec   backing vector of the buffer. this object takes
+         *              ownership at construction.
+         * \param pool  a LocalBufferPool object to return the vector at
+         *              destruction.
+         */
+        VectorBuffer(std::vector<uint8_t> &&vec, const std::shared_ptr<LocalBufferPool> &pool)
+            : ABuffer(vec.data(), vec.capacity()),
+              mVec(std::move(vec)),
+              mPool(pool) {
+        }
+
+        ~VectorBuffer() override {
+            std::shared_ptr<LocalBufferPool> pool = mPool.lock();
+            if (pool) {
+                // If pool is alive, return the vector back to the pool so that
+                // it can be recycled.
+                pool->returnVector(std::move(mVec));
+            }
+        }
+
+    private:
+        std::vector<uint8_t> mVec;
+        std::weak_ptr<LocalBufferPool> mPool;
+    };
+
+    Mutex mMutex;
+    size_t mPoolCapacity;
+    size_t mUsedSize;
+    std::list<std::vector<uint8_t>> mPool;
+
+    /**
+     * Private constructor to prevent constructing non-managed LocalBufferPool.
+     */
+    explicit LocalBufferPool(size_t poolCapacity)
+        : mPoolCapacity(poolCapacity), mUsedSize(0) {
+    }
+
+    /**
+     * Take back the ownership of vec from the destructed VectorBuffer and put
+     * it in front of the pool.
+     */
+    void returnVector(std::vector<uint8_t> &&vec) {
+        Mutex::Autolock lock(mMutex);
+        mPool.push_front(std::move(vec));
+    }
+
+    DISALLOW_EVIL_CONSTRUCTORS(LocalBufferPool);
+};
+
+sp<LinearBlockBuffer> AllocateLinearBuffer(
         const std::shared_ptr<C2BlockPool> &pool,
         const sp<AMessage> &format,
         size_t size,
         const C2MemoryUsage &usage) {
     std::shared_ptr<C2LinearBlock> block;
 
-    status_t err = pool->fetchLinearBlock(size, usage, &block);
-    if (err != OK) {
+    c2_status_t err = pool->fetchLinearBlock(size, usage, &block);
+    if (err != C2_OK) {
         return nullptr;
     }
 
     return LinearBlockBuffer::Allocate(format, block);
 }
 
+sp<GraphicBlockBuffer> AllocateGraphicBuffer(
+        const std::shared_ptr<C2BlockPool> &pool,
+        const sp<AMessage> &format,
+        uint32_t pixelFormat,
+        const C2MemoryUsage &usage,
+        const std::shared_ptr<LocalBufferPool> &localBufferPool) {
+    int32_t width, height;
+    if (!format->findInt32("width", &width) || !format->findInt32("height", &height)) {
+        ALOGD("format lacks width or height");
+        return nullptr;
+    }
+
+    std::shared_ptr<C2GraphicBlock> block;
+    c2_status_t err = pool->fetchGraphicBlock(
+            width, height, pixelFormat, usage, &block);
+    if (err != C2_OK) {
+        ALOGD("fetch graphic block failed: %d", err);
+        return nullptr;
+    }
+
+    return GraphicBlockBuffer::Allocate(
+            format,
+            block,
+            [localBufferPool](size_t capacity) {
+                return localBufferPool->newBuffer(capacity);
+            });
+}
+
 class BuffersArrayImpl;
 
 /**
@@ -383,6 +523,7 @@
 class InputBuffersArray : public CCodecBufferChannel::InputBuffers {
 public:
     InputBuffersArray() = default;
+    ~InputBuffersArray() override = default;
 
     void initialize(
             const FlexBuffersImpl &impl,
@@ -432,7 +573,8 @@
         // TODO: proper max input size
         // TODO: read usage from intf
         C2MemoryUsage usage = { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE };
-        sp<LinearBlockBuffer> newBuffer = allocateLinearBuffer(mPool, mFormat, kLinearBufferSize, usage);
+        sp<LinearBlockBuffer> newBuffer = AllocateLinearBuffer(
+                mPool, mFormat, kLinearBufferSize, usage);
         if (newBuffer == nullptr) {
             return false;
         }
@@ -458,7 +600,7 @@
                 kMinBufferArraySize,
                 [pool = mPool, format = mFormat] () -> sp<Codec2Buffer> {
                     C2MemoryUsage usage = { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE };
-                    return allocateLinearBuffer(pool, format, kLinearBufferSize, usage);
+                    return AllocateLinearBuffer(pool, format, kLinearBufferSize, usage);
                 });
         return std::move(array);
     }
@@ -469,7 +611,54 @@
 
 class GraphicInputBuffers : public CCodecBufferChannel::InputBuffers {
 public:
-    GraphicInputBuffers() = default;
+    GraphicInputBuffers() : mLocalBufferPool(LocalBufferPool::Create(1920 * 1080 * 16)) {}
+    ~GraphicInputBuffers() override = default;
+
+    bool requestNewBuffer(size_t *index, sp<MediaCodecBuffer> *buffer) override {
+        // TODO: proper max input size
+        // TODO: read usage from intf
+        C2MemoryUsage usage = { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE };
+        sp<GraphicBlockBuffer> newBuffer = AllocateGraphicBuffer(
+                mPool, mFormat, HAL_PIXEL_FORMAT_YV12, usage, mLocalBufferPool);
+        if (newBuffer == nullptr) {
+            return false;
+        }
+        *index = mImpl.assignSlot(newBuffer);
+        *buffer = newBuffer;
+        return true;
+    }
+
+    std::shared_ptr<C2Buffer> releaseBuffer(const sp<MediaCodecBuffer> &buffer) override {
+        return mImpl.releaseSlot(buffer);
+    }
+
+    void flush() override {
+        // This is no-op by default unless we're in array mode where we need to keep
+        // track of the flushed work.
+    }
+
+    std::unique_ptr<CCodecBufferChannel::InputBuffers> toArrayMode() final {
+        std::unique_ptr<InputBuffersArray> array(new InputBuffersArray);
+        array->setFormat(mFormat);
+        array->initialize(
+                mImpl,
+                kMinBufferArraySize,
+                [pool = mPool, format = mFormat, lbp = mLocalBufferPool]() -> sp<Codec2Buffer> {
+                    C2MemoryUsage usage = { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE };
+                    return AllocateGraphicBuffer(
+                            pool, format, HAL_PIXEL_FORMAT_YV12, usage, lbp);
+                });
+        return std::move(array);
+    }
+
+private:
+    FlexBuffersImpl mImpl;
+    std::shared_ptr<LocalBufferPool> mLocalBufferPool;
+};
+
+class DummyInputBuffers : public CCodecBufferChannel::InputBuffers {
+public:
+    DummyInputBuffers() = default;
 
     bool requestNewBuffer(size_t *, sp<MediaCodecBuffer> *) override {
         return false;
@@ -495,7 +684,8 @@
 
 class OutputBuffersArray : public CCodecBufferChannel::OutputBuffers {
 public:
-    using CCodecBufferChannel::OutputBuffers::OutputBuffers;
+    OutputBuffersArray() = default;
+    ~OutputBuffersArray() override = default;
 
     void initialize(
             const FlexBuffersImpl &impl,
@@ -522,12 +712,14 @@
                     return clientBuffer->canCopy(buffer);
                 });
         if (err != OK) {
-            return false;
-        }
-        if (!c2Buffer->copy(buffer)) {
+            ALOGD("grabBuffer failed: %d", err);
             return false;
         }
         c2Buffer->setFormat(mFormat);
+        if (!c2Buffer->copy(buffer)) {
+            ALOGD("copy buffer failed");
+            return false;
+        }
         *clientBuffer = c2Buffer;
         return true;
     }
@@ -585,6 +777,7 @@
             size_t *index,
             sp<MediaCodecBuffer> *clientBuffer) override {
         sp<Codec2Buffer> newBuffer = wrap(buffer);
+        newBuffer->setFormat(mFormat);
         *index = mImpl.assignSlot(newBuffer);
         *clientBuffer = newBuffer;
         return true;
@@ -627,8 +820,21 @@
         return std::move(array);
     }
 
+    /**
+     * Return an appropriate Codec2Buffer object for the type of buffers.
+     *
+     * \param buffer  C2Buffer object to wrap.
+     *
+     * \return  appropriate Codec2Buffer object to wrap |buffer|.
+     */
     virtual sp<Codec2Buffer> wrap(const std::shared_ptr<C2Buffer> &buffer) = 0;
 
+    /**
+     * Return an appropriate Codec2Buffer object for the type of buffers, to be
+     * used as an empty array buffer.
+     *
+     * \return  appropriate Codec2Buffer object which can copy() from C2Buffers.
+     */
     virtual sp<Codec2Buffer> allocateArrayBuffer() = 0;
 
 private:
@@ -673,6 +879,34 @@
     }
 };
 
+class RawGraphicOutputBuffers : public FlexOutputBuffers {
+public:
+    RawGraphicOutputBuffers()
+        : mLocalBufferPool(LocalBufferPool::Create(1920 * 1080 * 16)) {
+    }
+    ~RawGraphicOutputBuffers() override = default;
+
+    sp<Codec2Buffer> wrap(const std::shared_ptr<C2Buffer> &buffer) override {
+        return ConstGraphicBlockBuffer::Allocate(
+                mFormat,
+                buffer,
+                [lbp = mLocalBufferPool](size_t capacity) {
+                    return lbp->newBuffer(capacity);
+                });
+    }
+
+    sp<Codec2Buffer> allocateArrayBuffer() override {
+        return ConstGraphicBlockBuffer::AllocateEmpty(
+                mFormat,
+                [lbp = mLocalBufferPool](size_t capacity) {
+                    return lbp->newBuffer(capacity);
+                });
+    }
+
+private:
+    std::shared_ptr<LocalBufferPool> mLocalBufferPool;
+};
+
 }  // namespace
 
 CCodecBufferChannel::QueueGuard::QueueGuard(
@@ -735,56 +969,6 @@
 
 void CCodecBufferChannel::setComponent(const std::shared_ptr<C2Component> &component) {
     mComponent = component;
-
-    C2StreamFormatConfig::input inputFormat(0u);
-    C2StreamFormatConfig::output outputFormat(0u);
-    c2_status_t err = mComponent->intf()->query_vb(
-            { &inputFormat, &outputFormat },
-            {},
-            C2_DONT_BLOCK,
-            nullptr);
-    if (err != C2_OK) {
-        // TODO: error
-        return;
-    }
-
-    {
-        Mutexed<std::unique_ptr<InputBuffers>>::Locked buffers(mInputBuffers);
-
-        bool graphic = (inputFormat.value == C2FormatVideo);
-        if (graphic) {
-            buffers->reset(new GraphicInputBuffers);
-        } else {
-            buffers->reset(new LinearInputBuffers);
-        }
-
-        ALOGV("graphic = %s", graphic ? "true" : "false");
-        std::shared_ptr<C2BlockPool> pool;
-        err = GetCodec2BlockPool(
-                graphic ? C2BlockPool::BASIC_GRAPHIC : C2BlockPool::BASIC_LINEAR,
-                component,
-                &pool);
-        if (err == C2_OK) {
-            (*buffers)->setPool(pool);
-        } else {
-            // TODO: error
-        }
-        // TODO: remove once we switch to proper buffer pool.
-        if (!graphic) {
-            *buffers = (*buffers)->toArrayMode();
-        }
-    }
-
-    {
-        Mutexed<std::unique_ptr<OutputBuffers>>::Locked buffers(mOutputBuffers);
-
-        bool graphic = (outputFormat.value == C2FormatVideo);
-        if (graphic) {
-            buffers->reset(new GraphicOutputBuffers);
-        } else {
-            buffers->reset(new LinearOutputBuffers);
-        }
-    }
 }
 
 status_t CCodecBufferChannel::setInputSurface(
@@ -813,6 +997,7 @@
     if (buffer->meta()->findInt32("csd", &tmp) && tmp) {
         flags |= C2FrameData::FLAG_CODEC_CONFIG;
     }
+    ALOGV("queueInputBuffer: buffer->size() = %zu", buffer->size());
     std::unique_ptr<C2Work> work(new C2Work);
     work->input.flags = (C2FrameData::flags_t)flags;
     work->input.ordinal.timestamp = timeUs;
@@ -969,13 +1154,68 @@
     (*buffers)->getArray(array);
 }
 
-void CCodecBufferChannel::start(const sp<AMessage> &inputFormat, const sp<AMessage> &outputFormat) {
+status_t CCodecBufferChannel::start(
+        const sp<AMessage> &inputFormat, const sp<AMessage> &outputFormat) {
+    C2StreamFormatConfig::input iStreamFormat(0u);
+    C2StreamFormatConfig::output oStreamFormat(0u);
+    c2_status_t err = mComponent->intf()->query_vb(
+            { &iStreamFormat, &oStreamFormat },
+            {},
+            C2_DONT_BLOCK,
+            nullptr);
+    if (err != C2_OK) {
+        return UNKNOWN_ERROR;
+    }
+
     if (inputFormat != nullptr) {
         Mutexed<std::unique_ptr<InputBuffers>>::Locked buffers(mInputBuffers);
+
+        bool graphic = (iStreamFormat.value == C2FormatVideo);
+        if (graphic) {
+            if (mInputSurface) {
+                buffers->reset(new DummyInputBuffers);
+            } else {
+                buffers->reset(new GraphicInputBuffers);
+            }
+        } else {
+            buffers->reset(new LinearInputBuffers);
+        }
         (*buffers)->setFormat(inputFormat);
+
+        ALOGV("graphic = %s", graphic ? "true" : "false");
+        std::shared_ptr<C2BlockPool> pool;
+        if (graphic) {
+            err = GetCodec2BlockPool(C2BlockPool::BASIC_GRAPHIC, mComponent, &pool);
+        } else {
+            err = CreateCodec2BlockPool(C2PlatformAllocatorStore::ION,
+                                        mComponent, &pool);
+        }
+        if (err == C2_OK) {
+            (*buffers)->setPool(pool);
+        } else {
+            // TODO: error
+        }
     }
+
     if (outputFormat != nullptr) {
+        bool hasOutputSurface = false;
+        {
+            Mutexed<sp<Surface>>::Locked surface(mSurface);
+            hasOutputSurface = (*surface != nullptr);
+        }
+
         Mutexed<std::unique_ptr<OutputBuffers>>::Locked buffers(mOutputBuffers);
+
+        bool graphic = (oStreamFormat.value == C2FormatVideo);
+        if (graphic) {
+            if (hasOutputSurface) {
+                buffers->reset(new GraphicOutputBuffers);
+            } else {
+                buffers->reset(new RawGraphicOutputBuffers);
+            }
+        } else {
+            buffers->reset(new LinearOutputBuffers);
+        }
         (*buffers)->setFormat(outputFormat);
     }
 
@@ -990,9 +1230,7 @@
                 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();
+                        return NO_MEMORY;
                     } else {
                         ALOGV("start: cannot allocate memory, only %zu buffers allocated", i);
                     }
@@ -1004,6 +1242,7 @@
     } else {
         (void)mInputSurface->connect(mComponent);
     }
+    return OK;
 }
 
 void CCodecBufferChannel::stop() {
@@ -1011,6 +1250,7 @@
     mFirstValidFrameIndex = mFrameIndex.load();
     if (mInputSurface != nullptr) {
         mInputSurface->disconnect();
+        mInputSurface.reset();
     }
 }
 
@@ -1026,8 +1266,13 @@
 }
 
 void CCodecBufferChannel::onWorkDone(const std::unique_ptr<C2Work> &work) {
-    if (work->result != OK) {
-        ALOGE("work failed to complete: %d", work->result);
+    if (work->result != C2_OK) {
+        if (work->result == C2_NOT_FOUND) {
+            // TODO: Define what flushed work's result is.
+            ALOGD("flushed work; ignored.");
+            return;
+        }
+        ALOGD("work failed to complete: %d", work->result);
         mOnError(work->result, ACTION_CODE_FATAL);
         return;
     }
diff --git a/media/libstagefright/Codec2InfoBuilder.cpp b/media/libstagefright/Codec2InfoBuilder.cpp
index 7ce2ff1..78c4e38 100644
--- a/media/libstagefright/Codec2InfoBuilder.cpp
+++ b/media/libstagefright/Codec2InfoBuilder.cpp
@@ -31,6 +31,63 @@
 
 using ConstTraitsPtr = std::shared_ptr<const C2Component::Traits>;
 
+struct ProfileLevel {
+    uint32_t profile;
+    uint32_t level;
+};
+static const ProfileLevel kAvcProfileLevels[] = {
+    { 0x01, 0x0001 },  // { OMX_VIDEO_AVCProfileBaseline, OMX_VIDEO_AVCLevel1  },
+    { 0x01, 0x0002 },  // { OMX_VIDEO_AVCProfileBaseline, OMX_VIDEO_AVCLevel1b },
+    { 0x01, 0x0004 },  // { OMX_VIDEO_AVCProfileBaseline, OMX_VIDEO_AVCLevel11 },
+    { 0x01, 0x0008 },  // { OMX_VIDEO_AVCProfileBaseline, OMX_VIDEO_AVCLevel12 },
+    { 0x01, 0x0010 },  // { OMX_VIDEO_AVCProfileBaseline, OMX_VIDEO_AVCLevel13 },
+    { 0x01, 0x0020 },  // { OMX_VIDEO_AVCProfileBaseline, OMX_VIDEO_AVCLevel2  },
+    { 0x01, 0x0040 },  // { OMX_VIDEO_AVCProfileBaseline, OMX_VIDEO_AVCLevel21 },
+    { 0x01, 0x0080 },  // { OMX_VIDEO_AVCProfileBaseline, OMX_VIDEO_AVCLevel22 },
+    { 0x01, 0x0100 },  // { OMX_VIDEO_AVCProfileBaseline, OMX_VIDEO_AVCLevel3  },
+    { 0x01, 0x0200 },  // { OMX_VIDEO_AVCProfileBaseline, OMX_VIDEO_AVCLevel31 },
+    { 0x01, 0x0400 },  // { OMX_VIDEO_AVCProfileBaseline, OMX_VIDEO_AVCLevel32 },
+    { 0x01, 0x0800 },  // { OMX_VIDEO_AVCProfileBaseline, OMX_VIDEO_AVCLevel4  },
+    { 0x01, 0x1000 },  // { OMX_VIDEO_AVCProfileBaseline, OMX_VIDEO_AVCLevel41 },
+    { 0x01, 0x2000 },  // { OMX_VIDEO_AVCProfileBaseline, OMX_VIDEO_AVCLevel42 },
+    { 0x01, 0x4000 },  // { OMX_VIDEO_AVCProfileBaseline, OMX_VIDEO_AVCLevel5  },
+    { 0x01, 0x8000 },  // { OMX_VIDEO_AVCProfileBaseline, OMX_VIDEO_AVCLevel51 },
+
+    { 0x02, 0x0001 },  // { OMX_VIDEO_AVCProfileMain,     OMX_VIDEO_AVCLevel1  },
+    { 0x02, 0x0002 },  // { OMX_VIDEO_AVCProfileMain,     OMX_VIDEO_AVCLevel1b },
+    { 0x02, 0x0004 },  // { OMX_VIDEO_AVCProfileMain,     OMX_VIDEO_AVCLevel11 },
+    { 0x02, 0x0008 },  // { OMX_VIDEO_AVCProfileMain,     OMX_VIDEO_AVCLevel12 },
+    { 0x02, 0x0010 },  // { OMX_VIDEO_AVCProfileMain,     OMX_VIDEO_AVCLevel13 },
+    { 0x02, 0x0020 },  // { OMX_VIDEO_AVCProfileMain,     OMX_VIDEO_AVCLevel2  },
+    { 0x02, 0x0040 },  // { OMX_VIDEO_AVCProfileMain,     OMX_VIDEO_AVCLevel21 },
+    { 0x02, 0x0080 },  // { OMX_VIDEO_AVCProfileMain,     OMX_VIDEO_AVCLevel22 },
+    { 0x02, 0x0100 },  // { OMX_VIDEO_AVCProfileMain,     OMX_VIDEO_AVCLevel3  },
+    { 0x02, 0x0200 },  // { OMX_VIDEO_AVCProfileMain,     OMX_VIDEO_AVCLevel31 },
+    { 0x02, 0x0400 },  // { OMX_VIDEO_AVCProfileMain,     OMX_VIDEO_AVCLevel32 },
+    { 0x02, 0x0800 },  // { OMX_VIDEO_AVCProfileMain,     OMX_VIDEO_AVCLevel4  },
+    { 0x02, 0x1000 },  // { OMX_VIDEO_AVCProfileMain,     OMX_VIDEO_AVCLevel41 },
+    { 0x02, 0x2000 },  // { OMX_VIDEO_AVCProfileMain,     OMX_VIDEO_AVCLevel42 },
+    { 0x02, 0x4000 },  // { OMX_VIDEO_AVCProfileMain,     OMX_VIDEO_AVCLevel5  },
+    { 0x02, 0x8000 },  // { OMX_VIDEO_AVCProfileMain,     OMX_VIDEO_AVCLevel51 },
+
+    { 0x04, 0x0001 },  // { OMX_VIDEO_AVCProfileHigh,     OMX_VIDEO_AVCLevel1  },
+    { 0x04, 0x0002 },  // { OMX_VIDEO_AVCProfileHigh,     OMX_VIDEO_AVCLevel1b },
+    { 0x04, 0x0004 },  // { OMX_VIDEO_AVCProfileHigh,     OMX_VIDEO_AVCLevel11 },
+    { 0x04, 0x0008 },  // { OMX_VIDEO_AVCProfileHigh,     OMX_VIDEO_AVCLevel12 },
+    { 0x04, 0x0010 },  // { OMX_VIDEO_AVCProfileHigh,     OMX_VIDEO_AVCLevel13 },
+    { 0x04, 0x0020 },  // { OMX_VIDEO_AVCProfileHigh,     OMX_VIDEO_AVCLevel2  },
+    { 0x04, 0x0040 },  // { OMX_VIDEO_AVCProfileHigh,     OMX_VIDEO_AVCLevel21 },
+    { 0x04, 0x0080 },  // { OMX_VIDEO_AVCProfileHigh,     OMX_VIDEO_AVCLevel22 },
+    { 0x04, 0x0100 },  // { OMX_VIDEO_AVCProfileHigh,     OMX_VIDEO_AVCLevel3  },
+    { 0x04, 0x0200 },  // { OMX_VIDEO_AVCProfileHigh,     OMX_VIDEO_AVCLevel31 },
+    { 0x04, 0x0400 },  // { OMX_VIDEO_AVCProfileHigh,     OMX_VIDEO_AVCLevel32 },
+    { 0x04, 0x0800 },  // { OMX_VIDEO_AVCProfileHigh,     OMX_VIDEO_AVCLevel4  },
+    { 0x04, 0x1000 },  // { OMX_VIDEO_AVCProfileHigh,     OMX_VIDEO_AVCLevel41 },
+    { 0x04, 0x2000 },  // { OMX_VIDEO_AVCProfileHigh,     OMX_VIDEO_AVCLevel42 },
+    { 0x04, 0x4000 },  // { OMX_VIDEO_AVCProfileHigh,     OMX_VIDEO_AVCLevel5  },
+    { 0x04, 0x8000 },  // { OMX_VIDEO_AVCProfileHigh,     OMX_VIDEO_AVCLevel51 },
+};
+
 status_t Codec2InfoBuilder::buildMediaCodecList(MediaCodecListWriter* writer) {
     // Obtain C2ComponentStore
     std::shared_ptr<C2ComponentStore> store = GetCodec2PlatformComponentStore();
@@ -86,6 +143,12 @@
                     caps->addDetail(key.c_str(), value.c_str());
                 }
             }
+            // TODO: get this from intf(), and apply to other codecs as well.
+            if (mediaType.find("video/avc") != std::string::npos && !encoder) {
+                for (const auto& pl : kAvcProfileLevels) {
+                    caps->addProfileLevel(pl.profile, pl.level);
+                }
+            }
             // TODO: get this from intf().
             if (mediaType.find("video") != std::string::npos && !encoder) {
                 caps->addColorFormat(0x7F420888);  // COLOR_FormatYUV420Flexible
diff --git a/media/libstagefright/MediaCodec.cpp b/media/libstagefright/MediaCodec.cpp
index 7d5c63a..e59259d 100644
--- a/media/libstagefright/MediaCodec.cpp
+++ b/media/libstagefright/MediaCodec.cpp
@@ -278,7 +278,8 @@
     virtual void onReleaseCompleted() override;
     virtual void onFlushCompleted() override;
     virtual void onError(status_t err, enum ActionCode actionCode) override;
-    virtual void onComponentAllocated(const char *componentName) override;
+    virtual void onComponentAllocated(
+            const char *componentName, const sp<MediaCodecInfo> &codecInfo) override;
     virtual void onComponentConfigured(
             const sp<AMessage> &inputFormat, const sp<AMessage> &outputFormat) override;
     virtual void onInputSurfaceCreated(
@@ -338,10 +339,14 @@
     notify->post();
 }
 
-void CodecCallback::onComponentAllocated(const char *componentName) {
+void CodecCallback::onComponentAllocated(
+        const char *componentName, const sp<MediaCodecInfo> &codecInfo) {
     sp<AMessage> notify(mNotify->dup());
     notify->setInt32("what", kWhatComponentAllocated);
     notify->setString("componentName", componentName);
+    if (codecInfo != nullptr) {
+        notify->setObject("codecInfo", codecInfo);
+    }
     notify->post();
 }
 
@@ -422,14 +427,12 @@
         const sp<ALooper> &looper, const AString &mime, bool encoder, status_t *err, pid_t pid,
         uid_t uid) {
     Vector<AString> matchingCodecs;
-    Vector<AString> owners;
 
     MediaCodecList::findMatchingCodecs(
             mime.c_str(),
             encoder,
             0,
-            &matchingCodecs,
-            &owners);
+            &matchingCodecs);
 
     if (err != NULL) {
         *err = NAME_NOT_FOUND;
@@ -1205,6 +1208,22 @@
     return OK;
 }
 
+status_t MediaCodec::getCodecInfo(sp<MediaCodecInfo> *codecInfo) const {
+    sp<AMessage> msg = new AMessage(kWhatGetCodecInfo, this);
+
+    sp<AMessage> response;
+    status_t err;
+    if ((err = PostAndAwaitResponse(msg, &response)) != OK) {
+        return err;
+    }
+
+    sp<RefBase> obj;
+    CHECK(response->findObject("codecInfo", &obj));
+    *codecInfo = static_cast<MediaCodecInfo *>(obj.get());
+
+    return OK;
+}
+
 status_t MediaCodec::getMetrics(MediaAnalyticsItem * &reply) {
 
     reply = NULL;
@@ -1588,6 +1607,11 @@
 
                     CHECK(msg->findString("componentName", &mComponentName));
 
+                    sp<RefBase> obj;
+                    if (msg->findObject("codecInfo", &obj)) {
+                        mCodecInfo = static_cast<MediaCodecInfo *>(obj.get());
+                    }
+
                     if (mComponentName.c_str()) {
                         mAnalyticsItem->setCString(kCodecCodec, mComponentName.c_str());
                     }
@@ -2595,6 +2619,17 @@
             break;
         }
 
+        case kWhatGetCodecInfo:
+        {
+            sp<AReplyToken> replyID;
+            CHECK(msg->senderAwaitsResponse(&replyID));
+
+            sp<AMessage> response = new AMessage;
+            response->setObject("codecInfo", mCodecInfo);
+            response->postReply(replyID);
+            break;
+        }
+
         case kWhatSetParameters:
         {
             sp<AReplyToken> replyID;
diff --git a/media/libstagefright/MediaCodecList.cpp b/media/libstagefright/MediaCodecList.cpp
index f595646..9244886 100644
--- a/media/libstagefright/MediaCodecList.cpp
+++ b/media/libstagefright/MediaCodecList.cpp
@@ -307,11 +307,8 @@
 //static
 void MediaCodecList::findMatchingCodecs(
         const char *mime, bool encoder, uint32_t flags,
-        Vector<AString> *matches, Vector<AString> *owners) {
+        Vector<AString> *matches) {
     matches->clear();
-    if (owners != nullptr) {
-        owners->clear();
-    }
 
     const sp<IMediaCodecList> list = getInstance();
     if (list == nullptr) {
@@ -337,9 +334,6 @@
             ALOGV("skipping SW codec '%s'", componentName.c_str());
         } else {
             matches->push(componentName);
-            if (owners != nullptr) {
-                owners->push(AString(info->getOwnerName()));
-            }
             ALOGV("matching '%s'", componentName.c_str());
         }
     }
diff --git a/media/libstagefright/MetaDataUtils.cpp b/media/libstagefright/MetaDataUtils.cpp
index af8f539..04f6ade 100644
--- a/media/libstagefright/MetaDataUtils.cpp
+++ b/media/libstagefright/MetaDataUtils.cpp
@@ -24,11 +24,12 @@
 
 namespace android {
 
-bool MakeAVCCodecSpecificData(MetaDataBase &meta, const sp<ABuffer> &accessUnit) {
+bool MakeAVCCodecSpecificData(MetaDataBase &meta, const uint8_t *data, size_t size) {
     int32_t width;
     int32_t height;
     int32_t sarWidth;
     int32_t sarHeight;
+    sp<ABuffer> accessUnit = new ABuffer((void*)data,  size);
     sp<ABuffer> csd = MakeAVCCodecSpecificData(accessUnit, &width, &height, &sarWidth, &sarHeight);
     if (csd == nullptr) {
         return false;
diff --git a/media/libstagefright/NuMediaExtractor.cpp b/media/libstagefright/NuMediaExtractor.cpp
index 540cf8c..83ca112 100644
--- a/media/libstagefright/NuMediaExtractor.cpp
+++ b/media/libstagefright/NuMediaExtractor.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#define LOG_NDEBUG 0
+//#define LOG_NDEBUG 0
 #define LOG_TAG "NuMediaExtractor"
 #include <utils/Log.h>
 
@@ -446,12 +446,17 @@
 ssize_t NuMediaExtractor::fetchAllTrackSamples(
         int64_t seekTimeUs, MediaSource::ReadOptions::SeekMode mode) {
     TrackInfo *minInfo = NULL;
-    ssize_t minIndex = -1;
+    ssize_t minIndex = ERROR_END_OF_STREAM;
 
     for (size_t i = 0; i < mSelectedTracks.size(); ++i) {
         TrackInfo *info = &mSelectedTracks.editItemAt(i);
         fetchTrackSamples(info, seekTimeUs, mode);
 
+        status_t err = info->mFinalResult;
+        if (err != OK && err != ERROR_END_OF_STREAM) {
+            return err;
+        }
+
         if (info->mSamples.empty()) {
             continue;
         }
@@ -721,7 +726,8 @@
     ssize_t minIndex = fetchAllTrackSamples();
 
     if (minIndex < 0) {
-        return ERROR_END_OF_STREAM;
+        status_t err = minIndex;
+        return err;
     }
 
     TrackInfo *info = &mSelectedTracks.editItemAt(minIndex);
diff --git a/media/libstagefright/VideoFrameScheduler.cpp b/media/libstagefright/VideoFrameScheduler.cpp
index 03226c7..6819bba 100644
--- a/media/libstagefright/VideoFrameScheduler.cpp
+++ b/media/libstagefright/VideoFrameScheduler.cpp
@@ -129,6 +129,11 @@
         numSamplesToUse = mNumSamples;
     }
 
+    if ((period >> kPrecision) == 0 ) {
+        ALOGW("Period is 0, or after including precision is 0 - would cause div0, returning");
+        return false;
+    }
+
     int64_t sumX = 0;
     int64_t sumXX = 0;
     int64_t sumXY = 0;
diff --git a/media/libstagefright/codec2/SimpleC2Component.cpp b/media/libstagefright/codec2/SimpleC2Component.cpp
index 333cfeb..f3d95f6 100644
--- a/media/libstagefright/codec2/SimpleC2Component.cpp
+++ b/media/libstagefright/codec2/SimpleC2Component.cpp
@@ -51,6 +51,8 @@
     mQueue.push_back({ nullptr, drainMode });
 }
 
+////////////////////////////////////////////////////////////////////////////////
+
 SimpleC2Component::SimpleC2Component(
         const std::shared_ptr<C2ComponentInterface> &intf)
     : mIntf(intf) {
@@ -91,13 +93,13 @@
 }
 
 c2_status_t SimpleC2Component::announce_nb(const std::vector<C2WorkOutline> &items) {
-    (void) items;
+    (void)items;
     return C2_OMITTED;
 }
 
 c2_status_t SimpleC2Component::flush_sm(
-        flush_mode_t flushThrough, std::list<std::unique_ptr<C2Work>>* const flushedWork) {
-    (void) flushThrough;
+        flush_mode_t flushMode, std::list<std::unique_ptr<C2Work>>* const flushedWork) {
+    (void)flushMode;
     {
         Mutexed<ExecState>::Locked state(mExecState);
         if (state->mState != RUNNING) {
@@ -107,6 +109,7 @@
     {
         Mutexed<WorkQueue>::Locked queue(mWorkQueue);
         queue->incGeneration();
+        // TODO: queue->splicedBy(flushedWork, flushedWork->end());
         while (!queue->empty()) {
             std::unique_ptr<C2Work> work = queue->pop_front();
             if (work) {
@@ -122,7 +125,7 @@
         }
     }
 
-    return onFlush_sm();
+    return C2_OK;
 }
 
 c2_status_t SimpleC2Component::drain_nb(drain_mode_t drainMode) {
@@ -160,6 +163,10 @@
     }
     if (!state->mThread.joinable()) {
         mExitRequested = false;
+        {
+            Mutexed<ExitMonitor>::Locked monitor(mExitMonitor);
+            monitor->mExited = false;
+        }
         state->mThread = std::thread(
                 [](std::weak_ptr<SimpleC2Component> wp) {
                     while (true) {
@@ -168,6 +175,8 @@
                             return;
                         }
                         if (thiz->exitRequested()) {
+                            ALOGV("stop processing");
+                            thiz->signalExit();
                             return;
                         }
                         thiz->processQueue();
@@ -179,7 +188,42 @@
     return C2_OK;
 }
 
+void SimpleC2Component::signalExit() {
+    Mutexed<ExitMonitor>::Locked monitor(mExitMonitor);
+    monitor->mExited = true;
+    monitor->mCondition.broadcast();
+}
+
+void SimpleC2Component::requestExitAndWait(std::function<void()> job) {
+    {
+        Mutexed<ExecState>::Locked state(mExecState);
+        if (!state->mThread.joinable()) {
+            return;
+        }
+    }
+    mExitRequested = true;
+    {
+        Mutexed<WorkQueue>::Locked queue(mWorkQueue);
+        queue->mCondition.broadcast();
+    }
+    // TODO: timeout?
+    {
+        Mutexed<ExitMonitor>::Locked monitor(mExitMonitor);
+        while (!monitor->mExited) {
+            monitor.waitForCondition(monitor->mCondition);
+        }
+        job();
+    }
+    Mutexed<ExecState>::Locked state(mExecState);
+    if (state->mThread.joinable()) {
+        ALOGV("joining the processing thread");
+        state->mThread.join();
+        ALOGV("joined the processing thread");
+    }
+}
+
 c2_status_t SimpleC2Component::stop() {
+    ALOGV("stop");
     {
         Mutexed<ExecState>::Locked state(mExecState);
         if (state->mState != RUNNING) {
@@ -195,7 +239,8 @@
         Mutexed<PendingWork>::Locked pending(mPendingWork);
         pending->clear();
     }
-    c2_status_t err = onStop();
+    c2_status_t err;
+    requestExitAndWait([this, &err]{ err = onStop(); });
     if (err != C2_OK) {
         return err;
     }
@@ -203,6 +248,7 @@
 }
 
 c2_status_t SimpleC2Component::reset() {
+    ALOGV("reset");
     {
         Mutexed<ExecState>::Locked state(mExecState);
         state->mState = UNINITIALIZED;
@@ -215,21 +261,13 @@
         Mutexed<PendingWork>::Locked pending(mPendingWork);
         pending->clear();
     }
-    onReset();
+    requestExitAndWait([this]{ onReset(); });
     return C2_OK;
 }
 
 c2_status_t SimpleC2Component::release() {
-    std::thread releasing;
-    {
-        Mutexed<ExecState>::Locked state(mExecState);
-        releasing = std::move(state->mThread);
-    }
-    mExitRequested = true;
-    if (releasing.joinable()) {
-        releasing.join();
-    }
-    onRelease();
+    ALOGV("release");
+    requestExitAndWait([this]{ onRelease(); });
     return C2_OK;
 }
 
@@ -271,10 +309,14 @@
     std::unique_ptr<C2Work> work;
     uint64_t generation;
     int32_t drainMode;
+    bool isFlushPending = false;
     {
         Mutexed<WorkQueue>::Locked queue(mWorkQueue);
         nsecs_t deadline = systemTime() + ms2ns(250);
         while (queue->empty()) {
+            if (exitRequested()) {
+                return;
+            }
             nsecs_t now = systemTime();
             if (now >= deadline) {
                 return;
@@ -287,8 +329,17 @@
 
         generation = queue->generation();
         drainMode = queue->drainMode();
+        isFlushPending = queue->popPendingFlush();
         work = queue->pop_front();
     }
+    if (isFlushPending) {
+        ALOGV("processing pending flush");
+        c2_status_t err = onFlush_sm();
+        if (err != C2_OK) {
+            ALOGD("flush err: %d", err);
+            // TODO: error
+        }
+    }
 
     if (!mOutputBlockPool) {
         c2_status_t err = [this] {
@@ -302,12 +353,15 @@
             if (err != C2_OK) {
                 return err;
             }
-            err = GetCodec2BlockPool(
-                    (outputFormat.value == C2FormatVideo)
-                    ? C2BlockPool::BASIC_GRAPHIC
-                    : C2BlockPool::BASIC_LINEAR,
-                    shared_from_this(),
-                    &mOutputBlockPool);
+            if (outputFormat.value == C2FormatVideo) {
+                err = GetCodec2BlockPool(
+                        C2BlockPool::BASIC_GRAPHIC,
+                        shared_from_this(), &mOutputBlockPool);
+            } else {
+                err = CreateCodec2BlockPool(
+                        C2PlatformAllocatorStore::ION,
+                        shared_from_this(), &mOutputBlockPool);
+            }
             if (err != C2_OK) {
                 return err;
             }
@@ -330,10 +384,12 @@
     }
 
     process(work, mOutputBlockPool);
+    ALOGV("processed frame #%" PRIu64, work->input.ordinal.frameIndex.peeku());
     {
         Mutexed<WorkQueue>::Locked queue(mWorkQueue);
         if (queue->generation() != generation) {
-            ALOGW("work form old generation: was %" PRIu64 " now %" PRIu64, queue->generation(), generation);
+            ALOGD("work form old generation: was %" PRIu64 " now %" PRIu64,
+                    queue->generation(), generation);
             work->result = C2_NOT_FOUND;
             queue.unlock();
             {
@@ -358,9 +414,10 @@
                 unexpected = std::move(pending->at(frameIndex));
                 pending->erase(frameIndex);
             }
-            (void) pending->insert({ frameIndex, std::move(work) });
+            (void)pending->insert({ frameIndex, std::move(work) });
         }
         if (unexpected) {
+            ALOGD("unexpected pending work");
             unexpected->result = C2_CORRUPTED;
             Mutexed<ExecState>::Locked state(mExecState);
             state->mListener->onWorkDone_nb(shared_from_this(), vec(unexpected));
diff --git a/media/libstagefright/codec2/client/Android.bp b/media/libstagefright/codec2/client/Android.bp
new file mode 100644
index 0000000..0129e15
--- /dev/null
+++ b/media/libstagefright/codec2/client/Android.bp
@@ -0,0 +1,30 @@
+cc_library {
+    name: "libstagefright_codec2_client",
+
+    srcs: [
+        "client.cpp",
+    ],
+
+    shared_libs: [
+        "android.hardware.media.bufferpool@1.0",
+        "libcutils",
+        "libhidlbase",
+        "libhidltransport",
+        "liblog",
+        "libstagefright_codec2",
+        "libstagefright_codec2_vndk",
+        "libstagefright_codec2_hidl@1.0",
+        "libutils",
+        "vendor.google.media.c2@1.0",
+    ],
+
+    export_include_dirs: [
+        "include",
+    ],
+
+    export_shared_lib_headers: [
+        "libstagefright_codec2",
+    ],
+
+}
+
diff --git a/media/libstagefright/codec2/client/client.cpp b/media/libstagefright/codec2/client/client.cpp
new file mode 100644
index 0000000..5a176dc
--- /dev/null
+++ b/media/libstagefright/codec2/client/client.cpp
@@ -0,0 +1,509 @@
+/*
+ * 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
+#define LOG_TAG "Codec2Client-interfaces"
+#include <log/log.h>
+
+#include <media/stagefright/codec2/client.h>
+
+#include <codec2/hidl/1.0/types.h>
+
+#include <vendor/google/media/c2/1.0/IComponentListener.h>
+#include <vendor/google/media/c2/1.0/IConfigurable.h>
+#include <vendor/google/media/c2/1.0/IComponentInterface.h>
+#include <vendor/google/media/c2/1.0/IComponent.h>
+#include <vendor/google/media/c2/1.0/IComponentStore.h>
+
+#include <hidl/HidlSupport.h>
+
+#include <limits>
+#include <type_traits>
+
+namespace /* unnamed */ {
+
+// TODO: Find the appropriate error code for this
+constexpr c2_status_t C2_TRANSACTION_FAILED = C2_CORRUPTED;
+
+} // unnamed namespace
+
+namespace android {
+
+using ::android::hardware::hidl_vec;
+using ::android::hardware::hidl_string;
+using ::android::hardware::Return;
+using ::android::hardware::Void;
+
+using namespace ::vendor::google::media::c2::V1_0;
+using namespace ::vendor::google::media::c2::V1_0::implementation;
+
+// Codec2ConfigurableClient
+
+const C2String& Codec2ConfigurableClient::getName() const {
+    return mName;
+}
+
+Codec2ConfigurableClient::Base* Codec2ConfigurableClient::base() const {
+    return static_cast<Base*>(mBase.get());
+}
+
+Codec2ConfigurableClient::Codec2ConfigurableClient(
+        const sp<Codec2ConfigurableClient::Base>& base) : mBase(base) {
+    Return<void> transStatus = base->getName(
+            [this](const hidl_string& name) {
+                mName = name.c_str();
+            });
+    if (!transStatus.isOk()) {
+        ALOGE("Cannot obtain name from IConfigurable.");
+    }
+}
+
+c2_status_t Codec2ConfigurableClient::query(
+        const std::vector<C2Param::Index> &indices,
+        c2_blocking_t mayBlock,
+        std::vector<std::unique_ptr<C2Param>>* const params) const {
+    hidl_vec<ParamIndex> hidlIndices(indices.size());
+    size_t i = 0;
+    for (const C2Param::Index& index : indices) {
+        hidlIndices[i++] = static_cast<ParamIndex>(index.operator uint32_t());
+    }
+    c2_status_t status;
+    Return<void> transStatus = base()->query(
+            hidlIndices,
+            mayBlock == C2_MAY_BLOCK,
+            [&status, params](Status s, const Params& p) {
+                status = static_cast<c2_status_t>(s);
+                if (status != C2_OK) {
+                    return;
+                }
+                status = copyParamsFromBlob(params, p);
+            });
+    if (!transStatus.isOk()) {
+        ALOGE("query -- transaction failed.");
+        return C2_TRANSACTION_FAILED;
+    }
+    return status;
+}
+
+c2_status_t Codec2ConfigurableClient::config(
+        const std::vector<C2Param*> &params,
+        c2_blocking_t mayBlock,
+        std::vector<std::unique_ptr<C2SettingResult>>* const failures) {
+    Params hidlParams;
+    Status hidlStatus = createParamsBlob(&hidlParams, params);
+    if (hidlStatus != Status::OK) {
+        ALOGE("config -- bad input.");
+        return C2_TRANSACTION_FAILED;
+    }
+    c2_status_t status;
+    Return<void> transStatus = base()->config(
+            hidlParams,
+            mayBlock == C2_MAY_BLOCK,
+            [&status, &params, failures](
+                    Status s,
+                    const hidl_vec<SettingResult> f,
+                    const Params& o) {
+                status = static_cast<c2_status_t>(s);
+                if (status != C2_OK) {
+                    return;
+                }
+                failures->clear();
+                failures->resize(f.size());
+                size_t i = 0;
+                for (const SettingResult& sf : f) {
+                    status = objcpy(&(*failures)[i++], sf);
+                    if (status != C2_OK) {
+                        return;
+                    }
+                }
+                status = updateParamsFromBlob(params, o);
+            });
+    if (!transStatus.isOk()) {
+        ALOGE("config -- transaction failed.");
+        return C2_TRANSACTION_FAILED;
+    }
+    return status;
+}
+
+c2_status_t Codec2ConfigurableClient::querySupportedParams(
+        std::vector<std::shared_ptr<C2ParamDescriptor>>* const params) const {
+    // TODO: Cache and query properly!
+    c2_status_t status;
+    Return<void> transStatus = base()->querySupportedParams(
+            std::numeric_limits<uint32_t>::min(),
+            std::numeric_limits<uint32_t>::max(),
+            [&status, params](
+                    Status s,
+                    const hidl_vec<ParamDescriptor>& p) {
+                status = static_cast<c2_status_t>(s);
+                if (status != C2_OK) {
+                    return;
+                }
+                params->resize(p.size());
+                size_t i = 0;
+                for (const ParamDescriptor& sp : p) {
+                    status = objcpy(&(*params)[i++], sp);
+                    if (status != C2_OK) {
+                        return;
+                    }
+                }
+            });
+    if (!transStatus.isOk()) {
+        ALOGE("querySupportedParams -- transaction failed.");
+        return C2_TRANSACTION_FAILED;
+    }
+    return status;
+}
+
+c2_status_t Codec2ConfigurableClient::querySupportedValues(
+        std::vector<C2FieldSupportedValuesQuery>& fields,
+        c2_blocking_t mayBlock) const {
+    hidl_vec<FieldSupportedValuesQuery> inFields(fields.size());
+    for (size_t i = 0; i < fields.size(); ++i) {
+        Status hidlStatus = objcpy(&inFields[i], fields[i]);
+        if (hidlStatus != Status::OK) {
+            ALOGE("querySupportedValues -- bad input");
+            return C2_TRANSACTION_FAILED;
+        }
+    }
+
+    c2_status_t status;
+    Return<void> transStatus = base()->querySupportedValues(
+            inFields,
+            mayBlock == C2_MAY_BLOCK,
+            [&status, &inFields, &fields](
+                    Status s,
+                    const hidl_vec<FieldSupportedValuesQueryResult>& r) {
+                status = static_cast<c2_status_t>(s);
+                if (status != C2_OK) {
+                    return;
+                }
+                if (r.size() != fields.size()) {
+                    ALOGE("querySupportedValues -- input and output lists "
+                            "have different sizes.");
+                    status = C2_CORRUPTED;
+                    return;
+                }
+                for (size_t i = 0; i < fields.size(); ++i) {
+                    status = objcpy(&fields[i], inFields[i], r[i]);
+                    if (status != C2_OK) {
+                        return;
+                    }
+                }
+            });
+    if (!transStatus.isOk()) {
+        ALOGE("querySupportedValues -- transaction failed.");
+        return C2_TRANSACTION_FAILED;
+    }
+    return status;
+}
+
+// Codec2Client
+
+Codec2Client::Base* Codec2Client::base() const {
+    return static_cast<Base*>(mBase.get());
+}
+
+Codec2Client::Codec2Client(const sp<Codec2Client::Base>& base) :
+        Codec2ConfigurableClient(base), mListed(false) {
+}
+
+c2_status_t Codec2Client::createComponent(
+        const C2String& name,
+        const std::shared_ptr<Codec2Client::Listener>& listener,
+        std::shared_ptr<Codec2Client::Component>* const component) {
+
+    // TODO: Add support for Bufferpool
+
+    struct HidlListener : public IComponentListener {
+        std::shared_ptr<Codec2Client::Listener> base;
+        std::weak_ptr<Codec2Client::Component> component;
+
+        virtual Return<void> onWorkDone(const WorkBundle& workBundle) override {
+            std::list<std::unique_ptr<C2Work>> workItems;
+            c2_status_t status = objcpy(&workItems, workBundle);
+            if (status != C2_OK) {
+                ALOGE("onWorkDone -- received corrupted WorkBundle. "
+                        "Error code: %d", static_cast<int>(status));
+                return Void();
+            }
+            base->onWorkDone(component, workItems);
+            return Void();
+        }
+
+        virtual Return<void> onTripped(
+                const hidl_vec<SettingResult>& settingResults) override {
+            std::vector<std::shared_ptr<C2SettingResult>> c2SettingResults(
+                    settingResults.size());
+            c2_status_t status;
+            for (size_t i = 0; i < settingResults.size(); ++i) {
+                std::unique_ptr<C2SettingResult> c2SettingResult;
+                status = objcpy(&c2SettingResult, settingResults[i]);
+                if (status != C2_OK) {
+                    ALOGE("onTripped -- received corrupted SettingResult. "
+                            "Error code: %d", static_cast<int>(status));
+                    return Void();
+                }
+                c2SettingResults[i] = std::move(c2SettingResult);
+            }
+            base->onTripped(component, c2SettingResults);
+            return Void();
+        }
+
+        virtual Return<void> onError(Status s, uint32_t errorCode) override {
+            base->onError(component, s == Status::OK ?
+                    errorCode : static_cast<c2_status_t>(s));
+            return Void();
+        }
+    };
+
+    c2_status_t status;
+    sp<HidlListener> hidlListener = new HidlListener();
+    hidlListener->base = listener;
+    Return<void> transStatus = base()->createComponent(
+            name,
+            hidlListener,
+            nullptr,
+            [&status, component](
+                    Status s,
+                    const sp<IComponent>& c) {
+                status = static_cast<c2_status_t>(s);
+                if (status != C2_OK) {
+                    return;
+                }
+                *component = std::make_shared<Codec2Client::Component>(c);
+            });
+    if (!transStatus.isOk()) {
+        ALOGE("createComponent -- failed transaction.");
+        return C2_TRANSACTION_FAILED;
+    }
+    if (status != C2_OK) {
+        ALOGE("createComponent -- failed to create component.");
+        return status;
+    }
+    hidlListener->component = *component;
+    return status;
+}
+
+c2_status_t Codec2Client::createInterface(
+        const C2String& name,
+        std::shared_ptr<Codec2Client::Interface>* const interface) {
+    c2_status_t status;
+    Return<void> transStatus = base()->createInterface(
+            name,
+            [&status, interface](
+                    Status s,
+                    const sp<IComponentInterface>& i) {
+                status = static_cast<c2_status_t>(s);
+                if (status != C2_OK) {
+                    return;
+                }
+                *interface = std::make_shared<Codec2Client::Interface>(i);
+            });
+    if (!transStatus.isOk()) {
+        ALOGE("createInterface -- failed transaction.");
+        return C2_TRANSACTION_FAILED;
+    }
+    return status;
+}
+
+const std::vector<C2Component::Traits>& Codec2Client::listComponents()
+        const {
+    if (mListed) {
+        return mTraitsList;
+    }
+    Return<void> transStatus = base()->listComponents(
+            [this](const hidl_vec<IComponentStore::ComponentTraits>& t) {
+                mTraitsList.resize(t.size());
+                mAliasesBuffer.resize(t.size());
+                for (size_t i = 0; i < t.size(); ++i) {
+                    c2_status_t status = objcpy(
+                            &mTraitsList[i], &mAliasesBuffer[i], t[i]);
+                    if (status != C2_OK) {
+                        ALOGE("listComponents -- corrupted output.");
+                        return;
+                    }
+                }
+            });
+    if (!transStatus.isOk()) {
+        ALOGE("listComponents -- failed transaction.");
+    }
+    mListed = true;
+    return mTraitsList;
+}
+
+c2_status_t Codec2Client::copyBuffer(
+        const std::shared_ptr<C2Buffer>& src,
+        const std::shared_ptr<C2Buffer>& dst) {
+    // TODO: Implement?
+    (void)src;
+    (void)dst;
+    ALOGE("copyBuffer not implemented");
+    return C2_OMITTED;
+}
+
+std::shared_ptr<C2ParamReflector>
+        Codec2Client::getParamReflector() {
+    // TODO: Implement this once there is a way to construct C2StructDescriptor
+    // dynamically.
+    ALOGE("getParamReflector -- not implemented.");
+    return nullptr;
+}
+
+std::shared_ptr<Codec2Client> Codec2Client::CreateFromService(
+        const char* instanceName, bool waitForService) {
+    sp<Base> baseStore = waitForService ?
+            Base::getService(instanceName) :
+            Base::tryGetService(instanceName);
+    if (!baseStore) {
+        if (waitForService) {
+            ALOGE("Codec2.0 service inaccessible. Check the device manifest.");
+        } else {
+            ALOGW("Codec2.0 service not available right now. Try again later.");
+        }
+        return nullptr;
+    }
+    return std::make_shared<Codec2Client>(baseStore);
+}
+
+// Codec2Client::Listener
+
+Codec2Client::Listener::~Listener() {
+}
+
+// Codec2Client::Component
+
+Codec2Client::Component::Base* Codec2Client::Component::base() const {
+    return static_cast<Base*>(mBase.get());
+}
+
+Codec2Client::Component::Component(const sp<Codec2Client::Component::Base>& base) :
+        Codec2Client::Configurable(base) {
+}
+
+c2_status_t Codec2Client::Component::createBlockPool(
+        C2Allocator::id_t id,
+        C2BlockPool::local_id_t* localId,
+        std::shared_ptr<Codec2Client::Configurable>* configurable) {
+    c2_status_t status;
+    Return<void> transStatus = base()->createBlockPool(
+            static_cast<uint32_t>(id),
+            [&status, localId, configurable](
+                    Status s,
+                    uint64_t pId,
+                    const sp<IConfigurable>& c) {
+                status = static_cast<c2_status_t>(s);
+                if (status != C2_OK) {
+                    return;
+                }
+                *localId = static_cast<C2BlockPool::local_id_t>(pId);
+                *configurable = std::make_shared<Codec2Client::Configurable>(c);
+            });
+    if (!transStatus.isOk()) {
+        ALOGE("createBlockPool -- transaction failed.");
+        return C2_TRANSACTION_FAILED;
+    }
+    return status;
+}
+
+c2_status_t Codec2Client::Component::queue(
+        std::list<std::unique_ptr<C2Work>>* const items) {
+    WorkBundle workBundle;
+    Status hidlStatus = objcpy(&workBundle, *items);
+    if (hidlStatus != Status::OK) {
+        ALOGE("queue -- bad input.");
+        return C2_TRANSACTION_FAILED;
+    }
+    Return<Status> transStatus = base()->queue(workBundle);
+    if (!transStatus.isOk()) {
+        ALOGE("queue -- transaction failed.");
+        return C2_TRANSACTION_FAILED;
+    }
+    return static_cast<c2_status_t>(static_cast<Status>(transStatus));
+}
+
+c2_status_t Codec2Client::Component::flush(
+        C2Component::flush_mode_t mode,
+        std::list<std::unique_ptr<C2Work>>* const flushedWork) {
+    (void)mode; // Flush mode isn't supported in HIDL yet.
+    c2_status_t status;
+    Return<void> transStatus = base()->flush(
+            [&status, flushedWork](
+                    Status s, const WorkBundle& wb) {
+                status = static_cast<c2_status_t>(s);
+                if (status != C2_OK) {
+                    return;
+                }
+                status = objcpy(flushedWork, wb);
+            });
+    if (!transStatus.isOk()) {
+        ALOGE("flush -- transaction failed.");
+        return C2_TRANSACTION_FAILED;
+    }
+    return status;
+}
+
+c2_status_t Codec2Client::Component::drain(C2Component::drain_mode_t mode) {
+    Return<Status> transStatus = base()->drain(
+            mode == C2Component::DRAIN_COMPONENT_WITH_EOS);
+    if (!transStatus.isOk()) {
+        ALOGE("drain -- transaction failed.");
+        return C2_TRANSACTION_FAILED;
+    }
+    return static_cast<c2_status_t>(static_cast<Status>(transStatus));
+}
+
+c2_status_t Codec2Client::Component::start() {
+    Return<Status> transStatus = base()->start();
+    if (!transStatus.isOk()) {
+        ALOGE("start -- transaction failed.");
+        return C2_TRANSACTION_FAILED;
+    }
+    return static_cast<c2_status_t>(static_cast<Status>(transStatus));
+}
+
+c2_status_t Codec2Client::Component::stop() {
+    Return<Status> transStatus = base()->stop();
+    if (!transStatus.isOk()) {
+        ALOGE("stop -- transaction failed.");
+        return C2_TRANSACTION_FAILED;
+    }
+    return static_cast<c2_status_t>(static_cast<Status>(transStatus));
+}
+
+c2_status_t Codec2Client::Component::reset() {
+    Return<Status> transStatus = base()->reset();
+    if (!transStatus.isOk()) {
+        ALOGE("reset -- transaction failed.");
+        return C2_TRANSACTION_FAILED;
+    }
+    return static_cast<c2_status_t>(static_cast<Status>(transStatus));
+}
+
+c2_status_t Codec2Client::Component::release() {
+    Return<Status> transStatus = base()->release();
+    if (!transStatus.isOk()) {
+        ALOGE("release -- transaction failed.");
+        return C2_TRANSACTION_FAILED;
+    }
+    return static_cast<c2_status_t>(static_cast<Status>(transStatus));
+}
+
+
+
+
+}  // namespace android
+
diff --git a/media/libstagefright/codec2/client/include/media/stagefright/codec2/client.h b/media/libstagefright/codec2/client/include/media/stagefright/codec2/client.h
new file mode 100644
index 0000000..1bbf459
--- /dev/null
+++ b/media/libstagefright/codec2/client/include/media/stagefright/codec2/client.h
@@ -0,0 +1,220 @@
+/*
+ * 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 CODEC2_CLIENT_INTERFACES_H_
+#define CODEC2_CLIENT_INTERFACES_H_
+
+#include <C2Component.h>
+#include <C2Buffer.h>
+#include <C2Param.h>
+#include <C2.h>
+
+#include <utils/StrongPointer.h>
+
+#include <memory>
+
+/**
+ * This file contains minimal interfaces for the framework to access Codec2.0.
+ *
+ * Codec2Client is the main class that contains the following inner classes:
+ * - Listener
+ * - Configurable
+ * - Interface
+ * - Component
+ *
+ * Classes in Codec2Client, interfaces in Codec2.0, and  HIDL interfaces are
+ * related as follows:
+ * - Codec2Client <==> C2ComponentStore <==> IComponentStore
+ * - Codec2Client::Listener <==> C2Component::Listener <==> IComponentListener
+ * - Codec2Client::Configurable <==> [No equivalent] <==> IConfigurable
+ * - Codec2Client::Interface <==> C2ComponentInterface <==> IComponentInterface
+ * - Codec2Client::Component <==> C2Component <==> IComponent
+ *
+ * The entry point is Codec2Client::CreateFromService(), which creates a
+ * Codec2Client object. From Codec2Client, Interface and Component objects can
+ * be created by calling createComponent() and createInterface().
+ *
+ * createComponent() takes a Listener object, which must be implemented by the
+ * user.
+ *
+ * At the present, createBlockPool() is the only method that yields a
+ * Configurable object. Note, however, that Interface, Component and
+ * Codec2Client are all subclasses of Configurable.
+ */
+
+// Forward declaration of HIDL interfaces
+namespace vendor {
+namespace google {
+namespace media {
+namespace c2 {
+namespace V1_0 {
+struct IConfigurable;
+struct IComponentInterface;
+struct IComponent;
+struct IComponentStore;
+} // namespace V1_0
+} // namespace c2
+} // namespace media
+} // namespace google
+} // namespace vendor
+
+namespace android {
+
+// This class is supposed to be called Codec2Client::Configurable, but forward
+// declaration of an inner class is not possible.
+struct Codec2ConfigurableClient {
+
+    typedef ::vendor::google::media::c2::V1_0::IConfigurable Base;
+
+    const C2String& getName() const;
+
+    c2_status_t query(
+            const std::vector<C2Param::Index> &indices,
+            c2_blocking_t mayBlock,
+            std::vector<std::unique_ptr<C2Param>>* const params) const;
+
+    c2_status_t config(
+            const std::vector<C2Param*> &params,
+            c2_blocking_t mayBlock,
+            std::vector<std::unique_ptr<C2SettingResult>>* const failures
+            );
+
+    c2_status_t querySupportedParams(
+            std::vector<std::shared_ptr<C2ParamDescriptor>>* const params
+            ) const;
+
+    c2_status_t querySupportedValues(
+            std::vector<C2FieldSupportedValuesQuery>& fields,
+            c2_blocking_t mayBlock) const;
+
+    // base cannot be null.
+    Codec2ConfigurableClient(const sp<Base>& base);
+
+protected:
+    C2String mName;
+    sp<Base> mBase;
+
+    Base* base() const;
+
+    friend struct Codec2Client;
+};
+
+struct Codec2Client : public Codec2ConfigurableClient {
+
+    typedef ::vendor::google::media::c2::V1_0::IComponentStore Base;
+
+    struct Listener;
+
+    typedef Codec2ConfigurableClient Configurable;
+
+    typedef Configurable Interface; // These two types may diverge in the future.
+
+    struct Component;
+
+    typedef Codec2Client Store;
+
+    c2_status_t createComponent(
+            const C2String& name,
+            const std::shared_ptr<Listener>& listener,
+            std::shared_ptr<Component>* const component);
+
+    c2_status_t createInterface(
+            const C2String& name,
+            std::shared_ptr<Interface>* const interface);
+
+    const std::vector<C2Component::Traits>&
+            listComponents() const;
+
+    c2_status_t copyBuffer(
+            const std::shared_ptr<C2Buffer>& src,
+            const std::shared_ptr<C2Buffer>& dst);
+
+    std::shared_ptr<C2ParamReflector> getParamReflector();
+
+    static std::shared_ptr<Codec2Client> CreateFromService(
+            const char* instanceName,
+            bool waitForService = true);
+
+    // base cannot be null.
+    Codec2Client(const sp<Base>& base);
+
+protected:
+    mutable bool mListed;
+    mutable std::vector<C2Component::Traits> mTraitsList;
+    mutable std::vector<std::unique_ptr<std::vector<std::string>>>
+            mAliasesBuffer;
+
+    Base* base() const;
+};
+
+struct Codec2Client::Listener {
+
+    virtual void onWorkDone(
+            const std::weak_ptr<Codec2Client::Component>& comp,
+            const std::list<std::unique_ptr<C2Work>>& workItems) = 0;
+
+    virtual void onTripped(
+            const std::weak_ptr<Codec2Client::Component>& comp,
+            const std::vector<std::shared_ptr<C2SettingResult>>& settingResults
+            ) = 0;
+
+    virtual void onError(
+            const std::weak_ptr<Codec2Client::Component>& comp,
+            uint32_t errorCode) = 0;
+
+    virtual ~Listener();
+
+};
+
+struct Codec2Client::Component : public Codec2Client::Configurable {
+
+    typedef ::vendor::google::media::c2::V1_0::IComponent Base;
+
+    c2_status_t createBlockPool(
+            C2Allocator::id_t id,
+            C2BlockPool::local_id_t* localId,
+            std::shared_ptr<Codec2Client::Configurable>* configurable);
+
+    c2_status_t queue(
+            std::list<std::unique_ptr<C2Work>>* const items);
+
+    c2_status_t flush(
+            C2Component::flush_mode_t mode,
+            std::list<std::unique_ptr<C2Work>>* const flushedWork);
+
+    c2_status_t drain(C2Component::drain_mode_t mode);
+
+    c2_status_t start();
+
+    c2_status_t stop();
+
+    c2_status_t reset();
+
+    c2_status_t release();
+
+    // base cannot be null.
+    Component(const sp<Base>& base);
+
+protected:
+    Base* base() const;
+
+    friend struct Codec2Client;
+};
+
+}  // namespace android
+
+#endif  // CODEC2_CLIENT_INTERFACES_H_
+
diff --git a/media/libstagefright/codec2/hidl/impl/1.0/Android.bp b/media/libstagefright/codec2/hidl/impl/1.0/Android.bp
new file mode 100644
index 0000000..3d930c6
--- /dev/null
+++ b/media/libstagefright/codec2/hidl/impl/1.0/Android.bp
@@ -0,0 +1,42 @@
+cc_library {
+    name: "libstagefright_codec2_hidl@1.0",
+    vendor_available: true,
+    vndk: {
+        enabled: true,
+    },
+
+    defaults: ["hidl_defaults"],
+
+    srcs: [
+        "Component.cpp",
+        "ComponentStore.cpp",
+        "Configurable.cpp",
+        "types.cpp",
+    ],
+
+    shared_libs: [
+        "android.hardware.media.bufferpool@1.0",
+        "libcutils",
+        "libhidlbase",
+        "libhidltransport",
+        "liblog",
+        "libstagefright_codec2",
+        "libstagefright_codec2_vndk",
+        "libutils",
+        "vendor.google.media.c2@1.0",
+    ],
+
+    export_include_dirs: [
+        "include",
+    ],
+
+    export_shared_lib_headers: [
+        "libstagefright_codec2",
+    ],
+
+    // Private include directories
+    header_libs: [
+        "libstagefright_codec2_internal",
+    ],
+}
+
diff --git a/media/libstagefright/codec2/hidl/impl/1.0/Component.cpp b/media/libstagefright/codec2/hidl/impl/1.0/Component.cpp
new file mode 100644
index 0000000..2b34fde
--- /dev/null
+++ b/media/libstagefright/codec2/hidl/impl/1.0/Component.cpp
@@ -0,0 +1,246 @@
+/*
+ * 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
+#define LOG_TAG "Codec2-Component"
+#include <log/log.h>
+
+#include <codec2/hidl/1.0/Component.h>
+#include <codec2/hidl/1.0/types.h>
+
+namespace vendor {
+namespace google {
+namespace media {
+namespace c2 {
+namespace V1_0 {
+namespace implementation {
+
+using namespace ::android;
+
+// Implementation of ConfigurableC2Intf based on C2ComponentInterface
+struct CompIntf : public ConfigurableC2Intf {
+    CompIntf(const std::shared_ptr<C2ComponentInterface>& intf) :
+        ConfigurableC2Intf(intf->getName()),
+        mIntf(intf) {
+    }
+
+    virtual c2_status_t config(
+            const std::vector<C2Param*>& params,
+            c2_blocking_t mayBlock,
+            std::vector<std::unique_ptr<C2SettingResult>>* const failures
+            ) override {
+        return mIntf->config_vb(params, mayBlock, failures);
+    }
+
+    virtual c2_status_t query(
+            const std::vector<C2Param::Index>& indices,
+            c2_blocking_t mayBlock,
+            std::vector<std::unique_ptr<C2Param>>* const params) const override {
+        return mIntf->query_vb({}, indices, mayBlock, params);
+    }
+
+    virtual c2_status_t querySupportedParams(
+            std::vector<std::shared_ptr<C2ParamDescriptor>>* const params
+            ) const override {
+        return mIntf->querySupportedParams_nb(params);
+    }
+
+    virtual c2_status_t querySupportedValues(
+            std::vector<C2FieldSupportedValuesQuery>& fields,
+            c2_blocking_t mayBlock) const override {
+        return mIntf->querySupportedValues_vb(fields, mayBlock);
+    }
+
+protected:
+    std::shared_ptr<C2ComponentInterface> mIntf;
+};
+
+// ComponentInterface
+ComponentInterface::ComponentInterface(
+        const std::shared_ptr<C2ComponentInterface>& intf,
+        const sp<ComponentStore>& store) :
+    Configurable(new CachedConfigurable(std::make_unique<CompIntf>(intf))),
+    mInterface(intf) {
+    mInit = init(store.get());
+}
+
+c2_status_t ComponentInterface::status() const {
+    return mInit;
+}
+
+// ComponentListener wrapper
+struct Listener : public C2Component::Listener {
+    Listener(const wp<IComponentListener>& listener) : mListener(listener) {
+        // TODO: Should we track interface errors? We could reuse onError() or
+        // create our own error channel.
+    }
+
+    virtual void onError_nb(
+            std::weak_ptr<C2Component> /* c2component */,
+            uint32_t errorCode) override {
+        sp<IComponentListener> listener = mListener.promote();
+        if (listener) {
+            listener->onError(Status::OK, errorCode);
+        }
+    }
+
+    virtual void onTripped_nb(
+            std::weak_ptr<C2Component> /* c2component */,
+            std::vector<std::shared_ptr<C2SettingResult>> c2settingResult
+            ) override {
+        sp<IComponentListener> listener = mListener.promote();
+        if (listener) {
+            hidl_vec<SettingResult> settingResults(c2settingResult.size());
+            size_t ix = 0;
+            for (const std::shared_ptr<C2SettingResult> &c2result :
+                    c2settingResult) {
+                if (c2result) {
+                    if (objcpy(&settingResults[ix++], *c2result) != Status::OK) {
+                        break;
+                    }
+                }
+            }
+            settingResults.resize(ix);
+            listener->onTripped(settingResults);
+        }
+    }
+
+    virtual void onWorkDone_nb(
+            std::weak_ptr<C2Component> /* c2component */,
+            std::list<std::unique_ptr<C2Work>> c2workItems) override {
+        sp<IComponentListener> listener = mListener.promote();
+        if (listener) {
+            WorkBundle workBundle;
+
+            // TODO: Connect with bufferpool API to send Works & Buffers
+            if (objcpy(&workBundle, c2workItems) != Status::OK) {
+                ALOGE("onWorkDone() received corrupted work items.");
+                return;
+            }
+            listener->onWorkDone(workBundle);
+
+            // Finish buffer transfers: nothing else to do
+        }
+    }
+
+protected:
+    wp<IComponentListener> mListener;
+};
+
+// Component
+Component::Component(
+        const std::shared_ptr<C2Component>& component,
+        const sp<IComponentListener>& listener,
+        const sp<ComponentStore>& store) :
+    Configurable(new CachedConfigurable(
+            std::make_unique<CompIntf>(component->intf()))),
+    mComponent(component),
+    mInterface(component->intf()),
+    mListener(listener) /* , // TODO: Do we need store for anything?
+    mStore(store)*/ {
+    std::shared_ptr<C2Component::Listener> c2listener =
+            std::make_shared<Listener>(listener);
+    c2_status_t res = mComponent->setListener_vb(c2listener, C2_DONT_BLOCK);
+    // Retrieve supported parameters from store
+    // TODO: We could cache this per component/interface type
+    mInit = init(store.get());
+    mInit = mInit != C2_OK ? res : mInit;
+}
+
+// Methods from ::android::hardware::media::c2::V1_0::IComponent
+Return<Status> Component::queue(const WorkBundle& workBundle) {
+    std::list<std::unique_ptr<C2Work>> c2works;
+
+    // TODO: Connect with bufferpool API for buffer transfers
+    if (objcpy(&c2works, workBundle) != C2_OK) {
+        return Status::CORRUPTED;
+    }
+    return static_cast<Status>(mComponent->queue_nb(&c2works));
+}
+
+Return<void> Component::flush(flush_cb _hidl_cb) {
+    std::list<std::unique_ptr<C2Work>> c2flushedWorks;
+    c2_status_t c2res = mComponent->flush_sm(
+            C2Component::FLUSH_COMPONENT,
+            &c2flushedWorks);
+    WorkBundle flushedWorkBundle;
+
+    Status res = static_cast<Status>(c2res);
+    if (c2res == C2_OK) {
+        // TODO: Connect with bufferpool API for buffer transfers
+        res = objcpy(&flushedWorkBundle, c2flushedWorks);
+    }
+    _hidl_cb(res, flushedWorkBundle);
+    return Void();
+}
+
+Return<Status> Component::drain(bool withEos) {
+    return static_cast<Status>(mComponent->drain_nb(withEos ?
+            C2Component::DRAIN_COMPONENT_WITH_EOS :
+            C2Component::DRAIN_COMPONENT_NO_EOS));
+}
+
+Return<Status> Component::connectToInputSurface(const sp<IInputSurface>& surface) {
+    // TODO implement
+    (void)surface;
+    return Status::OK;
+}
+
+Return<Status> Component::connectToOmxInputSurface(
+        const sp<::android::hardware::graphics::bufferqueue::V1_0::
+        IGraphicBufferProducer>& producer,
+        const sp<::android::hardware::media::omx::V1_0::
+        IGraphicBufferSource>& source) {
+    // TODO implement
+    (void)producer;
+    (void)source;
+    return Status::OK;
+}
+
+Return<Status> Component::disconnectFromInputSurface() {
+    // TODO implement
+    return Status::OK;
+}
+
+Return<void> Component::createBlockPool(uint32_t allocatorId, createBlockPool_cb _hidl_cb) {
+    // TODO implement
+    (void)allocatorId;
+    _hidl_cb(Status::OK, 0 /* blockPoolId */, nullptr /* configurable */);
+    return Void();
+}
+
+Return<Status> Component::start() {
+    return static_cast<Status>(mComponent->start());
+}
+
+Return<Status> Component::stop() {
+    return static_cast<Status>(mComponent->stop());
+}
+
+Return<Status> Component::reset() {
+    return static_cast<Status>(mComponent->reset());
+}
+
+Return<Status> Component::release() {
+    return static_cast<Status>(mComponent->release());
+}
+
+}  // namespace implementation
+}  // namespace V1_0
+}  // namespace c2
+}  // namespace media
+}  // namespace google
+}  // namespace vendor
diff --git a/media/libstagefright/codec2/hidl/impl/1.0/ComponentStore.cpp b/media/libstagefright/codec2/hidl/impl/1.0/ComponentStore.cpp
new file mode 100644
index 0000000..4d51fba
--- /dev/null
+++ b/media/libstagefright/codec2/hidl/impl/1.0/ComponentStore.cpp
@@ -0,0 +1,212 @@
+/*
+ * 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
+#define LOG_TAG "Codec2-ComponentStore"
+#include <log/log.h>
+
+#include <codec2/hidl/1.0/ComponentStore.h>
+#include <codec2/hidl/1.0/Component.h>
+#include <codec2/hidl/1.0/ConfigurableC2Intf.h>
+#include <codec2/hidl/1.0/types.h>
+
+namespace vendor {
+namespace google {
+namespace media {
+namespace c2 {
+namespace V1_0 {
+namespace implementation {
+
+using namespace ::android;
+
+struct StoreIntf : public ConfigurableC2Intf {
+    StoreIntf(const std::shared_ptr<C2ComponentStore>& store) :
+        ConfigurableC2Intf(store ? store->getName() : ""),
+        mStore(store) {
+    }
+
+    c2_status_t config(
+            const std::vector<C2Param*> &params,
+            c2_blocking_t mayBlock,
+            std::vector<std::unique_ptr<C2SettingResult>> *const failures
+            ) override {
+        // Assume all params are blocking
+        // TODO: Filter for supported params
+        if (mayBlock == C2_DONT_BLOCK && params.size() != 0) {
+            return C2_BLOCKING;
+        }
+        return mStore->config_sm(params, failures);
+    }
+
+    c2_status_t query(
+            const std::vector<C2Param::Index> &indices,
+            c2_blocking_t mayBlock,
+            std::vector<std::unique_ptr<C2Param>> *const params) const override {
+        // Assume all params are blocking
+        // TODO: Filter for supported params
+        if (mayBlock == C2_DONT_BLOCK && indices.size() != 0) {
+            return C2_BLOCKING;
+        }
+        return mStore->query_sm({}, indices, params);
+    }
+
+    c2_status_t querySupportedParams(
+            std::vector<std::shared_ptr<C2ParamDescriptor>> *const params
+            ) const override {
+        return mStore->querySupportedParams_nb(params);
+    }
+
+    c2_status_t querySupportedValues(
+            std::vector<C2FieldSupportedValuesQuery> &fields,
+            c2_blocking_t mayBlock) const override {
+        // Assume all params are blocking
+        // TODO: Filter for supported params
+        if (mayBlock == C2_DONT_BLOCK && fields.size() != 0) {
+            return C2_BLOCKING;
+        }
+        return mStore->querySupportedValues_sm(fields);
+    }
+
+protected:
+    std::shared_ptr<C2ComponentStore> mStore;
+};
+
+
+ComponentStore::ComponentStore(const std::shared_ptr<C2ComponentStore>& store) :
+    Configurable(new CachedConfigurable(std::make_unique<StoreIntf>(store))),
+    mStore(store) {
+    // Retrieve struct descriptors
+    mParamReflector = mStore->getParamReflector();
+
+    // Retrieve supported parameters from store
+    mInit = init(this);
+}
+
+c2_status_t ComponentStore::validateSupportedParams(
+        const std::vector<std::shared_ptr<C2ParamDescriptor>>& params) {
+    c2_status_t res = C2_OK;
+
+    for (const std::shared_ptr<C2ParamDescriptor> &desc : params) {
+        if (!desc) {
+            // All descriptors should be valid
+            res = res ? res : C2_BAD_VALUE;
+            continue;
+        }
+        C2Param::CoreIndex coreIndex = desc->index().coreIndex();
+        auto it = mStructDescriptors.find(coreIndex);
+        if (it == mStructDescriptors.end()) {
+            std::shared_ptr<C2StructDescriptor> structDesc =
+                    mParamReflector->describe(coreIndex);
+            if (!structDesc) {
+                // All supported params must be described
+                res = C2_BAD_INDEX;
+            }
+            mStructDescriptors.insert({ coreIndex, structDesc });
+        }
+    }
+    return res;
+}
+
+// Methods from ::android::hardware::media::c2::V1_0::IComponentStore
+Return<void> ComponentStore::createComponent(
+        const hidl_string& name,
+        const sp<IComponentListener>& listener,
+        // TODO: Return the pool if the component has it.
+        const sp<IClientManager>& /* pool */,
+        createComponent_cb _hidl_cb) {
+    std::shared_ptr<C2Component> c2component;
+    c2_status_t res = mStore->createComponent(name, &c2component);
+    sp<IComponent> component;
+    if (res == C2_OK) {
+        component = new Component(c2component, listener, this);
+    }
+    _hidl_cb((Status)res, component);
+    return Void();
+}
+
+Return<void> ComponentStore::createInterface(
+        const hidl_string& name,
+        createInterface_cb _hidl_cb) {
+    std::shared_ptr<C2ComponentInterface> c2interface;
+    c2_status_t res = mStore->createInterface(name, &c2interface);
+    sp<IComponentInterface> interface;
+    if (res == C2_OK) {
+        interface = new ComponentInterface(c2interface, this);
+    }
+    _hidl_cb((Status)res, interface);
+    return Void();
+}
+
+Return<void> ComponentStore::listComponents(listComponents_cb _hidl_cb) {
+    std::vector<std::shared_ptr<const C2Component::Traits>> c2traits =
+            mStore->listComponents();
+    hidl_vec<IComponentStore::ComponentTraits> traits(c2traits.size());
+    size_t ix = 0;
+    for (const std::shared_ptr<const C2Component::Traits> &c2trait : c2traits) {
+        if (c2trait) {
+            objcpy(&traits[ix++], *c2trait);
+        }
+    }
+    traits.resize(ix);
+    _hidl_cb(traits);
+    return Void();
+}
+
+Return<sp<IInputSurface>> ComponentStore::createInputSurface() {
+    // TODO implement
+    return sp<IInputSurface> {};
+}
+
+Return<void> ComponentStore::getStructDescriptors(
+        const hidl_vec<uint32_t>& indices,
+        getStructDescriptors_cb _hidl_cb) {
+    hidl_vec<StructDescriptor> descriptors(indices.size());
+    size_t dstIx = 0;
+    Status res;
+    for (size_t srcIx = 0; srcIx < indices.size(); ++srcIx) {
+        const auto item = mStructDescriptors.find(
+                C2Param::CoreIndex(indices[srcIx]).coreIndex());
+        if (item == mStructDescriptors.end()) {
+            res = Status::NOT_FOUND;
+        } else if (item->second) {
+            objcpy(&descriptors[dstIx++], *item->second);
+        } else {
+            res = Status::NO_MEMORY;
+        }
+    }
+    descriptors.resize(dstIx);
+    _hidl_cb(res, descriptors);
+    return Void();
+}
+
+Return<sp<IClientManager>> ComponentStore::getPoolClientManager() {
+    // TODO implement
+    return sp<IClientManager> {};
+}
+
+Return<Status> ComponentStore::copyBuffer(const Buffer& src, const Buffer& dst) {
+    // TODO implement
+    (void)src;
+    (void)dst;
+    return Status::OMITTED;
+}
+
+}  // namespace implementation
+}  // namespace V1_0
+}  // namespace c2
+}  // namespace media
+}  // namespace google
+}  // namespace vendor
diff --git a/media/libstagefright/codec2/hidl/impl/1.0/Configurable.cpp b/media/libstagefright/codec2/hidl/impl/1.0/Configurable.cpp
new file mode 100644
index 0000000..3f041a9
--- /dev/null
+++ b/media/libstagefright/codec2/hidl/impl/1.0/Configurable.cpp
@@ -0,0 +1,167 @@
+/*
+ * 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
+#define LOG_TAG "Codec2-Configurable"
+#include <log/log.h>
+
+#include <codec2/hidl/1.0/Configurable.h>
+#include <codec2/hidl/1.0/ComponentStore.h>
+#include <codec2/hidl/1.0/types.h>
+#include <C2ParamInternal.h>
+
+namespace vendor {
+namespace google {
+namespace media {
+namespace c2 {
+namespace V1_0 {
+namespace implementation {
+
+using namespace ::android;
+
+CachedConfigurable::CachedConfigurable(
+        std::unique_ptr<ConfigurableC2Intf>&& intf) :
+    mIntf(std::move(intf)) {
+}
+
+c2_status_t CachedConfigurable::init(ComponentStore* store) {
+    // Retrieve supported parameters from store
+    c2_status_t init = mIntf->querySupportedParams(&mSupportedParams);
+    c2_status_t validate = store->validateSupportedParams(mSupportedParams);
+    return init == C2_OK ? C2_OK : validate;
+}
+
+// Methods from ::android::hardware::media::c2::V1_0::IConfigurable follow.
+Return<void> CachedConfigurable::getName(getName_cb _hidl_cb) {
+    _hidl_cb(mIntf->getName());
+    return Void();
+}
+
+Return<void> CachedConfigurable::query(
+        const hidl_vec<uint32_t>& indices,
+        bool mayBlock,
+        query_cb _hidl_cb) {
+    typedef C2Param::Index Index;
+    std::vector<Index> c2heapParamIndices(
+            (Index*)indices.data(),
+            (Index*)indices.data() + indices.size());
+    std::vector<std::unique_ptr<C2Param>> c2heapParams;
+    c2_status_t c2res = mIntf->query(
+            c2heapParamIndices,
+            mayBlock ? C2_MAY_BLOCK : C2_DONT_BLOCK,
+            &c2heapParams);
+
+    hidl_vec<uint8_t> params;
+    createParamsBlob(&params, c2heapParams);
+    _hidl_cb(static_cast<Status>(c2res), params);
+
+    return Void();
+}
+
+Return<void> CachedConfigurable::config(
+        const hidl_vec<uint8_t>& inParams,
+        bool mayBlock,
+        config_cb _hidl_cb) {
+    std::vector<C2Param*> c2params;
+    if (parseParamsBlob(&c2params, inParams) != C2_OK) {
+        _hidl_cb(Status::CORRUPTED,
+                hidl_vec<SettingResult>(),
+                hidl_vec<uint8_t>());
+        return Void();
+    }
+    // TODO: check if blob was invalid
+    std::vector<std::unique_ptr<C2SettingResult>> c2failures;
+    c2_status_t c2res = mIntf->config(
+            c2params,
+            mayBlock ? C2_MAY_BLOCK : C2_DONT_BLOCK,
+            &c2failures);
+    hidl_vec<SettingResult> failures(c2failures.size());
+    {
+        size_t ix = 0;
+        for (const std::unique_ptr<C2SettingResult>& c2result : c2failures) {
+            if (c2result) {
+                objcpy(&failures[ix++], *c2result);
+            }
+        }
+        failures.resize(ix);
+    }
+    hidl_vec<uint8_t> outParams;
+    createParamsBlob(&outParams, c2params);
+    _hidl_cb((Status)c2res, failures, outParams);
+    return Void();
+}
+
+Return<void> CachedConfigurable::querySupportedParams(
+        uint32_t start,
+        uint32_t count,
+        querySupportedParams_cb _hidl_cb) {
+    C2LinearRange request = C2LinearCapacity(mSupportedParams.size()).range(
+            start, count);
+    hidl_vec<ParamDescriptor> params(request.size());
+    Status res = Status::OK;
+    size_t dstIx = 0;
+    for (size_t srcIx = request.offset(); srcIx < request.endOffset(); ++srcIx) {
+        if (mSupportedParams[srcIx]) {
+            objcpy(&params[dstIx++], *mSupportedParams[srcIx]);
+        } else {
+            res = Status::CORRUPTED;
+        }
+    }
+    params.resize(dstIx);
+    _hidl_cb(res, params);
+    return Void();
+}
+
+Return<void> CachedConfigurable::querySupportedValues(
+        const hidl_vec<FieldSupportedValuesQuery>& inFields,
+        bool mayBlock,
+        querySupportedValues_cb _hidl_cb) {
+    std::vector<C2FieldSupportedValuesQuery> c2fields;
+    {
+        // C2FieldSupportedValuesQuery objects are restricted in that some
+        // members are const.
+        // C2ParamField - required for its constructor - has no constructors
+        // from fields. Use C2ParamInspector.
+        for (const FieldSupportedValuesQuery &query : inFields) {
+            c2fields.emplace_back(_C2ParamInspector::CreateParamField(
+                    query.field.index,
+                    query.field.fieldId.offset,
+                    query.field.fieldId.size),
+                    query.type == FieldSupportedValuesQuery::Type::POSSIBLE ?
+                    C2FieldSupportedValuesQuery::POSSIBLE :
+                    C2FieldSupportedValuesQuery::CURRENT);
+        }
+    }
+    c2_status_t c2res = mIntf->querySupportedValues(
+            c2fields,
+            mayBlock ? C2_MAY_BLOCK : C2_DONT_BLOCK);
+    hidl_vec<FieldSupportedValuesQueryResult> outFields(inFields.size());
+    {
+        size_t ix = 0;
+        for (const C2FieldSupportedValuesQuery &result : c2fields) {
+            objcpy(&outFields[ix++], result);
+        }
+    }
+    _hidl_cb((Status)c2res, outFields);
+    return Void();
+}
+
+}  // namespace implementation
+}  // namespace V1_0
+}  // namespace c2
+}  // namespace media
+}  // namespace google
+}  // namespace vendor
diff --git a/media/libstagefright/codec2/hidl/impl/1.0/include/codec2/hidl/1.0/Component.h b/media/libstagefright/codec2/hidl/impl/1.0/include/codec2/hidl/1.0/Component.h
new file mode 100644
index 0000000..0308fe0
--- /dev/null
+++ b/media/libstagefright/codec2/hidl/impl/1.0/include/codec2/hidl/1.0/Component.h
@@ -0,0 +1,85 @@
+#ifndef VENDOR_GOOGLE_MEDIA_C2_V1_0_COMPONENT_H
+#define VENDOR_GOOGLE_MEDIA_C2_V1_0_COMPONENT_H
+
+#include <codec2/hidl/1.0/Configurable.h>
+
+#include <vendor/google/media/c2/1.0/IComponentListener.h>
+#include <vendor/google/media/c2/1.0/IComponentStore.h>
+#include <vendor/google/media/c2/1.0/IComponent.h>
+#include <hidl/MQDescriptor.h>
+#include <hidl/Status.h>
+
+#include <C2Component.h>
+#include <C2.h>
+
+namespace vendor {
+namespace google {
+namespace media {
+namespace c2 {
+namespace V1_0 {
+namespace implementation {
+
+using ::android::hardware::hidl_array;
+using ::android::hardware::hidl_memory;
+using ::android::hardware::hidl_string;
+using ::android::hardware::hidl_vec;
+using ::android::hardware::Return;
+using ::android::hardware::Void;
+using ::android::sp;
+
+struct ComponentStore;
+
+struct ComponentInterface : public Configurable<IComponentInterface> {
+    ComponentInterface(
+            const std::shared_ptr<C2ComponentInterface>& interface,
+            const sp<ComponentStore>& store);
+    c2_status_t status() const;
+
+protected:
+    c2_status_t mInit;
+    std::shared_ptr<C2ComponentInterface> mInterface;
+    // sp<ComponentStore> mStore; // TODO needed?
+};
+
+struct Component : public Configurable<IComponent> {
+    Component(
+            const std::shared_ptr<C2Component>&,
+            const sp<IComponentListener>& listener,
+            const sp<ComponentStore>& store);
+
+    // Methods from gIComponent follow.
+    virtual Return<Status> queue(const WorkBundle& workBundle) override;
+    virtual Return<void> flush(flush_cb _hidl_cb) override;
+    virtual Return<Status> drain(bool withEos) override;
+    virtual Return<Status> connectToInputSurface(
+            const sp<IInputSurface>& surface) override;
+    virtual Return<Status> connectToOmxInputSurface(
+            const sp<::android::hardware::graphics::bufferqueue::V1_0::
+            IGraphicBufferProducer>& producer,
+            const sp<::android::hardware::media::omx::V1_0::
+            IGraphicBufferSource>& source) override;
+    virtual Return<Status> disconnectFromInputSurface() override;
+    virtual Return<void> createBlockPool(
+            uint32_t allocatorId,
+            createBlockPool_cb _hidl_cb) override;
+    virtual Return<Status> start() override;
+    virtual Return<Status> stop() override;
+    virtual Return<Status> reset() override;
+    virtual Return<Status> release() override;
+
+protected:
+    c2_status_t mInit;
+    std::shared_ptr<C2Component> mComponent;
+    std::shared_ptr<C2ComponentInterface> mInterface;
+    sp<IComponentListener> mListener;
+    // sp<ComponentStore> mStore; // TODO needed?
+};
+
+}  // namespace implementation
+}  // namespace V1_0
+}  // namespace c2
+}  // namespace media
+}  // namespace google
+}  // namespace vendor
+
+#endif  // VENDOR_GOOGLE_MEDIA_C2_V1_0_COMPONENT_H
diff --git a/media/libstagefright/codec2/hidl/impl/1.0/include/codec2/hidl/1.0/ComponentStore.h b/media/libstagefright/codec2/hidl/impl/1.0/include/codec2/hidl/1.0/ComponentStore.h
new file mode 100644
index 0000000..c3f92a0
--- /dev/null
+++ b/media/libstagefright/codec2/hidl/impl/1.0/include/codec2/hidl/1.0/ComponentStore.h
@@ -0,0 +1,99 @@
+/*
+ * 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 VENDOR_GOOGLE_MEDIA_C2_V1_0_COMPONENTSTORE_H
+#define VENDOR_GOOGLE_MEDIA_C2_V1_0_COMPONENTSTORE_H
+
+#include <codec2/hidl/1.0/Component.h>
+#include <codec2/hidl/1.0/Configurable.h>
+#include <C2Component.h>
+#include <C2Param.h>
+#include <C2.h>
+
+#include <vendor/google/media/c2/1.0/IComponentStore.h>
+#include <android/hardware/media/bufferpool/1.0/IClientManager.h>
+#include <hidl/MQDescriptor.h>
+#include <hidl/Status.h>
+
+#include <vector>
+#include <map>
+#include <memory>
+
+namespace vendor {
+namespace google {
+namespace media {
+namespace c2 {
+namespace V1_0 {
+namespace implementation {
+
+using ::android::hardware::media::bufferpool::V1_0::IClientManager;
+
+using ::android::hardware::hidl_array;
+using ::android::hardware::hidl_memory;
+using ::android::hardware::hidl_string;
+using ::android::hardware::hidl_vec;
+using ::android::hardware::Return;
+using ::android::hardware::Void;
+using ::android::sp;
+
+struct ComponentStore : public Configurable<IComponentStore> {
+    ComponentStore(const std::shared_ptr<C2ComponentStore>& store);
+    virtual ~ComponentStore() = default;
+
+    c2_status_t status() const {
+        return mInit;
+    }
+
+    c2_status_t validateSupportedParams(
+            const std::vector<std::shared_ptr<C2ParamDescriptor>>& params);
+
+    // Methods from ::android::hardware::media::c2::V1_0::IComponentStore
+    Return<void> createComponent(
+            const hidl_string& name,
+            const sp<IComponentListener>& listener,
+            const sp<IClientManager>& pool,
+            createComponent_cb _hidl_cb) override;
+    Return<void> createInterface(
+            const hidl_string& name,
+            createInterface_cb _hidl_cb) override;
+    Return<void> listComponents(listComponents_cb _hidl_cb) override;
+    Return<sp<IInputSurface>> createInputSurface() override;
+    Return<void> getStructDescriptors(
+            const hidl_vec<uint32_t>& indices,
+            getStructDescriptors_cb _hidl_cb) override;
+    Return<sp<IClientManager>> getPoolClientManager() override;
+    Return<Status> copyBuffer(
+            const Buffer& src,
+            const Buffer& dst) override;
+
+protected:
+    c2_status_t mInit;
+    std::shared_ptr<C2ComponentStore> mStore;
+    std::shared_ptr<C2ParamReflector> mParamReflector;
+    std::map<C2Param::CoreIndex, std::shared_ptr<C2StructDescriptor>>
+            mStructDescriptors;
+
+    sp<IClientManager> mPoolManager;
+};
+
+}  // namespace implementation
+}  // namespace V1_0
+}  // namespace c2
+}  // namespace media
+}  // namespace google
+}  // namespace vendor
+
+#endif  // VENDOR_GOOGLE_MEDIA_C2_V1_0_COMPONENTSTORE_H
diff --git a/media/libstagefright/codec2/hidl/impl/1.0/include/codec2/hidl/1.0/Configurable.h b/media/libstagefright/codec2/hidl/impl/1.0/include/codec2/hidl/1.0/Configurable.h
new file mode 100644
index 0000000..697d483
--- /dev/null
+++ b/media/libstagefright/codec2/hidl/impl/1.0/include/codec2/hidl/1.0/Configurable.h
@@ -0,0 +1,157 @@
+/*
+ * 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 VENDOR_GOOGLE_MEDIA_C2_V1_0_CONFIGURABLE_H
+#define VENDOR_GOOGLE_MEDIA_C2_V1_0_CONFIGURABLE_H
+
+#include <codec2/hidl/1.0/ConfigurableC2Intf.h>
+
+#include <C2Component.h>
+#include <C2Param.h>
+#include <C2.h>
+
+#include <vendor/google/media/c2/1.0/IConfigurable.h>
+#include <hidl/Status.h>
+
+#include <memory>
+
+namespace vendor {
+namespace google {
+namespace media {
+namespace c2 {
+namespace V1_0 {
+namespace implementation {
+
+using ::android::hardware::hidl_array;
+using ::android::hardware::hidl_memory;
+using ::android::hardware::hidl_string;
+using ::android::hardware::hidl_vec;
+using ::android::hardware::Return;
+using ::android::hardware::Void;
+using ::android::sp;
+
+struct ComponentStore;
+
+/**
+ * Implementation of the IConfigurable interface that supports caching of
+ * supported parameters from a supplied ComponentStore.
+ *
+ * This is mainly the same for all of the configurable C2 interfaces though
+ * there are slight differences in the blocking behavior. This is handled in the
+ * ConfigurableC2Intf implementations.
+ */
+struct CachedConfigurable : public IConfigurable {
+    CachedConfigurable(std::unique_ptr<ConfigurableC2Intf>&& intf);
+
+    c2_status_t init(ComponentStore* store);
+
+    // Methods from ::android::hardware::media::c2::V1_0::IConfigurable
+
+    virtual Return<void> getName(getName_cb _hidl_cb) override;
+
+    virtual Return<void> query(
+            const hidl_vec<uint32_t>& indices,
+            bool mayBlock,
+            query_cb _hidl_cb) override;
+
+    virtual Return<void> config(
+            const hidl_vec<uint8_t>& inParams,
+            bool mayBlock,
+            config_cb _hidl_cb) override;
+
+    virtual Return<void> querySupportedParams(
+            uint32_t start,
+            uint32_t count,
+            querySupportedParams_cb _hidl_cb) override;
+
+    virtual Return<void> querySupportedValues(
+            const hidl_vec<FieldSupportedValuesQuery>& inFields,
+            bool mayBlock,
+            querySupportedValues_cb _hidl_cb) override;
+
+protected:
+    // Common Codec2.0 interface wrapper
+    std::unique_ptr<ConfigurableC2Intf> mIntf;
+
+    // Cached supported params
+    std::vector<std::shared_ptr<C2ParamDescriptor>> mSupportedParams;
+};
+
+/**
+ * Template that implements the `IConfigurable` interface for an inherited
+ * interface. Classes that implement a child interface `I` of `IConfigurable`
+ * can derive from `Configurable<I>`.
+ */
+template <typename I>
+struct Configurable : public I {
+    Configurable(const sp<CachedConfigurable>& intf): mIntf(intf) {
+    }
+
+    c2_status_t init(ComponentStore* store) {
+        return mIntf->init(store);
+    }
+
+    // Methods from ::android::hardware::media::c2::V1_0::IConfigurable
+
+    using getName_cb = typename I::getName_cb;
+    virtual Return<void> getName(getName_cb _hidl_cb) override {
+        return mIntf->getName(_hidl_cb);
+    }
+
+    using query_cb = typename I::query_cb;
+    virtual Return<void> query(
+            const hidl_vec<uint32_t>& indices,
+            bool mayBlock,
+            query_cb _hidl_cb) override {
+        return mIntf->query(indices, mayBlock, _hidl_cb);
+    }
+
+    using config_cb = typename I::config_cb;
+    virtual Return<void> config(
+            const hidl_vec<uint8_t>& inParams,
+            bool mayBlock,
+            config_cb _hidl_cb) override {
+        return mIntf->config(inParams, mayBlock, _hidl_cb);
+    }
+
+    using querySupportedParams_cb = typename I::querySupportedParams_cb;
+    virtual Return<void> querySupportedParams(
+            uint32_t start,
+            uint32_t count,
+            querySupportedParams_cb _hidl_cb) override {
+        return mIntf->querySupportedParams(start, count, _hidl_cb);
+    }
+
+    using querySupportedValues_cb = typename I::querySupportedValues_cb;
+    virtual Return<void> querySupportedValues(
+            const hidl_vec<FieldSupportedValuesQuery>& inFields,
+            bool mayBlock,
+            querySupportedValues_cb _hidl_cb) override {
+        return mIntf->querySupportedValues(inFields, mayBlock, _hidl_cb);
+    }
+
+protected:
+    sp<CachedConfigurable> mIntf;
+};
+
+}  // namespace implementation
+}  // namespace V1_0
+}  // namespace c2
+}  // namespace media
+}  // namespace google
+}  // namespace vendor
+
+#endif  // VENDOR_GOOGLE_MEDIA_C2_V1_0_CONFIGURABLE_H
diff --git a/media/libstagefright/codec2/hidl/impl/1.0/include/codec2/hidl/1.0/ConfigurableC2Intf.h b/media/libstagefright/codec2/hidl/impl/1.0/include/codec2/hidl/1.0/ConfigurableC2Intf.h
new file mode 100644
index 0000000..da90996
--- /dev/null
+++ b/media/libstagefright/codec2/hidl/impl/1.0/include/codec2/hidl/1.0/ConfigurableC2Intf.h
@@ -0,0 +1,80 @@
+/*
+ * 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 VENDOR_GOOGLE_MEDIA_C2_V1_0_CONFIGURABLEC2INTF_H
+#define VENDOR_GOOGLE_MEDIA_C2_V1_0_CONFIGURABLEC2INTF_H
+
+#include <C2Work.h>
+#include <C2Component.h>
+#include <C2Param.h>
+#include <C2.h>
+
+#include <hidl/HidlSupport.h>
+#include <utils/StrongPointer.h>
+#include <vector>
+#include <memory>
+
+namespace vendor {
+namespace google {
+namespace media {
+namespace c2 {
+namespace V1_0 {
+namespace implementation {
+
+using ::android::sp;
+using ::android::hardware::hidl_string;
+using ::android::hardware::hidl_vec;
+using ::android::hardware::Return;
+using ::android::hardware::Void;
+
+/**
+ * Common Codec 2.0 interface wrapper.
+ */
+struct ConfigurableC2Intf {
+    C2String getName() const { return mName; }
+    /** C2ComponentInterface::query_vb sans stack params */
+    virtual c2_status_t query(
+            const std::vector<C2Param::Index> &indices,
+            c2_blocking_t mayBlock,
+            std::vector<std::unique_ptr<C2Param>>* const params) const = 0;
+    /** C2ComponentInterface::config_vb */
+    virtual c2_status_t config(
+            const std::vector<C2Param*> &params,
+            c2_blocking_t mayBlock,
+            std::vector<std::unique_ptr<C2SettingResult>>* const failures) = 0;
+    /** C2ComponentInterface::querySupportedParams_nb */
+    virtual c2_status_t querySupportedParams(
+            std::vector<std::shared_ptr<C2ParamDescriptor>>* const params) const = 0;
+    /** C2ComponentInterface::querySupportedParams_nb */
+    virtual c2_status_t querySupportedValues(
+            std::vector<C2FieldSupportedValuesQuery>& fields, c2_blocking_t mayBlock) const = 0;
+
+    virtual ~ConfigurableC2Intf() = default;
+
+    ConfigurableC2Intf(const C2String& name) : mName(name) {}
+
+protected:
+    C2String mName; /* cache component name */
+};
+
+}  // namespace implementation
+}  // namespace V1_0
+}  // namespace c2
+}  // namespace media
+}  // namespace google
+}  // namespace vendor
+
+#endif  // VENDOR_GOOGLE_MEDIA_C2_V1_0_CONFIGURABLEC2INTF_H
diff --git a/media/libstagefright/codec2/hidl/impl/1.0/include/codec2/hidl/1.0/types.h b/media/libstagefright/codec2/hidl/impl/1.0/include/codec2/hidl/1.0/types.h
new file mode 100644
index 0000000..1eace56
--- /dev/null
+++ b/media/libstagefright/codec2/hidl/impl/1.0/include/codec2/hidl/1.0/types.h
@@ -0,0 +1,194 @@
+/*
+ * 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 VENDOR_GOOGLE_MEDIA_C2_V1_0_TYPES_H
+#define VENDOR_GOOGLE_MEDIA_C2_V1_0_TYPES_H
+
+#include <vendor/google/media/c2/1.0/types.h>
+#include <vendor/google/media/c2/1.0/IComponentStore.h>
+
+#include <C2Param.h>
+#include <C2Component.h>
+#include <C2Work.h>
+
+namespace vendor {
+namespace google {
+namespace media {
+namespace c2 {
+namespace V1_0 {
+namespace implementation {
+
+using ::android::hardware::hidl_bitfield;
+using ::android::hardware::hidl_handle;
+using ::android::hardware::hidl_string;
+using ::android::hardware::hidl_vec;
+using ::android::sp;
+
+// Types of metadata for Blocks.
+struct C2Hidl_Range {
+    uint32_t offset;
+    uint32_t length; // Do not use "size" because the name collides with C2Info::size().
+};
+typedef C2GlobalParam<C2Info, C2Hidl_Range, 0> C2Hidl_RangeInfo;
+
+struct C2Hidl_Rect {
+    uint32_t left;
+    uint32_t top;
+    uint32_t width;
+    uint32_t height;
+};
+typedef C2GlobalParam<C2Info, C2Hidl_Rect, 1> C2Hidl_RectInfo;
+
+// C2SettingResult -> SettingResult
+Status objcpy(
+        SettingResult* d,
+        const C2SettingResult& s);
+
+// SettingResult -> std::unique_ptr<C2SettingResult>
+c2_status_t objcpy(
+        std::unique_ptr<C2SettingResult>* d,
+        const SettingResult& s);
+
+// C2ParamDescriptor -> ParamDescriptor
+Status objcpy(
+        ParamDescriptor* d,
+        const C2ParamDescriptor& s);
+
+// ParamDescriptor -> std::shared_ptr<C2ParamDescriptor>
+c2_status_t objcpy(
+        std::shared_ptr<C2ParamDescriptor>* d,
+        const ParamDescriptor& s);
+
+// C2FieldSupportedValuesQuery -> FieldSupportedValuesQuery
+Status objcpy(
+        FieldSupportedValuesQuery* d,
+        const C2FieldSupportedValuesQuery& s);
+
+// FieldSupportedValuesQuery -> C2FieldSupportedValuesQuery
+c2_status_t objcpy(
+        C2FieldSupportedValuesQuery* d,
+        const FieldSupportedValuesQuery& s);
+
+// C2FieldSupportedValuesQuery -> FieldSupportedValuesQueryResult
+Status objcpy(
+        FieldSupportedValuesQueryResult* d,
+        const C2FieldSupportedValuesQuery& s);
+
+// FieldSupportedValuesQuery, FieldSupportedValuesQueryResult -> C2FieldSupportedValuesQuery
+c2_status_t objcpy(
+        C2FieldSupportedValuesQuery* d,
+        const FieldSupportedValuesQuery& sq,
+        const FieldSupportedValuesQueryResult& sr);
+
+// C2Component::Traits -> ComponentTraits
+Status objcpy(
+        IComponentStore::ComponentTraits* d,
+        const C2Component::Traits& s);
+
+// ComponentTraits -> C2Component::Traits, std::unique_ptr<std::vector<std::string>>
+// Note: The output d is only valid as long as aliasesBuffer remains alive.
+c2_status_t objcpy(
+        C2Component::Traits* d,
+        std::unique_ptr<std::vector<std::string>>* aliasesBuffer,
+        const IComponentStore::ComponentTraits& s);
+
+// C2StructDescriptor -> StructDescriptor
+Status objcpy(
+        StructDescriptor* d,
+        const C2StructDescriptor& s);
+
+// StructDescriptor -> C2StructDescriptor
+// TODO: This cannot be implemented yet because C2StructDescriptor does not
+// allow dynamic construction/modification.
+c2_status_t objcpy(
+        C2StructDescriptor* d,
+        const StructDescriptor& s);
+
+// std::list<std::unique_ptr<C2Work>> -> WorkBundle
+// TODO: Connect with Bufferpool
+Status objcpy(
+        WorkBundle* d,
+        const std::list<std::unique_ptr<C2Work>>& s);
+
+// WorkBundle -> std::list<std::unique_ptr<C2Work>>
+// TODO: Connect with Bufferpool
+c2_status_t objcpy(
+        std::list<std::unique_ptr<C2Work>>* d,
+        const WorkBundle& s);
+
+/**
+ * Parses a params blob and returns C2Param pointers to its params.
+ * \param[out] params target vector of C2Param pointers
+ * \param[in] blob parameter blob to parse
+ * \retval C2_OK if the full blob was parsed
+ * \retval C2_BAD_VALUE otherwise
+ */
+c2_status_t parseParamsBlob(
+        std::vector<C2Param*> *params,
+        const hidl_vec<uint8_t> &blob);
+
+/**
+ * Concatenates a list of C2Params into a params blob.
+ * \param[out] blob target blob
+ * \param[in] params parameters to concatenate
+ * \retval C2_OK if the blob was successfully created
+ * \retval C2_BAD_VALUE if the blob was not successful (this only happens if the parameters were
+ *         not const)
+ */
+Status createParamsBlob(
+        hidl_vec<uint8_t> *blob,
+        const std::vector<C2Param*> &params);
+Status createParamsBlob(
+        hidl_vec<uint8_t> *blob,
+        const std::vector<std::unique_ptr<C2Param>> &params);
+Status createParamsBlob(
+        hidl_vec<uint8_t> *blob,
+        const std::vector<std::shared_ptr<const C2Info>> &params);
+Status createParamsBlob(
+        hidl_vec<uint8_t> *blob,
+        const std::vector<std::unique_ptr<C2Tuning>> &params);
+
+/**
+ * Parses a params blob and create a vector of C2Params whose members are copies
+ * of the params in the blob.
+ * \param[out] params the resulting vector
+ * \param[in] blob parameter blob to parse
+ * \retval C2_OK if the full blob was parsed and params was constructed
+ * \retval C2_BAD_VALUE otherwise
+ */
+c2_status_t copyParamsFromBlob(
+        std::vector<std::unique_ptr<C2Param>>* params,
+        Params blob);
+
+/**
+ * Parses a params blob and applies updates to params
+ * \param[in,out] params params to be updated
+ * \param[in] blob parameter blob containing updates
+ * \retval C2_OK if the full blob was parsed and params was updated
+ * \retval C2_BAD_VALUE otherwise
+ */
+c2_status_t updateParamsFromBlob(
+        const std::vector<C2Param*>& params,
+        const Params& blob);
+
+}  // namespace implementation
+}  // namespace V1_0
+}  // namespace c2
+}  // namespace media
+}  // namespace google
+}  // namespace vendor
+
+#endif  // VENDOR_GOOGLE_MEDIA_C2_V1_0_TYPES_H
diff --git a/media/libstagefright/codec2/hidl/impl/1.0/types.cpp b/media/libstagefright/codec2/hidl/impl/1.0/types.cpp
new file mode 100644
index 0000000..f14c21a
--- /dev/null
+++ b/media/libstagefright/codec2/hidl/impl/1.0/types.cpp
@@ -0,0 +1,1265 @@
+/*
+ * 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
+#define LOG_TAG "Codec2-types"
+#include <log/log.h>
+
+#include <codec2/hidl/1.0/types.h>
+
+#include <C2AllocatorIon.h>
+#include <C2AllocatorGralloc.h>
+#include <C2PlatformSupport.h>
+#include <C2BlockInternal.h>
+#include <C2ParamInternal.h>
+#include <C2Param.h>
+#include <C2Buffer.h>
+#include <C2Work.h>
+#include <C2Component.h>
+#include <util/C2ParamUtils.h>
+
+#include <unordered_map>
+#include <algorithm>
+
+namespace vendor {
+namespace google {
+namespace media {
+namespace c2 {
+namespace V1_0 {
+namespace implementation {
+
+using namespace ::android;
+
+namespace /* unnamed */ {
+
+template <typename Common, typename DstVector, typename SrcVector>
+void copyVector(DstVector* d, const SrcVector& s) {
+    static_assert(sizeof(Common) == sizeof(decltype((*d)[0])),
+            "DstVector's component size does not match Common");
+    static_assert(sizeof(Common) == sizeof(decltype(s[0])),
+            "SrcVector's component size does not match Common");
+    d->resize(s.size());
+    std::copy(
+            reinterpret_cast<const Common*>(&s[0]),
+            reinterpret_cast<const Common*>(&s[0] + s.size()),
+            reinterpret_cast<Common*>(&(*d)[0]));
+}
+
+// C2ParamField -> ParamField
+void objcpy(ParamField *d, const C2ParamField &s) {
+    d->index = static_cast<ParamIndex>(_C2ParamInspector::GetIndex(s));
+    d->fieldId.offset = static_cast<uint32_t>(_C2ParamInspector::GetOffset(s));
+    d->fieldId.size = static_cast<uint32_t>(_C2ParamInspector::GetSize(s));
+}
+
+struct C2ParamFieldBuilder : public C2ParamField {
+    C2ParamFieldBuilder() : C2ParamField(
+            static_cast<C2Param::Index>(static_cast<uint32_t>(0)), 0, 0) {
+    }
+    // ParamField -> C2ParamField
+    C2ParamFieldBuilder(const ParamField& s) : C2ParamField(
+            static_cast<C2Param::Index>(static_cast<uint32_t>(s.index)),
+            static_cast<uint32_t>(s.fieldId.offset),
+            static_cast<uint32_t>(s.fieldId.size)) {
+    }
+};
+
+// C2WorkOrdinalStruct -> WorkOrdinal
+void objcpy(WorkOrdinal *d, const C2WorkOrdinalStruct &s) {
+    d->frameIndex = static_cast<uint64_t>(s.frameIndex.peeku());
+    d->timestampUs = static_cast<uint64_t>(s.timestamp.peeku());
+    d->customOrdinal = static_cast<uint64_t>(s.customOrdinal.peeku());
+}
+
+// WorkOrdinal -> C2WorkOrdinalStruct
+void objcpy(C2WorkOrdinalStruct *d, const WorkOrdinal &s) {
+    d->frameIndex = c2_cntr64_t(s.frameIndex);
+    d->timestamp = c2_cntr64_t(s.timestampUs);
+    d->customOrdinal = c2_cntr64_t(s.customOrdinal);
+}
+
+// C2FieldSupportedValues::range's type -> FieldSupportedValues::Range
+void objcpy(
+        FieldSupportedValues::Range* d,
+        const decltype(C2FieldSupportedValues::range)& s) {
+    d->min = static_cast<PrimitiveValue>(s.min.u64);
+    d->max = static_cast<PrimitiveValue>(s.max.u64);
+    d->step = static_cast<PrimitiveValue>(s.step.u64);
+    d->num = static_cast<PrimitiveValue>(s.num.u64);
+    d->denom = static_cast<PrimitiveValue>(s.denom.u64);
+}
+
+// C2FieldSupportedValues -> FieldSupportedValues
+Status objcpy(FieldSupportedValues *d, const C2FieldSupportedValues &s) {
+    d->typeOther = static_cast<int32_t>(s.type);
+    switch (s.type) {
+    case C2FieldSupportedValues::EMPTY:
+        d->type = FieldSupportedValues::Type::EMPTY;
+        d->values.resize(0);
+        return Status::OK;
+    case C2FieldSupportedValues::RANGE:
+        d->type = FieldSupportedValues::Type::RANGE;
+        objcpy(&d->range, s.range);
+        d->values.resize(0);
+        return Status::OK;
+    default:
+        switch (s.type) {
+        case C2FieldSupportedValues::VALUES:
+            d->type = FieldSupportedValues::Type::VALUES;
+            break;
+        case C2FieldSupportedValues::FLAGS:
+            d->type = FieldSupportedValues::Type::FLAGS;
+            break;
+        default:
+            d->type = FieldSupportedValues::Type::OTHER;
+            // Copy all fields in this case
+            objcpy(&d->range, s.range);
+        }
+        d->values.resize(s.values.size());
+        copyVector<uint64_t>(&d->values, s.values);
+        return Status::OK;
+    }
+}
+
+// FieldSupportedValues::Range -> C2FieldSupportedValues::range's type
+void objcpy(
+        decltype(C2FieldSupportedValues::range)* d,
+        const FieldSupportedValues::Range& s) {
+    d->min.u64 = static_cast<uint64_t>(s.min);
+    d->max.u64 = static_cast<uint64_t>(s.max);
+    d->step.u64 = static_cast<uint64_t>(s.step);
+    d->num.u64 = static_cast<uint64_t>(s.num);
+    d->denom.u64 = static_cast<uint64_t>(s.denom);
+}
+
+// FieldSupportedValues -> C2FieldSupportedValues
+c2_status_t objcpy(C2FieldSupportedValues *d, const FieldSupportedValues &s) {
+    switch (s.type) {
+    case FieldSupportedValues::Type::EMPTY:
+        d->type = C2FieldSupportedValues::EMPTY;
+        return C2_OK;
+    case FieldSupportedValues::Type::RANGE:
+        d->type = C2FieldSupportedValues::RANGE;
+        objcpy(&d->range, s.range);
+        d->values.resize(0);
+        return C2_OK;
+    default:
+        switch (s.type) {
+        case FieldSupportedValues::Type::VALUES:
+            d->type = C2FieldSupportedValues::VALUES;
+            break;
+        case FieldSupportedValues::Type::FLAGS:
+            d->type = C2FieldSupportedValues::FLAGS;
+            break;
+        default:
+            d->type = static_cast<C2FieldSupportedValues::type_t>(s.typeOther);
+            // Copy all fields in this case
+            objcpy(&d->range, s.range);
+        }
+        copyVector<uint64_t>(&d->values, s.values);
+        return C2_OK;
+    }
+}
+
+} // unnamed namespace
+
+// C2FieldSupportedValuesQuery -> FieldSupportedValuesQuery
+Status objcpy(
+        FieldSupportedValuesQuery* d,
+        const C2FieldSupportedValuesQuery& s) {
+    objcpy(&d->field, s.field());
+    switch (s.type()) {
+    case C2FieldSupportedValuesQuery::POSSIBLE:
+        d->type = FieldSupportedValuesQuery::Type::POSSIBLE;
+        break;
+    case C2FieldSupportedValuesQuery::CURRENT:
+        d->type = FieldSupportedValuesQuery::Type::CURRENT;
+        break;
+    default:
+        ALOGE("Unknown type of C2FieldSupportedValuesQuery: %u",
+                static_cast<unsigned>(s.type()));
+        return Status::BAD_VALUE;
+    }
+    return Status::OK;
+}
+
+// FieldSupportedValuesQuery -> C2FieldSupportedValuesQuery
+c2_status_t objcpy(
+        C2FieldSupportedValuesQuery* d,
+        const FieldSupportedValuesQuery& s) {
+    C2FieldSupportedValuesQuery::type_t dType;
+    switch (s.type) {
+    case FieldSupportedValuesQuery::Type::POSSIBLE:
+        dType = C2FieldSupportedValuesQuery::POSSIBLE;
+        break;
+    case FieldSupportedValuesQuery::Type::CURRENT:
+        dType = C2FieldSupportedValuesQuery::CURRENT;
+        break;
+    default:
+        ALOGE("Unknown type of FieldSupportedValuesQuery: %u",
+                static_cast<unsigned>(s.type));
+        return C2_BAD_VALUE;
+    }
+    *d = C2FieldSupportedValuesQuery(C2ParamFieldBuilder(s.field), dType);
+    return C2_OK;
+}
+
+// C2FieldSupportedValuesQuery -> FieldSupportedValuesQueryResult
+Status objcpy(
+        FieldSupportedValuesQueryResult* d,
+        const C2FieldSupportedValuesQuery& s) {
+    d->status = static_cast<Status>(s.status);
+    return objcpy(&d->values, s.values);
+}
+
+// FieldSupportedValuesQuery, FieldSupportedValuesQueryResult ->
+// C2FieldSupportedValuesQuery
+c2_status_t objcpy(
+        C2FieldSupportedValuesQuery* d,
+        const FieldSupportedValuesQuery& sq,
+        const FieldSupportedValuesQueryResult& sr) {
+    c2_status_t status = objcpy(d, sq);
+    if (status != C2_OK) {
+        return status;
+    }
+    d->status = static_cast<c2_status_t>(sr.status);
+    return objcpy(&d->values, sr.values);
+}
+
+// C2Component::Traits -> IComponentStore::ComponentTraits
+Status objcpy(
+        IComponentStore::ComponentTraits *d,
+        const C2Component::Traits &s) {
+    d->name = s.name;
+
+    // TODO: Currently, we do not have any domain values defined in Codec2.0.
+    d->domain = IComponentStore::ComponentTraits::Domain::OTHER;
+    d->domainOther = static_cast<uint32_t>(s.domain);
+
+    // TODO: Currently, we do not have any kind values defined in Codec2.0.
+    d->kind = IComponentStore::ComponentTraits::Kind::OTHER;
+    d->kindOther = static_cast<uint32_t>(s.kind);
+
+    d->rank = static_cast<uint32_t>(s.rank);
+
+    d->mediaType = s.mediaType;
+
+    d->aliases.resize(s.aliases.size());
+    for (size_t ix = s.aliases.size(); ix > 0; ) {
+        --ix;
+        d->aliases[ix] = s.aliases[ix];
+    }
+    return Status::OK;
+}
+
+// ComponentTraits -> C2Component::Traits, std::unique_ptr<std::vector<std::string>>
+c2_status_t objcpy(
+        C2Component::Traits* d,
+        std::unique_ptr<std::vector<std::string>>* aliasesBuffer,
+        const IComponentStore::ComponentTraits& s) {
+    d->name = s.name.c_str();
+    d->domain = static_cast<C2Component::domain_t>(s.domainOther);
+    d->kind = static_cast<C2Component::kind_t>(s.kindOther);
+    d->rank = static_cast<C2Component::rank_t>(s.rank);
+    d->mediaType = s.mediaType.c_str();
+
+    // aliasesBuffer must not be resized after this.
+    *aliasesBuffer = std::make_unique<std::vector<std::string>>(
+            s.aliases.size());
+    (*aliasesBuffer)->resize(s.aliases.size());
+    std::vector<C2StringLiteral> dAliases(s.aliases.size());
+    for (size_t i = 0; i < s.aliases.size(); ++i) {
+        (**aliasesBuffer)[i] = s.aliases[i].c_str();
+        d->aliases[i] = (**aliasesBuffer)[i].c_str();
+    }
+    return C2_OK;
+}
+
+namespace /* unnamed */ {
+
+// C2ParamFieldValues -> ParamFieldValues
+Status objcpy(ParamFieldValues *d, const C2ParamFieldValues &s) {
+    objcpy(&d->paramOrField, s.paramOrField);
+    if (s.values) {
+        d->values.resize(1);
+        return objcpy(&d->values[0], *s.values);
+    }
+    d->values.resize(0);
+    return Status::OK;
+}
+
+// ParamFieldValues -> C2ParamFieldValues
+c2_status_t objcpy(C2ParamFieldValues *d, const ParamFieldValues &s) {
+    d->paramOrField = C2ParamFieldBuilder(s.paramOrField);
+    if (s.values.size() == 1) {
+        d->values = std::make_unique<C2FieldSupportedValues>();
+        return objcpy(d->values.get(), s.values[0]);
+    } else if (s.values.size() == 0) {
+        d->values.reset();
+        return C2_OK;
+    }
+    ALOGE("Multiple FieldSupportedValues objects. "
+            "(Only one is allowed.)");
+    return C2_BAD_VALUE;
+}
+
+} // unnamed namespace
+
+// C2SettingResult -> SettingResult
+Status objcpy(SettingResult *d, const C2SettingResult &s) {
+    d->failureOther = static_cast<uint32_t>(s.failure);
+    switch (s.failure) {
+    case C2SettingResult::READ_ONLY:
+        d->failure = SettingResult::Failure::READ_ONLY;
+        break;
+    case C2SettingResult::MISMATCH:
+        d->failure = SettingResult::Failure::MISMATCH;
+        break;
+    case C2SettingResult::BAD_VALUE:
+        d->failure = SettingResult::Failure::BAD_VALUE;
+        break;
+    case C2SettingResult::BAD_TYPE:
+        d->failure = SettingResult::Failure::BAD_TYPE;
+        break;
+    case C2SettingResult::BAD_PORT:
+        d->failure = SettingResult::Failure::BAD_PORT;
+        break;
+    case C2SettingResult::BAD_INDEX:
+        d->failure = SettingResult::Failure::BAD_INDEX;
+        break;
+    case C2SettingResult::CONFLICT:
+        d->failure = SettingResult::Failure::CONFLICT;
+        break;
+    case C2SettingResult::UNSUPPORTED:
+        d->failure = SettingResult::Failure::UNSUPPORTED;
+        break;
+    case C2SettingResult::INFO_CONFLICT:
+        d->failure = SettingResult::Failure::INFO_CONFLICT;
+        break;
+    default:
+        d->failure = SettingResult::Failure::OTHER;
+    }
+    Status status = objcpy(&d->field, s.field);
+    if (status != Status::OK) {
+        return status;
+    }
+    d->conflicts.resize(s.conflicts.size());
+    size_t i = 0;
+    for (const C2ParamFieldValues& sConflict : s.conflicts) {
+        ParamFieldValues &dConflict = d->conflicts[i++];
+        status = objcpy(&dConflict, sConflict);
+        if (status != Status::OK) {
+            return status;
+        }
+    }
+    return Status::OK;
+}
+
+// SettingResult -> std::unique_ptr<C2SettingResult>
+c2_status_t objcpy(std::unique_ptr<C2SettingResult> *d, const SettingResult &s) {
+    *d = std::unique_ptr<C2SettingResult>(new C2SettingResult {
+            .field = C2ParamFieldValues(C2ParamFieldBuilder()) });
+    if (!*d) {
+        return C2_NO_MEMORY;
+    }
+
+    // failure
+    switch (s.failure) {
+    case SettingResult::Failure::READ_ONLY:
+        (*d)->failure = C2SettingResult::READ_ONLY;
+        break;
+    case SettingResult::Failure::MISMATCH:
+        (*d)->failure = C2SettingResult::MISMATCH;
+        break;
+    case SettingResult::Failure::BAD_VALUE:
+        (*d)->failure = C2SettingResult::BAD_VALUE;
+        break;
+    case SettingResult::Failure::BAD_TYPE:
+        (*d)->failure = C2SettingResult::BAD_TYPE;
+        break;
+    case SettingResult::Failure::BAD_PORT:
+        (*d)->failure = C2SettingResult::BAD_PORT;
+        break;
+    case SettingResult::Failure::BAD_INDEX:
+        (*d)->failure = C2SettingResult::BAD_INDEX;
+        break;
+    case SettingResult::Failure::CONFLICT:
+        (*d)->failure = C2SettingResult::CONFLICT;
+        break;
+    case SettingResult::Failure::UNSUPPORTED:
+        (*d)->failure = C2SettingResult::UNSUPPORTED;
+        break;
+    case SettingResult::Failure::INFO_CONFLICT:
+        (*d)->failure = C2SettingResult::INFO_CONFLICT;
+        break;
+    default:
+        (*d)->failure = static_cast<C2SettingResult::Failure>(s.failureOther);
+    }
+
+    // field
+    c2_status_t status = objcpy(&(*d)->field, s.field);
+    if (status != C2_OK) {
+        return status;
+    }
+
+    // conflicts
+    (*d)->conflicts.clear();
+    (*d)->conflicts.reserve(s.conflicts.size());
+    for (const ParamFieldValues& sConflict : s.conflicts) {
+        (*d)->conflicts.emplace_back(
+                C2ParamFieldValues{ C2ParamFieldBuilder(), nullptr });
+        status = objcpy(&(*d)->conflicts.back(), sConflict);
+        if (status != C2_OK) {
+            return status;
+        }
+    }
+    return C2_OK;
+}
+
+// C2ParamDescriptor -> ParamDescriptor
+Status objcpy(ParamDescriptor *d, const C2ParamDescriptor &s) {
+    d->index = static_cast<ParamIndex>(s.index());
+    d->attrib = static_cast<hidl_bitfield<ParamDescriptor::Attrib>>(
+            _C2ParamInspector::GetAttrib(s));
+    d->name = s.name();
+    copyVector<uint32_t>(&d->dependencies, s.dependencies());
+    return Status::OK;
+}
+
+// ParamDescriptor -> C2ParamDescriptor
+c2_status_t objcpy(std::shared_ptr<C2ParamDescriptor> *d, const ParamDescriptor &s) {
+    std::vector<C2Param::Index> dDependencies;
+    dDependencies.reserve(s.dependencies.size());
+    for (const ParamIndex& sDependency : s.dependencies) {
+        dDependencies.emplace_back(static_cast<uint32_t>(sDependency));
+    }
+    *d = std::make_shared<C2ParamDescriptor>(
+            C2Param::Index(static_cast<uint32_t>(s.index)),
+            static_cast<C2ParamDescriptor::attrib_t>(s.attrib),
+            C2String(s.name.c_str()),
+            std::move(dDependencies));
+    return C2_OK;
+}
+
+// C2StructDescriptor -> StructDescriptor
+Status objcpy(StructDescriptor *d, const C2StructDescriptor &s) {
+    d->type = static_cast<ParamIndex>(s.coreIndex().coreIndex());
+    d->fields.resize(s.numFields());
+    size_t i = 0;
+    for (const auto& sField : s) {
+        FieldDescriptor& dField = d->fields[i++];
+        dField.fieldId.offset = static_cast<uint32_t>(
+                _C2ParamInspector::GetOffset(sField));
+        dField.fieldId.size = static_cast<uint32_t>(
+                _C2ParamInspector::GetSize(sField));
+        dField.type = static_cast<hidl_bitfield<FieldDescriptor::Type>>(
+                sField.type());
+        dField.length = static_cast<uint32_t>(sField.extent());
+        dField.name = static_cast<hidl_string>(sField.name());
+        const auto& sNamedValues = sField.namedValues();
+        dField.namedValues.resize(sNamedValues.size());
+        size_t j = 0;
+        for (const auto& sNamedValue : sNamedValues) {
+            FieldDescriptor::NamedValue& dNamedValue = dField.namedValues[j++];
+            dNamedValue.name = static_cast<hidl_string>(sNamedValue.first);
+            dNamedValue.value = static_cast<PrimitiveValue>(
+                    sNamedValue.second.u64);
+        }
+    }
+    return Status::OK;
+}
+
+// StructDescriptor -> C2StructDescriptor
+c2_status_t objcpy(C2StructDescriptor *d, const StructDescriptor &s) {
+    // TODO: Implement this when C2StructDescriptor can be dynamically
+    // constructed.
+    (void)d;
+    (void)s;
+    ALOGE("Conversion StructDescriptor -> C2StructDescriptor "
+            "not implemented.");
+    return C2_OMITTED;
+}
+
+// Finds or adds a hidl BaseBlock object from a given C2Handle* to a list and an
+// associated map.
+// Note: Native handles are not duplicated. The original handles must not be
+// closed before the transaction is complete.
+namespace /* unnamed */ {
+
+Status addBaseBlock(uint32_t* index, const C2Handle* handle,
+        std::vector<BaseBlock>* baseBlocks,
+        std::map<const C2Handle*, uint32_t>* baseBlockIndices) {
+    if (handle == nullptr) {
+        return Status::BAD_VALUE;
+    }
+    auto it = baseBlockIndices->find(handle);
+    if (it != baseBlockIndices->end()) {
+        *index = it->second;
+    } else {
+        *index = baseBlocks->size();
+        BaseBlock dBaseBlock;
+        // TODO: Use BufferPool.
+        dBaseBlock.type = BaseBlock::Type::NATIVE;
+        // This does not clone the handle.
+        dBaseBlock.nativeBlock =
+                reinterpret_cast<const native_handle_t*>(handle);
+        baseBlocks->push_back(dBaseBlock);
+        baseBlockIndices->emplace(handle, *index);
+    }
+    return Status::OK;
+}
+
+// C2Fence -> hidl_handle
+// Note: File descriptors are not duplicated. The original file descriptor must
+// not be closed before the transaction is complete.
+Status objcpy(hidl_handle* d, const C2Fence& s) {
+    (void)s; // TODO: implement s.fd()
+    int fenceFd = -1;
+    d->setTo(nullptr);
+    if (fenceFd >= 0) {
+        native_handle_t *handle = native_handle_create(1, 0);
+        if (!handle) {
+            return Status::NO_MEMORY;
+        }
+        handle->data[0] = fenceFd;
+        d->setTo(handle, true /* owns */);
+    }
+    return Status::OK;
+}
+
+// C2ConstLinearBlock -> Block
+// Note: Native handles are not duplicated. The original handles must not be
+// closed before the transaction is complete.
+Status objcpy(Block* d, const C2ConstLinearBlock& s,
+        std::vector<BaseBlock>* baseBlocks,
+        std::map<const C2Handle*, uint32_t>* baseBlockIndices) {
+    // Find the BaseBlock index.
+    // TODO: Use BufferPool.
+    Status status = addBaseBlock(
+            &d->index, s.handle(), baseBlocks, baseBlockIndices);
+    if (status != Status::OK) {
+        return status;
+    }
+
+    // Create the metadata.
+    C2Hidl_RangeInfo dRangeInfo;
+    dRangeInfo.offset = static_cast<uint32_t>(s.offset());
+    dRangeInfo.length = static_cast<uint32_t>(s.size());
+    status = createParamsBlob(&d->meta,
+            std::vector<C2Param*>{ &dRangeInfo });
+    if (status != Status::OK) {
+        return Status::BAD_VALUE;
+    }
+
+    // Copy the fence
+    return objcpy(&d->fence, s.fence());
+}
+
+// C2ConstGraphicBlock -> Block
+// Note: Native handles are not duplicated. The original handles must not be
+// closed before the transaction is complete.
+Status objcpy(Block* d, const C2ConstGraphicBlock& s,
+        std::vector<BaseBlock>* baseBlocks,
+        std::map<const C2Handle*, uint32_t>* baseBlockIndices) {
+    // Find the BaseBlock index.
+    // TODO: Use BufferPool.
+    Status status = addBaseBlock(
+            &d->index, s.handle(), baseBlocks, baseBlockIndices);
+    if (status != Status::OK) {
+        return status;
+    }
+
+    // Create the metadata.
+    C2Hidl_RectInfo dRectInfo;
+    C2Rect sRect = s.crop();
+    dRectInfo.left = static_cast<uint32_t>(sRect.left);
+    dRectInfo.top = static_cast<uint32_t>(sRect.top);
+    dRectInfo.width = static_cast<uint32_t>(sRect.width);
+    dRectInfo.height = static_cast<uint32_t>(sRect.height);
+    status = createParamsBlob(&d->meta,
+            std::vector<C2Param*>{ &dRectInfo });
+    if (status != Status::OK) {
+        return Status::BAD_VALUE;
+    }
+
+    // Copy the fence
+    return objcpy(&d->fence, s.fence());
+}
+
+// C2BufferData -> Buffer
+// This function only fills in d->blocks.
+Status objcpy(Buffer* d, const C2BufferData& s,
+        std::vector<BaseBlock>* baseBlocks,
+        std::map<const C2Handle*, uint32_t>* baseBlockIndices) {
+    Status status;
+    d->blocks.resize(
+            s.linearBlocks().size() +
+            s.graphicBlocks().size());
+    size_t i = 0;
+    for (const C2ConstLinearBlock& linearBlock : s.linearBlocks()) {
+        Block& dBlock = d->blocks[i++];
+        status = objcpy(
+                &dBlock, linearBlock, baseBlocks, baseBlockIndices);
+        if (status != Status::OK) {
+            return status;
+        }
+    }
+    for (const C2ConstGraphicBlock& graphicBlock : s.graphicBlocks()) {
+        Block& dBlock = d->blocks[i++];
+        status = objcpy(
+                &dBlock, graphicBlock, baseBlocks, baseBlockIndices);
+        if (status != Status::OK) {
+            return status;
+        }
+    }
+    return Status::OK;
+}
+
+// C2Buffer -> Buffer
+Status objcpy(Buffer* d, const C2Buffer& s,
+        std::vector<BaseBlock>* baseBlocks,
+        std::map<const C2Handle*, uint32_t>* baseBlockIndices) {
+    Status status = createParamsBlob(&d->info, s.info());
+    if (status != Status::OK) {
+        return status;
+    }
+    return objcpy(d, s.data(), baseBlocks, baseBlockIndices);
+}
+
+// C2InfoBuffer -> InfoBuffer
+Status objcpy(InfoBuffer* d, const C2InfoBuffer& s,
+        std::vector<BaseBlock>* baseBlocks,
+        std::map<const C2Handle*, uint32_t>* baseBlockIndices) {
+    // TODO: C2InfoBuffer is not implemented.
+    (void)d;
+    (void)s;
+    (void)baseBlocks;
+    (void)baseBlockIndices;
+    return Status::OK;
+    /*
+    // Stub implementation that may work in the future.
+    d->index = static_cast<uint32_t>(s.index());
+    d->buffer.info.resize(0);
+    return objcpy(&d->buffer, s.data(), baseBlocks, baseBlockIndices);
+    */
+}
+
+// C2FrameData -> FrameData
+Status objcpy(FrameData* d, const C2FrameData& s,
+        std::vector<BaseBlock>* baseBlocks,
+        std::map<const C2Handle*, uint32_t>* baseBlockIndices) {
+    d->flags = static_cast<hidl_bitfield<FrameData::Flags>>(s.flags);
+    objcpy(&d->ordinal, s.ordinal);
+
+    Status status;
+    d->buffers.resize(s.buffers.size());
+    size_t i = 0;
+    for (const std::shared_ptr<C2Buffer>& sBuffer : s.buffers) {
+        Buffer& dBuffer = d->buffers[i++];
+        if (!sBuffer) {
+            ALOGE("Null C2Buffer");
+            return Status::BAD_VALUE;
+        }
+        status = objcpy(&dBuffer, *sBuffer, baseBlocks, baseBlockIndices);
+        if (status != Status::OK) {
+            return status;
+        }
+    }
+
+    status = createParamsBlob(&d->configUpdate, s.configUpdate);
+    if (status != Status::OK) {
+        return status;
+    }
+
+    d->infoBuffers.resize(s.infoBuffers.size());
+    i = 0;
+    for (const std::shared_ptr<C2InfoBuffer>& sInfoBuffer : s.infoBuffers) {
+        InfoBuffer& dInfoBuffer = d->infoBuffers[i++];
+        if (!sInfoBuffer) {
+            ALOGE("Null C2InfoBuffer");
+            return Status::BAD_VALUE;
+        }
+        status = objcpy(&dInfoBuffer, *sInfoBuffer, baseBlocks, baseBlockIndices);
+        if (status != Status::OK) {
+            return status;
+        }
+    }
+
+    return status;
+}
+
+} // unnamed namespace
+
+// std::list<std::unique_ptr<C2Work>> -> WorkBundle
+// TODO: Connect with Bufferpool
+Status objcpy(WorkBundle* d, const std::list<std::unique_ptr<C2Work>>& s) {
+    Status status = Status::OK;
+
+    std::vector<BaseBlock> baseBlocks;
+    std::map<const C2Handle*, uint32_t> baseBlockIndices;
+    d->works.resize(s.size());
+    size_t i = 0;
+    for (const std::unique_ptr<C2Work>& sWork : s) {
+        Work &dWork = d->works[i++];
+        if (!sWork) {
+            ALOGW("Null C2Work encountered.");
+            continue;
+        }
+        status = objcpy(&dWork.input, sWork->input,
+                &baseBlocks, &baseBlockIndices);
+        if (status != Status::OK) {
+            return status;
+        }
+        if (sWork->worklets.size() == 0) {
+            ALOGW("Work with no worklets.");
+        } else {
+            if (sWork->worklets.size() > 1) {
+                ALOGW("Work with multiple worklets. "
+                        "Only the first worklet will be marshalled.");
+            }
+            if (!sWork->worklets.front()) {
+                ALOGE("Null worklet encountered.");
+                return Status::BAD_VALUE;
+            }
+
+            // Parcel the first worklet.
+            const C2Worklet &sWorklet = *sWork->worklets.front();
+            Worklet &dWorklet = dWork.worklet;
+
+            dWorklet.tunings.resize(sWorklet.tunings.size());
+            size_t j = 0;
+            for (const std::unique_ptr<C2Tuning>& sTuning : sWorklet.tunings) {
+                status = createParamsBlob(
+                        &dWorklet.tunings[j++],
+                        std::vector<C2Param*>
+                        { reinterpret_cast<C2Param*>(sTuning.get()) });
+                if (status != Status::OK) {
+                    return status;
+                }
+            }
+
+            dWorklet.failures.resize(sWorklet.failures.size());
+            j = 0;
+            for (const std::unique_ptr<C2SettingResult>& sFailure :
+                    sWorklet.failures) {
+                if (!sFailure) {
+                    ALOGE("Null C2SettingResult");
+                    return Status::BAD_VALUE;
+                }
+                status = objcpy(&dWorklet.failures[j++], *sFailure);
+                if (status != Status::OK) {
+                    return status;
+                }
+            }
+
+            status = objcpy(&dWorklet.output, sWorklet.output,
+                    &baseBlocks, &baseBlockIndices);
+            if (status != Status::OK) {
+                return status;
+            }
+        }
+        dWork.workletProcessed = sWork->workletsProcessed > 0;
+        dWork.result = static_cast<Status>(sWork->result);
+    }
+
+    d->baseBlocks = baseBlocks;
+
+    return Status::OK;
+}
+
+namespace /* unnamed */ {
+
+// hidl_handle -> C2Fence
+// Note: File descriptors are not duplicated. The original file descriptor must
+// not be closed before the transaction is complete.
+c2_status_t objcpy(C2Fence* d, const hidl_handle& s) {
+    // TODO: Implement.
+    (void)s;
+    *d = C2Fence();
+    return C2_OK;
+}
+
+// Buffer -> C2Buffer
+// Note: The native handles will be cloned.
+c2_status_t objcpy(std::shared_ptr<C2Buffer>* d, const Buffer& s,
+        const hidl_vec<BaseBlock>& baseBlocks) {
+    c2_status_t status;
+
+    // First, construct C2Buffer with blocks from s.blocks.
+    *d = nullptr;
+
+    // TODO: Only buffers with 1 block are supported.
+    if (s.blocks.size() == 1) {
+        // Obtain the BaseBlock.
+        const Block &sBlock = s.blocks[0];
+        if (sBlock.index >= baseBlocks.size()) {
+            ALOGE("Index into baseBlocks is out of range.");
+            return C2_BAD_VALUE;
+        }
+        const BaseBlock &sBaseBlock = baseBlocks[sBlock.index];
+
+        // Parse meta.
+        std::vector<C2Param*> sBlockMeta;
+        status = parseParamsBlob(&sBlockMeta, sBlock.meta);
+        if (status != C2_OK) {
+            ALOGE("Invalid block params blob.");
+            return C2_BAD_VALUE;
+        }
+
+        // Copy fence.
+        C2Fence dFence;
+        status = objcpy(&dFence, sBlock.fence);
+
+        // Construct a block.
+        switch (sBaseBlock.type) {
+        case BaseBlock::Type::NATIVE: {
+            const native_handle_t* sHandle = sBaseBlock.nativeBlock;
+            if (sHandle == nullptr) {
+                ALOGE("Null native handle in a block.");
+                return C2_BAD_VALUE;
+            }
+            sHandle = native_handle_clone(sHandle);
+            if (sHandle == nullptr) {
+                ALOGE("Cannot clone native handle.");
+                return C2_NO_MEMORY;
+            }
+            const C2Handle *sC2Handle =
+                    reinterpret_cast<const C2Handle*>(sHandle);
+
+            // Currently, there are only 2 types of C2Allocation: ion and
+            // gralloc.
+            if (C2AllocatorIon::isValid(sC2Handle)) {
+                // Check the block meta. It should have exactly 1 C2Info:
+                // C2Hidl_RangeInfo.
+                if ((sBlockMeta.size() != 1) || !sBlockMeta[0]) {
+                    ALOGE("Invalid block metadata for ion block.");
+                    return C2_BAD_VALUE;
+                }
+                if (sBlockMeta[0]->size() != sizeof(C2Hidl_RangeInfo)) {
+                    ALOGE("Invalid block metadata for ion block: range.");
+                    return C2_BAD_VALUE;
+                }
+                C2Hidl_RangeInfo *rangeInfo =
+                        reinterpret_cast<C2Hidl_RangeInfo*>(sBlockMeta[0]);
+
+                std::shared_ptr<C2Allocator> allocator;
+                c2_status_t status = GetCodec2PlatformAllocatorStore(
+                        )->fetchAllocator(
+                        C2PlatformAllocatorStore::ION,
+                        &allocator);
+                if (status != C2_OK) {
+                    ALOGE("Cannot fetch platform linear allocator.");
+                    return status;
+                }
+                std::shared_ptr<C2LinearAllocation> allocation;
+                status = allocator->priorLinearAllocation(
+                        sC2Handle, &allocation);
+                if (status != C2_OK) {
+                    ALOGE("Error constructing linear allocation.");
+                    return status;
+                } else if (!allocation) {
+                    ALOGE("Null linear allocation.");
+                    return C2_BAD_VALUE;
+                }
+                std::shared_ptr<C2LinearBlock> block =
+                        _C2BlockFactory::CreateLinearBlock(allocation);
+                if (!block) {
+                    ALOGE("Cannot create a block.");
+                    return C2_BAD_VALUE;
+                }
+                *d = C2Buffer::CreateLinearBuffer(block->share(
+                        rangeInfo->offset, rangeInfo->length, dFence));
+                if (!(*d)) {
+                    ALOGE("Cannot create a linear buffer.");
+                    return C2_BAD_VALUE;
+                }
+            } else if (C2AllocatorGralloc::isValid(sC2Handle)) {
+                // Check the block meta. It should have exactly 1 C2Info:
+                // C2Hidl_RectInfo.
+                if ((sBlockMeta.size() != 1) || !sBlockMeta[0]) {
+                    ALOGE("Invalid block metadata for graphic block.");
+                    return C2_BAD_VALUE;
+                }
+                if (sBlockMeta[0]->size() != sizeof(C2Hidl_RectInfo)) {
+                    ALOGE("Invalid block metadata for graphic block: crop rect.");
+                    return C2_BAD_VALUE;
+                }
+                C2Hidl_RectInfo *rectInfo =
+                        reinterpret_cast<C2Hidl_RectInfo*>(sBlockMeta[0]);
+
+                std::shared_ptr<C2Allocator> allocator;
+                c2_status_t status = GetCodec2PlatformAllocatorStore(
+                        )->fetchAllocator(
+                        C2PlatformAllocatorStore::GRALLOC,
+                        &allocator);
+                if (status != C2_OK) {
+                    ALOGE("Cannot fetch platform graphic allocator.");
+                    return status;
+                }
+
+                std::shared_ptr<C2GraphicAllocation> allocation;
+                status = allocator->priorGraphicAllocation(
+                        sC2Handle, &allocation);
+                if (status != C2_OK) {
+                    ALOGE("Error constructing graphic allocation.");
+                    return status;
+                } else if (!allocation) {
+                    ALOGE("Null graphic allocation.");
+                    return C2_BAD_VALUE;
+                }
+                std::shared_ptr<C2GraphicBlock> block =
+                        _C2BlockFactory::CreateGraphicBlock(allocation);
+                if (!block) {
+                    ALOGE("Cannot create a block.");
+                    return C2_BAD_VALUE;
+                }
+                *d = C2Buffer::CreateGraphicBuffer(block->share(
+                        C2Rect(rectInfo->width, rectInfo->height,
+                               rectInfo->left, rectInfo->top),
+                        dFence));
+                if (!(*d)) {
+                    ALOGE("Cannot create a graphic buffer.");
+                    return C2_BAD_VALUE;
+                }
+            } else {
+                ALOGE("Unknown handle type.");
+                return C2_BAD_VALUE;
+            }
+            break;
+        }
+        case BaseBlock::Type::POOLED: {
+            // TODO: Implement. Use BufferPool.
+            return C2_OMITTED;
+        }
+        default:
+            ALOGE("Invalid BaseBlock type.");
+            return C2_BAD_VALUE;
+        }
+    } else {
+        ALOGE("Currently a buffer must contain exactly 1 block.");
+        return C2_BAD_VALUE;
+    }
+
+    // Parse info
+    std::vector<C2Param*> params;
+    status = parseParamsBlob(&params, s.info);
+    if (status != C2_OK) {
+        ALOGE("Invalid buffer params blob.");
+        return status;
+    }
+    for (C2Param* param : params) {
+        if (param == nullptr) {
+            ALOGE("Null buffer param encountered.");
+            return C2_BAD_VALUE;
+        }
+        std::shared_ptr<C2Param> c2param(
+                C2Param::Copy(*param).release());
+        if (!c2param) {
+            ALOGE("Invalid buffer param inside a blob.");
+            return C2_BAD_VALUE;
+        }
+        status = (*d)->setInfo(std::static_pointer_cast<C2Info>(c2param));
+        if (status != C2_OK) {
+            ALOGE("C2Buffer::setInfo failed().");
+            return C2_BAD_VALUE;
+        }
+    }
+
+    return C2_OK;
+}
+
+// FrameData -> C2FrameData
+c2_status_t objcpy(C2FrameData* d, const FrameData& s,
+        const hidl_vec<BaseBlock>& baseBlocks) {
+    c2_status_t status;
+    d->flags = static_cast<C2FrameData::flags_t>(s.flags);
+    objcpy(&d->ordinal, s.ordinal);
+    d->buffers.clear();
+    d->buffers.reserve(s.buffers.size());
+    for (const Buffer& sBuffer : s.buffers) {
+        std::shared_ptr<C2Buffer> dBuffer;
+        status = objcpy(&dBuffer, sBuffer, baseBlocks);
+        if (status != C2_OK) {
+            return status;
+        }
+        d->buffers.emplace_back(dBuffer);
+    }
+
+    std::vector<C2Param*> params;
+    status = parseParamsBlob(&params, s.configUpdate);
+    if (status != C2_OK) {
+        ALOGE("Failed to parse frame data params.");
+        return status;
+    }
+    d->configUpdate.clear();
+    for (C2Param* param : params) {
+        d->configUpdate.emplace_back(C2Param::Copy(*param));
+        if (!d->configUpdate.back()) {
+            ALOGE("Unexpected error while parsing frame data params.");
+            return C2_BAD_VALUE;
+        }
+    }
+
+    // TODO: Implement this once C2InfoBuffer has constructors.
+    d->infoBuffers.clear();
+    return C2_OK;
+}
+
+} // unnamed namespace
+
+// WorkBundle -> std::list<std::unique_ptr<C2Work>>
+// TODO: Connect with Bufferpool
+c2_status_t objcpy(std::list<std::unique_ptr<C2Work>>* d, const WorkBundle& s) {
+    c2_status_t status;
+    d->clear();
+    for (const Work& sWork : s.works) {
+        d->emplace_back(std::make_unique<C2Work>());
+        C2Work& dWork = *d->back();
+
+        // input
+        status = objcpy(&dWork.input, sWork.input, s.baseBlocks);
+        if (status != C2_OK) {
+            ALOGE("Error constructing C2Work's input.");
+            return C2_BAD_VALUE;
+        }
+
+        // worklet(s)
+        // TODO: Currently, tunneling is not supported.
+        if (sWork.workletProcessed) {
+            dWork.worklets.clear();
+            dWork.workletsProcessed = 1;
+
+            const Worklet &sWorklet = sWork.worklet;
+            std::unique_ptr<C2Worklet> dWorklet = std::make_unique<C2Worklet>();
+
+            // tunings
+            dWorklet->tunings.clear();
+            dWorklet->tunings.reserve(sWorklet.tunings.size());
+            for (const Params& sTuning : sWorklet.tunings) {
+                std::vector<C2Param*> dParams;
+                status = parseParamsBlob(&dParams, sTuning);
+                if (status != C2_OK) {
+                    ALOGE("Failed to parse C2Tuning in C2Worklet.");
+                    return C2_BAD_VALUE;
+                }
+                for (C2Param* param : dParams) {
+                    std::unique_ptr<C2Param> dParam = C2Param::Copy(*param);
+                    if (!dParam) {
+                        ALOGE("Null C2Tuning encountered while "
+                                "parsing C2Worklet.");
+                        return C2_BAD_VALUE;
+                    }
+                    dWorklet->tunings.emplace_back(
+                            std::unique_ptr<C2Tuning>(
+                            reinterpret_cast<C2Tuning*>(
+                            dParam.release())));
+                }
+            }
+            // failures
+            dWorklet->failures.clear();
+            dWorklet->failures.reserve(sWorklet.failures.size());
+            for (const SettingResult& sFailure : sWorklet.failures) {
+                std::unique_ptr<C2SettingResult> dFailure;
+                status = objcpy(&dFailure, sFailure);
+                if (status != C2_OK) {
+                    ALOGE("Failed to create C2SettingResult in C2Worklet.");
+                    return C2_BAD_VALUE;
+                }
+                dWorklet->failures.emplace_back(std::move(dFailure));
+            }
+            // output
+            status = objcpy(&dWorklet->output, sWorklet.output, s.baseBlocks);
+            if (status != C2_OK) {
+                ALOGE("Failed to create output C2FrameData.");
+                return C2_BAD_VALUE;
+            }
+            dWork.worklets.emplace_back(std::move(dWorklet));
+        } else {
+            dWork.worklets.clear();
+            dWork.workletsProcessed = 0;
+        }
+
+        // result
+        dWork.result = static_cast<c2_status_t>(sWork.result);
+    }
+
+    return C2_OK;
+}
+
+// Params -> std::vector<C2Param*>
+c2_status_t parseParamsBlob(std::vector<C2Param*> *params, const hidl_vec<uint8_t> &blob) {
+    // assuming blob is const here
+    size_t size = blob.size();
+    const uint8_t *data = blob.data();
+    C2Param *p = nullptr;
+
+    do {
+        p = C2ParamUtils::ParseFirst(data, size);
+        if (p) {
+            params->emplace_back(p);
+            size -= p->size();
+            data += p->size();
+        }
+    } while (p);
+
+    return size == 0 ? C2_OK : C2_BAD_VALUE;
+}
+
+namespace /* unnamed */ {
+
+/**
+ * Concatenates a list of C2Params into a params blob.
+ * \param[out] blob target blob
+ * \param[in] params parameters to concatenate
+ * \retval C2_OK if the blob was successfully created
+ * \retval C2_BAD_VALUE if the blob was not successful (this only happens if the parameters were
+ *         not const)
+ */
+template<typename T>
+Status _createParamsBlob(hidl_vec<uint8_t> *blob, const T &params) {
+    // assuming the parameter values are const
+    size_t size = 0;
+    for (const auto &p : params) {
+        size += p->size();
+    }
+    blob->resize(size);
+    size_t ix = 0;
+    for (const auto &p : params) {
+        // NEVER overwrite even if param values (e.g. size) changed
+        size_t paramSize = std::min(p->size(), size - ix);
+//        memcpy(&blob[ix], &*p, paramSize);
+        std::copy(
+                reinterpret_cast<const uint8_t*>(&*p),
+                reinterpret_cast<const uint8_t*>(&*p) + paramSize,
+                &blob[ix]);
+        ix += paramSize;
+    }
+    blob->resize(ix);
+    return ix == size ? Status::OK : Status::CORRUPTED;
+}
+
+} // unnamed namespace
+
+// std::vector<const C2Param*> -> Params
+Status createParamsBlob(
+        hidl_vec<uint8_t> *blob,
+        const std::vector<const C2Param*> &params) {
+    return _createParamsBlob(blob, params);
+}
+
+// std::vector<C2Param*> -> Params
+Status createParamsBlob(
+        hidl_vec<uint8_t> *blob,
+        const std::vector<C2Param*> &params) {
+    return _createParamsBlob(blob, params);
+}
+
+// std::vector<std::unique_ptr<C2Param>> -> Params
+Status createParamsBlob(
+        hidl_vec<uint8_t> *blob,
+        const std::vector<std::unique_ptr<C2Param>> &params) {
+    return _createParamsBlob(blob, params);
+}
+
+// std::vector<std::unique_ptr<C2Tuning>> -> Params
+Status createParamsBlob(
+        hidl_vec<uint8_t> *blob,
+        const std::vector<std::unique_ptr<C2Tuning>> &params) {
+    return _createParamsBlob(blob, params);
+}
+
+// std::vector<std::shared_ptr<const C2Info>> -> Params
+Status createParamsBlob(
+        hidl_vec<uint8_t> *blob,
+        const std::vector<std::shared_ptr<const C2Info>> &params) {
+    return _createParamsBlob(blob, params);
+}
+
+// Params -> std::vector<std::unique_ptr<C2Param>>
+c2_status_t copyParamsFromBlob(
+        std::vector<std::unique_ptr<C2Param>>* params,
+        Params blob) {
+    std::vector<C2Param*> paramPointers;
+    c2_status_t status = parseParamsBlob(&paramPointers, blob);
+    if (status != C2_OK) {
+        ALOGE("copyParamsFromBlob -- blob parsing failed.");
+        return status;
+    }
+    params->resize(paramPointers.size());
+    size_t i = 0;
+    for (C2Param* const& paramPointer : paramPointers) {
+        if (!paramPointer) {
+            ALOGE("copyParamsFromBlob -- corrupted params blob.");
+            return C2_BAD_VALUE;
+        }
+        (*params)[i++] = C2Param::Copy(*paramPointer);
+    }
+    return C2_OK;
+}
+
+// Params -> update std::vector<std::unique_ptr<C2Param>>
+c2_status_t updateParamsFromBlob(
+        const std::vector<C2Param*>& params,
+        const Params& blob) {
+    std::unordered_map<uint32_t, C2Param*> index2param;
+    for (C2Param* const& param : params) {
+        if (!param) {
+            ALOGE("updateParamsFromBlob -- corrupted input params.");
+            return C2_BAD_VALUE;
+        }
+        if (index2param.find(param->index()) == index2param.end()) {
+            index2param.emplace(param->index(), param);
+        }
+    }
+
+    std::vector<C2Param*> paramPointers;
+    c2_status_t status = parseParamsBlob(&paramPointers, blob);
+    if (status != C2_OK) {
+        ALOGE("updateParamsFromBlob -- blob parsing failed.");
+        return status;
+    }
+
+    for (C2Param* const& paramPointer : paramPointers) {
+        if (!paramPointer) {
+            ALOGE("updateParamsFromBlob -- corrupted param in blob.");
+            return C2_BAD_VALUE;
+        }
+        decltype(index2param)::iterator i = index2param.find(
+                paramPointer->index());
+        if (i == index2param.end()) {
+            ALOGW("updateParamsFromBlob -- unseen param index.");
+            continue;
+        }
+        if (!i->second->updateFrom(*paramPointer)) {
+            ALOGE("updateParamsFromBlob -- mismatching sizes: "
+                    "%u vs %u (index = %u).",
+                    static_cast<unsigned>(params.size()),
+                    static_cast<unsigned>(paramPointer->size()),
+                    static_cast<unsigned>(i->first));
+            return C2_BAD_VALUE;
+        }
+    }
+    return C2_OK;
+}
+
+
+}  // namespace implementation
+}  // namespace V1_0
+}  // namespace c2
+}  // namespace media
+}  // namespace hardware
+}  // namespace android
+
diff --git a/media/libstagefright/codec2/include/SimpleC2Component.h b/media/libstagefright/codec2/include/SimpleC2Component.h
index a4b6ee1..168e79f 100644
--- a/media/libstagefright/codec2/include/SimpleC2Component.h
+++ b/media/libstagefright/codec2/include/SimpleC2Component.h
@@ -52,6 +52,7 @@
     // for thread
     inline bool exitRequested() { return mExitRequested; }
     void processQueue();
+    void signalExit();
 
 protected:
     /**
@@ -157,16 +158,21 @@
 
     class WorkQueue {
     public:
-        inline WorkQueue() : mGeneration(0ul) {}
+        inline WorkQueue() : mFlush(false), mGeneration(0ul) {}
 
         inline uint64_t generation() const { return mGeneration; }
-        inline void incGeneration() { ++mGeneration; }
+        inline void incGeneration() { ++mGeneration; mFlush = true; }
 
         std::unique_ptr<C2Work> pop_front();
         void push_back(std::unique_ptr<C2Work> work);
         bool empty() const;
         uint32_t drainMode() const;
         void markDrain(uint32_t drainMode);
+        inline bool popPendingFlush() {
+            bool flush = mFlush;
+            mFlush = false;
+            return flush;
+        }
         void clear();
 
         Condition mCondition;
@@ -177,6 +183,7 @@
             uint32_t drainMode;
         };
 
+        bool mFlush;
         uint64_t mGeneration;
         std::list<Entry> mQueue;
     };
@@ -185,9 +192,18 @@
     typedef std::unordered_map<uint64_t, std::unique_ptr<C2Work>> PendingWork;
     Mutexed<PendingWork> mPendingWork;
 
+    struct ExitMonitor {
+        inline ExitMonitor() : mExited(false) {}
+        Condition mCondition;
+        bool mExited;
+    };
+    Mutexed<ExitMonitor> mExitMonitor;
+
     std::shared_ptr<C2BlockPool> mOutputBlockPool;
 
     SimpleC2Component() = delete;
+
+    void requestExitAndWait(std::function<void()> job);
 };
 
 }  // namespace android
diff --git a/media/libstagefright/codec2/tests/vndk/C2BufferTest.cpp b/media/libstagefright/codec2/tests/vndk/C2BufferTest.cpp
index 038be48..f389abc 100644
--- a/media/libstagefright/codec2/tests/vndk/C2BufferTest.cpp
+++ b/media/libstagefright/codec2/tests/vndk/C2BufferTest.cpp
@@ -232,7 +232,8 @@
 class C2BufferTest : public ::testing::Test {
 public:
     C2BufferTest()
-        : mLinearAllocator(std::make_shared<C2AllocatorIon>('i')),
+        : mBlockPoolId(C2BlockPool::PLATFORM_START),
+          mLinearAllocator(std::make_shared<C2AllocatorIon>('i')),
           mSize(0u),
           mAddr(nullptr),
           mGraphicAllocator(std::make_shared<C2AllocatorGralloc>('g')) {
@@ -281,7 +282,7 @@
     }
 
     std::shared_ptr<C2BlockPool> makeLinearBlockPool() {
-        return std::make_shared<C2BasicLinearBlockPool>(mLinearAllocator);
+        return std::make_shared<C2PooledBlockPool>(mLinearAllocator, mBlockPoolId++);
     }
 
     void allocateGraphic(uint32_t width, uint32_t height) {
@@ -328,6 +329,7 @@
     }
 
 private:
+    C2BlockPool::local_id_t mBlockPoolId;
     std::shared_ptr<C2Allocator> mLinearAllocator;
     std::shared_ptr<C2LinearAllocation> mLinearAllocation;
     size_t mSize;
@@ -745,4 +747,23 @@
     EXPECT_FALSE(buffer->hasInfo(info2->type()));
 }
 
+TEST_F(C2BufferTest, MultipleLinearMapTest) {
+    std::shared_ptr<C2BlockPool> pool(makeLinearBlockPool());
+    constexpr size_t kCapacity = 524288u;
+    for (int i = 0; i < 100; ++i) {
+        std::vector<C2WriteView> wViews;
+        std::vector<C2ReadView> rViews;
+        for (int j = 0; j < 16; ++j) {
+            std::shared_ptr<C2LinearBlock> block;
+            ASSERT_EQ(C2_OK, pool->fetchLinearBlock(
+                    kCapacity,
+                    { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE },
+                    &block));
+            wViews.push_back(block->map().get());
+            C2ConstLinearBlock cBlock = block->share(0, kCapacity / 2, C2Fence());
+            rViews.push_back(cBlock.map().get());
+        }
+    }
+}
+
 } // namespace android
diff --git a/media/libstagefright/codec2/vndk/Android.bp b/media/libstagefright/codec2/vndk/Android.bp
index d7709a0..95e7c39 100644
--- a/media/libstagefright/codec2/vndk/Android.bp
+++ b/media/libstagefright/codec2/vndk/Android.bp
@@ -6,6 +6,9 @@
     ],
 
     vendor_available: false,
+    vndk: {
+        enabled: true,
+    },
 }
 
 cc_library_shared {
@@ -36,14 +39,17 @@
     shared_libs: [
         "android.hardware.graphics.allocator@2.0",
         "android.hardware.graphics.mapper@2.0",
+        "android.hardware.media.bufferpool@1.0",
         "libbinder",
         "libcutils",
         "libdl",
         "libhardware",
         "libhidlbase",
         "libion",
+        "libfmq",
         "liblog",
         "libstagefright_foundation",
+        "libstagefright_bufferpool@1.0",
         "libui",
         "libutils",
     ],
diff --git a/media/libstagefright/codec2/vndk/C2AllocatorGralloc.cpp b/media/libstagefright/codec2/vndk/C2AllocatorGralloc.cpp
index 3d7fc8d..a90e094 100644
--- a/media/libstagefright/codec2/vndk/C2AllocatorGralloc.cpp
+++ b/media/libstagefright/codec2/vndk/C2AllocatorGralloc.cpp
@@ -646,4 +646,8 @@
     return mImpl->status();
 }
 
+bool C2AllocatorGralloc::isValid(const C2Handle* const o) {
+    return C2HandleGralloc::isValid(o);
+}
+
 } // namespace android
diff --git a/media/libstagefright/codec2/vndk/C2AllocatorIon.cpp b/media/libstagefright/codec2/vndk/C2AllocatorIon.cpp
index 6b0cffb..664c09e 100644
--- a/media/libstagefright/codec2/vndk/C2AllocatorIon.cpp
+++ b/media/libstagefright/codec2/vndk/C2AllocatorIon.cpp
@@ -18,6 +18,8 @@
 #define LOG_TAG "C2AllocatorIon"
 #include <utils/Log.h>
 
+#include <list>
+
 #include <ion/ion.h>
 #include <sys/mman.h>
 #include <unistd.h>
@@ -148,6 +150,7 @@
      * so that we can capture the error.
      *
      * \param ionFd     ion client (ownership transferred to created object)
+     * \param owned     whehter native_handle_t is owned by an allocation or not.
      * \param capacity  size of allocation
      * \param bufferFd  buffer handle (ownership transferred to created object). Must be
      *                  invalid if err is not 0.
@@ -155,14 +158,14 @@
      *                  invalid if err is not 0.
      * \param err       errno during buffer allocation or import
      */
-    Impl(int ionFd, size_t capacity, int bufferFd, ion_user_handle_t buffer, C2Allocator::id_t id, int err)
+    Impl(int ionFd, bool owned, size_t capacity, int bufferFd, ion_user_handle_t buffer, C2Allocator::id_t id, int err)
         : mIonFd(ionFd),
+          mHandleOwned(owned),
           mHandle(bufferFd, capacity),
           mBuffer(buffer),
           mId(id),
           mInit(c2_map_errno<ENOMEM, EACCES, EINVAL>(err)),
-          mMapFd(-1),
-          mMapSize(0) {
+          mMapFd(-1) {
         if (mInit != C2_OK) {
             // close ionFd now on error
             if (mIonFd >= 0) {
@@ -188,7 +191,7 @@
     static Impl *Import(int ionFd, size_t capacity, int bufferFd, C2Allocator::id_t id) {
         ion_user_handle_t buffer = -1;
         int ret = ion_import(ionFd, bufferFd, &buffer);
-        return new Impl(ionFd, capacity, bufferFd, buffer, id, ret);
+        return new Impl(ionFd, false, capacity, bufferFd, buffer, id, ret);
     }
 
     /**
@@ -215,13 +218,14 @@
                 buffer = -1;
             }
         }
-        return new Impl(ionFd, size, bufferFd, buffer, id, ret);
+        return new Impl(ionFd, true, size, bufferFd, buffer, id, ret);
     }
 
     c2_status_t map(size_t offset, size_t size, C2MemoryUsage usage, C2Fence *fence, void **addr) {
         (void)fence; // TODO: wait for fence
         *addr = nullptr;
-        if (mMapSize > 0) {
+        if (!mMappings.empty()) {
+            ALOGV("multiple map");
             // TODO: technically we should return DUPLICATE here, but our block views don't
             // actually unmap, so we end up remapping an ion buffer multiple times.
             //
@@ -244,54 +248,63 @@
         size_t alignmentBytes = offset % PAGE_SIZE;
         size_t mapOffset = offset - alignmentBytes;
         size_t mapSize = size + alignmentBytes;
+        Mapping map = { nullptr, alignmentBytes, mapSize };
 
         c2_status_t err = C2_OK;
         if (mMapFd == -1) {
             int ret = ion_map(mIonFd, mBuffer, mapSize, prot,
-                              flags, mapOffset, (unsigned char**)&mMapAddr, &mMapFd);
+                              flags, mapOffset, (unsigned char**)&map.addr, &mMapFd);
             if (ret) {
                 mMapFd = -1;
-                *addr = nullptr;
+                map.addr = *addr = nullptr;
                 err = c2_map_errno<EINVAL>(-ret);
             } else {
-                *addr = (uint8_t *)mMapAddr + alignmentBytes;
-                mMapAlignmentBytes = alignmentBytes;
-                mMapSize = mapSize;
+                *addr = (uint8_t *)map.addr + alignmentBytes;
             }
         } else {
-            mMapAddr = mmap(nullptr, mapSize, prot, flags, mMapFd, mapOffset);
-            if (mMapAddr == MAP_FAILED) {
-                mMapAddr = *addr = nullptr;
+            map.addr = mmap(nullptr, mapSize, prot, flags, mMapFd, mapOffset);
+            if (map.addr == MAP_FAILED) {
+                map.addr = *addr = nullptr;
                 err = c2_map_errno<EINVAL>(errno);
             } else {
-                *addr = (uint8_t *)mMapAddr + alignmentBytes;
-                mMapAlignmentBytes = alignmentBytes;
-                mMapSize = mapSize;
+                *addr = (uint8_t *)map.addr + alignmentBytes;
             }
         }
+        if (map.addr) {
+            mMappings.push_back(map);
+        }
         return err;
     }
 
     c2_status_t unmap(void *addr, size_t size, C2Fence *fence) {
-        if (mMapFd < 0 || mMapSize == 0) {
+        if (mMapFd < 0 || mMappings.empty()) {
             return C2_NOT_FOUND;
         }
-        if (addr != (uint8_t *)mMapAddr + mMapAlignmentBytes ||
-                size + mMapAlignmentBytes != mMapSize) {
-            return C2_BAD_VALUE;
+        for (auto it = mMappings.begin(); it != mMappings.end(); ++it) {
+            if (addr != (uint8_t *)it->addr + it->alignmentBytes ||
+                    size + it->alignmentBytes != it->size) {
+                continue;
+            }
+            int err = munmap(it->addr, it->size);
+            if (err != 0) {
+                return c2_map_errno<EINVAL>(errno);
+            }
+            if (fence) {
+                *fence = C2Fence(); // not using fences
+            }
+            (void)mMappings.erase(it);
+            return C2_OK;
         }
-        int err = munmap(mMapAddr, mMapSize);
-        if (err != 0) {
-            return c2_map_errno<EINVAL>(errno);
-        }
-        if (fence) {
-            *fence = C2Fence(); // not using fences
-        }
-        mMapSize = 0;
-        return C2_OK;
+        return C2_BAD_VALUE;
     }
 
     ~Impl() {
+        if (!mMappings.empty()) {
+            ALOGD("Dangling mappings!");
+            for (const Mapping &map : mMappings) {
+                (void)munmap(map.addr, map.size);
+            }
+        }
         if (mMapFd >= 0) {
             close(mMapFd);
             mMapFd = -1;
@@ -302,7 +315,9 @@
         if (mIonFd >= 0) {
             close(mIonFd);
         }
-        native_handle_close(&mHandle);
+        if (mHandleOwned) {
+            native_handle_close(&mHandle);
+        }
     }
 
     c2_status_t status() const {
@@ -323,14 +338,18 @@
 
 private:
     int mIonFd;
+    bool mHandleOwned;
     C2HandleIon mHandle;
     ion_user_handle_t mBuffer;
     C2Allocator::id_t mId;
     c2_status_t mInit;
     int mMapFd; // only one for now
-    void *mMapAddr;
-    size_t mMapAlignmentBytes;
-    size_t mMapSize;
+    struct Mapping {
+        void *addr;
+        size_t alignmentBytes;
+        size_t size;
+    };
+    std::list<Mapping> mMappings;
 };
 
 c2_status_t C2AllocationIon::map(
@@ -466,5 +485,9 @@
     return ret;
 }
 
+bool C2AllocatorIon::isValid(const C2Handle* const o) {
+    return C2HandleIon::isValid(o);
+}
+
 } // namespace android
 
diff --git a/media/libstagefright/codec2/vndk/C2Buffer.cpp b/media/libstagefright/codec2/vndk/C2Buffer.cpp
index dc765f5..af2c20d 100644
--- a/media/libstagefright/codec2/vndk/C2Buffer.cpp
+++ b/media/libstagefright/codec2/vndk/C2Buffer.cpp
@@ -25,8 +25,16 @@
 #include <C2BufferPriv.h>
 #include <C2BlockInternal.h>
 
+#include <ClientManager.h>
+
 namespace {
 
+using android::hardware::media::bufferpool::V1_0::ResultStatus;
+using android::hardware::media::bufferpool::V1_0::implementation::BufferPoolAllocation;
+using android::hardware::media::bufferpool::V1_0::implementation::BufferPoolAllocator;
+using android::hardware::media::bufferpool::V1_0::implementation::ClientManager;
+using android::hardware::media::bufferpool::V1_0::implementation::ConnectionId;
+
 // This anonymous namespace contains the helper classes that allow our implementation to create
 // block/buffer objects.
 //
@@ -341,6 +349,257 @@
     return std::shared_ptr<C2LinearBlock>(new C2LinearBlock(impl, *impl));
 }
 
+/**
+ * Wrapped C2Allocator which is injected to buffer pool on behalf of
+ * C2BlockPool.
+ */
+class _C2BufferPoolAllocator : public BufferPoolAllocator {
+public:
+    _C2BufferPoolAllocator(const std::shared_ptr<C2Allocator> &allocator)
+        : mAllocator(allocator) {}
+
+    ~_C2BufferPoolAllocator() override {}
+
+    ResultStatus allocate(const std::vector<uint8_t> &params,
+                          std::shared_ptr<BufferPoolAllocation> *alloc) override;
+
+    bool compatible(const std::vector<uint8_t> &newParams,
+                    const std::vector<uint8_t> &oldParams) override;
+
+    // Methods for codec2 component (C2BlockPool).
+    /**
+     * Transforms linear allocation parameters for C2Allocator to parameters
+     * for buffer pool.
+     *
+     * @param capacity      size of linear allocation
+     * @param usage         memory usage pattern for linear allocation
+     * @param params        allocation parameters for buffer pool
+     */
+    void getLinearParams(uint32_t capacity, C2MemoryUsage usage,
+                         std::vector<uint8_t> *params);
+
+    /**
+     * Transforms graphic allocation parameters for C2Allocator to parameters
+     * for buffer pool.
+     *
+     * @param width         width of graphic allocation
+     * @param height        height of graphic allocation
+     * @param format        color format of graphic allocation
+     * @param params        allocation parameter for buffer pool
+     */
+    void getGraphicParams(uint32_t width, uint32_t height,
+                          uint32_t format, C2MemoryUsage usage,
+                          std::vector<uint8_t> *params);
+
+    /**
+     * Transforms an existing native handle to an C2LinearAllcation.
+     * Wrapper to C2Allocator#priorLinearAllocation
+     */
+    c2_status_t priorLinearAllocation(
+            const C2Handle *handle,
+            std::shared_ptr<C2LinearAllocation> *c2Allocation);
+
+    /**
+     * Transforms an existing native handle to an C2GraphicAllcation.
+     * Wrapper to C2Allocator#priorGraphicAllocation
+     */
+    c2_status_t priorGraphicAllocation(
+            const C2Handle *handle,
+            std::shared_ptr<C2GraphicAllocation> *c2Allocation);
+
+private:
+    static constexpr int kMaxIntParams = 5; // large enough number;
+
+    enum AllocType : uint8_t {
+        ALLOC_NONE = 0,
+
+        ALLOC_LINEAR,
+        ALLOC_GRAPHIC,
+    };
+
+    union AllocParams {
+        struct {
+            AllocType allocType;
+            C2MemoryUsage usage;
+            uint32_t params[kMaxIntParams];
+        } data;
+        uint8_t array[0];
+
+        AllocParams() : data{ALLOC_NONE, {0, 0}, {0}} {}
+        AllocParams(C2MemoryUsage usage, uint32_t capacity)
+            : data{ALLOC_LINEAR, usage, {[0] = capacity}} {}
+        AllocParams(
+                C2MemoryUsage usage,
+                uint32_t width, uint32_t height, uint32_t format)
+                : data{ALLOC_GRAPHIC, usage, {width, height, format}} {}
+    };
+
+    const std::shared_ptr<C2Allocator> mAllocator;
+};
+
+struct LinearAllocationDtor {
+    LinearAllocationDtor(const std::shared_ptr<C2LinearAllocation> &alloc)
+        : mAllocation(alloc) {}
+
+    void operator()(BufferPoolAllocation *poolAlloc) { delete poolAlloc; }
+
+    const std::shared_ptr<C2LinearAllocation> mAllocation;
+};
+
+ResultStatus _C2BufferPoolAllocator::allocate(
+        const std::vector<uint8_t>  &params,
+        std::shared_ptr<BufferPoolAllocation> *alloc) {
+    AllocParams c2Params;
+    memcpy(&c2Params, params.data(), std::min(sizeof(AllocParams), params.size()));
+    std::shared_ptr<C2LinearAllocation> c2Linear;
+    c2_status_t status = C2_BAD_VALUE;
+    switch(c2Params.data.allocType) {
+        case ALLOC_NONE:
+            break;
+        case ALLOC_LINEAR:
+            status = mAllocator->newLinearAllocation(
+                    c2Params.data.params[0], c2Params.data.usage, &c2Linear);
+            if (status == C2_OK && c2Linear) {
+                BufferPoolAllocation *ptr = new BufferPoolAllocation(c2Linear->handle());
+                if (ptr) {
+                    *alloc = std::shared_ptr<BufferPoolAllocation>(
+                            ptr, LinearAllocationDtor(c2Linear));
+                    if (*alloc) {
+                        return ResultStatus::OK;
+                    }
+                    delete ptr;
+                }
+                return ResultStatus::NO_MEMORY;
+            }
+            break;
+        case ALLOC_GRAPHIC:
+            // TODO
+            break;
+        default:
+            break;
+    }
+    return ResultStatus::CRITICAL_ERROR;
+}
+
+bool _C2BufferPoolAllocator::compatible(
+        const std::vector<uint8_t>  &newParams,
+        const std::vector<uint8_t>  &oldParams) {
+    size_t newSize = newParams.size();
+    size_t oldSize = oldParams.size();
+
+    // TODO: support not exact matching. e.g) newCapacity < oldCapacity
+    if (newSize == oldSize) {
+        for (size_t i = 0; i < newSize; ++i) {
+            if (newParams[i] != oldParams[i]) {
+                return false;
+            }
+        }
+        return true;
+    }
+    return false;
+}
+
+void _C2BufferPoolAllocator::getLinearParams(
+        uint32_t capacity, C2MemoryUsage usage, std::vector<uint8_t> *params) {
+    AllocParams c2Params(usage, capacity);
+    params->assign(c2Params.array, c2Params.array + sizeof(AllocParams));
+}
+
+void _C2BufferPoolAllocator::getGraphicParams(
+        uint32_t width, uint32_t height, uint32_t format, C2MemoryUsage usage,
+        std::vector<uint8_t> *params) {
+    AllocParams c2Params(usage, width, height, format);
+    params->assign(c2Params.array, c2Params.array + sizeof(AllocParams));
+}
+
+c2_status_t _C2BufferPoolAllocator::priorLinearAllocation(
+        const C2Handle *handle,
+        std::shared_ptr<C2LinearAllocation> *c2Allocation) {
+    return mAllocator->priorLinearAllocation(handle, c2Allocation);
+}
+
+c2_status_t _C2BufferPoolAllocator::priorGraphicAllocation(
+        const C2Handle *handle,
+        std::shared_ptr<C2GraphicAllocation> *c2Allocation) {
+    return mAllocator->priorGraphicAllocation(handle, c2Allocation);
+}
+
+class C2PooledBlockPool::Impl {
+public:
+    Impl(const std::shared_ptr<C2Allocator> &allocator)
+            : mInit(C2_OK),
+              mBufferPoolManager(ClientManager::getInstance()),
+              mAllocator(std::make_shared<_C2BufferPoolAllocator>(allocator)) {
+        if (mAllocator && mBufferPoolManager) {
+            if (mBufferPoolManager->create(
+                    mAllocator, &mConnectionId) == ResultStatus::OK) {
+                return;
+            }
+        }
+        mInit = C2_NO_INIT;
+    }
+
+    ~Impl() {
+        if (mInit == C2_OK) {
+            mBufferPoolManager->close(mConnectionId);
+        }
+    }
+
+    c2_status_t fetchLinearBlock(
+            uint32_t capacity, C2MemoryUsage usage,
+            std::shared_ptr<C2LinearBlock> *block /* nonnull */) {
+        block->reset();
+        if (mInit != C2_OK) {
+            return mInit;
+        }
+        std::vector<uint8_t> params;
+        mAllocator->getLinearParams(capacity, usage, &params);
+        std::shared_ptr<_C2BlockPoolData> poolData;
+        ResultStatus status = mBufferPoolManager->allocate(
+                mConnectionId, params, &poolData);
+        if (status == ResultStatus::OK) {
+            std::shared_ptr<C2LinearAllocation> alloc;
+            c2_status_t err = mAllocator->priorLinearAllocation(
+                    poolData->mHandle, &alloc);
+            if (err == C2_OK && poolData && alloc) {
+                *block = _C2BlockFactory::CreateLinearBlock(
+                        alloc, poolData, 0, capacity);
+                if (*block) {
+                    return C2_OK;
+                }
+            }
+            return C2_NO_MEMORY;
+        }
+        if (status == ResultStatus::NO_MEMORY) {
+            return C2_NO_MEMORY;
+        }
+        return C2_CORRUPTED;
+    }
+
+private:
+    c2_status_t mInit;
+    const android::sp<ClientManager> mBufferPoolManager;
+    ConnectionId mConnectionId; // locally
+    const std::shared_ptr<_C2BufferPoolAllocator> mAllocator;
+};
+
+C2PooledBlockPool::C2PooledBlockPool(
+        const std::shared_ptr<C2Allocator> &allocator, const local_id_t localId)
+        : mAllocator(allocator), mLocalId(localId), mImpl(new Impl(allocator)) {}
+
+C2PooledBlockPool::~C2PooledBlockPool() {
+}
+
+c2_status_t C2PooledBlockPool::fetchLinearBlock(
+        uint32_t capacity,
+        C2MemoryUsage usage,
+        std::shared_ptr<C2LinearBlock> *block /* nonnull */) {
+    if (mImpl) {
+        return mImpl->fetchLinearBlock(capacity, usage, block);
+    }
+    return C2_CORRUPTED;
+}
+
 /* ========================================== 2D BLOCK ========================================= */
 
 /**
@@ -388,7 +647,8 @@
     std::shared_ptr<_C2BlockPoolData> mPoolData;
 };
 
-class C2_HIDE _C2MappingBlock2DImpl : public _C2Block2DImpl {
+class C2_HIDE _C2MappingBlock2DImpl
+    : public _C2Block2DImpl, public std::enable_shared_from_this<_C2MappingBlock2DImpl> {
 public:
     using _C2Block2DImpl::_C2Block2DImpl;
 
@@ -399,7 +659,7 @@
     private:
         friend class _C2MappingBlock2DImpl;
 
-        Mapped(const _C2Block2DImpl *impl, bool writable, C2Fence *fence __unused)
+        Mapped(const std::shared_ptr<_C2Block2DImpl> &impl, bool writable, C2Fence *fence __unused)
             : mImpl(impl), mWritable(writable) {
             memset(mData, 0, sizeof(mData));
             const C2Rect crop = mImpl->crop();
@@ -467,7 +727,7 @@
         bool writable() const { return mWritable; }
 
     private:
-        const _C2Block2DImpl *mImpl;
+        const std::shared_ptr<_C2Block2DImpl> mImpl;
         bool mWritable;
         c2_status_t mError;
         uint8_t *mData[C2PlanarLayout::MAX_NUM_PLANES];
@@ -485,7 +745,7 @@
         std::lock_guard<std::mutex> lock(mMappedLock);
         std::shared_ptr<Mapped> existing = mMapped.lock();
         if (!existing) {
-            existing = std::shared_ptr<Mapped>(new Mapped(this, writable, fence));
+            existing = std::shared_ptr<Mapped>(new Mapped(shared_from_this(), writable, fence));
             mMapped = existing;
         } else {
             // if we mapped the region read-only, we cannot remap it read-write
diff --git a/media/libstagefright/codec2/vndk/C2Store.cpp b/media/libstagefright/codec2/vndk/C2Store.cpp
index f612b4f..6f752ae 100644
--- a/media/libstagefright/codec2/vndk/C2Store.cpp
+++ b/media/libstagefright/codec2/vndk/C2Store.cpp
@@ -152,6 +152,39 @@
     return res;
 }
 
+c2_status_t CreateCodec2BlockPool(
+        C2PlatformAllocatorStore::id_t allocatorId,
+        std::shared_ptr<const C2Component> component,
+        std::shared_ptr<C2BlockPool> *pool) {
+    pool->reset();
+    if (!component) {
+        return C2_BAD_VALUE;
+    }
+    // TODO: support caching block pool along with GetCodec2BlockPool.
+    static std::atomic_int sBlockPoolId(C2BlockPool::PLATFORM_START);
+    std::shared_ptr<C2AllocatorStore> allocatorStore = GetCodec2PlatformAllocatorStore();
+    std::shared_ptr<C2Allocator> allocator;
+    c2_status_t res = C2_NOT_FOUND;
+
+    switch (allocatorId) {
+    case C2PlatformAllocatorStore::ION:
+        res = allocatorStore->fetchAllocator(C2AllocatorStore::DEFAULT_LINEAR, &allocator);
+        if (res == C2_OK) {
+            *pool = std::make_shared<C2PooledBlockPool>(allocator, sBlockPoolId++);
+            if (!*pool) {
+                res = C2_NO_MEMORY;
+            }
+        }
+        break;
+    case C2PlatformAllocatorStore::GRALLOC:
+        // TODO: support gralloc
+        break;
+    default:
+        break;
+    }
+    return res;
+}
+
 class C2PlatformComponentStore : public C2ComponentStore {
 public:
     virtual std::vector<std::shared_ptr<const C2Component::Traits>> listComponents() override;
@@ -433,9 +466,29 @@
     mComponents.emplace("c2.google.avc.encoder", "libstagefright_soft_c2avcenc.so");
     mComponents.emplace("c2.google.aac.decoder", "libstagefright_soft_c2aacdec.so");
     mComponents.emplace("c2.google.aac.encoder", "libstagefright_soft_c2aacenc.so");
-    mComponents.emplace("c2.google.mp3.decoder", "libstagefright_soft_c2mp3dec.so");
+    mComponents.emplace("c2.google.amrnb.decoder", "libstagefright_soft_c2amrnbdec.so");
+    mComponents.emplace("c2.google.amrnb.encoder", "libstagefright_soft_c2amrnbenc.so");
+    mComponents.emplace("c2.google.amrwb.decoder", "libstagefright_soft_c2amrwbdec.so");
+    mComponents.emplace("c2.google.amrwb.encoder", "libstagefright_soft_c2amrwbenc.so");
+    mComponents.emplace("c2.google.hevc.decoder", "libstagefright_soft_c2hevcdec.so");
     mComponents.emplace("c2.google.g711.alaw.decoder", "libstagefright_soft_c2g711alawdec.so");
     mComponents.emplace("c2.google.g711.mlaw.decoder", "libstagefright_soft_c2g711mlawdec.so");
+    mComponents.emplace("c2.google.mpeg2.decoder", "libstagefright_soft_c2mpeg2dec.so");
+    mComponents.emplace("c2.google.h263.decoder", "libstagefright_soft_c2h263dec.so");
+    mComponents.emplace("c2.google.h263.encoder", "libstagefright_soft_c2h263enc.so");
+    mComponents.emplace("c2.google.mpeg4.decoder", "libstagefright_soft_c2mpeg4dec.so");
+    mComponents.emplace("c2.google.mpeg4.encoder", "libstagefright_soft_c2mpeg4enc.so");
+    mComponents.emplace("c2.google.mp3.decoder", "libstagefright_soft_c2mp3dec.so");
+    mComponents.emplace("c2.google.vorbis.decoder", "libstagefright_soft_c2vorbisdec.so");
+    mComponents.emplace("c2.google.opus.decoder", "libstagefright_soft_c2opusdec.so");
+    mComponents.emplace("c2.google.vp8.decoder", "libstagefright_soft_c2vp8dec.so");
+    mComponents.emplace("c2.google.vp9.decoder", "libstagefright_soft_c2vp9dec.so");
+    mComponents.emplace("c2.google.vp8.encoder", "libstagefright_soft_c2vp8enc.so");
+    mComponents.emplace("c2.google.vp9.encoder", "libstagefright_soft_c2vp9enc.so");
+    mComponents.emplace("c2.google.raw.decoder", "libstagefright_soft_c2rawdec.so");
+    mComponents.emplace("c2.google.flac.decoder", "libstagefright_soft_c2flacdec.so");
+    mComponents.emplace("c2.google.flac.encoder", "libstagefright_soft_c2flacenc.so");
+    mComponents.emplace("c2.google.gsm.decoder", "libstagefright_soft_c2gsmdec.so");
 }
 
 c2_status_t C2PlatformComponentStore::copyBuffer(
diff --git a/media/libstagefright/codec2/vndk/bufferpool/Accessor.cpp b/media/libstagefright/codec2/vndk/bufferpool/Accessor.cpp
index 3571ed5..1b1b9be 100644
--- a/media/libstagefright/codec2/vndk/bufferpool/Accessor.cpp
+++ b/media/libstagefright/codec2/vndk/bufferpool/Accessor.cpp
@@ -43,8 +43,8 @@
     return Void();
 }
 
-Accessor::Accessor(const std::shared_ptr<C2Allocator> &allocator, bool linear)
-    : mImpl(new Impl(allocator, linear)) {}
+Accessor::Accessor(const std::shared_ptr<BufferPoolAllocator> &allocator)
+    : mImpl(new Impl(allocator)) {}
 
 Accessor::~Accessor() {
 }
diff --git a/media/libstagefright/codec2/vndk/bufferpool/Accessor.h b/media/libstagefright/codec2/vndk/bufferpool/Accessor.h
index 6fd82ba..ad42245 100644
--- a/media/libstagefright/codec2/vndk/bufferpool/Accessor.h
+++ b/media/libstagefright/codec2/vndk/bufferpool/Accessor.h
@@ -20,7 +20,6 @@
 #include <android/hardware/media/bufferpool/1.0/IAccessor.h>
 #include <hidl/MQDescriptor.h>
 #include <hidl/Status.h>
-#include <C2Buffer.h>
 #include <BufferPoolTypes.h>
 #include "BufferStatus.h"
 
@@ -53,9 +52,8 @@
      * Creates a buffer pool accessor which uses the specified allocator.
      *
      * @param allocator buffer allocator.
-     * @param linear    whether the allocator is linear or not.
      */
-    Accessor(const std::shared_ptr<C2Allocator> &allocator, bool linear);
+    explicit Accessor(const std::shared_ptr<BufferPoolAllocator> &allocator);
 
     /** Destructs a buffer pool accessor. */
     ~Accessor();
diff --git a/media/libstagefright/codec2/vndk/bufferpool/AccessorImpl.cpp b/media/libstagefright/codec2/vndk/bufferpool/AccessorImpl.cpp
index f8aec53..32d76c0 100644
--- a/media/libstagefright/codec2/vndk/bufferpool/AccessorImpl.cpp
+++ b/media/libstagefright/codec2/vndk/bufferpool/AccessorImpl.cpp
@@ -22,7 +22,6 @@
 #include <time.h>
 #include <unistd.h>
 #include <utils/Log.h>
-#include <C2Buffer.h>
 #include "AccessorImpl.h"
 #include "Connection.h"
 
@@ -38,48 +37,18 @@
     BufferId mId;
     size_t mOwnerCount;
     size_t mTransactionCount;
-    const bool mLinear;
-    const std::shared_ptr<C2LinearAllocation> mLinearAllocation;
-    const std::shared_ptr<C2GraphicAllocation> mGraphicAllocation;
+    const std::shared_ptr<BufferPoolAllocation> mAllocation;
     const std::vector<uint8_t> mConfig;
 
     InternalBuffer(
             BufferId id,
-            const std::shared_ptr<C2LinearAllocation> &alloc,
+            const std::shared_ptr<BufferPoolAllocation> &alloc,
             const std::vector<uint8_t> &allocConfig)
             : mId(id), mOwnerCount(0), mTransactionCount(0),
-            mLinear(true), mLinearAllocation(alloc),
-            mGraphicAllocation(nullptr),
-            mConfig(allocConfig) {}
-
-    InternalBuffer(
-            BufferId id,
-            const std::shared_ptr<C2GraphicAllocation> &alloc,
-            const std::vector<uint8_t> &allocConfig)
-            : mId(id), mOwnerCount(0), mTransactionCount(0),
-            mLinear(false), mLinearAllocation(nullptr),
-            mGraphicAllocation(alloc),
-            mConfig(allocConfig) {}
+            mAllocation(alloc), mConfig(allocConfig) {}
 
     const native_handle_t *handle() {
-        if (mLinear) {
-            return mLinearAllocation->handle();
-        } else {
-            return mGraphicAllocation->handle();
-        }
-    }
-
-    // TODO : support non exact matching. e.g) capacity
-    bool isRecyclable(const std::vector<uint8_t> &config) {
-        if (mConfig.size() == config.size()) {
-            for (size_t i = 0; i < config.size(); ++i) {
-                if (mConfig[i] != config[i]) {
-                    return false;
-                }
-            }
-                return true;
-        }
-        return false;
+        return mAllocation->handle();
     }
 };
 
@@ -154,8 +123,8 @@
 uint32_t Accessor::Impl::sSeqId = time(NULL);
 
 Accessor::Impl::Impl(
-        const std::shared_ptr<C2Allocator> &allocator, bool linear)
-        : mAllocator(allocator), mLinear(linear) {}
+        const std::shared_ptr<BufferPoolAllocator> &allocator)
+        : mAllocator(allocator) {}
 
 Accessor::Impl::~Impl() {
 }
@@ -193,14 +162,8 @@
     std::lock_guard<std::mutex> lock(mBufferPool.mMutex);
     mBufferPool.processStatusMessages();
     ResultStatus status = ResultStatus::OK;
-    if (!mBufferPool.getFreeBuffer(params, bufferId, handle)) {
-        if (mLinear) {
-            status = mBufferPool.getNewLinearBuffer(
-                    mAllocator, params, bufferId, handle);
-        } else {
-            status = mBufferPool.getNewGraphicBuffer(
-                    mAllocator, params, bufferId, handle);
-        }
+    if (!mBufferPool.getFreeBuffer(mAllocator, params, bufferId, handle)) {
+        status = mBufferPool.getNewBuffer(mAllocator, params, bufferId, handle);
         ALOGV("create a buffer %d : %u %p",
               status == ResultStatus::OK, *bufferId, *handle);
     }
@@ -437,12 +400,13 @@
 }
 
 bool Accessor::Impl::BufferPool::getFreeBuffer(
+        const std::shared_ptr<BufferPoolAllocator> &allocator,
         const std::vector<uint8_t> &params, BufferId *pId,
         const native_handle_t** handle) {
     auto bufferIt = mFreeBuffers.begin();
     for (;bufferIt != mFreeBuffers.end(); ++bufferIt) {
         BufferId bufferId = *bufferIt;
-        if (mBuffers[bufferId]->isRecyclable(params)) {
+        if (allocator->compatible(params, mBuffers[bufferId]->mConfig)) {
             break;
         }
     }
@@ -457,81 +421,30 @@
     return false;
 }
 
-ResultStatus Accessor::Impl::BufferPool::getNewLinearBuffer(
-        const std::shared_ptr<C2Allocator> &allocator,
+ResultStatus Accessor::Impl::BufferPool::getNewBuffer(
+        const std::shared_ptr<BufferPoolAllocator> &allocator,
         const std::vector<uint8_t> &params, BufferId *pId,
         const native_handle_t** handle) {
-    union LinearParam {
-        struct {
-            uint32_t capacity;
-            C2MemoryUsage usage;
-        } data;
-        uint8_t array[0];
-        LinearParam() : data{0, {0, 0}} {}
-    } linearParam;
-    memcpy(&linearParam, params.data(),
-           std::min(sizeof(linearParam), params.size()));
-    std::shared_ptr<C2LinearAllocation> linearAlloc;
-    c2_status_t status = allocator->newLinearAllocation(
-            linearParam.data.capacity, linearParam.data.usage, &linearAlloc);
-    if (status == C2_OK) {
+    std::shared_ptr<BufferPoolAllocation> alloc;
+    ResultStatus status = allocator->allocate(params, &alloc);
+
+    if (status == ResultStatus::OK) {
         BufferId bufferId = mSeq++;
         std::unique_ptr<InternalBuffer> buffer =
                 std::make_unique<InternalBuffer>(
-                        bufferId, linearAlloc, params);
+                        bufferId, alloc, params);
         if (buffer) {
             auto res = mBuffers.insert(std::make_pair(
                     bufferId, std::move(buffer)));
             if (res.second) {
-                *handle = linearAlloc->handle();
+                *handle = alloc->handle();
                 *pId = bufferId;
                 return ResultStatus::OK;
             }
         }
         return ResultStatus::NO_MEMORY;
     }
-    // TODO: map C2 error code
-    return ResultStatus::CRITICAL_ERROR;
-}
-
-ResultStatus Accessor::Impl::BufferPool::getNewGraphicBuffer(
-        const std::shared_ptr<C2Allocator> &allocator,
-        const std::vector<uint8_t> &params, BufferId *pId,
-        const native_handle_t** handle) {
-    union GraphicParam {
-        struct {
-            uint32_t width;
-            uint32_t height;
-            uint32_t format;
-            C2MemoryUsage usage;
-        } data;
-        uint8_t array[0];
-        GraphicParam() : data{0, 0, 0, {0, 0}} {}
-    } graphicParam;
-    memcpy(&graphicParam, params.data(),
-           std::min(sizeof(graphicParam), params.size()));
-    std::shared_ptr<C2GraphicAllocation> graphicAlloc;
-    c2_status_t status = allocator->newGraphicAllocation(
-            graphicParam.data.width, graphicParam.data.height,
-            graphicParam.data.format, graphicParam.data.usage, &graphicAlloc);
-    if (status == C2_OK) {
-        BufferId bufferId = mSeq;
-        std::unique_ptr<InternalBuffer> buffer =
-                std::make_unique<InternalBuffer>(
-                        bufferId, graphicAlloc, params);
-        if (buffer) {
-            auto res = mBuffers.insert(std::make_pair(
-                    bufferId, std::move(buffer)));
-            if (res.second) {
-                *handle = graphicAlloc->handle();
-                *pId = bufferId;
-                return ResultStatus::OK;
-            }
-        }
-        return ResultStatus::NO_MEMORY;
-    }
-    // TODO: map C2 error code
-    return ResultStatus::CRITICAL_ERROR;
+    return status;
 }
 
 }  // namespace implementation
diff --git a/media/libstagefright/codec2/vndk/bufferpool/AccessorImpl.h b/media/libstagefright/codec2/vndk/bufferpool/AccessorImpl.h
index 92d926c..1260550 100644
--- a/media/libstagefright/codec2/vndk/bufferpool/AccessorImpl.h
+++ b/media/libstagefright/codec2/vndk/bufferpool/AccessorImpl.h
@@ -35,7 +35,7 @@
  * An implementation of a buffer pool accessor(or a buffer pool implementation.) */
 class Accessor::Impl {
 public:
-    Impl(const std::shared_ptr<C2Allocator> &allocator, bool linear);
+    Impl(const std::shared_ptr<BufferPoolAllocator> &allocator);
 
     ~Impl();
 
@@ -64,8 +64,7 @@
     static uint32_t sSeqId;
     static int32_t sPid;
 
-    const std::shared_ptr<C2Allocator> mAllocator;
-    bool mLinear;
+    const std::shared_ptr<BufferPoolAllocator> mAllocator;
 
     /**
      * Buffer pool implementation.
@@ -172,6 +171,7 @@
         /**
          * Recycles a existing free buffer if it is possible.
          *
+         * @param allocator the buffer allocator
          * @param params    the allocation parameters.
          * @param pId       the id of the recycled buffer.
          * @param handle    the native handle of the recycled buffer.
@@ -179,40 +179,25 @@
          * @return {@code true} when a buffer is recycled, {@code false}
          *         otherwise.
          */
-        bool getFreeBuffer(const std::vector<uint8_t> &params, BufferId *pId,
-                           const native_handle_t **handle);
+        bool getFreeBuffer(
+                const std::shared_ptr<BufferPoolAllocator> &allocator,
+                const std::vector<uint8_t> &params,
+                BufferId *pId, const native_handle_t **handle);
 
         /**
-         * Creates a new linear buffer.
+         * Creates a new buffer.
          *
-         * @param allocator the linear buffer allocator
+         * @param allocator the buffer allocator
          * @param params    the allocator parameters
          * @param pId       the buffer id for the newly allocated buffer.
          * @param handle    the native handle for the newly allocated buffer.
          *
-         * @return OK when a linear allocation is successfully allocated.
+         * @return OK when an allocation is successfully allocated.
          *         NO_MEMORY when there is no memory.
          *         CRITICAL_ERROR otherwise.
          */
-        ResultStatus getNewLinearBuffer(
-                const std::shared_ptr<C2Allocator> &allocator,
-                const std::vector<uint8_t> &params, BufferId *pId,
-                const native_handle_t **handle);
-
-        /**
-         * Creates a new graphic buffer.
-         *
-         * @param allocator the graphic buffer allocator
-         * @param params    the allocator parameters
-         * @param pId       the buffer id for the newly allocated buffer.
-         * @param handle    the native handle for the newly allocated buffer.
-         *
-         * @return OK when a graphic allocation is successfully allocated.
-         *         NO_MEMORY when there is no memory.
-         *         CRITICAL_ERROR otherwise.
-         */
-        ResultStatus getNewGraphicBuffer(
-                const std::shared_ptr<C2Allocator> &allocator,
+        ResultStatus getNewBuffer(
+                const std::shared_ptr<BufferPoolAllocator> &allocator,
                 const std::vector<uint8_t> &params, BufferId *pId,
                 const native_handle_t **handle);
 
diff --git a/media/libstagefright/codec2/vndk/bufferpool/Android.bp b/media/libstagefright/codec2/vndk/bufferpool/Android.bp
index c7aa07b..1ea1f35 100644
--- a/media/libstagefright/codec2/vndk/bufferpool/Android.bp
+++ b/media/libstagefright/codec2/vndk/bufferpool/Android.bp
@@ -24,7 +24,6 @@
         "libhidlbase",
         "libhidltransport",
         "liblog",
-        "libstagefright_codec2",
         "libutils",
         "android.hardware.media.bufferpool@1.0",
     ],
diff --git a/media/libstagefright/codec2/vndk/bufferpool/ClientManager.cpp b/media/libstagefright/codec2/vndk/bufferpool/ClientManager.cpp
index 97efee4..89aee8b 100644
--- a/media/libstagefright/codec2/vndk/bufferpool/ClientManager.cpp
+++ b/media/libstagefright/codec2/vndk/bufferpool/ClientManager.cpp
@@ -36,8 +36,7 @@
     ResultStatus registerSender(const sp<IAccessor> &accessor,
                                 ConnectionId *pConnectionId);
 
-    ResultStatus create(const std::shared_ptr<C2Allocator> &allocator,
-                        bool linear,
+    ResultStatus create(const std::shared_ptr<BufferPoolAllocator> &allocator,
                         ConnectionId *pConnectionId);
 
     ResultStatus close(ConnectionId connectionId);
@@ -139,10 +138,9 @@
 }
 
 ResultStatus ClientManager::Impl::create(
-        const std::shared_ptr<C2Allocator> &allocator,
-        bool linear,
+        const std::shared_ptr<BufferPoolAllocator> &allocator,
         ConnectionId *pConnectionId) {
-    const sp<Accessor> accessor = new Accessor(allocator, linear);
+    const sp<Accessor> accessor = new Accessor(allocator);
     if (!accessor || !accessor->isValid()) {
         return ResultStatus::CRITICAL_ERROR;
     }
@@ -273,11 +271,10 @@
 }
 
 ResultStatus ClientManager::create(
-        const std::shared_ptr<C2Allocator> &allocator,
-        bool linear,
+        const std::shared_ptr<BufferPoolAllocator> &allocator,
         ConnectionId *pConnectionId) {
     if (mImpl) {
-        return mImpl->create(allocator, linear, pConnectionId);
+        return mImpl->create(allocator, pConnectionId);
     }
     return ResultStatus::CRITICAL_ERROR;
 }
diff --git a/media/libstagefright/codec2/vndk/bufferpool/include/BufferPoolTypes.h b/media/libstagefright/codec2/vndk/bufferpool/include/BufferPoolTypes.h
index 4b7363f..0cf023c 100644
--- a/media/libstagefright/codec2/vndk/bufferpool/include/BufferPoolTypes.h
+++ b/media/libstagefright/codec2/vndk/bufferpool/include/BufferPoolTypes.h
@@ -22,9 +22,8 @@
 #include <fmq/MessageQueue.h>
 #include <hidl/MQDescriptor.h>
 #include <hidl/Status.h>
-#include <C2Buffer.h>
 
-struct C2_HIDE _C2BlockPoolData {
+struct __attribute__((visibility("hidden"))) _C2BlockPoolData {
     uint32_t mId; //BufferId
     native_handle_t *mHandle;
 
@@ -57,6 +56,52 @@
 typedef android::hardware::MessageQueue<BufferStatusMessage, kSynchronizedReadWrite> BufferStatusQueue;
 typedef BufferStatusQueue::Descriptor QueueDescriptor;
 
+/**
+ * Allocation wrapper class for buffer pool.
+ */
+struct BufferPoolAllocation {
+    const native_handle_t *mHandle;
+
+    const native_handle_t *handle() {
+        return mHandle;
+    }
+
+    BufferPoolAllocation(const native_handle_t *handle) : mHandle(handle) {}
+
+    ~BufferPoolAllocation() {};
+};
+
+/**
+ * Allocator wrapper class for buffer pool.
+ */
+class BufferPoolAllocator {
+public:
+
+    /**
+     * Allocate an allocation(buffer) for bufer pool.
+     *
+     * @param params    allocation parameters
+     * @param alloc     created allocation
+     *
+     * @return OK when an allocation is created successfully.
+     */
+    virtual ResultStatus allocate(
+            const std::vector<uint8_t> &params,
+            std::shared_ptr<BufferPoolAllocation> *alloc) = 0;
+
+    /**
+     * Returns whether allocation parameters of an old allocation are
+     * compatible with new allocation parameters.
+     */
+    virtual bool compatible(const std::vector<uint8_t> &newParams,
+                            const std::vector<uint8_t> &oldParams) = 0;
+
+protected:
+    BufferPoolAllocator() = default;
+
+    virtual ~BufferPoolAllocator() = default;
+};
+
 }  // namespace implementation
 }  // namespace V1_0
 }  // namespace bufferpool
diff --git a/media/libstagefright/codec2/vndk/bufferpool/include/ClientManager.h b/media/libstagefright/codec2/vndk/bufferpool/include/ClientManager.h
index 412fa59..f91f46b 100644
--- a/media/libstagefright/codec2/vndk/bufferpool/include/ClientManager.h
+++ b/media/libstagefright/codec2/vndk/bufferpool/include/ClientManager.h
@@ -20,7 +20,6 @@
 #include <android/hardware/media/bufferpool/1.0/IClientManager.h>
 #include <hidl/MQDescriptor.h>
 #include <hidl/Status.h>
-#include <C2Buffer.h>
 #include <memory>
 #include <BufferPoolTypes.h>
 
@@ -52,7 +51,6 @@
      * Creates a local connection with a newly created buffer pool.
      *
      * @param allocator     for new buffer allocation.
-     * @param linear        whether the allocator is linear or not.
      * @param pConnectionId Id of the created connection. This is
      *                      system-wide unique.
      *
@@ -61,8 +59,7 @@
      *         NO_MEMORY when there is no memory.
      *         CRITICAL_ERROR otherwise.
      */
-    ResultStatus create(const std::shared_ptr<C2Allocator> &allocator,
-                        bool linear,
+    ResultStatus create(const std::shared_ptr<BufferPoolAllocator> &allocator,
                         ConnectionId *pConnectionId);
 
     /**
diff --git a/media/libstagefright/codec2/vndk/bufferpool/vts/Android.bp b/media/libstagefright/codec2/vndk/bufferpool/vts/Android.bp
index 074d4bc..62286f3 100644
--- a/media/libstagefright/codec2/vndk/bufferpool/vts/Android.bp
+++ b/media/libstagefright/codec2/vndk/bufferpool/vts/Android.bp
@@ -18,6 +18,7 @@
     name: "VtsVndkHidlBufferpoolV1_0TargetSingleTest",
     defaults: ["VtsHalTargetTestDefaults"],
     srcs: [
+        "allocator.cpp",
         "single.cpp",
     ],
     static_libs: [
@@ -37,6 +38,7 @@
     name: "VtsVndkHidlBufferpoolV1_0TargetMultiTest",
     defaults: ["VtsHalTargetTestDefaults"],
     srcs: [
+        "allocator.cpp",
         "multi.cpp",
     ],
     static_libs: [
diff --git a/media/libstagefright/codec2/vndk/bufferpool/vts/allocator.cpp b/media/libstagefright/codec2/vndk/bufferpool/vts/allocator.cpp
new file mode 100644
index 0000000..230ee3f
--- /dev/null
+++ b/media/libstagefright/codec2/vndk/bufferpool/vts/allocator.cpp
@@ -0,0 +1,84 @@
+/*
+ * 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.
+ */
+
+#include <C2Buffer.h>
+#include "allocator.h"
+
+union Params {
+  struct {
+    uint32_t capacity;
+    C2MemoryUsage usage;
+  } data;
+  uint8_t array[0];
+  Params() : data{0, {0, 0}} {}
+  Params(uint32_t size)
+      : data{size, {C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE}} {}
+};
+
+struct AllocationDtor {
+  AllocationDtor(const std::shared_ptr<C2LinearAllocation> &alloc)
+      : mAlloc(alloc) {}
+
+  void operator()(BufferPoolAllocation *poolAlloc) { delete poolAlloc; }
+
+  const std::shared_ptr<C2LinearAllocation> mAlloc;
+};
+
+ResultStatus VtsBufferPoolAllocator::allocate(
+    const std::vector<uint8_t> &params,
+    std::shared_ptr<BufferPoolAllocation> *alloc) {
+  Params ionParams;
+  memcpy(&ionParams, params.data(), std::min(sizeof(Params), params.size()));
+
+  std::shared_ptr<C2LinearAllocation> linearAlloc;
+  c2_status_t status = mAllocator->newLinearAllocation(
+      ionParams.data.capacity, ionParams.data.usage, &linearAlloc);
+  if (status == C2_OK && linearAlloc) {
+    BufferPoolAllocation *ptr = new BufferPoolAllocation(linearAlloc->handle());
+    if (ptr) {
+      *alloc = std::shared_ptr<BufferPoolAllocation>(
+          ptr, AllocationDtor(linearAlloc));
+      if (*alloc) {
+        return ResultStatus::OK;
+      }
+      delete ptr;
+      return ResultStatus::NO_MEMORY;
+    }
+  }
+  return ResultStatus::CRITICAL_ERROR;
+}
+
+bool VtsBufferPoolAllocator::compatible(const std::vector<uint8_t> &newParams,
+                                        const std::vector<uint8_t> &oldParams) {
+  size_t newSize = newParams.size();
+  size_t oldSize = oldParams.size();
+  if (newSize == oldSize) {
+    for (size_t i = 0; i < newSize; ++i) {
+      if (newParams[i] != oldParams[i]) {
+        return false;
+      }
+    }
+    return true;
+  }
+  return false;
+}
+
+void getVtsAllocatorParams(std::vector<uint8_t> *params) {
+  constexpr static int kAllocationSize = 1024 * 10;
+  Params ionParams(kAllocationSize);
+
+  params->assign(ionParams.array, ionParams.array + sizeof(ionParams));
+}
diff --git a/media/libstagefright/codec2/vndk/bufferpool/vts/allocator.h b/media/libstagefright/codec2/vndk/bufferpool/vts/allocator.h
new file mode 100644
index 0000000..2fbb7fb
--- /dev/null
+++ b/media/libstagefright/codec2/vndk/bufferpool/vts/allocator.h
@@ -0,0 +1,49 @@
+/*
+ * 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 VTS_VNDK_HIDL_BUFFERPOOL_V1_0_ALLOCATOR_H
+#define VTS_VNDK_HIDL_BUFFERPOOL_V1_0_ALLOCATOR_H
+
+#include <BufferPoolTypes.h>
+
+using android::hardware::media::bufferpool::V1_0::ResultStatus;
+using android::hardware::media::bufferpool::V1_0::implementation::
+    BufferPoolAllocation;
+using android::hardware::media::bufferpool::V1_0::implementation::
+    BufferPoolAllocator;
+
+// buffer allocator for the tests
+class VtsBufferPoolAllocator : public BufferPoolAllocator {
+ public:
+  VtsBufferPoolAllocator(const std::shared_ptr<C2Allocator> &allocator)
+      : mAllocator(allocator) {}
+
+  ~VtsBufferPoolAllocator() override {}
+
+  ResultStatus allocate(const std::vector<uint8_t> &params,
+                        std::shared_ptr<BufferPoolAllocation> *alloc) override;
+
+  bool compatible(const std::vector<uint8_t> &newParams,
+                  const std::vector<uint8_t> &oldParams) override;
+
+ private:
+  const std::shared_ptr<C2Allocator> mAllocator;
+};
+
+// retrieve buffer allocator paramters
+void getVtsAllocatorParams(std::vector<uint8_t> *params);
+
+#endif  // VTS_VNDK_HIDL_BUFFERPOOL_V1_0_ALLOCATOR_H
diff --git a/media/libstagefright/codec2/vndk/bufferpool/vts/multi.cpp b/media/libstagefright/codec2/vndk/bufferpool/vts/multi.cpp
index 3ad8b6c..35127b8 100644
--- a/media/libstagefright/codec2/vndk/bufferpool/vts/multi.cpp
+++ b/media/libstagefright/codec2/vndk/bufferpool/vts/multi.cpp
@@ -35,6 +35,7 @@
 #include <iostream>
 #include <memory>
 #include <vector>
+#include "allocator.h"
 
 using android::C2AllocatorIon;
 using android::C2PlatformAllocatorStore;
@@ -50,9 +51,6 @@
 
 namespace {
 
-// Buffer allocation size for tests.
-constexpr static int kAllocationSize = 1024 * 10;
-
 // communication message types between processes.
 enum PipeCommand : int32_t {
     INIT_OK = 0,
@@ -95,11 +93,14 @@
     mManager = ClientManager::getInstance();
     ASSERT_NE(mManager, nullptr);
 
-    mAllocator =
+    std::shared_ptr<C2Allocator> allocator =
         std::make_shared<C2AllocatorIon>(C2PlatformAllocatorStore::ION);
+    ASSERT_TRUE((bool)allocator);
+
+    mAllocator = std::make_shared<VtsBufferPoolAllocator>(allocator);
     ASSERT_TRUE((bool)mAllocator);
 
-    status = mManager->create(mAllocator, true, &mConnectionId);
+    status = mManager->create(mAllocator, &mConnectionId);
     ASSERT_TRUE(status == ResultStatus::OK);
 
     status = mManager->getAccessor(mConnectionId, &mAccessor);
@@ -121,26 +122,12 @@
 
   android::sp<ClientManager> mManager;
   android::sp<IAccessor> mAccessor;
-  std::shared_ptr<C2Allocator> mAllocator;
+  std::shared_ptr<BufferPoolAllocator> mAllocator;
   ConnectionId mConnectionId;
   pid_t mReceiverPid;
   int mCommandPipeFds[2];
   int mResultPipeFds[2];
 
-  void getAllocationParams(std::vector<uint8_t>* vecParams) {
-    union Params {
-      struct {
-        uint32_t capacity;
-        C2MemoryUsage usage;
-      } data;
-      uint8_t array[0];
-      Params()
-          : data{kAllocationSize,
-                 {C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE}} {}
-    } params;
-    vecParams->assign(params.array, params.array + sizeof(params));
-  }
-
   bool sendMessage(int *pipes, const PipeMessage &message) {
     int ret = write(pipes[1], message.array, sizeof(PipeMessage));
     return ret == sizeof(PipeMessage);
@@ -211,7 +198,7 @@
     int64_t postUs;
     std::vector<uint8_t> vecParams;
 
-    getAllocationParams(&vecParams);
+    getVtsAllocatorParams(&vecParams);
     status = mManager->allocate(mConnectionId, vecParams, &sbuffer);
     ASSERT_TRUE(status == ResultStatus::OK);
 
diff --git a/media/libstagefright/codec2/vndk/bufferpool/vts/single.cpp b/media/libstagefright/codec2/vndk/bufferpool/vts/single.cpp
index 89eb631..c8878f3 100644
--- a/media/libstagefright/codec2/vndk/bufferpool/vts/single.cpp
+++ b/media/libstagefright/codec2/vndk/bufferpool/vts/single.cpp
@@ -32,6 +32,7 @@
 #include <iostream>
 #include <memory>
 #include <vector>
+#include "allocator.h"
 
 using android::C2AllocatorIon;
 using android::C2PlatformAllocatorStore;
@@ -45,9 +46,6 @@
 
 namespace {
 
-// Buffer allocation size for tests.
-constexpr static int kAllocationSize = 1024 * 10;
-
 // Number of iteration for buffer allocation test.
 constexpr static int kNumAllocationTest = 3;
 
@@ -63,11 +61,14 @@
     mManager = ClientManager::getInstance();
     ASSERT_NE(mManager, nullptr);
 
-    mAllocator =
+    std::shared_ptr<C2Allocator> allocator =
         std::make_shared<C2AllocatorIon>(C2PlatformAllocatorStore::ION);
+    ASSERT_TRUE((bool)allocator);
+
+    mAllocator = std::make_shared<VtsBufferPoolAllocator>(allocator);
     ASSERT_TRUE((bool)mAllocator);
 
-    status = mManager->create(mAllocator, true, &mConnectionId);
+    status = mManager->create(mAllocator, &mConnectionId);
     ASSERT_TRUE(status == ResultStatus::OK);
 
     status = mManager->getAccessor(mConnectionId, &mAccessor);
@@ -91,23 +92,10 @@
 
   android::sp<ClientManager> mManager;
   android::sp<IAccessor> mAccessor;
-  std::shared_ptr<C2Allocator> mAllocator;
+  std::shared_ptr<BufferPoolAllocator> mAllocator;
   ConnectionId mConnectionId;
   ConnectionId mReceiverId;
 
-  void getAllocationParams(std::vector<uint8_t>* vecParams) {
-    union Params {
-      struct {
-        uint32_t capacity;
-        C2MemoryUsage usage;
-      } data;
-      uint8_t array[0];
-      Params()
-          : data{kAllocationSize,
-                 {C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE}} {}
-    } params;
-    vecParams->assign(params.array, params.array + sizeof(params));
-  }
 };
 
 // Buffer allocation test.
@@ -116,7 +104,7 @@
 TEST_F(BufferpoolSingleTest, AllocateBuffer) {
   ResultStatus status;
   std::vector<uint8_t> vecParams;
-  getAllocationParams(&vecParams);
+  getVtsAllocatorParams(&vecParams);
 
   std::shared_ptr<_C2BlockPoolData> buffer[kNumAllocationTest];
   for (int i = 0; i < kNumAllocationTest; ++i) {
@@ -136,7 +124,7 @@
 TEST_F(BufferpoolSingleTest, RecycleBuffer) {
   ResultStatus status;
   std::vector<uint8_t> vecParams;
-  getAllocationParams(&vecParams);
+  getVtsAllocatorParams(&vecParams);
 
   BufferId bid[kNumRecycleTest];
   for (int i = 0; i < kNumRecycleTest; ++i) {
@@ -156,7 +144,7 @@
 TEST_F(BufferpoolSingleTest, TransferBuffer) {
   ResultStatus status;
   std::vector<uint8_t> vecParams;
-  getAllocationParams(&vecParams);
+  getVtsAllocatorParams(&vecParams);
   std::shared_ptr<_C2BlockPoolData> sbuffer, rbuffer;
 
   TransactionId transactionId;
diff --git a/media/libstagefright/codec2/vndk/include/C2AllocatorGralloc.h b/media/libstagefright/codec2/vndk/include/C2AllocatorGralloc.h
index 9dc7152..8d28192 100644
--- a/media/libstagefright/codec2/vndk/include/C2AllocatorGralloc.h
+++ b/media/libstagefright/codec2/vndk/include/C2AllocatorGralloc.h
@@ -65,6 +65,8 @@
 
     virtual ~C2AllocatorGralloc() override;
 
+    static bool isValid(const C2Handle* const o);
+
 private:
     class Impl;
     Impl *mImpl;
diff --git a/media/libstagefright/codec2/vndk/include/C2AllocatorIon.h b/media/libstagefright/codec2/vndk/include/C2AllocatorIon.h
index 716eae0..8c0992e 100644
--- a/media/libstagefright/codec2/vndk/include/C2AllocatorIon.h
+++ b/media/libstagefright/codec2/vndk/include/C2AllocatorIon.h
@@ -49,6 +49,8 @@
 
     virtual ~C2AllocatorIon() override;
 
+    static bool isValid(const C2Handle* const o);
+
 private:
     std::shared_ptr<const Traits> mTraits;
     c2_status_t mInit;
diff --git a/media/libstagefright/codec2/vndk/include/C2BufferPriv.h b/media/libstagefright/codec2/vndk/include/C2BufferPriv.h
index 977cf7b..211c13a 100644
--- a/media/libstagefright/codec2/vndk/include/C2BufferPriv.h
+++ b/media/libstagefright/codec2/vndk/include/C2BufferPriv.h
@@ -71,4 +71,32 @@
     const std::shared_ptr<C2Allocator> mAllocator;
 };
 
+class C2PooledBlockPool : public C2BlockPool {
+public:
+    C2PooledBlockPool(const std::shared_ptr<C2Allocator> &allocator, const local_id_t localId);
+
+    virtual ~C2PooledBlockPool() override;
+
+    virtual C2Allocator::id_t getAllocatorId() const override {
+        return mAllocator->getId();
+    }
+
+    virtual local_id_t getLocalId() const override {
+        return mLocalId;
+    }
+
+    virtual c2_status_t fetchLinearBlock(
+            uint32_t capacity,
+            C2MemoryUsage usage,
+            std::shared_ptr<C2LinearBlock> *block /* nonnull */) override;
+
+    // TODO: fetchGraphicBlock, fetchCircularBlock
+private:
+    const std::shared_ptr<C2Allocator> mAllocator;
+    const local_id_t mLocalId;
+
+    class Impl;
+    std::unique_ptr<Impl> mImpl;
+};
+
 #endif // STAGEFRIGHT_CODEC2_BUFFER_PRIV_H_
diff --git a/media/libstagefright/codec2/vndk/include/C2PlatformSupport.h b/media/libstagefright/codec2/vndk/include/C2PlatformSupport.h
index afa51ee..211ee0c 100644
--- a/media/libstagefright/codec2/vndk/include/C2PlatformSupport.h
+++ b/media/libstagefright/codec2/vndk/include/C2PlatformSupport.h
@@ -82,6 +82,23 @@
         std::shared_ptr<C2BlockPool> *pool);
 
 /**
+ * Creates a block pool.
+ * \param allocatorId  the allocator ID which is used to allocate blocks
+ * \param component     the component using the block pool (must be non-null)
+ * \param pool          pointer to where the created block pool shall be store on success.
+ *                      nullptr will be stored here on failure
+ *
+ * \retval C2_OK        the operation was successful
+ * \retval C2_BAD_VALUE the component is null
+ * \retval C2_NOT_FOUND if the allocator does not exist
+ * \retval C2_NO_MEMORY not enough memory to create a block pool
+ */
+c2_status_t CreateCodec2BlockPool(
+        C2PlatformAllocatorStore::id_t allocatorId,
+        std::shared_ptr<const C2Component> component,
+        std::shared_ptr<C2BlockPool> *pool);
+
+/**
  * Returns the platform component store.
  * \retval nullptr if the platform component store could not be obtained
  */
diff --git a/media/libstagefright/codec2/vndk/internal/C2BlockInternal.h b/media/libstagefright/codec2/vndk/internal/C2BlockInternal.h
index 25003cb..ff67788 100644
--- a/media/libstagefright/codec2/vndk/internal/C2BlockInternal.h
+++ b/media/libstagefright/codec2/vndk/internal/C2BlockInternal.h
@@ -39,7 +39,7 @@
      *         create this block.
      */
     static
-    std::shared_ptr<C2LinearBlock> C2_HIDE CreateLinearBlock(
+    std::shared_ptr<C2LinearBlock> CreateLinearBlock(
             const std::shared_ptr<C2LinearAllocation> &alloc,
             const std::shared_ptr<_C2BlockPoolData> &data = nullptr,
             size_t offset = 0,
diff --git a/media/libstagefright/codec2/vndk/internal/C2ParamInternal.h b/media/libstagefright/codec2/vndk/internal/C2ParamInternal.h
index 34797a9..ba7c5d5 100644
--- a/media/libstagefright/codec2/vndk/internal/C2ParamInternal.h
+++ b/media/libstagefright/codec2/vndk/internal/C2ParamInternal.h
@@ -20,6 +20,14 @@
 #include <C2Param.h>
 
 struct C2_HIDE _C2ParamInspector {
+    inline static uint32_t GetOffset(const C2FieldDescriptor &fd) {
+        return fd._mFieldId._mOffset;
+    }
+
+    inline static uint32_t GetSize(const C2FieldDescriptor &fd) {
+        return fd._mFieldId._mSize;
+    }
+
     inline static uint32_t GetIndex(const C2ParamField &pf) {
         return pf._mIndex;
     }
diff --git a/media/libstagefright/codecs/aacdec/C2SoftAac.cpp b/media/libstagefright/codecs/aacdec/C2SoftAac.cpp
index c82ea45..6bd15a5 100644
--- a/media/libstagefright/codecs/aacdec/C2SoftAac.cpp
+++ b/media/libstagefright/codecs/aacdec/C2SoftAac.cpp
@@ -305,37 +305,57 @@
         ALOGV("getting %d from ringbuffer", numSamples);
 
         std::shared_ptr<C2LinearBlock> block;
-        C2MemoryUsage usage = { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE };
-        // TODO: error handling, proper usage, etc.
-        c2_status_t err = pool->fetchLinearBlock(numSamples * sizeof(int16_t), usage, &block);
-        if (err != C2_OK) {
-            ALOGE("err = %d", err);
-        }
+        std::function<void(const std::unique_ptr<C2Work>&)> fillWork =
+            [&block, numSamples, pool, this]()
+                    -> std::function<void(const std::unique_ptr<C2Work>&)> {
+                auto fillEmptyWork = [](const std::unique_ptr<C2Work> &work, c2_status_t err) {
+                    work->result = err;
+                    work->worklets.front()->output.flags = work->input.flags;
+                    work->worklets.front()->output.buffers.clear();
+                    work->worklets.front()->output.ordinal = work->input.ordinal;
+                    work->workletsProcessed = 1u;
+                };
 
-        C2WriteView wView = block->map().get();
-        // TODO
-        INT_PCM *outBuffer = reinterpret_cast<INT_PCM *>(wView.data());
-        int32_t ns = outputDelayRingBufferGetSamples(outBuffer, numSamples);
-        if (ns != numSamples) {
-            ALOGE("not a complete frame of samples available");
-            mSignalledError = true;
-            // TODO: notify(OMX_EventError, OMX_ErrorUndefined, 0, NULL);
-            return;
-        }
-        auto fillWork = [buffer = createLinearBuffer(block)](const std::unique_ptr<C2Work> &work) {
-            work->worklets.front()->output.flags = work->input.flags;
-            work->worklets.front()->output.buffers.clear();
-            work->worklets.front()->output.buffers.push_back(buffer);
-            work->worklets.front()->output.ordinal = work->input.ordinal;
-            work->workletsProcessed = 1u;
-        };
+                using namespace std::placeholders;
+                if (numSamples == 0) {
+                    return std::bind(fillEmptyWork, _1, C2_OK);
+                }
+
+                // TODO: error handling, proper usage, etc.
+                C2MemoryUsage usage = { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE };
+                c2_status_t err = pool->fetchLinearBlock(
+                        numSamples * sizeof(int16_t), usage, &block);
+                if (err != C2_OK) {
+                    ALOGD("failed to fetch a linear block (%d)", err);
+                    mSignalledError = true;
+                    return std::bind(fillEmptyWork, _1, C2_NO_MEMORY);
+                }
+                C2WriteView wView = block->map().get();
+                // TODO
+                INT_PCM *outBuffer = reinterpret_cast<INT_PCM *>(wView.data());
+                int32_t ns = outputDelayRingBufferGetSamples(outBuffer, numSamples);
+                if (ns != numSamples) {
+                    ALOGE("not a complete frame of samples available");
+                    mSignalledError = true;
+                    return std::bind(fillEmptyWork, _1, C2_CORRUPTED);
+                }
+                return [buffer = createLinearBuffer(block)](const std::unique_ptr<C2Work> &work) {
+                    work->result = C2_OK;
+                    work->worklets.front()->output.flags = work->input.flags;
+                    work->worklets.front()->output.buffers.clear();
+                    work->worklets.front()->output.buffers.push_back(buffer);
+                    work->worklets.front()->output.ordinal = work->input.ordinal;
+                    work->workletsProcessed = 1u;
+                };
+            }();
+
         if (work && work->input.ordinal.frameIndex == c2_cntr64_t(outInfo.frameIndex)) {
             fillWork(work);
         } else {
             finish(outInfo.frameIndex, fillWork);
         }
 
-        ALOGV("out timestamp %" PRIu64 " / %u", outInfo.timestamp, block->capacity());
+        ALOGV("out timestamp %" PRIu64 " / %u", outInfo.timestamp, block ? block->capacity() : 0);
         mBuffersInfo.pop_front();
     }
 }
diff --git a/media/libstagefright/codecs/amrnb/enc/Android.bp b/media/libstagefright/codecs/amrnb/enc/Android.bp
index f844459..4001d46 100644
--- a/media/libstagefright/codecs/amrnb/enc/Android.bp
+++ b/media/libstagefright/codecs/amrnb/enc/Android.bp
@@ -122,6 +122,50 @@
 
 //###############################################################################
 
+cc_library_shared {
+    name: "libstagefright_soft_c2amrnbenc",
+//    vendor_available: true,
+//    vndk: {
+//        enabled: true,
+//    },
+
+    srcs: ["C2SoftAmrNbEnc.cpp"],
+
+    local_include_dirs: ["src"],
+
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+
+    sanitize: {
+        misc_undefined: [
+            "signed-integer-overflow",
+            "unsigned-integer-overflow",
+        ],
+        cfi: true,
+        diag: {
+            cfi: true,
+        },
+    },
+
+    static_libs: [
+        "libstagefright_amrnbenc",
+    ],
+
+    shared_libs: [
+        "libutils",
+        "liblog",
+        "libstagefright_codec2",
+        "libstagefright_codec2_vndk",
+        "libstagefright_foundation",
+        "libstagefright_simple_c2component",
+        "libstagefright_amrnb_common",
+    ],
+}
+
+//###############################################################################
+
 cc_test {
     name: "libstagefright_amrnbenc_test",
     gtest: false,
diff --git a/media/libstagefright/codecs/amrnb/enc/C2SoftAmrNbEnc.cpp b/media/libstagefright/codecs/amrnb/enc/C2SoftAmrNbEnc.cpp
new file mode 100644
index 0000000..406d1ca
--- /dev/null
+++ b/media/libstagefright/codecs/amrnb/enc/C2SoftAmrNbEnc.cpp
@@ -0,0 +1,252 @@
+/*
+ * 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
+#define LOG_TAG "C2SoftAmrNbEnc"
+#include <utils/Log.h>
+
+#include "gsmamr_enc.h"
+
+#include "C2SoftAmrNbEnc.h"
+
+#include <C2PlatformSupport.h>
+#include <SimpleC2Interface.h>
+
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/MediaDefs.h>
+
+namespace android {
+
+constexpr char kComponentName[] = "c2.google.amrnb.encoder";
+
+static std::shared_ptr<C2ComponentInterface> BuildIntf(
+        const char *name, c2_node_id_t id,
+        std::function<void(C2ComponentInterface*)> deleter =
+            std::default_delete<C2ComponentInterface>()) {
+    return SimpleC2Interface::Builder(name, id, deleter)
+            .inputFormat(C2FormatAudio)
+            .outputFormat(C2FormatCompressed)
+            .inputMediaType(MEDIA_MIMETYPE_AUDIO_RAW)
+            .outputMediaType(MEDIA_MIMETYPE_AUDIO_AMR_NB)
+            .build();
+}
+
+C2SoftAmrNbEnc::C2SoftAmrNbEnc(const char *name, c2_node_id_t id)
+    : SimpleC2Component(BuildIntf(name, id)),
+      mEncState(nullptr),
+      mSidState(nullptr) {
+}
+
+C2SoftAmrNbEnc::~C2SoftAmrNbEnc() {
+    onRelease();
+}
+
+c2_status_t C2SoftAmrNbEnc::onInit() {
+    bool dtx_enable = false;
+
+    if (AMREncodeInit(&mEncState, &mSidState, dtx_enable) != 0)
+        return C2_CORRUPTED;
+    mMode = MR795;
+    mIsFirst = true;
+    mSignalledError = false;
+    mSignalledOutputEos = false;
+    mAnchorTimeStamp = 0;
+    mProcessedSamples = 0;
+    mFilledLen = 0;
+
+    return C2_OK;
+}
+
+void C2SoftAmrNbEnc::onRelease() {
+    if (mEncState) {
+        AMREncodeExit(&mEncState, &mSidState);
+        mEncState = mSidState = nullptr;
+    }
+}
+
+c2_status_t C2SoftAmrNbEnc::onStop() {
+    if (AMREncodeReset(mEncState, mSidState) != 0)
+        return C2_CORRUPTED;
+    mIsFirst = true;
+    mSignalledError = false;
+    mSignalledOutputEos = false;
+    mAnchorTimeStamp = 0;
+    mProcessedSamples = 0;
+    mFilledLen = 0;
+
+    return C2_OK;
+}
+
+void C2SoftAmrNbEnc::onReset() {
+    (void) onStop();
+}
+
+c2_status_t C2SoftAmrNbEnc::onFlush_sm() {
+    return onStop();
+}
+
+static void fillEmptyWork(const std::unique_ptr<C2Work> &work) {
+    work->worklets.front()->output.flags = work->input.flags;
+    work->worklets.front()->output.buffers.clear();
+    work->worklets.front()->output.ordinal = work->input.ordinal;
+    work->workletsProcessed = 1u;
+}
+
+void C2SoftAmrNbEnc::process(
+        const std::unique_ptr<C2Work> &work,
+        const std::shared_ptr<C2BlockPool> &pool) {
+    work->result = C2_OK;
+    work->workletsProcessed = 0u;
+    if (mSignalledError || mSignalledOutputEos) {
+        work->result = C2_BAD_VALUE;
+        return;
+    }
+
+    const C2ConstLinearBlock inBuffer = work->input.buffers[0]->data().linearBlocks().front();
+    bool eos = ((work->input.flags & C2FrameData::FLAG_END_OF_STREAM) != 0);
+    size_t inOffset = inBuffer.offset();
+    size_t inSize = inBuffer.size();
+    C2ReadView rView = work->input.buffers[0]->data().linearBlocks().front().map().get();
+    if (inSize && rView.error()) {
+        ALOGE("read view map failed %d", rView.error());
+        work->result = C2_CORRUPTED;
+        return;
+    }
+
+    ALOGV("in buffer attr. size %zu timestamp %d frameindex %d, flags %x",
+          inSize, (int)work->input.ordinal.timestamp.peeku(),
+          (int)work->input.ordinal.frameIndex.peeku(), work->input.flags);
+
+    size_t outCapacity = kNumBytesPerInputFrame;
+    outCapacity += mFilledLen + inSize;
+    std::shared_ptr<C2LinearBlock> outputBlock;
+    C2MemoryUsage usage = { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE };
+    c2_status_t err = pool->fetchLinearBlock(outCapacity, usage, &outputBlock);
+    if (err != C2_OK) {
+        ALOGE("fetchLinearBlock for Output failed with status %d", err);
+        work->result = C2_NO_MEMORY;
+        return;
+    }
+    C2WriteView wView = outputBlock->map().get();
+    if (wView.error()) {
+        ALOGE("write view map failed %d", wView.error());
+        work->result = C2_CORRUPTED;
+        return;
+    }
+
+    uint64_t outTimeStamp = mProcessedSamples * 1000000ll / kSampleRate;
+    const uint8_t *inPtr = rView.data() + inOffset;
+    size_t inPos = 0;
+    size_t outPos = 0;
+    while (inPos < inSize) {
+        int validSamples = mFilledLen / sizeof(int16_t);
+        if ((inPos + (kNumBytesPerInputFrame - mFilledLen)) <= inSize) {
+            memcpy(mInputFrame + validSamples, inPtr + inPos,
+                   (kNumBytesPerInputFrame - mFilledLen));
+            inPos += (kNumBytesPerInputFrame - mFilledLen);
+        } else {
+            memcpy(mInputFrame + validSamples, inPtr + inPos, (inSize - inPos));
+            mFilledLen += (inSize - inPos);
+            inPos += (inSize - inPos);
+            if (eos) {
+                validSamples = mFilledLen / sizeof(int16_t);
+                memset(mInputFrame + validSamples, 0, (kNumBytesPerInputFrame - mFilledLen));
+            } else break;
+        }
+        Frame_Type_3GPP frameType;
+        int numEncBytes = AMREncode(mEncState, mSidState, mMode, mInputFrame,
+                                    wView.data() + outPos, &frameType,
+                                    AMR_TX_WMF);
+        if (numEncBytes < 0 || numEncBytes > ((int)outCapacity - (int)outPos)) {
+            ALOGE("encodeFrame call failed, state [%d %zu %zu]", numEncBytes, outPos, outCapacity);
+            mSignalledError = true;
+            work->result = C2_CORRUPTED;
+            return;
+        }
+        // Convert header byte from WMF to IETF format.
+        if (numEncBytes > 0)
+            wView.data()[outPos] = ((wView.data()[outPos] << 3) | 4) & 0x7c;
+        outPos += numEncBytes;
+        mProcessedSamples += kNumSamplesPerFrame;
+        mFilledLen = 0;
+    }
+    ALOGV("causal sample size %d", mFilledLen);
+    if (mIsFirst) {
+        mIsFirst = false;
+        mAnchorTimeStamp = work->input.ordinal.timestamp.peekull();
+    }
+    fillEmptyWork(work);
+    if (outPos != 0) {
+        work->worklets.front()->output.buffers.push_back(
+                createLinearBuffer(std::move(outputBlock), 0, outPos));
+        work->worklets.front()->output.ordinal.timestamp = mAnchorTimeStamp + outTimeStamp;
+    }
+    if (eos) {
+        mSignalledOutputEos = true;
+        ALOGV("signalled EOS");
+        if (mFilledLen) ALOGV("Discarding trailing %d bytes", mFilledLen);
+    }
+}
+
+c2_status_t C2SoftAmrNbEnc::drain(
+        uint32_t drainMode,
+        const std::shared_ptr<C2BlockPool> &pool) {
+    (void) pool;
+    if (drainMode == NO_DRAIN) {
+        ALOGW("drain with NO_DRAIN: no-op");
+        return C2_OK;
+    }
+    if (drainMode == DRAIN_CHAIN) {
+        ALOGW("DRAIN_CHAIN not supported");
+        return C2_OMITTED;
+    }
+
+    onFlush_sm();
+    return C2_OK;
+}
+
+class C2SoftAmrNbEncFactory : public C2ComponentFactory {
+public:
+    virtual c2_status_t createComponent(
+            c2_node_id_t id,
+            std::shared_ptr<C2Component>* const component,
+            std::function<void(C2Component*)> deleter) override {
+        *component = std::shared_ptr<C2Component>(new C2SoftAmrNbEnc(kComponentName, id), deleter);
+        return C2_OK;
+    }
+
+    virtual c2_status_t createInterface(
+            c2_node_id_t id,
+            std::shared_ptr<C2ComponentInterface>* const interface,
+            std::function<void(C2ComponentInterface*)> deleter) override {
+        *interface = BuildIntf(kComponentName, id, deleter);
+        return C2_OK;
+    }
+
+    virtual ~C2SoftAmrNbEncFactory() override = default;
+};
+
+}  // namespace android
+
+extern "C" ::C2ComponentFactory* CreateCodec2Factory() {
+    ALOGV("in %s", __func__);
+    return new ::android::C2SoftAmrNbEncFactory();
+}
+
+extern "C" void DestroyCodec2Factory(::C2ComponentFactory* factory) {
+    ALOGV("in %s", __func__);
+    delete factory;
+}
diff --git a/media/libstagefright/codecs/amrnb/enc/C2SoftAmrNbEnc.h b/media/libstagefright/codecs/amrnb/enc/C2SoftAmrNbEnc.h
new file mode 100644
index 0000000..9ced921
--- /dev/null
+++ b/media/libstagefright/codecs/amrnb/enc/C2SoftAmrNbEnc.h
@@ -0,0 +1,65 @@
+/*
+ * 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 C2_SOFT_AMRNB_ENC_H_
+#define C2_SOFT_AMRNB_ENC_H_
+
+#include <SimpleC2Component.h>
+
+#include <media/stagefright/foundation/ADebug.h>
+
+namespace android {
+
+class C2SoftAmrNbEnc : public SimpleC2Component {
+public:
+    C2SoftAmrNbEnc(const char *name, c2_node_id_t id);
+    virtual ~C2SoftAmrNbEnc();
+
+    // From SimpleC2Component
+    c2_status_t onInit() override;
+    c2_status_t onStop() override;
+    void onReset() override;
+    void onRelease() override;
+    c2_status_t onFlush_sm() override;
+    void process(
+            const std::unique_ptr<C2Work> &work,
+            const std::shared_ptr<C2BlockPool> &pool) override;
+    c2_status_t drain(
+            uint32_t drainMode,
+            const std::shared_ptr<C2BlockPool> &pool) override;
+
+private:
+    static const int32_t kNumSamplesPerFrame = L_FRAME;
+    static const int32_t kNumBytesPerInputFrame = kNumSamplesPerFrame * sizeof(int16_t);
+    static const int32_t kSampleRate = 8000;
+
+    void *mEncState;
+    void *mSidState;
+    Mode mMode;
+    bool mIsFirst;
+    bool mSignalledError;
+    bool mSignalledOutputEos;
+    uint64_t mAnchorTimeStamp;
+    uint64_t mProcessedSamples;
+    int32_t mFilledLen;
+    int16_t mInputFrame[kNumSamplesPerFrame];
+
+    DISALLOW_EVIL_CONSTRUCTORS(C2SoftAmrNbEnc);
+};
+
+}  // namespace android
+
+#endif  // C2_SOFT_AMRNB_ENC_H_
diff --git a/media/libstagefright/codecs/avcdec/C2SoftAvcDec.cpp b/media/libstagefright/codecs/avcdec/C2SoftAvcDec.cpp
index b9ba251..1fc2c8c 100644
--- a/media/libstagefright/codecs/avcdec/C2SoftAvcDec.cpp
+++ b/media/libstagefright/codecs/avcdec/C2SoftAvcDec.cpp
@@ -37,15 +37,6 @@
 
 #include "ih264d_defs.h"
 
-namespace {
-
-template <class T>
-inline int32_t floor32(T arg) {
-   return (int32_t) std::llround(std::floor(arg));
-}
-
-} // namespace
-
 namespace android {
 
 struct iv_obj_t : public ::iv_obj_t {};
@@ -83,138 +74,19 @@
             .build();
 }
 
-#if 0
-using SupportedValuesWithFields = C2SoftAvcDecIntf::SupportedValuesWithFields;
-
-struct ValidateParam {
-    explicit ValidateParam(
-            const std::map<C2ParamField, SupportedValuesWithFields> &supportedValues)
-        : mSupportedValues(supportedValues) {}
-
-    template <class T, bool SIGNED = std::is_signed<T>::value, size_t SIZE = sizeof(T)>
-    struct Getter {
-        static T get(const C2Value::Primitive &) {
-            static_assert(!std::is_arithmetic<T>::value, "non-arithmetic type");
-            static_assert(!std::is_floating_point<T>::value || std::is_same<T, float>::value,
-                    "float is the only supported floating point type");
-            static_assert(sizeof(T) <= 8, "type exceeds 64-bit");
+void CopyPlane(
+        uint8_t *dst, const C2PlaneInfo &plane,
+        const uint8_t *src, uint32_t width, uint32_t height) {
+    for (uint32_t row = 0; row < height; ++row) {
+        for (uint32_t col = 0; col < width; ++col) {
+            *dst = *src;
+            dst += plane.colInc;
+            ++src;
         }
-    };
-
-    template <class T>
-    bool validateField(
-            const C2FieldSupportedValues &supportedValues, const T &value) {
-        switch (supportedValues.type) {
-        case C2FieldSupportedValues::EMPTY:
-            {
-                return false;
-            }
-        case C2FieldSupportedValues::RANGE:
-            {
-                // TODO: handle step, num, denom
-                return Getter<T>::get(supportedValues.range.min) <= value
-                        && value <= Getter<T>::get(supportedValues.range.max);
-            }
-        case C2FieldSupportedValues::VALUES:
-            {
-                for (const auto &val : supportedValues.values) {
-                    if (Getter<T>::get(val) == value) {
-                        return true;
-                    }
-                }
-                return false;
-            }
-        case C2FieldSupportedValues::FLAGS:
-            // TODO
-            return false;
-        }
-        return false;
+        dst -= plane.colInc * width;
+        dst += plane.rowInc;
     }
-
-protected:
-    const std::map<C2ParamField, SupportedValuesWithFields> &mSupportedValues;
-};
-
-template <>
-struct ValidateParam::Getter<float> {
-    static float get(const C2Value::Primitive &value) { return value.fp; }
-};
-template <class T>
-struct ValidateParam::Getter<T, true, 8u> {
-    static int64_t get(const C2Value::Primitive &value) { return value.i64; }
-};
-template <class T>
-struct ValidateParam::Getter<T, true, 4u> {
-    static int32_t get(const C2Value::Primitive &value) { return value.i32; }
-};
-template <class T>
-struct ValidateParam::Getter<T, false, 8u> {
-    static uint64_t get(const C2Value::Primitive &value) { return value.u64; }
-};
-template <class T>
-struct ValidateParam::Getter<T, false, 4u> {
-    static uint32_t get(const C2Value::Primitive &value) { return value.u32; }
-};
-
-template <class T>
-struct ValidateSimpleParam : public ValidateParam {
-    explicit ValidateSimpleParam(
-            const std::map<C2ParamField, SupportedValuesWithFields> &supportedValues)
-        : ValidateParam(supportedValues) {}
-
-    std::unique_ptr<C2SettingResult> operator() (C2Param *c2param) {
-        T* param = (T*)c2param;
-        C2ParamField field(param, &T::value);
-        const C2FieldSupportedValues &supportedValues = mSupportedValues.at(field).supported;
-        if (!validateField(supportedValues, param->value)) {
-            return std::unique_ptr<C2SettingResult>(
-                    new C2SettingResult {C2SettingResult::BAD_VALUE, {field, nullptr}, {}});
-        }
-        return nullptr;
-    }
-};
-
-template <class T>
-struct ValidateVideoSize : public ValidateParam {
-    explicit ValidateVideoSize(
-            const std::map<C2ParamField, SupportedValuesWithFields> &supportedValues)
-        : ValidateParam(supportedValues) {}
-
-    std::unique_ptr<C2SettingResult> operator() (C2Param *c2param) {
-        T* param = (T*)c2param;
-        C2ParamField field(param, &T::width);
-        const C2FieldSupportedValues &supportedWidth = mSupportedValues.at(field).supported;
-        if (!validateField(supportedWidth, param->width)) {
-            return std::unique_ptr<C2SettingResult>(
-                    new C2SettingResult {C2SettingResult::BAD_VALUE, {field, nullptr}, {}});
-        }
-        field = C2ParamField(param, &T::height);
-        const C2FieldSupportedValues &supportedHeight = mSupportedValues.at(field).supported;
-        if (!validateField(supportedHeight, param->height)) {
-            return std::unique_ptr<C2SettingResult>(
-                    new C2SettingResult {C2SettingResult::BAD_VALUE, {field, nullptr}, {}});
-        }
-        return nullptr;
-    }
-};
-
-template <class T>
-struct ValidateCString {
-    explicit ValidateCString(const char *expected) : mExpected(expected) {}
-
-    std::unique_ptr<C2SettingResult> operator() (C2Param *c2param) {
-        T* param = (T*)c2param;
-        if (strncmp(param->m.value, mExpected, param->flexCount()) != 0) {
-            return std::unique_ptr<C2SettingResult>(
-                    new C2SettingResult {C2SettingResult::BAD_VALUE, {C2ParamField(param, &T::m), nullptr}, {}});
-        }
-        return nullptr;
-    }
-
-private:
-    const char *mExpected;
-};
-#endif
+}
 
 void fillEmptyWork(const std::unique_ptr<C2Work> &work) {
     uint32_t flags = 0;
@@ -229,398 +101,6 @@
 
 }  // namespace
 
-#if 0
-#define CASE(member) \
-    case decltype(component->member)::CORE_INDEX: \
-        return std::unique_ptr<C2StructDescriptor>(new C2StructDescriptor( \
-                static_cast<decltype(component->member) *>(nullptr)))
-
-class C2SoftAvcDecIntf::ParamReflector : public C2ParamReflector {
-public:
-    virtual std::unique_ptr<C2StructDescriptor> describe(C2Param::CoreIndex coreIndex) override {
-        constexpr C2SoftAvcDecIntf *component = nullptr;
-        switch (coreIndex.coreIndex()) {
-        CASE(mDomainInfo);
-        CASE(mInputStreamCount);
-        CASE(mInputStreamFormat);
-        // Output counterparts for the above would be redundant.
-        CASE(mVideoSize);
-        CASE(mMaxVideoSizeHint);
-
-        // port mime configs are stored as unique_ptr.
-        case C2PortMimeConfig::CORE_INDEX:
-            return std::unique_ptr<C2StructDescriptor>(new C2StructDescriptor(
-                    static_cast<C2PortMimeConfig *>(nullptr)));
-        }
-        return nullptr;
-    }
-};
-#undef CASE
-
-// static const CodecProfileLevel kProfileLevels[] = {
-//     { OMX_VIDEO_AVCProfileBaseline, OMX_VIDEO_AVCLevel52 },
-//     { OMX_VIDEO_AVCProfileMain,     OMX_VIDEO_AVCLevel52 },
-//     { OMX_VIDEO_AVCProfileHigh,     OMX_VIDEO_AVCLevel52 },
-// };
-C2SoftAvcDecIntf::C2SoftAvcDecIntf(const char *name, c2_node_id_t id)
-    : mName(name),
-      mId(id),
-      mDomainInfo(C2DomainVideo),
-      mInputStreamCount(1u),
-      mOutputStreamCount(1u),
-      mInputStreamFormat(0u, C2FormatCompressed),
-      mOutputStreamFormat(0u, C2FormatVideo),
-      mProfile(0u, kAvcProfileUnknown),
-      mLevel(0u, kAvcLevelUnknown),
-      mBlockSize(0u),
-      mAlignment(0u),
-      mFrameRate(0u, 0),
-      mBlocksPerSecond(0u, 0),
-      mParamReflector(new ParamReflector) {
-    ALOGV("in %s", __func__);
-    mInputPortMime = C2PortMimeConfig::input::AllocUnique(strlen(CODEC_MIME_TYPE) + 1);
-    strcpy(mInputPortMime->m.value, CODEC_MIME_TYPE);
-    mOutputPortMime = C2PortMimeConfig::output::AllocUnique(strlen(MEDIA_MIMETYPE_VIDEO_RAW) + 1);
-    strcpy(mOutputPortMime->m.value, MEDIA_MIMETYPE_VIDEO_RAW);
-
-    mVideoSize.width = 320;
-    mVideoSize.height = 240;
-    mBlockSize.width = 16;
-    mBlockSize.height = 16;
-    mAlignment.width = 2;
-    mAlignment.height = 2;
-
-    mMaxVideoSizeHint.width = H264_MAX_FRAME_WIDTH;
-    mMaxVideoSizeHint.height = H264_MAX_FRAME_HEIGHT;
-
-    mOutputBlockPools = C2PortBlockPoolsTuning::output::AllocUnique({});
-
-    auto insertParam = [&params = mParams] (C2Param *param) {
-        params[param->index()] = param;
-    };
-
-    auto markReadOnly = [&supported = mSupportedValues] (auto *param) {
-        supported.emplace(
-                C2ParamField(param, &std::remove_pointer<decltype(param)>::type::value),
-                C2FieldSupportedValues(false /* flags */, {}));
-    };
-
-    auto markReadOnlyVideoSize = [&supported = mSupportedValues] (auto *param) {
-        supported.emplace(
-                C2ParamField(param, &std::remove_pointer<decltype(param)>::type::width),
-                C2FieldSupportedValues(false /* flags */, {}));
-        supported.emplace(
-                C2ParamField(param, &std::remove_pointer<decltype(param)>::type::height),
-                C2FieldSupportedValues(false /* flags */, {}));
-    };
-
-    insertParam(&mDomainInfo);
-    markReadOnly(&mDomainInfo);
-    mFieldVerifiers[mDomainInfo.index()] =
-            ValidateSimpleParam<decltype(mDomainInfo)>(mSupportedValues);
-
-    insertParam(mInputPortMime.get());
-    mFieldVerifiers[mInputPortMime->index()] =
-            ValidateCString<std::remove_reference<decltype(*mInputPortMime)>::type>(CODEC_MIME_TYPE);
-
-    insertParam(&mInputStreamCount);
-    markReadOnly(&mInputStreamCount);
-    mFieldVerifiers[mInputStreamCount.index()] =
-            ValidateSimpleParam<decltype(mInputStreamCount)>(mSupportedValues);
-
-    insertParam(mOutputPortMime.get());
-    mFieldVerifiers[mOutputPortMime->index()] =
-            ValidateCString<std::remove_reference<decltype(*mOutputPortMime)>::type>(MEDIA_MIMETYPE_VIDEO_RAW);
-
-    insertParam(&mOutputStreamCount);
-    markReadOnly(&mOutputStreamCount);
-    mFieldVerifiers[mOutputStreamCount.index()] =
-            ValidateSimpleParam<decltype(mOutputStreamCount)>(mSupportedValues);
-
-    insertParam(&mInputStreamFormat);
-    markReadOnly(&mInputStreamFormat);
-    mFieldVerifiers[mInputStreamFormat.index()] =
-            ValidateSimpleParam<decltype(mInputStreamFormat)>(mSupportedValues);
-
-    insertParam(&mOutputStreamFormat);
-    markReadOnly(&mOutputStreamFormat);
-    mFieldVerifiers[mOutputStreamFormat.index()] =
-            ValidateSimpleParam<decltype(mOutputStreamFormat)>(mSupportedValues);
-
-    insertParam(&mVideoSize);
-    markReadOnlyVideoSize(&mVideoSize);
-    mFieldVerifiers[mVideoSize.index()] =
-            ValidateVideoSize<decltype(mVideoSize)>(mSupportedValues);
-
-    insertParam(&mMaxVideoSizeHint);
-    mSupportedValues.emplace(
-            C2ParamField(&mMaxVideoSizeHint, &C2MaxVideoSizeHintPortSetting::width),
-            C2FieldSupportedValues(H264_MIN_FRAME_WIDTH, H264_MAX_FRAME_WIDTH, mAlignment.width));
-    mSupportedValues.emplace(
-            C2ParamField(&mMaxVideoSizeHint, &C2MaxVideoSizeHintPortSetting::height),
-            C2FieldSupportedValues(H264_MIN_FRAME_HEIGHT, H264_MAX_FRAME_HEIGHT, mAlignment.height));
-    mFieldVerifiers[mMaxVideoSizeHint.index()] =
-            ValidateVideoSize<decltype(mMaxVideoSizeHint)>(mSupportedValues);
-
-    insertParam(&mProfile);
-    mSupportedValues.emplace(
-            C2ParamField(&mProfile, &C2AvcProfileInfo::value),
-            C2FieldSupportedValues(false /* flags */, {
-                kAvcProfileUnknown,
-                kAvcProfileBaseline,
-                kAvcProfileMain,
-                kAvcProfileHigh,
-            }));
-    mFieldVerifiers[mProfile.index()] =
-            ValidateSimpleParam<decltype(mProfile)>(mSupportedValues);
-
-    insertParam(&mLevel);
-    mSupportedValues.emplace(
-            C2ParamField(&mLevel, &C2AvcLevelInfo::value),
-            C2FieldSupportedValues(false /* flags */, {
-                kAvcLevelUnknown,
-                kAvcLevel10,
-                kAvcLevel1b,
-                kAvcLevel11,
-                kAvcLevel12,
-                kAvcLevel13,
-                kAvcLevel20,
-                kAvcLevel21,
-                kAvcLevel22,
-                kAvcLevel30,
-                kAvcLevel31,
-                kAvcLevel32,
-                kAvcLevel40,
-                kAvcLevel41,
-                kAvcLevel42,
-                kAvcLevel50,
-                kAvcLevel51,
-                kAvcLevel52,
-            }));
-    mFieldVerifiers[mLevel.index()] =
-            ValidateSimpleParam<decltype(mLevel)>(mSupportedValues);
-
-    insertParam(&mBlockSize);
-    markReadOnlyVideoSize(&mBlockSize);
-    mFieldVerifiers[mBlockSize.index()] =
-            ValidateVideoSize<decltype(mBlockSize)>(mSupportedValues);
-
-    insertParam(&mAlignment);
-    markReadOnlyVideoSize(&mAlignment);
-    mFieldVerifiers[mAlignment.index()] =
-            ValidateVideoSize<decltype(mAlignment)>(mSupportedValues);
-
-    insertParam(&mFrameRate);
-    mSupportedValues.emplace(
-            C2ParamField(&mFrameRate, &C2FrameRateInfo::value),
-            C2FieldSupportedValues(0, 240));
-    mFieldVerifiers[mFrameRate.index()] =
-            ValidateSimpleParam<decltype(mFrameRate)>(mSupportedValues);
-
-    insertParam(&mBlocksPerSecond);
-    mSupportedValues.emplace(
-            C2ParamField(&mFrameRate, &C2BlocksPerSecondInfo::value),
-            C2FieldSupportedValues(0, 244800));
-    mFieldVerifiers[mBlocksPerSecond.index()] =
-            ValidateSimpleParam<decltype(mBlocksPerSecond)>(mSupportedValues);
-
-    mParamDescs.push_back(std::make_shared<C2ParamDescriptor>(
-            true, "_domain", &mDomainInfo));
-    mParamDescs.push_back(std::make_shared<C2ParamDescriptor>(
-            true, "_input_port_mime", mInputPortMime.get()));
-    mParamDescs.push_back(std::make_shared<C2ParamDescriptor>(
-            true, "_input_stream_count", &mInputStreamCount));
-    mParamDescs.push_back(std::make_shared<C2ParamDescriptor>(
-            true, "_output_port_mime", mOutputPortMime.get()));
-    mParamDescs.push_back(std::make_shared<C2ParamDescriptor>(
-            true, "_output_stream_count", &mOutputStreamCount));
-    mParamDescs.push_back(std::make_shared<C2ParamDescriptor>(
-            true, "_input_stream_format", &mInputStreamFormat));
-    mParamDescs.push_back(std::make_shared<C2ParamDescriptor>(
-            true, "_output_stream_format", &mOutputStreamFormat));
-    mParamDescs.push_back(std::make_shared<C2ParamDescriptor>(
-            false, "_video_size", &mVideoSize));
-    mParamDescs.push_back(std::make_shared<C2ParamDescriptor>(
-            false, "_max_video_size_hint", &mMaxVideoSizeHint));
-    mParamDescs.push_back(std::make_shared<C2ParamDescriptor>(
-            false, "_output_block_pools", mOutputBlockPools.get()));
-}
-
-C2SoftAvcDecIntf::~C2SoftAvcDecIntf() {
-    ALOGV("in %s", __func__);
-}
-
-C2String C2SoftAvcDecIntf::getName() const {
-    return mName;
-}
-
-c2_node_id_t C2SoftAvcDecIntf::getId() const {
-    return mId;
-}
-
-c2_status_t C2SoftAvcDecIntf::query_vb(
-        const std::vector<C2Param*> & stackParams,
-        const std::vector<C2Param::Index> & heapParamIndices,
-        c2_blocking_t mayBlock,
-        std::vector<std::unique_ptr<C2Param>>* const heapParams) const {
-    (void)mayBlock;
-    for (C2Param* const param : stackParams) {
-        if (!*param) {
-            continue;
-        }
-
-        uint32_t index = param->index();
-        if (!mParams.count(index)) {
-            // TODO: add support for output-block-pools (this will be done when we move all
-            // config to shared ptr)
-            continue;
-        }
-
-        C2Param *myParam = mParams.find(index)->second;
-        if (myParam->size() != param->size()) {
-            param->invalidate();
-            continue;
-        }
-
-        param->updateFrom(*myParam);
-    }
-
-    for (const C2Param::Index index : heapParamIndices) {
-        if (mParams.count(index)) {
-            C2Param *myParam = mParams.find(index)->second;
-            heapParams->emplace_back(C2Param::Copy(*myParam));
-        }
-    }
-
-    return C2_OK;
-}
-
-c2_status_t C2SoftAvcDecIntf::config_vb(
-        const std::vector<C2Param*> &params,
-        c2_blocking_t mayBlock,
-        std::vector<std::unique_ptr<C2SettingResult>>* const failures) {
-    (void)mayBlock;
-    c2_status_t err = C2_OK;
-    for (C2Param *param : params) {
-        uint32_t index = param->index();
-        if (param->index() == mOutputBlockPools.get()->index()) {
-            // setting output block pools
-            mOutputBlockPools.reset(
-                    (C2PortBlockPoolsTuning::output *)C2Param::Copy(*param).release());
-            continue;
-        }
-
-        if (mParams.count(index) == 0) {
-            // We can't create C2SettingResult with no field, so just skipping in this case.
-            err = C2_BAD_INDEX;
-            continue;
-        }
-        C2Param *myParam = mParams.find(index)->second;
-        std::unique_ptr<C2SettingResult> result;
-        if (!(result = mFieldVerifiers[index](param))) {
-            myParam->updateFrom(*param);
-            updateSupportedValues();
-        } else {
-            failures->push_back(std::move(result));
-            err = C2_BAD_VALUE;
-        }
-    }
-    return err;
-}
-
-c2_status_t C2SoftAvcDecIntf::createTunnel_sm(c2_node_id_t targetComponent) {
-    // Tunneling is not supported
-    (void) targetComponent;
-    return C2_OMITTED;
-}
-
-c2_status_t C2SoftAvcDecIntf::releaseTunnel_sm(c2_node_id_t targetComponent) {
-    // Tunneling is not supported
-    (void) targetComponent;
-    return C2_OMITTED;
-}
-
-std::shared_ptr<C2ParamReflector> C2SoftAvcDecIntf::getParamReflector() const {
-    return mParamReflector;
-}
-
-c2_status_t C2SoftAvcDecIntf::querySupportedParams_nb(
-        std::vector<std::shared_ptr<C2ParamDescriptor>> * const params) const {
-    params->insert(params->begin(), mParamDescs.begin(), mParamDescs.end());
-    return C2_OK;
-}
-
-c2_status_t C2SoftAvcDecIntf::querySupportedValues_vb(
-        std::vector<C2FieldSupportedValuesQuery> &fields, c2_blocking_t mayBlock) const {
-    (void)mayBlock;
-    c2_status_t res = C2_OK;
-    for (C2FieldSupportedValuesQuery &query : fields) {
-        if (mSupportedValues.count(query.field) == 0) {
-            query.status = C2_BAD_INDEX;
-            res = C2_BAD_INDEX;
-        } else {
-            query.status = C2_OK;
-            query.values = mSupportedValues.at(query.field).supported;
-        }
-    }
-    return res;
-}
-
-void C2SoftAvcDecIntf::updateSupportedValues() {
-    int32_t maxWidth = H264_MAX_FRAME_WIDTH;
-    int32_t maxHeight = H264_MAX_FRAME_HEIGHT;
-    // cf: Rec. ITU-T H.264 A.3
-    int maxFrameRate = 172;
-    std::vector<C2ParamField> fields;
-    if (mLevel.value != kAvcLevelUnknown) {
-        // cf: Rec. ITU-T H.264 Table A-1
-        constexpr int MaxFS[] = {
-        //  0       1       2       3       4       5       6       7       8       9
-            0,      0,      0,      0,      0,      0,      0,      0,      0,      99,
-            99,     396,    396,    396,    0,      0,      0,      0,      0,      0,
-            396,    792,    1620,   0,      0,      0,      0,      0,      0,      0,
-            1620,   3600,   5120,   0,      0,      0,      0,      0,      0,      0,
-            8192,   8192,   8704,   0,      0,      0,      0,      0,      0,      0,
-            22080,  36864,  36864,
-        };
-        constexpr int MaxMBPS[] = {
-        //  0       1       2       3       4       5       6       7       8       9
-            0,      0,      0,      0,      0,      0,      0,      0,      0,      1485,
-            1485,   3000,   6000,   11880,  0,      0,      0,      0,      0,      0,
-            11880,  19800,  20250,  0,      0,      0,      0,      0,      0,      0,
-            40500,  108000, 216000, 0,      0,      0,      0,      0,      0,      0,
-            245760, 245760, 522240, 0,      0,      0,      0,      0,      0,      0,
-            589824, 983040, 2073600,
-        };
-
-        // cf: Rec. ITU-T H.264 A.3.1
-        maxWidth = std::min(maxWidth, floor32(std::sqrt(MaxFS[mLevel.value] * 8)) * MB_SIZE);
-        maxHeight = std::min(maxHeight, floor32(std::sqrt(MaxFS[mLevel.value] * 8)) * MB_SIZE);
-        int32_t MBs = ((mVideoSize.width + 15) / 16) * ((mVideoSize.height + 15) / 16);
-        maxFrameRate = std::min(maxFrameRate, MaxMBPS[mLevel.value] / MBs);
-        fields.push_back(C2ParamField(&mLevel, &C2AvcLevelInfo::value));
-    }
-
-    SupportedValuesWithFields &maxWidthVals = mSupportedValues.at(
-            C2ParamField(&mMaxVideoSizeHint, &C2MaxVideoSizeHintPortSetting::width));
-    maxWidthVals.supported.range.max = maxWidth;
-    maxWidthVals.restrictingFields.clear();
-    maxWidthVals.restrictingFields.insert(fields.begin(), fields.end());
-
-    SupportedValuesWithFields &maxHeightVals = mSupportedValues.at(
-            C2ParamField(&mMaxVideoSizeHint, &C2MaxVideoSizeHintPortSetting::height));
-    maxHeightVals.supported.range.max = maxHeight;
-    maxHeightVals.restrictingFields.clear();
-    maxHeightVals.restrictingFields.insert(fields.begin(), fields.end());
-
-    SupportedValuesWithFields &frameRate = mSupportedValues.at(
-            C2ParamField(&mFrameRate, &C2FrameRateInfo::value));
-    frameRate.supported.range.max = maxFrameRate;
-    frameRate.restrictingFields.clear();
-    frameRate.restrictingFields.insert(fields.begin(), fields.end());
-}
-#endif
-
 ///////////////////////////////////////////////////////////////////////////////
 
 C2SoftAvcDec::C2SoftAvcDec(
@@ -628,7 +108,7 @@
         c2_node_id_t id)
     : SimpleC2Component(BuildIntf(name, id)),
       mCodecCtx(NULL),
-      mFlushOutBuffer(NULL),
+      mOutBuffer(NULL),
       mIvColorFormat(IV_YUV_420P),
       mChangingResolution(false),
       mSignalledError(false),
@@ -654,6 +134,7 @@
 }
 
 c2_status_t C2SoftAvcDec::onStop() {
+    ALOGV("onStop");
     mSignalledError = false;
     resetDecoder();
     resetPlugin();
@@ -662,6 +143,7 @@
 }
 
 void C2SoftAvcDec::onReset() {
+    ALOGV("onReset");
     (void)onStop();
 }
 
@@ -672,17 +154,6 @@
 c2_status_t C2SoftAvcDec::onFlush_sm() {
     setFlushMode();
 
-    /* Allocate a picture buffer to flushed data */
-    uint32_t displayStride = mWidth;
-    uint32_t displayHeight = mHeight;
-
-    uint32_t bufferSize = displayStride * displayHeight * 3 / 2;
-    mFlushOutBuffer = (uint8_t *)memalign(128, bufferSize);
-    if (NULL == mFlushOutBuffer) {
-        ALOGE("Could not allocate flushOutputBuffer of size %u", bufferSize);
-        return C2_NO_MEMORY;
-    }
-
     while (true) {
         ivd_video_decode_ip_t s_dec_ip;
         ivd_video_decode_op_t s_dec_op;
@@ -698,9 +169,9 @@
         }
     }
 
-    if (mFlushOutBuffer) {
-        free(mFlushOutBuffer);
-        mFlushOutBuffer = NULL;
+    if (mOutBuffer) {
+        free(mOutBuffer);
+        mOutBuffer = NULL;
     }
     return C2_OK;
 }
@@ -778,6 +249,16 @@
 
         return UNKNOWN_ERROR;
     }
+
+    if (mOutBuffer != NULL) {
+        free(mOutBuffer);
+    }
+    uint32_t bufferSize = mWidth * mHeight * 3 / 2;
+    mOutBuffer = (uint8_t *)memalign(128, bufferSize);
+    if (NULL == mOutBuffer) {
+        ALOGE("Could not allocate output buffer of size %u", bufferSize);
+        return C2_NO_MEMORY;
+    }
     return OK;
 }
 
@@ -854,6 +335,7 @@
                 s_video_flush_op.u4_error_code);
         return UNKNOWN_ERROR;
     }
+
     return OK;
 }
 
@@ -993,6 +475,7 @@
 
     ps_dec_ip->u4_size = sizeof(ivd_video_decode_ip_t);
     ps_dec_op->u4_size = sizeof(ivd_video_decode_op_t);
+    ps_dec_op->u4_output_present = 0;
 
     ps_dec_ip->e_cmd = IVD_CMD_VIDEO_DECODE;
 
@@ -1020,14 +503,29 @@
                   outBuffer->width(), outBuffer->height(), width, height);
             return false;
         }
+        ALOGV("width = %u, stride[0] = %u, stride[1] = %u, stride[2] = %u",
+                outBuffer->width(),
+                outBuffer->layout().planes[0].rowInc,
+                outBuffer->layout().planes[1].rowInc,
+                outBuffer->layout().planes[2].rowInc);
+        const C2PlanarLayout &layout = outBuffer->layout();
         ps_dec_ip->s_out_buffer.pu1_bufs[0] = outBuffer->data()[0];
+        if (layout.planes[0].rowInc != (int32_t)mWidth || layout.planes[1].colInc != 1) {
+            ps_dec_ip->s_out_buffer.pu1_bufs[0] = mOutBuffer;
+        }
         ps_dec_ip->s_out_buffer.pu1_bufs[1] = outBuffer->data()[1];
+        if (layout.planes[1].rowInc != (int32_t)mWidth / 2 || layout.planes[1].colInc != 1) {
+            ps_dec_ip->s_out_buffer.pu1_bufs[1] = mOutBuffer + sizeY;
+        }
         ps_dec_ip->s_out_buffer.pu1_bufs[2] = outBuffer->data()[2];
+        if (layout.planes[2].rowInc != (int32_t)mWidth / 2 || layout.planes[2].colInc != 1) {
+            ps_dec_ip->s_out_buffer.pu1_bufs[2] = mOutBuffer + sizeY + sizeUV;
+        }
     } else {
-        // mFlushOutBuffer always has the right size.
-        ps_dec_ip->s_out_buffer.pu1_bufs[0] = mFlushOutBuffer;
-        ps_dec_ip->s_out_buffer.pu1_bufs[1] = mFlushOutBuffer + sizeY;
-        ps_dec_ip->s_out_buffer.pu1_bufs[2] = mFlushOutBuffer + sizeY + sizeUV;
+        // mOutBuffer always has the right size.
+        ps_dec_ip->s_out_buffer.pu1_bufs[0] = mOutBuffer;
+        ps_dec_ip->s_out_buffer.pu1_bufs[1] = mOutBuffer + sizeY;
+        ps_dec_ip->s_out_buffer.pu1_bufs[2] = mOutBuffer + sizeY + sizeUV;
     }
 
     ps_dec_ip->s_out_buffer.u4_num_bufs = 3;
@@ -1065,7 +563,8 @@
 }
 
 void C2SoftAvcDec::finishWork(uint64_t index, const std::unique_ptr<C2Work> &work) {
-    std::shared_ptr<C2Buffer> buffer = createGraphicBuffer(std::move(mAllocatedBlock));
+    std::shared_ptr<C2Buffer> buffer = createGraphicBuffer(mAllocatedBlock);
+    mAllocatedBlock.reset();
     auto fillWork = [buffer](const std::unique_ptr<C2Work> &work) {
         uint32_t flags = 0;
         if (work->input.flags & C2FrameData::FLAG_END_OF_STREAM) {
@@ -1093,9 +592,9 @@
     work->result = C2_OK;
     work->workletsProcessed = 0u;
 
-    const C2ConstLinearBlock &buffer =
+    const C2ConstLinearBlock buffer =
         work->input.buffers[0]->data().linearBlocks().front();
-    if (buffer.capacity() == 0) {
+    if (buffer.size() == 0) {
         ALOGV("empty input: %llu", work->input.ordinal.frameIndex.peekull());
         // TODO: result?
         fillEmptyWork(work);
@@ -1109,6 +608,13 @@
     }
 
     C2ReadView input = work->input.buffers[0]->data().linearBlocks().front().map().get();
+    if (input.error() != C2_OK) {
+        work->result = input.error();
+        fillEmptyWork(work);
+        ALOGD("map error: %d", input.error());
+        return;
+    }
+    ALOGV("buffer.size() = %u, input.capacity() = %u", buffer.size(), input.capacity());
     uint32_t workIndex = work->input.ordinal.frameIndex.peeku() & 0xFFFFFFFF;
     size_t inOffset = 0u;
 
@@ -1117,34 +623,54 @@
             break;
         }
         (void)ensureDecoderState(pool);
-        C2GraphicView output = mAllocatedBlock->map().get();
-        if (output.error() != OK) {
-            ALOGE("mapped err = %d", output.error());
-        }
-
         ivd_video_decode_ip_t s_dec_ip;
         ivd_video_decode_op_t s_dec_op;
         WORD32 timeDelay, timeTaken;
         //size_t sizeY, sizeUV;
 
-        if (!setDecodeArgs(&s_dec_ip, &s_dec_op, &input, &output, workIndex, inOffset)) {
-            ALOGE("Decoder arg setup failed");
-            // TODO: notify(OMX_EventError, OMX_ErrorUndefined, 0, NULL);
-            mSignalledError = true;
-            break;
+        {
+            C2GraphicView output = mAllocatedBlock->map().get();
+            if (output.error() != C2_OK) {
+                ALOGE("mapped err = %d", output.error());
+                work->result = output.error();
+                fillEmptyWork(work);
+                return;
+            }
+            if (!setDecodeArgs(&s_dec_ip, &s_dec_op, &input, &output, workIndex, inOffset)) {
+                ALOGE("Decoder arg setup failed");
+                // TODO: notify(OMX_EventError, OMX_ErrorUndefined, 0, NULL);
+                mSignalledError = true;
+                break;
+            }
+            ALOGV("Decoder arg setup succeeded");
+            // If input dump is enabled, then write to file
+            DUMP_TO_FILE(mInFile, s_dec_ip.pv_stream_buffer, s_dec_ip.u4_num_Bytes, mInputOffset);
+
+            GETTIME(&mTimeStart, NULL);
+            /* Compute time elapsed between end of previous decode()
+             * to start of current decode() */
+            TIME_DIFF(mTimeEnd, mTimeStart, timeDelay);
+
+            IV_API_CALL_STATUS_T status;
+            status = ivdec_api_function(mCodecCtx, (void *)&s_dec_ip, (void *)&s_dec_op);
+            ALOGV("status = %d, error_code = %d", status, (s_dec_op.u4_error_code & 0xFF));
+            if (s_dec_op.u4_output_present) {
+                const C2PlanarLayout &layout = output.layout();
+                if (layout.planes[0].rowInc != (int32_t)mWidth || layout.planes[1].colInc != 1) {
+                    CopyPlane(output.data()[0], layout.planes[0], mOutBuffer, mWidth, mHeight);
+                }
+                if (layout.planes[1].rowInc != (int32_t)mWidth / 2 || layout.planes[1].colInc != 1) {
+                    CopyPlane(
+                            output.data()[1], layout.planes[1],
+                            mOutBuffer + (mWidth * mHeight), mWidth / 2, mHeight / 2);
+                }
+                if (layout.planes[2].rowInc != (int32_t)mWidth / 2 || layout.planes[2].colInc != 1) {
+                    CopyPlane(
+                            output.data()[2], layout.planes[2],
+                            mOutBuffer + (mWidth * mHeight * 5 / 4), mWidth / 2, mHeight / 2);
+                }
+            }
         }
-        ALOGV("Decoder arg setup succeeded");
-        // If input dump is enabled, then write to file
-        DUMP_TO_FILE(mInFile, s_dec_ip.pv_stream_buffer, s_dec_ip.u4_num_Bytes, mInputOffset);
-
-        GETTIME(&mTimeStart, NULL);
-        /* Compute time elapsed between end of previous decode()
-         * to start of current decode() */
-        TIME_DIFF(mTimeEnd, mTimeStart, timeDelay);
-
-        IV_API_CALL_STATUS_T status;
-        status = ivdec_api_function(mCodecCtx, (void *)&s_dec_ip, (void *)&s_dec_op);
-        ALOGV("status = %d, error_code = %d", status, (s_dec_op.u4_error_code & 0xFF));
 
         bool unsupportedResolution =
             (IVD_STREAM_WIDTH_HEIGHT_NOT_SUPPORTED == (s_dec_op.u4_error_code & 0xFF));
diff --git a/media/libstagefright/codecs/avcdec/C2SoftAvcDec.h b/media/libstagefright/codecs/avcdec/C2SoftAvcDec.h
index 6632bf3..d324a0f 100644
--- a/media/libstagefright/codecs/avcdec/C2SoftAvcDec.h
+++ b/media/libstagefright/codecs/avcdec/C2SoftAvcDec.h
@@ -189,7 +189,7 @@
     struct timeval mTimeEnd;     // Time at the end of decode()
 
     // Internal buffer to be used to flush out the buffers from decoder
-    uint8_t *mFlushOutBuffer;
+    uint8_t *mOutBuffer;
 
 #ifdef FILE_DUMP_ENABLE
     char mInFile[200];
diff --git a/media/libstagefright/codecs/avcenc/C2SoftAvcEnc.cpp b/media/libstagefright/codecs/avcenc/C2SoftAvcEnc.cpp
index 8fb8122..d75e4c1 100644
--- a/media/libstagefright/codecs/avcenc/C2SoftAvcEnc.cpp
+++ b/media/libstagefright/codecs/avcenc/C2SoftAvcEnc.cpp
@@ -909,6 +909,9 @@
     }
 
     ALOGV("width = %d, height = %d", input->width(), input->height());
+    if (mWidth != input->width() || mHeight != input->height()) {
+        return C2_BAD_VALUE;
+    }
     const C2PlanarLayout &layout = input->layout();
     uint8_t *yPlane = const_cast<uint8_t *>(input->data()[C2PlanarLayout::PLANE_Y]);
     uint8_t *uPlane = const_cast<uint8_t *>(input->data()[C2PlanarLayout::PLANE_U]);
diff --git a/media/libstagefright/codecs/cmds/codec2.cpp b/media/libstagefright/codecs/cmds/codec2.cpp
index 0ec1a77..295a5b0 100644
--- a/media/libstagefright/codecs/cmds/codec2.cpp
+++ b/media/libstagefright/codecs/cmds/codec2.cpp
@@ -93,6 +93,8 @@
 
     sp<IProducerListener> mProducerListener;
 
+    std::atomic_int mLinearPoolId;
+
     std::shared_ptr<C2Allocator> mAllocIon;
     std::shared_ptr<C2BlockPool> mLinearPool;
 
@@ -137,12 +139,13 @@
 SimplePlayer::SimplePlayer()
     : mListener(new Listener(this)),
       mProducerListener(new DummyProducerListener),
+      mLinearPoolId(C2BlockPool::PLATFORM_START),
       mComposerClient(new SurfaceComposerClient) {
     CHECK_EQ(mComposerClient->initCheck(), (status_t)OK);
 
     std::shared_ptr<C2AllocatorStore> store = GetCodec2PlatformAllocatorStore();
     CHECK_EQ(store->fetchAllocator(C2AllocatorStore::DEFAULT_LINEAR, &mAllocIon), C2_OK);
-    mLinearPool = std::make_shared<C2BasicLinearBlockPool>(mAllocIon);
+    mLinearPool = std::make_shared<C2PooledBlockPool>(mAllocIon, mLinearPoolId++);
 
     mControl = mComposerClient->createSurface(
             String8("A Surface"),
@@ -245,7 +248,7 @@
             ALOGV("Render: Frame #%lld", work->worklets.front()->output.ordinal.frameIndex.peekll());
             const std::shared_ptr<C2Buffer> &output = work->worklets.front()->output.buffers[0];
             if (output) {
-                const C2ConstGraphicBlock &block = output->data().graphicBlocks().front();
+                const C2ConstGraphicBlock block = output->data().graphicBlocks().front();
                 native_handle_t *grallocHandle = UnwrapNativeCodec2GrallocHandle(block.handle());
                 sp<GraphicBuffer> buffer(new GraphicBuffer(
                         grallocHandle,
@@ -284,7 +287,7 @@
     });
 
     long numFrames = 0;
-    mLinearPool.reset(new C2BasicLinearBlockPool(mAllocIon));
+    mLinearPool.reset(new C2PooledBlockPool(mAllocIon, mLinearPoolId++));
 
     for (;;) {
         size_t size = 0u;
diff --git a/media/libstagefright/codecs/flac/dec/Android.bp b/media/libstagefright/codecs/flac/dec/Android.bp
index 9af086b..68fdd15 100644
--- a/media/libstagefright/codecs/flac/dec/Android.bp
+++ b/media/libstagefright/codecs/flac/dec/Android.bp
@@ -1,4 +1,48 @@
 cc_library_shared {
+    name: "libstagefright_soft_c2flacdec",
+//    vendor_available: true,
+//    vndk: {
+//        enabled: true,
+//    },
+
+    srcs: [
+        "C2SoftFlacDecoder.cpp",
+    ],
+
+    include_dirs: [
+        "external/flac/include",
+        "frameworks/av/media/libstagefright/flac/dec",
+    ],
+
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+
+    sanitize: {
+        misc_undefined: [
+            "signed-integer-overflow",
+            "unsigned-integer-overflow",
+        ],
+        cfi: true,
+        diag: {
+            cfi: true,
+        },
+    },
+
+    shared_libs: [
+        "liblog",
+        "libutils",
+        "libstagefright_flacdec",
+        "libstagefright_codec2",
+        "libstagefright_codec2_vndk",
+        "libstagefright_foundation",
+        "libstagefright_simple_c2component",
+    ],
+}
+
+
+cc_library_shared {
     name: "libstagefright_soft_flacdec",
     vendor_available: true,
     vndk: {
diff --git a/media/libstagefright/codecs/flac/dec/C2SoftFlacDecoder.cpp b/media/libstagefright/codecs/flac/dec/C2SoftFlacDecoder.cpp
new file mode 100644
index 0000000..87138af
--- /dev/null
+++ b/media/libstagefright/codecs/flac/dec/C2SoftFlacDecoder.cpp
@@ -0,0 +1,270 @@
+/*
+ * 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
+#define LOG_TAG "C2SoftFlacDecoder"
+#include <utils/Log.h>
+
+#include "C2SoftFlacDecoder.h"
+
+#include <C2PlatformSupport.h>
+#include <SimpleC2Interface.h>
+
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/MediaDefs.h>
+
+namespace android {
+
+constexpr char kComponentName[] = "c2.google.flac.decoder";
+
+static std::shared_ptr<C2ComponentInterface> BuildIntf(
+        const char *name, c2_node_id_t id,
+        std::function<void(C2ComponentInterface*)> deleter =
+            std::default_delete<C2ComponentInterface>()) {
+    return SimpleC2Interface::Builder(name, id, deleter)
+            .inputFormat(C2FormatCompressed)
+            .outputFormat(C2FormatAudio)
+            .inputMediaType(MEDIA_MIMETYPE_AUDIO_FLAC)
+            .outputMediaType(MEDIA_MIMETYPE_AUDIO_RAW)
+            .build();
+}
+
+C2SoftFlacDecoder::C2SoftFlacDecoder(const char *name, c2_node_id_t id)
+    : SimpleC2Component(BuildIntf(name, id)),
+      mFLACDecoder(nullptr) {
+}
+
+C2SoftFlacDecoder::~C2SoftFlacDecoder() {
+    delete mFLACDecoder;
+}
+
+c2_status_t C2SoftFlacDecoder::onInit() {
+    status_t err = initDecoder();
+    return err == OK ? C2_OK : C2_NO_MEMORY;
+}
+
+c2_status_t C2SoftFlacDecoder::onStop() {
+    if (mFLACDecoder) mFLACDecoder->flush();
+    memset(&mStreamInfo, 0, sizeof(mStreamInfo));
+    mHasStreamInfo = false;
+    mSignalledError = false;
+    mSignalledOutputEos = false;
+    mInputBufferCount = 0;
+    return C2_OK;
+}
+
+void C2SoftFlacDecoder::onReset() {
+    (void)onStop();
+}
+
+void C2SoftFlacDecoder::onRelease() {
+}
+
+c2_status_t C2SoftFlacDecoder::onFlush_sm() {
+    return onStop();
+}
+
+status_t C2SoftFlacDecoder::initDecoder() {
+    if (mFLACDecoder) {
+        delete mFLACDecoder;
+    }
+    mFLACDecoder = FLACDecoder::Create();
+    if (!mFLACDecoder) {
+        ALOGE("initDecoder: failed to create FLACDecoder");
+        mSignalledError = true;
+        return NO_MEMORY;
+    }
+
+    memset(&mStreamInfo, 0, sizeof(mStreamInfo));
+    mHasStreamInfo = false;
+    mSignalledError = false;
+    mSignalledOutputEos = false;
+    mInputBufferCount = 0;
+
+    return OK;
+}
+
+static void fillEmptyWork(const std::unique_ptr<C2Work> &work) {
+    work->worklets.front()->output.flags = work->input.flags;
+    work->worklets.front()->output.buffers.clear();
+    work->worklets.front()->output.ordinal = work->input.ordinal;
+    work->workletsProcessed = 1u;
+}
+
+// (TODO) add multiframe support, in plugin and FLACDecoder.cpp
+void C2SoftFlacDecoder::process(
+        const std::unique_ptr<C2Work> &work,
+        const std::shared_ptr<C2BlockPool> &pool) {
+    work->result = C2_OK;
+    work->workletsProcessed = 0u;
+    if (mSignalledError || mSignalledOutputEos) {
+        work->result = C2_BAD_VALUE;
+        return;
+    }
+
+    const C2ConstLinearBlock inBuffer = work->input.buffers[0]->data().linearBlocks().front();
+    size_t inOffset = inBuffer.offset();
+    size_t inSize = inBuffer.size();
+    C2ReadView rView = work->input.buffers[0]->data().linearBlocks().front().map().get();
+    if (inSize && rView.error()) {
+        ALOGE("read view map failed %d", rView.error());
+        work->result = C2_CORRUPTED;
+        return;
+    }
+    bool eos = (work->input.flags & C2FrameData::FLAG_END_OF_STREAM) != 0;
+    bool codecConfig = (work->input.flags & C2FrameData::FLAG_CODEC_CONFIG) != 0;
+
+    ALOGV("in buffer attr. size %zu timestamp %d frameindex %d", inSize,
+          (int)work->input.ordinal.timestamp.peeku(), (int)work->input.ordinal.frameIndex.peeku());
+
+    if (inSize == 0) {
+        fillEmptyWork(work);
+        if (eos) {
+            mSignalledOutputEos = true;
+            ALOGV("signalled EOS");
+        }
+        return;
+    }
+
+    if (mInputBufferCount == 0 && !codecConfig) {
+        ALOGV("First frame has to include configuration, forcing config");
+        codecConfig = true;
+    }
+
+    uint8_t *input = const_cast<uint8_t *>(rView.data() + inOffset);
+    if (codecConfig) {
+        status_t decoderErr = mFLACDecoder->parseMetadata(input, inSize);
+        if (decoderErr != OK && decoderErr != WOULD_BLOCK) {
+            ALOGE("process: FLACDecoder parseMetaData returns error %d", decoderErr);
+            mSignalledError = true;
+            work->result = C2_CORRUPTED;
+            return;
+        }
+
+        mInputBufferCount++;
+        fillEmptyWork(work);
+        if (eos) {
+            mSignalledOutputEos = true;
+            ALOGV("signalled EOS");
+        }
+
+        if (decoderErr == WOULD_BLOCK) {
+            ALOGV("process: parseMetadata is Blocking, Continue %d", decoderErr);
+        } else {
+            mStreamInfo = mFLACDecoder->getStreamInfo();
+            if (mStreamInfo.max_blocksize && mStreamInfo.channels)
+                mHasStreamInfo = true;
+            ALOGD("process: decoder configuration : %d Hz, %d channels, %d samples,"
+                  " %d block size", mStreamInfo.sample_rate, mStreamInfo.channels,
+                  (int)mStreamInfo.total_samples, mStreamInfo.max_blocksize);
+        }
+        return;
+    }
+
+    size_t outSize;
+    if (mHasStreamInfo)
+        outSize = mStreamInfo.max_blocksize * mStreamInfo.channels * sizeof(short);
+    else
+        outSize = kMaxBlockSize * FLACDecoder::kMaxChannels * sizeof(short);
+
+    std::shared_ptr<C2LinearBlock> block;
+    C2MemoryUsage usage = { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE };
+    c2_status_t err = pool->fetchLinearBlock(outSize, usage, &block);
+    if (err != C2_OK) {
+        ALOGE("fetchLinearBlock for Output failed with status %d", err);
+        work->result = C2_NO_MEMORY;
+        return;
+    }
+    C2WriteView wView = block->map().get();
+    if (wView.error()) {
+        ALOGE("write view map failed %d", wView.error());
+        work->result = C2_CORRUPTED;
+        return;
+    }
+
+    short *output = reinterpret_cast<short *>(wView.data());
+    status_t decoderErr = mFLACDecoder->decodeOneFrame(
+                            input, inSize, output, &outSize);
+    if (decoderErr != OK) {
+        ALOGE("process: FLACDecoder decodeOneFrame returns error %d", decoderErr);
+        mSignalledError = true;
+        work->result = C2_CORRUPTED;
+        return;
+    }
+
+    mInputBufferCount++;
+    ALOGV("out buffer attr. size %zu", outSize);
+    work->worklets.front()->output.flags = work->input.flags;
+    work->worklets.front()->output.buffers.clear();
+    work->worklets.front()->output.buffers.push_back(createLinearBuffer(block, 0, outSize));
+    work->worklets.front()->output.ordinal = work->input.ordinal;
+    work->workletsProcessed = 1u;
+    if (eos) {
+        mSignalledOutputEos = true;
+        ALOGV("signalled EOS");
+    }
+}
+
+c2_status_t C2SoftFlacDecoder::drain(
+        uint32_t drainMode,
+        const std::shared_ptr<C2BlockPool> &pool) {
+    (void) pool;
+    if (drainMode == NO_DRAIN) {
+        ALOGW("drain with NO_DRAIN: no-op");
+        return C2_OK;
+    }
+    if (drainMode == DRAIN_CHAIN) {
+        ALOGW("DRAIN_CHAIN not supported");
+        return C2_OMITTED;
+    }
+
+    if (mFLACDecoder) mFLACDecoder->flush();
+
+    return C2_OK;
+}
+
+class C2SoftFlacDecFactory : public C2ComponentFactory {
+public:
+    virtual c2_status_t createComponent(
+            c2_node_id_t id,
+            std::shared_ptr<C2Component>* const component,
+            std::function<void(C2Component*)> deleter) override {
+        *component = std::shared_ptr<C2Component>(new C2SoftFlacDecoder(kComponentName, id), deleter);
+        return C2_OK;
+    }
+
+    virtual c2_status_t createInterface(
+            c2_node_id_t id,
+            std::shared_ptr<C2ComponentInterface>* const interface,
+            std::function<void(C2ComponentInterface*)> deleter) override {
+        *interface = BuildIntf(kComponentName, id, deleter);
+        return C2_OK;
+    }
+
+    virtual ~C2SoftFlacDecFactory() override = default;
+};
+
+}  // namespace android
+
+extern "C" ::C2ComponentFactory* CreateCodec2Factory() {
+    ALOGV("in %s", __func__);
+    return new ::android::C2SoftFlacDecFactory();
+}
+
+extern "C" void DestroyCodec2Factory(::C2ComponentFactory* factory) {
+    ALOGV("in %s", __func__);
+    delete factory;
+}
diff --git a/media/libstagefright/codecs/flac/dec/C2SoftFlacDecoder.h b/media/libstagefright/codecs/flac/dec/C2SoftFlacDecoder.h
new file mode 100644
index 0000000..43d913b
--- /dev/null
+++ b/media/libstagefright/codecs/flac/dec/C2SoftFlacDecoder.h
@@ -0,0 +1,63 @@
+/*
+ * 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 C2_SOFT_FLAC_DECODER_H_
+#define C2_SOFT_FLAC_DECODER_H_
+
+#include <SimpleC2Component.h>
+
+#include "FLACDecoder.h"
+#include <media/stagefright/foundation/ABase.h>
+
+namespace android {
+
+struct C2SoftFlacDecoder : public SimpleC2Component {
+    C2SoftFlacDecoder(const char *name, c2_node_id_t id);
+    virtual ~C2SoftFlacDecoder();
+
+    // From SimpleC2Component
+    c2_status_t onInit() override;
+    c2_status_t onStop() override;
+    void onReset() override;
+    void onRelease() override;
+    c2_status_t onFlush_sm() override;
+    void process(
+            const std::unique_ptr<C2Work> &work,
+            const std::shared_ptr<C2BlockPool> &pool) override;
+    c2_status_t drain(
+            uint32_t drainMode,
+            const std::shared_ptr<C2BlockPool> &pool) override;
+
+private:
+    enum {
+        kMaxBlockSize   = 4096
+    };
+
+    FLACDecoder *mFLACDecoder;
+    FLAC__StreamMetadata_StreamInfo mStreamInfo;
+    bool mSignalledError;
+    bool mSignalledOutputEos;
+    bool mHasStreamInfo;
+    size_t mInputBufferCount;
+
+    status_t initDecoder();
+
+    DISALLOW_EVIL_CONSTRUCTORS(C2SoftFlacDecoder);
+};
+
+}  // namespace android
+
+#endif  // C2_SOFT_FLAC_DECODER_H_
diff --git a/media/libstagefright/codecs/flac/dec/SoftFlacDecoder.cpp b/media/libstagefright/codecs/flac/dec/SoftFlacDecoder.cpp
index 4ab1ab2..d0b72b7 100644
--- a/media/libstagefright/codecs/flac/dec/SoftFlacDecoder.cpp
+++ b/media/libstagefright/codecs/flac/dec/SoftFlacDecoder.cpp
@@ -57,6 +57,7 @@
 
 SoftFlacDecoder::~SoftFlacDecoder() {
     ALOGV("dtor:");
+    delete mFLACDecoder;
 }
 
 void SoftFlacDecoder::initPorts() {
diff --git a/media/libstagefright/codecs/flac/dec/SoftFlacDecoder.h b/media/libstagefright/codecs/flac/dec/SoftFlacDecoder.h
index 4a21c34..0f17ed8 100644
--- a/media/libstagefright/codecs/flac/dec/SoftFlacDecoder.h
+++ b/media/libstagefright/codecs/flac/dec/SoftFlacDecoder.h
@@ -50,7 +50,7 @@
         kNumOutputBuffers  = 4,
     };
 
-    sp<FLACDecoder> mFLACDecoder;
+    FLACDecoder *mFLACDecoder;
     FLAC__StreamMetadata_StreamInfo mStreamInfo;
     bool mHasStreamInfo;
     size_t mInputBufferCount;
diff --git a/media/libstagefright/codecs/flac/enc/Android.bp b/media/libstagefright/codecs/flac/enc/Android.bp
index 46b974d..bb705dd 100644
--- a/media/libstagefright/codecs/flac/enc/Android.bp
+++ b/media/libstagefright/codecs/flac/enc/Android.bp
@@ -1,4 +1,43 @@
 cc_library_shared {
+    name: "libstagefright_soft_c2flacenc",
+//    vendor_available: true,
+//    vndk: {
+//        enabled: true,
+//    },
+
+    srcs: ["C2SoftFlacEnc.cpp"],
+
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+
+    sanitize: {
+        misc_undefined: [
+            "signed-integer-overflow",
+            "unsigned-integer-overflow",
+        ],
+        cfi: true,
+        diag: {
+            cfi: true,
+        },
+    },
+
+    static_libs: [
+        "libFLAC",
+    ],
+
+    shared_libs: [
+        "liblog",
+        "libstagefright_codec2",
+        "libstagefright_codec2_vndk",
+        "libstagefright_foundation",
+        "libstagefright_simple_c2component",
+        "libutils",
+    ],
+}
+
+cc_library_shared {
 
     srcs: ["SoftFlacEncoder.cpp"],
 
diff --git a/media/libstagefright/codecs/flac/enc/C2SoftFlacEnc.cpp b/media/libstagefright/codecs/flac/enc/C2SoftFlacEnc.cpp
new file mode 100644
index 0000000..fa93fe5
--- /dev/null
+++ b/media/libstagefright/codecs/flac/enc/C2SoftFlacEnc.cpp
@@ -0,0 +1,389 @@
+/*
+ * 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
+#define LOG_TAG "C2SoftFlacEnc"
+#include <utils/Log.h>
+
+#include "C2SoftFlacEnc.h"
+
+#include <C2PlatformSupport.h>
+#include <SimpleC2Interface.h>
+
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/MediaDefs.h>
+
+namespace android {
+
+constexpr char kComponentName[] = "c2.google.flac.encoder";
+
+static std::shared_ptr<C2ComponentInterface> BuildIntf(
+        const char *name, c2_node_id_t id,
+        std::function<void(C2ComponentInterface*)> deleter =
+            std::default_delete<C2ComponentInterface>()) {
+    return SimpleC2Interface::Builder(name, id, deleter)
+            .inputFormat(C2FormatAudio)
+            .outputFormat(C2FormatCompressed)
+            .inputMediaType(MEDIA_MIMETYPE_AUDIO_RAW)
+            .outputMediaType(MEDIA_MIMETYPE_AUDIO_FLAC)
+            .build();
+}
+
+C2SoftFlacEnc::C2SoftFlacEnc(const char *name, c2_node_id_t id)
+    : SimpleC2Component(BuildIntf(name, id)),
+      mFlacStreamEncoder(nullptr),
+      mInputBufferPcm32(nullptr) {
+}
+
+C2SoftFlacEnc::~C2SoftFlacEnc() {
+    onRelease();
+}
+
+c2_status_t C2SoftFlacEnc::onInit() {
+    mFlacStreamEncoder = FLAC__stream_encoder_new();
+    if (!mFlacStreamEncoder) return C2_CORRUPTED;
+
+    mInputBufferPcm32 = (FLAC__int32*) malloc(
+            kInBlockSize * kMaxNumChannels * sizeof(FLAC__int32));
+    if (!mInputBufferPcm32) return C2_NO_MEMORY;
+
+    mSignalledError = false;
+    mSignalledOutputEos = false;
+    mNumChannels = 1;
+    mSampleRate = 44100;
+    mCompressionLevel = FLAC_COMPRESSION_LEVEL_DEFAULT;
+    mIsFirstFrame = true;
+    mAnchorTimeStamp = 0ull;
+    mProcessedSamples = 0u;
+    mEncoderWriteData = false;
+    mEncoderReturnedNbBytes = 0;
+#ifdef WRITE_FLAC_HEADER_IN_FIRST_BUFFER
+    mHeaderOffset = 0;
+    mWroteHeader = false;
+#endif
+
+    status_t err = configureEncoder();
+    return err == OK ? C2_OK : C2_CORRUPTED;
+}
+
+void C2SoftFlacEnc::onRelease() {
+    if (mFlacStreamEncoder) {
+        FLAC__stream_encoder_delete(mFlacStreamEncoder);
+        mFlacStreamEncoder = nullptr;
+    }
+
+    if (mInputBufferPcm32) {
+        free(mInputBufferPcm32);
+        mInputBufferPcm32 = nullptr;
+    }
+}
+
+void C2SoftFlacEnc::onReset() {
+    mNumChannels = 1;
+    mSampleRate = 44100;
+    mCompressionLevel = FLAC_COMPRESSION_LEVEL_DEFAULT;
+    (void) onStop();
+}
+
+c2_status_t C2SoftFlacEnc::onStop() {
+    mSignalledError = false;
+    mSignalledOutputEos = false;
+    mIsFirstFrame = true;
+    mAnchorTimeStamp = 0ull;
+    mProcessedSamples = 0u;
+    mEncoderWriteData = false;
+    mEncoderReturnedNbBytes = 0;
+#ifdef WRITE_FLAC_HEADER_IN_FIRST_BUFFER
+    mHeaderOffset = 0;
+    mWroteHeader = false;
+#endif
+
+    c2_status_t status = drain(DRAIN_COMPONENT_NO_EOS, nullptr);
+    if (C2_OK != status) return status;
+
+    status_t err = configureEncoder();
+    if (err != OK) mSignalledError = true;
+    return C2_OK;
+}
+
+c2_status_t C2SoftFlacEnc::onFlush_sm() {
+    return onStop();
+}
+
+static void fillEmptyWork(const std::unique_ptr<C2Work> &work) {
+    work->worklets.front()->output.flags = work->input.flags;
+    work->worklets.front()->output.buffers.clear();
+    work->worklets.front()->output.ordinal = work->input.ordinal;
+    work->workletsProcessed = 1u;
+}
+
+void C2SoftFlacEnc::process(
+        const std::unique_ptr<C2Work> &work,
+        const std::shared_ptr<C2BlockPool> &pool) {
+    work->result = C2_OK;
+    work->workletsProcessed = 0u;
+    if (mSignalledError || mSignalledOutputEos) {
+        work->result = C2_BAD_VALUE;
+        return;
+    }
+
+    const C2ConstLinearBlock inBuffer = work->input.buffers[0]->data().linearBlocks().front();
+    bool eos = ((work->input.flags & C2FrameData::FLAG_END_OF_STREAM) != 0);
+    size_t inOffset = inBuffer.offset();
+    size_t inSize = inBuffer.size();
+    C2ReadView rView = work->input.buffers[0]->data().linearBlocks().front().map().get();
+    if (inSize && rView.error()) {
+        ALOGE("read view map failed %d", rView.error());
+        work->result = C2_CORRUPTED;
+        return;
+    }
+
+    ALOGV("in buffer attr. size %zu timestamp %d frameindex %d, flags %x",
+              inSize, (int)work->input.ordinal.timestamp.peeku(),
+              (int)work->input.ordinal.frameIndex.peeku(), work->input.flags);
+    if (mIsFirstFrame && inSize) {
+        mAnchorTimeStamp = work->input.ordinal.timestamp.peekull();
+        mIsFirstFrame = false;
+    }
+    uint64_t outTimeStamp = mProcessedSamples * 1000000ll / mSampleRate;
+
+    size_t outCapacity = inSize;
+    outCapacity += mBlockSize * mNumChannels * sizeof(int16_t);
+#ifdef WRITE_FLAC_HEADER_IN_FIRST_BUFFER
+    outCapacity += FLAC_HEADER_SIZE;
+#endif
+    C2MemoryUsage usage = { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE };
+    c2_status_t err = pool->fetchLinearBlock(outCapacity, usage, &mOutputBlock);
+    if (err != C2_OK) {
+        ALOGE("fetchLinearBlock for Output failed with status %d", err);
+        work->result = C2_NO_MEMORY;
+        return;
+    }
+    C2WriteView wView = mOutputBlock->map().get();
+    if (wView.error()) {
+        ALOGE("write view map failed %d", wView.error());
+        work->result = C2_CORRUPTED;
+        return;
+    }
+
+    mEncoderWriteData = true;
+    mEncoderReturnedNbBytes = 0;
+    while (inOffset < inSize) {
+        size_t processSize = MIN(kInBlockSize * mNumChannels * sizeof(int16_t), (inSize - inOffset));
+        const unsigned nbInputFrames = processSize / (mNumChannels * sizeof(int16_t));
+        const unsigned nbInputSamples = processSize / sizeof(int16_t);
+        const int16_t *pcm16 = reinterpret_cast<const int16_t *>(rView.data() + inOffset);
+        ALOGV("about to encode %zu bytes", processSize);
+
+        for (unsigned i = 0; i < nbInputSamples; i++) {
+            mInputBufferPcm32[i] = (FLAC__int32) pcm16[i];
+        }
+
+        FLAC__bool ok = FLAC__stream_encoder_process_interleaved(
+                mFlacStreamEncoder, mInputBufferPcm32, nbInputFrames);
+        if (!ok) {
+            ALOGE("error encountered during encoding");
+            mSignalledError = true;
+            work->result = C2_CORRUPTED;
+            mOutputBlock.reset();
+            return;
+        }
+        inOffset += processSize;
+    }
+    if (eos && !drain(DRAIN_COMPONENT_WITH_EOS, pool)) {
+        ALOGE("error encountered during encoding");
+        mSignalledError = true;
+        work->result = C2_CORRUPTED;
+        mOutputBlock.reset();
+        return;
+    }
+    fillEmptyWork(work);
+    if (mEncoderReturnedNbBytes != 0) {
+        std::shared_ptr<C2Buffer> buffer = createLinearBuffer(std::move(mOutputBlock), 0, mEncoderReturnedNbBytes);
+        work->worklets.front()->output.buffers.push_back(buffer);
+        work->worklets.front()->output.ordinal.timestamp = mAnchorTimeStamp + outTimeStamp;
+    } else {
+        ALOGV("encoder process_interleaved returned without data to write");
+    }
+    mOutputBlock = nullptr;
+    if (eos) {
+        mSignalledOutputEos = true;
+        ALOGV("signalled EOS");
+    }
+    mEncoderWriteData = false;
+    mEncoderReturnedNbBytes = 0;
+}
+
+FLAC__StreamEncoderWriteStatus C2SoftFlacEnc::onEncodedFlacAvailable(
+        const FLAC__byte buffer[], size_t bytes, unsigned samples,
+        unsigned current_frame) {
+    (void) current_frame;
+    ALOGV("%s (bytes=%zu, samples=%u, curr_frame=%u)", __func__, bytes, samples,
+          current_frame);
+
+#ifdef WRITE_FLAC_HEADER_IN_FIRST_BUFFER
+    if (samples == 0) {
+        ALOGI("saving %zu bytes of header", bytes);
+        memcpy(mHeader + mHeaderOffset, buffer, bytes);
+        mHeaderOffset += bytes;// will contain header size when finished receiving header
+        return FLAC__STREAM_ENCODER_WRITE_STATUS_OK;
+    }
+#endif
+
+    if ((samples == 0) || !mEncoderWriteData) {
+        // called by the encoder because there's header data to save, but it's not the role
+        // of this component (unless WRITE_FLAC_HEADER_IN_FIRST_BUFFER is defined)
+        ALOGV("ignoring %zu bytes of header data (samples=%d)", bytes, samples);
+        return FLAC__STREAM_ENCODER_WRITE_STATUS_OK;
+    }
+
+    // write encoded data
+    C2WriteView wView = mOutputBlock->map().get();
+    uint8_t* outData = wView.data();
+#ifdef WRITE_FLAC_HEADER_IN_FIRST_BUFFER
+    if (!mWroteHeader) {
+        ALOGI("writing %d bytes of header on output", mHeaderOffset);
+        memcpy(outData + mEncoderReturnedNbBytes, mHeader, mHeaderOffset);
+        mEncoderReturnedNbBytes += mHeaderOffset;
+        mWroteHeader = true;
+    }
+#endif
+    ALOGV("writing %zu bytes of encoded data on output", bytes);
+    // increment mProcessedSamples to maintain audio synchronization during
+    // play back
+    mProcessedSamples += samples;
+    if (bytes + mEncoderReturnedNbBytes > mOutputBlock->capacity()) {
+        ALOGE("not enough space left to write encoded data, dropping %zu bytes", bytes);
+        // a fatal error would stop the encoding
+        return FLAC__STREAM_ENCODER_WRITE_STATUS_OK;
+    }
+    memcpy(outData + mEncoderReturnedNbBytes, buffer, bytes);
+    mEncoderReturnedNbBytes += bytes;
+    return FLAC__STREAM_ENCODER_WRITE_STATUS_OK;
+}
+
+
+status_t C2SoftFlacEnc::configureEncoder() {
+    ALOGV("%s numChannel=%d, sampleRate=%d", __func__, mNumChannels, mSampleRate);
+
+    if (mSignalledError || !mFlacStreamEncoder) {
+        ALOGE("can't configure encoder: no encoder or invalid state");
+        return UNKNOWN_ERROR;
+    }
+
+    FLAC__bool ok = true;
+    ok = ok && FLAC__stream_encoder_set_channels(mFlacStreamEncoder, mNumChannels);
+    ok = ok && FLAC__stream_encoder_set_sample_rate(mFlacStreamEncoder, mSampleRate);
+    ok = ok && FLAC__stream_encoder_set_bits_per_sample(mFlacStreamEncoder, 16);
+    ok = ok && FLAC__stream_encoder_set_compression_level(mFlacStreamEncoder, mCompressionLevel);
+    ok = ok && FLAC__stream_encoder_set_verify(mFlacStreamEncoder, false);
+    if (!ok) {
+        ALOGE("unknown error when configuring encoder");
+        return UNKNOWN_ERROR;
+    }
+
+    ok &= FLAC__STREAM_ENCODER_INIT_STATUS_OK ==
+            FLAC__stream_encoder_init_stream(mFlacStreamEncoder,
+                    flacEncoderWriteCallback    /*write_callback*/,
+                    nullptr /*seek_callback*/,
+                    nullptr /*tell_callback*/,
+                    nullptr /*metadata_callback*/,
+                    (void *) this /*client_data*/);
+
+    if (!ok) {
+        ALOGE("unknown error when configuring encoder");
+        return UNKNOWN_ERROR;
+    }
+
+    mBlockSize = FLAC__stream_encoder_get_blocksize(mFlacStreamEncoder);
+
+    ALOGV("encoder successfully configured");
+    return OK;
+}
+
+FLAC__StreamEncoderWriteStatus C2SoftFlacEnc::flacEncoderWriteCallback(
+            const FLAC__StreamEncoder *,
+            const FLAC__byte buffer[],
+            size_t bytes,
+            unsigned samples,
+            unsigned current_frame,
+            void *client_data) {
+    return ((C2SoftFlacEnc*) client_data)->onEncodedFlacAvailable(
+            buffer, bytes, samples, current_frame);
+}
+
+c2_status_t C2SoftFlacEnc::drain(
+        uint32_t drainMode,
+        const std::shared_ptr<C2BlockPool> &pool) {
+    (void) pool;
+    switch (drainMode) {
+        case NO_DRAIN:
+            ALOGW("drain with NO_DRAIN: no-op");
+            return C2_OK;
+        case DRAIN_CHAIN:
+            ALOGW("DRAIN_CHAIN not supported");
+            return C2_OMITTED;
+        case DRAIN_COMPONENT_WITH_EOS:
+            // TODO: This flag is not being sent back to the client
+            // because there are no items in PendingWork queue as all the
+            // inputs are being sent back with emptywork or valid encoded data
+            // mSignalledOutputEos = true;
+        case DRAIN_COMPONENT_NO_EOS:
+            break;
+        default:
+            return C2_BAD_VALUE;
+    }
+    FLAC__bool ok = FLAC__stream_encoder_finish(mFlacStreamEncoder);
+    if (!ok) return C2_CORRUPTED;
+    mIsFirstFrame = true;
+    mAnchorTimeStamp = 0ull;
+    mProcessedSamples = 0u;
+
+    return C2_OK;
+}
+
+class C2SoftFlacEncFactory : public C2ComponentFactory {
+public:
+    virtual c2_status_t createComponent(
+            c2_node_id_t id,
+            std::shared_ptr<C2Component>* const component,
+            std::function<void(C2Component*)> deleter) override {
+        *component = std::shared_ptr<C2Component>(new C2SoftFlacEnc(kComponentName, id), deleter);
+        return C2_OK;
+    }
+
+    virtual c2_status_t createInterface(
+            c2_node_id_t id,
+            std::shared_ptr<C2ComponentInterface>* const interface,
+            std::function<void(C2ComponentInterface*)> deleter) override {
+        *interface = BuildIntf(kComponentName, id, deleter);
+        return C2_OK;
+    }
+
+    virtual ~C2SoftFlacEncFactory() override = default;
+};
+
+}  // namespace android
+
+extern "C" ::C2ComponentFactory* CreateCodec2Factory() {
+    ALOGV("in %s", __func__);
+    return new ::android::C2SoftFlacEncFactory();
+}
+
+extern "C" void DestroyCodec2Factory(::C2ComponentFactory* factory) {
+    ALOGV("in %s", __func__);
+    delete factory;
+}
diff --git a/media/libstagefright/codecs/flac/enc/C2SoftFlacEnc.h b/media/libstagefright/codecs/flac/enc/C2SoftFlacEnc.h
new file mode 100644
index 0000000..cc8cb86
--- /dev/null
+++ b/media/libstagefright/codecs/flac/enc/C2SoftFlacEnc.h
@@ -0,0 +1,96 @@
+/*
+ * 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 C2_SOFT_FLAC_ENC_H_
+#define C2_SOFT_FLAC_ENC_H_
+
+#include <SimpleC2Component.h>
+
+#include "FLAC/stream_encoder.h"
+
+#include <media/stagefright/foundation/ADebug.h>
+
+// use this symbol to have the first output buffer start with FLAC frame header so a dump of
+// all the output buffers can be opened as a .flac file
+//#define WRITE_FLAC_HEADER_IN_FIRST_BUFFER
+
+#define FLAC_COMPRESSION_LEVEL_MIN     0
+#define FLAC_COMPRESSION_LEVEL_DEFAULT 5
+#define FLAC_COMPRESSION_LEVEL_MAX     8
+
+#define FLAC_HEADER_SIZE               128
+
+#define MIN(a, b)                      (((a) < (b)) ? (a) : (b))
+
+namespace android {
+
+class C2SoftFlacEnc : public SimpleC2Component {
+public:
+    C2SoftFlacEnc(const char *name, c2_node_id_t id);
+    virtual ~C2SoftFlacEnc();
+
+    // From SimpleC2Component
+    c2_status_t onInit() override;
+    c2_status_t onStop() override;
+    void onReset() override;
+    void onRelease() override;
+    c2_status_t onFlush_sm() override;
+    void process(
+            const std::unique_ptr<C2Work> &work,
+            const std::shared_ptr<C2BlockPool> &pool) override;
+    c2_status_t drain(
+            uint32_t drainMode,
+            const std::shared_ptr<C2BlockPool> &pool) override;
+
+private:
+    status_t configureEncoder();
+    static FLAC__StreamEncoderWriteStatus flacEncoderWriteCallback(
+            const FLAC__StreamEncoder *encoder, const FLAC__byte buffer[],
+            size_t bytes, unsigned samples, unsigned current_frame,
+            void *client_data);
+    FLAC__StreamEncoderWriteStatus onEncodedFlacAvailable(
+            const FLAC__byte buffer[], size_t bytes, unsigned samples,
+            unsigned current_frame);
+
+    const unsigned int kInBlockSize = 1152;
+    const unsigned int kMaxNumChannels = 2;
+    FLAC__StreamEncoder* mFlacStreamEncoder;
+    FLAC__int32* mInputBufferPcm32;
+    std::shared_ptr<C2LinearBlock> mOutputBlock;
+    bool mSignalledError;
+    bool mSignalledOutputEos;
+    uint32_t mNumChannels;
+    uint32_t mSampleRate;
+    uint32_t mCompressionLevel;
+    uint32_t mBlockSize;
+    bool mIsFirstFrame;
+    uint64_t mAnchorTimeStamp;
+    uint64_t mProcessedSamples;
+    // should the data received by the callback be written to the output port
+    bool mEncoderWriteData;
+    size_t mEncoderReturnedNbBytes;
+#ifdef WRITE_FLAC_HEADER_IN_FIRST_BUFFER
+    unsigned mHeaderOffset;
+    bool mWroteHeader;
+    char mHeader[FLAC_HEADER_SIZE];
+#endif
+
+    DISALLOW_EVIL_CONSTRUCTORS(C2SoftFlacEnc);
+};
+
+}  // namespace android
+
+#endif  // C2_SOFT_FLAC_ENC_H_
diff --git a/media/libstagefright/codecs/g711/dec/C2SoftG711.cpp b/media/libstagefright/codecs/g711/dec/C2SoftG711.cpp
index d296a3d..1049420 100644
--- a/media/libstagefright/codecs/g711/dec/C2SoftG711.cpp
+++ b/media/libstagefright/codecs/g711/dec/C2SoftG711.cpp
@@ -92,8 +92,8 @@
         return;
     }
 
-    const C2ConstLinearBlock &inBuffer =
-	work->input.buffers[0]->data().linearBlocks().front();
+    const C2ConstLinearBlock inBuffer =
+        work->input.buffers[0]->data().linearBlocks().front();
     C2ReadView rView = inBuffer.map().get();
     size_t inOffset = inBuffer.offset();
     size_t inSize = inBuffer.size();
diff --git a/media/libstagefright/codecs/m4v_h263/dec/Android.bp b/media/libstagefright/codecs/m4v_h263/dec/Android.bp
index ca70cc2..d24a116 100644
--- a/media/libstagefright/codecs/m4v_h263/dec/Android.bp
+++ b/media/libstagefright/codecs/m4v_h263/dec/Android.bp
@@ -112,3 +112,91 @@
     },
     compile_multilib: "32",
 }
+
+//###############################################################################
+
+cc_library_shared {
+    name: "libstagefright_soft_c2mpeg4dec",
+//    vendor_available: true,
+//    vndk: {
+//        enabled: true,
+//    },
+
+    srcs: ["C2SoftMpeg4Dec.cpp"],
+
+    cflags: [
+        "-DOSCL_IMPORT_REF=",
+        "-DMPEG4",
+        "-Wall",
+        "-Werror",
+    ],
+
+    local_include_dirs: ["src"],
+    export_include_dirs: ["include"],
+
+    sanitize: {
+        misc_undefined: [
+            "signed-integer-overflow",
+        ],
+        cfi: true,
+        diag: {
+            cfi: true,
+        },
+    },
+
+    static_libs: ["libstagefright_m4vh263dec"],
+
+    shared_libs: [
+        "liblog",
+        "libutils",
+        "libstagefright_codec2",
+        "libstagefright_codec2_vndk",
+        "libstagefright_foundation",
+        "libstagefright_simple_c2component",
+    ],
+
+    ldflags: ["-Wl,-Bsymbolic"],
+}
+
+//###############################################################################
+cc_library_shared {
+    name: "libstagefright_soft_c2h263dec",
+//    vendor_available: true,
+//    vndk: {
+//        enabled: true,
+//    },
+
+    srcs: ["C2SoftMpeg4Dec.cpp"],
+
+    cflags: [
+        "-DOSCL_IMPORT_REF=",
+        "-Wall",
+        "-Werror",
+    ],
+
+    local_include_dirs: ["src"],
+    export_include_dirs: ["include"],
+
+    sanitize: {
+        misc_undefined: [
+            "signed-integer-overflow",
+        ],
+        cfi: true,
+        diag: {
+            cfi: true,
+        },
+    },
+
+    static_libs: ["libstagefright_m4vh263dec"],
+
+    shared_libs: [
+        "liblog",
+        "libutils",
+        "libstagefright_codec2",
+        "libstagefright_codec2_vndk",
+        "libstagefright_foundation",
+        "libstagefright_simple_c2component",
+    ],
+
+    ldflags: ["-Wl,-Bsymbolic"],
+}
diff --git a/media/libstagefright/codecs/m4v_h263/dec/C2SoftMpeg4Dec.cpp b/media/libstagefright/codecs/m4v_h263/dec/C2SoftMpeg4Dec.cpp
new file mode 100644
index 0000000..641c342
--- /dev/null
+++ b/media/libstagefright/codecs/m4v_h263/dec/C2SoftMpeg4Dec.cpp
@@ -0,0 +1,502 @@
+/*
+ * 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
+#define LOG_TAG "C2SoftMpeg4Dec"
+#include <utils/Log.h>
+
+#include "C2SoftMpeg4Dec.h"
+
+#include <C2PlatformSupport.h>
+#include <SimpleC2Interface.h>
+
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AUtils.h>
+#include <media/stagefright/foundation/MediaDefs.h>
+
+#include "mp4dec_api.h"
+
+namespace android {
+
+#ifdef MPEG4
+constexpr char kComponentName[] = "c2.google.mpeg4.decoder";
+#else
+constexpr char kComponentName[] = "c2.google.h263.decoder";
+#endif
+
+static std::shared_ptr<C2ComponentInterface> BuildIntf(
+        const char *name, c2_node_id_t id,
+        std::function<void(C2ComponentInterface*)> deleter =
+            std::default_delete<C2ComponentInterface>()) {
+    return SimpleC2Interface::Builder(name, id, deleter)
+            .inputFormat(C2FormatCompressed)
+            .outputFormat(C2FormatVideo)
+            .inputMediaType(
+#ifdef MPEG4
+                    MEDIA_MIMETYPE_VIDEO_MPEG4
+#else
+                    MEDIA_MIMETYPE_VIDEO_H263
+#endif
+            )
+            .outputMediaType(MEDIA_MIMETYPE_VIDEO_RAW)
+            .build();
+}
+
+C2SoftMpeg4Dec::C2SoftMpeg4Dec(const char *name, c2_node_id_t id)
+    : SimpleC2Component(BuildIntf(name, id)),
+      mDecHandle(nullptr) {
+}
+
+C2SoftMpeg4Dec::~C2SoftMpeg4Dec() {
+    onRelease();
+}
+
+c2_status_t C2SoftMpeg4Dec::onInit() {
+    status_t err = initDecoder();
+    return err == OK ? C2_OK : C2_CORRUPTED;
+}
+
+c2_status_t C2SoftMpeg4Dec::onStop() {
+    if (mInitialized) {
+        PVCleanUpVideoDecoder(mDecHandle);
+        mInitialized = false;
+    }
+    for (int32_t i = 0; i < kNumOutputBuffers; ++i) {
+        if (mOutputBuffer[i]) {
+            free(mOutputBuffer[i]);
+            mOutputBuffer[i] = nullptr;
+        }
+    }
+    mNumSamplesOutput = 0;
+    mFramesConfigured = false;
+    mSignalledOutputEos = false;
+    mSignalledError = false;
+
+    return C2_OK;
+}
+
+void C2SoftMpeg4Dec::onReset() {
+    (void) onStop();
+}
+
+void C2SoftMpeg4Dec::onRelease() {
+    if (mInitialized) {
+        PVCleanUpVideoDecoder(mDecHandle);
+    }
+    if (mOutBlock) {
+        mOutBlock.reset();
+    }
+    for (int32_t i = 0; i < kNumOutputBuffers; ++i) {
+        if (mOutputBuffer[i]) {
+            free(mOutputBuffer[i]);
+            mOutputBuffer[i] = nullptr;
+        }
+    }
+
+    delete mDecHandle;
+    mDecHandle = nullptr;
+}
+
+c2_status_t C2SoftMpeg4Dec::onFlush_sm() {
+    if (mInitialized) {
+        if (PV_TRUE != PVResetVideoDecoder(mDecHandle)) return C2_CORRUPTED;
+    }
+    mSignalledOutputEos = false;
+    mSignalledError = false;
+    return C2_OK;
+}
+
+status_t C2SoftMpeg4Dec::initDecoder() {
+#ifdef MPEG4
+    mIsMpeg4 = true;
+#else
+    mIsMpeg4 = false;
+#endif
+    if (!mDecHandle) {
+        mDecHandle = new tagvideoDecControls;
+    }
+    memset(mDecHandle, 0, sizeof(tagvideoDecControls));
+
+    for (int32_t i = 0; i < kNumOutputBuffers; ++i) {
+        mOutputBuffer[i] = nullptr;
+    }
+
+    /* TODO: bring these values to 352 and 288. It cannot be done as of now
+     * because, h263 doesn't seem to allow port reconfiguration. In OMX, the
+     * problem of larger width and height than default width and height is
+     * overcome by adaptivePlayBack() api call. This call gets width and height
+     * information from extractor. Such a thing is not possible here.
+     * So we are configuring to larger values.*/
+    mWidth = 1408;
+    mHeight = 1152;
+    mNumSamplesOutput = 0;
+    mInitialized = false;
+    mFramesConfigured = false;
+    mSignalledOutputEos = false;
+    mSignalledError = false;
+
+    return OK;
+}
+
+void fillEmptyWork(const std::unique_ptr<C2Work> &work) {
+    uint32_t flags = 0;
+    if (work->input.flags & C2FrameData::FLAG_END_OF_STREAM) {
+        flags |= C2FrameData::FLAG_END_OF_STREAM;
+        ALOGV("signalling eos");
+    }
+    work->worklets.front()->output.flags = (C2FrameData::flags_t)flags;
+    work->worklets.front()->output.buffers.clear();
+    work->worklets.front()->output.ordinal = work->input.ordinal;
+    work->workletsProcessed = 1u;
+}
+
+void C2SoftMpeg4Dec::finishWork(uint64_t index, const std::unique_ptr<C2Work> &work) {
+    std::shared_ptr<C2Buffer> buffer = createGraphicBuffer(std::move(mOutBlock),
+                                                           C2Rect(mWidth, mHeight));
+    mOutBlock = nullptr;
+    auto fillWork = [buffer, index](const std::unique_ptr<C2Work> &work) {
+        uint32_t flags = 0;
+        if ((work->input.flags & C2FrameData::FLAG_END_OF_STREAM) &&
+                (c2_cntr64_t(index) == work->input.ordinal.frameIndex)) {
+            flags |= C2FrameData::FLAG_END_OF_STREAM;
+            ALOGV("signalling eos");
+        }
+        work->worklets.front()->output.flags = (C2FrameData::flags_t)flags;
+        work->worklets.front()->output.buffers.clear();
+        work->worklets.front()->output.buffers.push_back(buffer);
+        work->worklets.front()->output.ordinal = work->input.ordinal;
+        work->workletsProcessed = 1u;
+    };
+    if (work && c2_cntr64_t(index) == work->input.ordinal.frameIndex) {
+        fillWork(work);
+    } else {
+        finish(index, fillWork);
+    }
+}
+
+c2_status_t C2SoftMpeg4Dec::ensureDecoderState(const std::shared_ptr<C2BlockPool> &pool) {
+    if (!mDecHandle) {
+        ALOGE("not supposed to be here, invalid decoder context");
+        return C2_CORRUPTED;
+    }
+
+    uint32_t outSize = align(mWidth, 16) * align(mHeight, 16) * 3 / 2;
+    for (int32_t i = 0; i < kNumOutputBuffers; ++i) {
+        if (!mOutputBuffer[i]) {
+            mOutputBuffer[i] = (uint8_t *)malloc(outSize * sizeof(uint8_t));
+            if (!mOutputBuffer[i]) return C2_NO_MEMORY;
+        }
+    }
+    if (mOutBlock &&
+            (mOutBlock->width() != align(mWidth, 16) || mOutBlock->height() != mHeight)) {
+        mOutBlock.reset();
+    }
+    if (!mOutBlock) {
+        uint32_t format = HAL_PIXEL_FORMAT_YV12;
+        C2MemoryUsage usage = { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE };
+        c2_status_t err = pool->fetchGraphicBlock(align(mWidth, 16), mHeight, format, usage, &mOutBlock);
+        if (err != C2_OK) {
+            ALOGE("fetchGraphicBlock for Output failed with status %d", err);
+            return err;
+        }
+        ALOGV("provided (%dx%d) required (%dx%d)",
+              mOutBlock->width(), mOutBlock->height(), mWidth, mHeight);
+    }
+    return C2_OK;
+}
+
+bool C2SoftMpeg4Dec::handleResChange(const std::unique_ptr<C2Work> &work) {
+    uint32_t disp_width, disp_height;
+    PVGetVideoDimensions(mDecHandle, (int32 *)&disp_width, (int32 *)&disp_height);
+
+    uint32_t buf_width, buf_height;
+    PVGetBufferDimensions(mDecHandle, (int32 *)&buf_width, (int32 *)&buf_height);
+
+    CHECK_LE(disp_width, buf_width);
+    CHECK_LE(disp_height, buf_height);
+
+    ALOGV("display size (%dx%d), buffer size (%dx%d)",
+           disp_width, disp_height, buf_width, buf_height);
+
+    bool resChanged = false;
+    if (disp_width != mWidth || disp_height != mHeight) {
+        mWidth = disp_width;
+        mHeight = disp_height;
+        resChanged = true;
+        for (int32_t i = 0; i < kNumOutputBuffers; ++i) {
+            if (mOutputBuffer[i]) {
+                free(mOutputBuffer[i]);
+                mOutputBuffer[i] = nullptr;
+            }
+        }
+
+        if (!mIsMpeg4) {
+            PVCleanUpVideoDecoder(mDecHandle);
+
+            uint8_t *vol_data[1]{};
+            int32_t vol_size = 0;
+
+            if (!PVInitVideoDecoder(
+                    mDecHandle, vol_data, &vol_size, 1, mWidth, mHeight, H263_MODE)) {
+                ALOGE("Error in PVInitVideoDecoder H263_MODE while resChanged was set to true");
+                work->result = C2_CORRUPTED;
+                mSignalledError = true;
+                return true;
+            }
+        }
+        mFramesConfigured = false;
+    }
+    return resChanged;
+}
+
+/* TODO: can remove temporary copy after library supports writing to display
+ * buffer Y, U and V plane pointers using stride info. */
+static void copyOutputBufferToYV12Frame(uint8_t *dst, uint8_t *src, size_t dstYStride,
+                                        size_t srcYStride, uint32_t width, uint32_t height) {
+    size_t dstUVStride = align(dstYStride / 2, 16);
+    size_t srcUVStride = srcYStride / 2;
+    uint8_t *srcStart = src;
+    uint8_t *dstStart = dst;
+    size_t vStride = align(height, 16);
+    for (size_t i = 0; i < height; ++i) {
+         memcpy(dst, src, width);
+         src += srcYStride;
+         dst += dstYStride;
+    }
+    /* U buffer */
+    src = srcStart + vStride * srcYStride;
+    dst = dstStart + (dstYStride * height) + (dstUVStride * height / 2);
+    for (size_t i = 0; i < height / 2; ++i) {
+         memcpy(dst, src, width / 2);
+         src += srcUVStride;
+         dst += dstUVStride;
+    }
+    /* V buffer */
+    src = srcStart + vStride * srcYStride * 5 / 4;
+    dst = dstStart + (dstYStride * height);
+    for (size_t i = 0; i < height / 2; ++i) {
+         memcpy(dst, src, width / 2);
+         src += srcUVStride;
+         dst += dstUVStride;
+    }
+}
+
+void C2SoftMpeg4Dec::process(
+        const std::unique_ptr<C2Work> &work,
+        const std::shared_ptr<C2BlockPool> &pool) {
+    work->result = C2_OK;
+    work->workletsProcessed = 0u;
+    if (mSignalledError || mSignalledOutputEos) {
+        work->result = C2_BAD_VALUE;
+        return;
+    }
+
+    const C2ConstLinearBlock inBuffer = work->input.buffers[0]->data().linearBlocks().front();
+    size_t inOffset = inBuffer.offset();
+    size_t inSize = inBuffer.size();
+    uint32_t workIndex = work->input.ordinal.frameIndex.peeku() & 0xFFFFFFFF;
+    C2ReadView rView = work->input.buffers[0]->data().linearBlocks().front().map().get();
+    if (inSize && rView.error()) {
+        ALOGE("read view map failed %d", rView.error());
+        work->result = C2_CORRUPTED;
+        return;
+    }
+    ALOGV("in buffer attr. size %zu timestamp %d frameindex %d, flags %x",
+          inSize, (int)work->input.ordinal.timestamp.peeku(),
+          (int)work->input.ordinal.frameIndex.peeku(), work->input.flags);
+
+    bool eos = ((work->input.flags & C2FrameData::FLAG_END_OF_STREAM) != 0);
+    if (inSize == 0) {
+        fillEmptyWork(work);
+        if (eos) {
+            mSignalledOutputEos = true;
+        }
+        return;
+    }
+
+    uint8_t *bitstream = const_cast<uint8_t *>(rView.data() + inOffset);
+    uint32_t *start_code = (uint32_t *)bitstream;
+    bool volHeader = *start_code == 0xB0010000;
+    if (volHeader) {
+        PVCleanUpVideoDecoder(mDecHandle);
+        mInitialized = false;
+    }
+
+    if (!mInitialized) {
+        uint8_t *vol_data[1]{};
+        int32_t vol_size = 0;
+
+        bool codecConfig = (work->input.flags & C2FrameData::FLAG_CODEC_CONFIG) != 0;
+        if (codecConfig || volHeader) {
+            vol_data[0] = bitstream;
+            vol_size = inSize;
+        }
+        MP4DecodingMode mode = (mIsMpeg4) ? MPEG4_MODE : H263_MODE;
+
+        if (!PVInitVideoDecoder(
+                mDecHandle, vol_data, &vol_size, 1,
+                mWidth, mHeight, mode)) {
+            ALOGE("PVInitVideoDecoder failed. Unsupported content?");
+            work->result = C2_CORRUPTED;
+            mSignalledError = true;
+            return;
+        }
+        mInitialized = true;
+        MP4DecodingMode actualMode = PVGetDecBitstreamMode(mDecHandle);
+        if (mode != actualMode) {
+            ALOGE("Decoded mode not same as actual mode of the decoder");
+            work->result = C2_CORRUPTED;
+            mSignalledError = true;
+            return;
+        }
+
+        PVSetPostProcType(mDecHandle, 0);
+        (void) handleResChange(work);
+        if (codecConfig) {
+            fillEmptyWork(work);
+            return;
+        }
+    }
+
+    while (inOffset < inSize) {
+        c2_status_t err = ensureDecoderState(pool);
+        if (C2_OK != err) {
+            mSignalledError = true;
+            work->result = err;
+            return;
+        }
+        C2GraphicView wView = mOutBlock->map().get();
+        if (wView.error()) {
+            ALOGE("graphic view map failed %d", wView.error());
+            work->result = C2_CORRUPTED;
+            return;
+        }
+
+        uint32_t outSize = align(mWidth, 16) * align(mHeight, 16) * 3 / 2;
+        uint32_t yFrameSize = sizeof(uint8) * mDecHandle->size;
+        if (outSize < yFrameSize * 3 / 2){
+            ALOGE("Too small output buffer: %d bytes", outSize);
+            work->result = C2_NO_MEMORY;
+            mSignalledError = true;
+            return;
+        }
+
+        if (!mFramesConfigured) {
+            PVSetReferenceYUV(mDecHandle,mOutputBuffer[1]);
+            mFramesConfigured = true;
+        }
+
+        // Need to check if header contains new info, e.g., width/height, etc.
+        VopHeaderInfo header_info;
+        uint32_t useExtTimestamp = (inOffset == 0);
+        int32_t tmpInSize = (int32_t)inSize;
+        uint8_t *bitstreamTmp = bitstream;
+        uint32_t timestamp = workIndex;
+        if (PVDecodeVopHeader(
+                    mDecHandle, &bitstreamTmp, &timestamp, &tmpInSize,
+                    &header_info, &useExtTimestamp,
+                    mOutputBuffer[mNumSamplesOutput & 1]) != PV_TRUE) {
+            ALOGE("failed to decode vop header.");
+            work->result = C2_CORRUPTED;
+            mSignalledError = true;
+            return;
+        }
+
+        // H263 doesn't have VOL header, the frame size information is in short header, i.e. the
+        // decoder may detect size change after PVDecodeVopHeader.
+        bool resChange = handleResChange(work);
+        if (mIsMpeg4 && resChange) {
+            work->result = C2_CORRUPTED;
+            mSignalledError = true;
+            return;
+        } else if (resChange) {
+            continue;
+        }
+
+        if (PVDecodeVopBody(mDecHandle, &tmpInSize) != PV_TRUE) {
+            ALOGE("failed to decode video frame.");
+            work->result = C2_CORRUPTED;
+            mSignalledError = true;
+            return;
+        }
+        if (handleResChange(work)) {
+            work->result = C2_CORRUPTED;
+            mSignalledError = true;
+            return;
+        }
+
+        uint8_t *outputBufferY = wView.data()[C2PlanarLayout::PLANE_Y];
+        (void)copyOutputBufferToYV12Frame(outputBufferY, mOutputBuffer[mNumSamplesOutput & 1],
+                                          wView.width(), align(mWidth, 16), mWidth, mHeight);
+
+        inOffset += inSize - (size_t)tmpInSize;
+        finishWork(workIndex, work);
+        ++mNumSamplesOutput;
+        if (inSize - inOffset) {
+            ALOGD("decoded frame, ignoring further trailing bytes %zu",
+                   inSize - (size_t)tmpInSize);
+            break;
+        }
+    }
+}
+
+c2_status_t C2SoftMpeg4Dec::drain(
+        uint32_t drainMode,
+        const std::shared_ptr<C2BlockPool> &pool) {
+    (void) pool;
+    if (drainMode == NO_DRAIN) {
+        ALOGW("drain with NO_DRAIN: no-op");
+        return C2_OK;
+    }
+    if (drainMode == DRAIN_CHAIN) {
+        ALOGW("DRAIN_CHAIN not supported");
+        return C2_OMITTED;
+    }
+    return C2_OK;
+}
+
+class C2SoftMpeg4DecFactory : public C2ComponentFactory {
+public:
+    virtual c2_status_t createComponent(
+            c2_node_id_t id,
+            std::shared_ptr<C2Component>* const component,
+            std::function<void(C2Component*)> deleter) override {
+        *component = std::shared_ptr<C2Component>(new C2SoftMpeg4Dec(kComponentName, id), deleter);
+        return C2_OK;
+    }
+
+    virtual c2_status_t createInterface(
+            c2_node_id_t id,
+            std::shared_ptr<C2ComponentInterface>* const interface,
+            std::function<void(C2ComponentInterface*)> deleter) override {
+        *interface = BuildIntf(kComponentName, id, deleter);
+        return C2_OK;
+    }
+
+    virtual ~C2SoftMpeg4DecFactory() override = default;
+};
+
+}  // namespace android
+
+extern "C" ::C2ComponentFactory* CreateCodec2Factory() {
+    ALOGV("in %s", __func__);
+    return new ::android::C2SoftMpeg4DecFactory();
+}
+
+extern "C" void DestroyCodec2Factory(::C2ComponentFactory* factory) {
+    ALOGV("in %s", __func__);
+    delete factory;
+}
diff --git a/media/libstagefright/codecs/m4v_h263/dec/C2SoftMpeg4Dec.h b/media/libstagefright/codecs/m4v_h263/dec/C2SoftMpeg4Dec.h
new file mode 100644
index 0000000..8eb316e
--- /dev/null
+++ b/media/libstagefright/codecs/m4v_h263/dec/C2SoftMpeg4Dec.h
@@ -0,0 +1,73 @@
+/*
+ * 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 C2_SOFT_MPEG4_DEC_H_
+#define C2_SOFT_MPEG4_DEC_H_
+
+#include <SimpleC2Component.h>
+
+#include <media/stagefright/foundation/ABase.h>
+
+struct tagvideoDecControls;
+
+namespace android {
+
+struct C2SoftMpeg4Dec : public SimpleC2Component {
+    C2SoftMpeg4Dec(const char *name, c2_node_id_t id);
+    virtual ~C2SoftMpeg4Dec();
+
+    // From SimpleC2Component
+    c2_status_t onInit() override;
+    c2_status_t onStop() override;
+    void onReset() override;
+    void onRelease() override;
+    c2_status_t onFlush_sm() override;
+    void process(
+            const std::unique_ptr<C2Work> &work,
+            const std::shared_ptr<C2BlockPool> &pool) override;
+    c2_status_t drain(
+            uint32_t drainMode,
+            const std::shared_ptr<C2BlockPool> &pool) override;
+ private:
+    enum {
+        kNumOutputBuffers = 2,
+    };
+
+    status_t initDecoder();
+    c2_status_t ensureDecoderState(const std::shared_ptr<C2BlockPool> &pool);
+    void finishWork(uint64_t index, const std::unique_ptr<C2Work> &work);
+    bool handleResChange(const std::unique_ptr<C2Work> &work);
+
+    tagvideoDecControls *mDecHandle;
+    std::shared_ptr<C2GraphicBlock> mOutBlock;
+    uint8_t *mOutputBuffer[kNumOutputBuffers];
+
+    uint32_t mWidth;
+    uint32_t mHeight;
+    uint32_t mNumSamplesOutput;
+
+    bool mIsMpeg4;
+    bool mInitialized;
+    bool mFramesConfigured;
+    bool mSignalledOutputEos;
+    bool mSignalledError;
+
+    DISALLOW_EVIL_CONSTRUCTORS(C2SoftMpeg4Dec);
+};
+
+}  // namespace android
+
+#endif  // C2_SOFT_MPEG4_DEC_H_
diff --git a/media/libstagefright/codecs/mp3dec/C2SoftMP3.cpp b/media/libstagefright/codecs/mp3dec/C2SoftMP3.cpp
index 9cac87e..0a8891e 100644
--- a/media/libstagefright/codecs/mp3dec/C2SoftMP3.cpp
+++ b/media/libstagefright/codecs/mp3dec/C2SoftMP3.cpp
@@ -288,7 +288,7 @@
         return;
     }
 
-    const C2ConstLinearBlock &inBuffer = work->input.buffers[0]->data().linearBlocks().front();
+    const C2ConstLinearBlock inBuffer = work->input.buffers[0]->data().linearBlocks().front();
     bool eos = ((work->input.flags & C2FrameData::FLAG_END_OF_STREAM) != 0);
     size_t inOffset = inBuffer.offset();
     size_t inSize = inBuffer.size();
diff --git a/media/libstagefright/codecs/mp3dec/src/pvmp3_decode_header.cpp b/media/libstagefright/codecs/mp3dec/src/pvmp3_decode_header.cpp
index d443b7c..bc5fd79 100644
--- a/media/libstagefright/codecs/mp3dec/src/pvmp3_decode_header.cpp
+++ b/media/libstagefright/codecs/mp3dec/src/pvmp3_decode_header.cpp
@@ -184,7 +184,7 @@
     info->emphasis           = (temp << 30) >> 30;  /* 2 */
 
 
-    if (!info->bitrate_index || info->sampling_frequency == 3)
+    if (!info->bitrate_index || info->bitrate_index == 15 || info->sampling_frequency == 3)
     {
         err = UNSUPPORTED_FREE_BITRATE;
     }
diff --git a/media/libstagefright/codecs/mpeg2dec/Android.bp b/media/libstagefright/codecs/mpeg2dec/Android.bp
index fb0db8f..3123376 100644
--- a/media/libstagefright/codecs/mpeg2dec/Android.bp
+++ b/media/libstagefright/codecs/mpeg2dec/Android.bp
@@ -1,4 +1,47 @@
 cc_library_shared {
+    name: "libstagefright_soft_c2mpeg2dec",
+//    vendor_available: true,
+//    vndk: {
+//        enabled: true,
+//    },
+
+    srcs: ["C2SoftMpeg2Dec.cpp"],
+
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+
+    include_dirs: [
+        "external/libmpeg2/decoder",
+        "external/libmpeg2/common",
+    ],
+
+    sanitize: {
+        misc_undefined: [
+            "signed-integer-overflow",
+        ],
+        cfi: false, // true,
+        diag: {
+            cfi: false, // true,
+        },
+    },
+
+    static_libs: ["libmpeg2dec"],
+
+    shared_libs: [
+        "liblog",
+        "libutils",
+        "libstagefright_codec2",
+        "libstagefright_codec2_vndk",
+        "libstagefright_foundation",
+        "libstagefright_simple_c2component",
+    ],
+
+    ldflags: ["-Wl,-Bsymbolic"],
+}
+
+cc_library_shared {
     name: "libstagefright_soft_mpeg2dec",
     vendor_available: true,
     vndk: {
diff --git a/media/libstagefright/codecs/mpeg2dec/C2SoftMpeg2Dec.cpp b/media/libstagefright/codecs/mpeg2dec/C2SoftMpeg2Dec.cpp
new file mode 100644
index 0000000..74ea340
--- /dev/null
+++ b/media/libstagefright/codecs/mpeg2dec/C2SoftMpeg2Dec.cpp
@@ -0,0 +1,795 @@
+/*
+ * 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
+#define LOG_TAG "C2SoftMpeg2Dec"
+#include <utils/Log.h>
+
+#include "iv_datatypedef.h"
+#include "iv.h"
+#include "ivd.h"
+#include "impeg2d.h"
+#include "C2SoftMpeg2Dec.h"
+
+#include <C2PlatformSupport.h>
+#include <SimpleC2Interface.h>
+
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/MediaDefs.h>
+
+namespace android {
+
+constexpr char kComponentName[] = "c2.google.mpeg2.decoder";
+
+static std::shared_ptr<C2ComponentInterface> BuildIntf(
+        const char *name, c2_node_id_t id,
+        std::function<void(C2ComponentInterface*)> deleter =
+            std::default_delete<C2ComponentInterface>()) {
+    return SimpleC2Interface::Builder(name, id, deleter)
+            .inputFormat(C2FormatCompressed)
+            .outputFormat(C2FormatVideo)
+            .inputMediaType(MEDIA_MIMETYPE_VIDEO_MPEG2)
+            .outputMediaType(MEDIA_MIMETYPE_VIDEO_RAW)
+            .build();
+}
+
+static size_t getCpuCoreCount() {
+    long cpuCoreCount = 1;
+#if defined(_SC_NPROCESSORS_ONLN)
+    cpuCoreCount = sysconf(_SC_NPROCESSORS_ONLN);
+#else
+    // _SC_NPROC_ONLN must be defined...
+    cpuCoreCount = sysconf(_SC_NPROC_ONLN);
+#endif
+    CHECK(cpuCoreCount >= 1);
+    ALOGV("Number of CPU cores: %ld", cpuCoreCount);
+    return (size_t)cpuCoreCount;
+}
+
+static void *ivd_aligned_malloc(WORD32 alignment, WORD32 size) {
+    return memalign(alignment, size);
+}
+
+static void ivd_aligned_free(void *mem) {
+    free(mem);
+}
+
+C2SoftMpeg2Dec::C2SoftMpeg2Dec(const char *name, c2_node_id_t id)
+    : SimpleC2Component(BuildIntf(name, id)),
+            mDecHandle(nullptr),
+            mMemRecords(nullptr),
+            mOutBufferDrain(nullptr),
+            mIvColorformat(IV_YUV_420P),
+            mWidth(320),
+            mHeight(240) {
+    // If input dump is enabled, then open create an empty file
+    GENERATE_FILE_NAMES();
+    CREATE_DUMP_FILE(mInFile);
+}
+
+C2SoftMpeg2Dec::~C2SoftMpeg2Dec() {
+    onRelease();
+}
+
+c2_status_t C2SoftMpeg2Dec::onInit() {
+    status_t err = initDecoder();
+    return err == OK ? C2_OK : C2_CORRUPTED;
+}
+
+c2_status_t C2SoftMpeg2Dec::onStop() {
+    if (OK != resetDecoder()) return C2_CORRUPTED;
+    resetPlugin();
+    return C2_OK;
+}
+
+void C2SoftMpeg2Dec::onReset() {
+    (void) onStop();
+}
+
+void C2SoftMpeg2Dec::onRelease() {
+    (void) deleteDecoder();
+    if (mOutBufferDrain) {
+        ivd_aligned_free(mOutBufferDrain);
+        mOutBufferDrain = nullptr;
+    }
+    if (mOutBlock) {
+        mOutBlock.reset();
+    }
+    if (mMemRecords) {
+        ivd_aligned_free(mMemRecords);
+        mMemRecords = nullptr;
+    }
+}
+
+c2_status_t C2SoftMpeg2Dec::onFlush_sm() {
+    if (OK != setFlushMode()) return C2_CORRUPTED;
+
+    uint32_t displayStride = mStride;
+    uint32_t displayHeight = mHeight;
+    uint32_t bufferSize = displayStride * displayHeight * 3 / 2;
+    mOutBufferDrain = (uint8_t *)ivd_aligned_malloc(128, bufferSize);
+    if (!mOutBufferDrain) {
+        ALOGE("could not allocate tmp output buffer (for flush) of size %u ", bufferSize);
+        return C2_NO_MEMORY;
+    }
+
+    while (true) {
+        ivd_video_decode_ip_t s_decode_ip;
+        ivd_video_decode_op_t s_decode_op;
+
+        setDecodeArgs(&s_decode_ip, &s_decode_op, nullptr, nullptr, 0, 0, 0);
+        (void) ivdec_api_function(mDecHandle, &s_decode_ip, &s_decode_op);
+        if (0 == s_decode_op.u4_output_present) {
+            resetPlugin();
+            break;
+        }
+    }
+
+    ivd_aligned_free(mOutBufferDrain);
+    mOutBufferDrain = nullptr;
+
+    return C2_OK;
+}
+
+status_t C2SoftMpeg2Dec::getNumMemRecords() {
+    iv_num_mem_rec_ip_t s_num_mem_rec_ip;
+    iv_num_mem_rec_op_t s_num_mem_rec_op;
+
+    s_num_mem_rec_ip.u4_size = sizeof(s_num_mem_rec_ip);
+    s_num_mem_rec_ip.e_cmd = IV_CMD_GET_NUM_MEM_REC;
+    s_num_mem_rec_op.u4_size = sizeof(s_num_mem_rec_op);
+
+    IV_API_CALL_STATUS_T status = ivdec_api_function(mDecHandle,
+                                                     &s_num_mem_rec_ip,
+                                                     &s_num_mem_rec_op);
+    if (IV_SUCCESS != status) {
+        ALOGE("Error in getting mem records: 0x%x", s_num_mem_rec_op.u4_error_code);
+        return UNKNOWN_ERROR;
+    }
+    mNumMemRecords = s_num_mem_rec_op.u4_num_mem_rec;
+
+    return OK;
+}
+
+status_t C2SoftMpeg2Dec::fillMemRecords() {
+    iv_mem_rec_t *ps_mem_rec = (iv_mem_rec_t *) ivd_aligned_malloc(
+            128, mNumMemRecords * sizeof(iv_mem_rec_t));
+    if (!ps_mem_rec) {
+        ALOGE("Allocation failure");
+        return NO_MEMORY;
+    }
+    memset(ps_mem_rec, 0, mNumMemRecords * sizeof(iv_mem_rec_t));
+    for (size_t i = 0; i < mNumMemRecords; i++)
+        ps_mem_rec[i].u4_size = sizeof(iv_mem_rec_t);
+    mMemRecords = ps_mem_rec;
+
+    ivdext_fill_mem_rec_ip_t s_fill_mem_ip;
+    ivdext_fill_mem_rec_op_t s_fill_mem_op;
+
+    s_fill_mem_ip.s_ivd_fill_mem_rec_ip_t.u4_size = sizeof(ivdext_fill_mem_rec_ip_t);
+    s_fill_mem_ip.u4_share_disp_buf = 0;
+    s_fill_mem_ip.e_output_format = mIvColorformat;
+    s_fill_mem_ip.u4_deinterlace = 1;
+    s_fill_mem_ip.s_ivd_fill_mem_rec_ip_t.e_cmd = IV_CMD_FILL_NUM_MEM_REC;
+    s_fill_mem_ip.s_ivd_fill_mem_rec_ip_t.pv_mem_rec_location = mMemRecords;
+    s_fill_mem_ip.s_ivd_fill_mem_rec_ip_t.u4_max_frm_wd = mWidth;
+    s_fill_mem_ip.s_ivd_fill_mem_rec_ip_t.u4_max_frm_ht = mHeight;
+    s_fill_mem_op.s_ivd_fill_mem_rec_op_t.u4_size = sizeof(ivdext_fill_mem_rec_op_t);
+    IV_API_CALL_STATUS_T status = ivdec_api_function(mDecHandle,
+                                                     &s_fill_mem_ip,
+                                                     &s_fill_mem_op);
+    if (IV_SUCCESS != status) {
+        ALOGE("Error in filling mem records: 0x%x",
+              s_fill_mem_op.s_ivd_fill_mem_rec_op_t.u4_error_code);
+        return UNKNOWN_ERROR;
+    }
+
+    CHECK_EQ(mNumMemRecords, s_fill_mem_op.s_ivd_fill_mem_rec_op_t.u4_num_mem_rec_filled);
+    for (size_t i = 0; i < mNumMemRecords; i++, ps_mem_rec++) {
+        ps_mem_rec->pv_base = ivd_aligned_malloc(
+                ps_mem_rec->u4_mem_alignment, ps_mem_rec->u4_mem_size);
+        if (!ps_mem_rec->pv_base) {
+            ALOGE("Allocation failure for memory record #%zu of size %u",
+                  i, ps_mem_rec->u4_mem_size);
+            return NO_MEMORY;
+        }
+    }
+
+    return OK;
+}
+
+status_t C2SoftMpeg2Dec::createDecoder() {
+    ivdext_init_ip_t s_init_ip;
+    ivdext_init_op_t s_init_op;
+
+    s_init_ip.s_ivd_init_ip_t.u4_size = sizeof(ivdext_init_ip_t);
+    s_init_ip.s_ivd_init_ip_t.e_cmd = (IVD_API_COMMAND_TYPE_T)IV_CMD_INIT;
+    s_init_ip.s_ivd_init_ip_t.pv_mem_rec_location = mMemRecords;
+    s_init_ip.s_ivd_init_ip_t.u4_frm_max_wd = mWidth;
+    s_init_ip.s_ivd_init_ip_t.u4_frm_max_ht = mHeight;
+    s_init_ip.u4_share_disp_buf = 0;
+    s_init_ip.u4_deinterlace = 1;
+    s_init_ip.s_ivd_init_ip_t.u4_num_mem_rec = mNumMemRecords;
+    s_init_ip.s_ivd_init_ip_t.e_output_format = mIvColorformat;
+    s_init_op.s_ivd_init_op_t.u4_size = sizeof(ivdext_init_op_t);
+
+    mDecHandle = (iv_obj_t *)mMemRecords[0].pv_base;
+    mDecHandle->pv_fxns = (void *)ivdec_api_function;
+    mDecHandle->u4_size = sizeof(iv_obj_t);
+
+    IV_API_CALL_STATUS_T status = ivdec_api_function(mDecHandle,
+                                                     &s_init_ip,
+                                                     &s_init_op);
+    if (status != IV_SUCCESS) {
+        ALOGE("error in %s: 0x%x", __func__,
+              s_init_op.s_ivd_init_op_t.u4_error_code);
+        return UNKNOWN_ERROR;
+    }
+
+    return OK;
+}
+
+status_t C2SoftMpeg2Dec::setNumCores() {
+    ivdext_ctl_set_num_cores_ip_t s_set_num_cores_ip;
+    ivdext_ctl_set_num_cores_op_t s_set_num_cores_op;
+
+    s_set_num_cores_ip.u4_size = sizeof(ivdext_ctl_set_num_cores_ip_t);
+    s_set_num_cores_ip.e_cmd = IVD_CMD_VIDEO_CTL;
+    s_set_num_cores_ip.e_sub_cmd = IVDEXT_CMD_CTL_SET_NUM_CORES;
+    s_set_num_cores_ip.u4_num_cores = mNumCores;
+    s_set_num_cores_op.u4_size = sizeof(ivdext_ctl_set_num_cores_op_t);
+    IV_API_CALL_STATUS_T status = ivdec_api_function(mDecHandle,
+                                                     &s_set_num_cores_ip,
+                                                     &s_set_num_cores_op);
+    if (status != IV_SUCCESS) {
+        ALOGD("error in %s: 0x%x", __func__, s_set_num_cores_op.u4_error_code);
+        return UNKNOWN_ERROR;
+    }
+
+    return OK;
+}
+
+status_t C2SoftMpeg2Dec::setParams(size_t stride) {
+    ivd_ctl_set_config_ip_t s_set_dyn_params_ip;
+    ivd_ctl_set_config_op_t s_set_dyn_params_op;
+
+    s_set_dyn_params_ip.u4_size = sizeof(ivd_ctl_set_config_ip_t);
+    s_set_dyn_params_ip.e_cmd = IVD_CMD_VIDEO_CTL;
+    s_set_dyn_params_ip.e_sub_cmd = IVD_CMD_CTL_SETPARAMS;
+    s_set_dyn_params_ip.u4_disp_wd = (UWORD32) stride;
+    s_set_dyn_params_ip.e_frm_skip_mode = IVD_SKIP_NONE;
+    s_set_dyn_params_ip.e_frm_out_mode = IVD_DISPLAY_FRAME_OUT;
+    s_set_dyn_params_ip.e_vid_dec_mode = IVD_DECODE_FRAME;
+    s_set_dyn_params_op.u4_size = sizeof(ivd_ctl_set_config_op_t);
+    IV_API_CALL_STATUS_T status = ivdec_api_function(mDecHandle,
+                                                     &s_set_dyn_params_ip,
+                                                     &s_set_dyn_params_op);
+    if (status != IV_SUCCESS) {
+        ALOGE("error in %s: 0x%x", __func__, s_set_dyn_params_op.u4_error_code);
+        return UNKNOWN_ERROR;
+    }
+
+    return OK;
+}
+
+status_t C2SoftMpeg2Dec::getVersion() {
+    ivd_ctl_getversioninfo_ip_t s_get_versioninfo_ip;
+    ivd_ctl_getversioninfo_op_t s_get_versioninfo_op;
+    UWORD8 au1_buf[512];
+
+    s_get_versioninfo_ip.u4_size = sizeof(ivd_ctl_getversioninfo_ip_t);
+    s_get_versioninfo_ip.e_cmd = IVD_CMD_VIDEO_CTL;
+    s_get_versioninfo_ip.e_sub_cmd = IVD_CMD_CTL_GETVERSION;
+    s_get_versioninfo_ip.pv_version_buffer = au1_buf;
+    s_get_versioninfo_ip.u4_version_buffer_size = sizeof(au1_buf);
+    s_get_versioninfo_op.u4_size = sizeof(ivd_ctl_getversioninfo_op_t);
+    IV_API_CALL_STATUS_T status = ivdec_api_function(mDecHandle,
+                                                     &s_get_versioninfo_ip,
+                                                     &s_get_versioninfo_op);
+    if (status != IV_SUCCESS) {
+        ALOGD("error in %s: 0x%x", __func__,
+              s_get_versioninfo_op.u4_error_code);
+    } else {
+        ALOGV("ittiam decoder version number: %s",
+              (char *) s_get_versioninfo_ip.pv_version_buffer);
+    }
+
+    return OK;
+}
+
+status_t C2SoftMpeg2Dec::initDecoder() {
+    status_t ret = getNumMemRecords();
+    if (OK != ret) return ret;
+
+    ret = fillMemRecords();
+    if (OK != ret) return ret;
+
+    if (OK != createDecoder()) return UNKNOWN_ERROR;
+
+    mNumCores = MIN(getCpuCoreCount(), MAX_NUM_CORES);
+    mStride = ALIGN64(mWidth);
+    mSignalledError = false;
+    mPreference = kPreferBitstream;
+    memset(&mDefaultColorAspects, 0, sizeof(ColorAspects));
+    memset(&mBitstreamColorAspects, 0, sizeof(ColorAspects));
+    memset(&mFinalColorAspects, 0, sizeof(ColorAspects));
+    mUpdateColorAspects = false;
+    resetPlugin();
+    (void) setNumCores();
+    if (OK != setParams(mStride)) return UNKNOWN_ERROR;
+    (void) getVersion();
+
+    return OK;
+}
+
+bool C2SoftMpeg2Dec::setDecodeArgs(ivd_video_decode_ip_t *ps_decode_ip,
+                                   ivd_video_decode_op_t *ps_decode_op,
+                                   C2ReadView *inBuffer,
+                                   C2GraphicView *outBuffer,
+                                   size_t inOffset,
+                                   size_t inSize,
+                                   uint32_t tsMarker) {
+    uint32_t displayStride = mStride;
+    uint32_t displayHeight = mHeight;
+    size_t lumaSize = displayStride * displayHeight;
+    size_t chromaSize = lumaSize >> 2;
+
+    ps_decode_ip->u4_size = sizeof(ivd_video_decode_ip_t);
+    ps_decode_ip->e_cmd = IVD_CMD_VIDEO_DECODE;
+    if (inBuffer) {
+        ps_decode_ip->u4_ts = tsMarker;
+        ps_decode_ip->pv_stream_buffer = const_cast<uint8_t *>(inBuffer->data() + inOffset);
+        ps_decode_ip->u4_num_Bytes = inSize - inOffset;
+    } else {
+        ps_decode_ip->u4_ts = 0;
+        ps_decode_ip->pv_stream_buffer = nullptr;
+        ps_decode_ip->u4_num_Bytes = 0;
+    }
+    ps_decode_ip->s_out_buffer.u4_min_out_buf_size[0] = lumaSize;
+    ps_decode_ip->s_out_buffer.u4_min_out_buf_size[1] = chromaSize;
+    ps_decode_ip->s_out_buffer.u4_min_out_buf_size[2] = chromaSize;
+    if (outBuffer) {
+        if (outBuffer->width() < displayStride || outBuffer->height() < displayHeight) {
+            ALOGE("Output buffer too small: provided (%dx%d) required (%ux%u)",
+                  outBuffer->width(), outBuffer->height(), displayStride, displayHeight);
+            return false;
+        }
+        ps_decode_ip->s_out_buffer.pu1_bufs[0] = outBuffer->data()[C2PlanarLayout::PLANE_Y];
+        ps_decode_ip->s_out_buffer.pu1_bufs[1] = outBuffer->data()[C2PlanarLayout::PLANE_U];
+        ps_decode_ip->s_out_buffer.pu1_bufs[2] = outBuffer->data()[C2PlanarLayout::PLANE_V];
+    } else {
+        ps_decode_ip->s_out_buffer.pu1_bufs[0] = mOutBufferDrain;
+        ps_decode_ip->s_out_buffer.pu1_bufs[1] = mOutBufferDrain + lumaSize;
+        ps_decode_ip->s_out_buffer.pu1_bufs[2] = mOutBufferDrain + lumaSize + chromaSize;
+    }
+    ps_decode_ip->s_out_buffer.u4_num_bufs = 3;
+    ps_decode_op->u4_size = sizeof(ivd_video_decode_op_t);
+
+    return true;
+}
+
+bool C2SoftMpeg2Dec::colorAspectsDiffer(
+        const ColorAspects &a, const ColorAspects &b) {
+    if (a.mRange != b.mRange
+        || a.mPrimaries != b.mPrimaries
+        || a.mTransfer != b.mTransfer
+        || a.mMatrixCoeffs != b.mMatrixCoeffs) {
+        return true;
+    }
+    return false;
+}
+
+void C2SoftMpeg2Dec::updateFinalColorAspects(
+        const ColorAspects &otherAspects, const ColorAspects &preferredAspects) {
+    Mutex::Autolock autoLock(mColorAspectsLock);
+    ColorAspects newAspects;
+    newAspects.mRange = preferredAspects.mRange != ColorAspects::RangeUnspecified ?
+        preferredAspects.mRange : otherAspects.mRange;
+    newAspects.mPrimaries = preferredAspects.mPrimaries != ColorAspects::PrimariesUnspecified ?
+        preferredAspects.mPrimaries : otherAspects.mPrimaries;
+    newAspects.mTransfer = preferredAspects.mTransfer != ColorAspects::TransferUnspecified ?
+        preferredAspects.mTransfer : otherAspects.mTransfer;
+    newAspects.mMatrixCoeffs = preferredAspects.mMatrixCoeffs != ColorAspects::MatrixUnspecified ?
+        preferredAspects.mMatrixCoeffs : otherAspects.mMatrixCoeffs;
+
+    // Check to see if need update mFinalColorAspects.
+    if (colorAspectsDiffer(mFinalColorAspects, newAspects)) {
+        mFinalColorAspects = newAspects;
+        mUpdateColorAspects = true;
+    }
+}
+
+status_t C2SoftMpeg2Dec::handleColorAspectsChange() {
+    if (mPreference == kPreferBitstream) {
+        updateFinalColorAspects(mDefaultColorAspects, mBitstreamColorAspects);
+    } else if (mPreference == kPreferContainer) {
+        updateFinalColorAspects(mBitstreamColorAspects, mDefaultColorAspects);
+    } else {
+        return C2_CORRUPTED;
+    }
+    return C2_OK;
+}
+
+bool C2SoftMpeg2Dec::getSeqInfo() {
+    ivdext_ctl_get_seq_info_ip_t s_ctl_get_seq_info_ip;
+    ivdext_ctl_get_seq_info_op_t s_ctl_get_seq_info_op;
+
+    s_ctl_get_seq_info_ip.u4_size = sizeof(ivdext_ctl_get_seq_info_ip_t);
+    s_ctl_get_seq_info_ip.e_cmd = IVD_CMD_VIDEO_CTL;
+    s_ctl_get_seq_info_ip.e_sub_cmd =
+        (IVD_CONTROL_API_COMMAND_TYPE_T)IMPEG2D_CMD_CTL_GET_SEQ_INFO;
+    s_ctl_get_seq_info_op.u4_size = sizeof(ivdext_ctl_get_seq_info_op_t);
+    IV_API_CALL_STATUS_T status = ivdec_api_function(mDecHandle,
+                                                     &s_ctl_get_seq_info_ip,
+                                                     &s_ctl_get_seq_info_op);
+    if (status != IV_SUCCESS) {
+        ALOGW("Error in getting Sequence info: 0x%x", s_ctl_get_seq_info_op.u4_error_code);
+        return false;
+    }
+
+    int32_t primaries = s_ctl_get_seq_info_op.u1_colour_primaries;
+    int32_t transfer = s_ctl_get_seq_info_op.u1_transfer_characteristics;
+    int32_t coeffs = s_ctl_get_seq_info_op.u1_matrix_coefficients;
+    bool full_range =  false;  // mpeg2 video has limited range.
+
+    ColorAspects colorAspects;
+    ColorUtils::convertIsoColorAspectsToCodecAspects(
+            primaries, transfer, coeffs, full_range, colorAspects);
+    // Update color aspects if necessary.
+    if (colorAspectsDiffer(colorAspects, mBitstreamColorAspects)) {
+        mBitstreamColorAspects = colorAspects;
+        status_t err = handleColorAspectsChange();
+        CHECK(err == OK);
+    }
+
+    return true;
+}
+
+status_t C2SoftMpeg2Dec::setFlushMode() {
+    ivd_ctl_flush_ip_t s_set_flush_ip;
+    ivd_ctl_flush_op_t s_set_flush_op;
+
+    s_set_flush_ip.u4_size = sizeof(ivd_ctl_flush_ip_t);
+    s_set_flush_ip.e_cmd = IVD_CMD_VIDEO_CTL;
+    s_set_flush_ip.e_sub_cmd = IVD_CMD_CTL_FLUSH;
+    s_set_flush_op.u4_size = sizeof(ivd_ctl_flush_op_t);
+    IV_API_CALL_STATUS_T status = ivdec_api_function(mDecHandle,
+                                                     &s_set_flush_ip,
+                                                     &s_set_flush_op);
+    if (status != IV_SUCCESS) {
+        ALOGE("error in %s: 0x%x", __func__, s_set_flush_op.u4_error_code);
+        return UNKNOWN_ERROR;
+    }
+
+    return OK;
+}
+
+status_t C2SoftMpeg2Dec::resetDecoder() {
+    ivd_ctl_reset_ip_t s_reset_ip;
+    ivd_ctl_reset_op_t s_reset_op;
+
+    s_reset_ip.u4_size = sizeof(ivd_ctl_reset_ip_t);
+    s_reset_ip.e_cmd = IVD_CMD_VIDEO_CTL;
+    s_reset_ip.e_sub_cmd = IVD_CMD_CTL_RESET;
+    s_reset_op.u4_size = sizeof(ivd_ctl_reset_op_t);
+    IV_API_CALL_STATUS_T status = ivdec_api_function(mDecHandle,
+                                                     &s_reset_ip,
+                                                     &s_reset_op);
+    if (IV_SUCCESS != status) {
+        ALOGE("error in %s: 0x%x", __func__, s_reset_op.u4_error_code);
+        return UNKNOWN_ERROR;
+    }
+    (void) setNumCores();
+    mStride = 0;
+    mSignalledError = false;
+
+    return OK;
+}
+
+void C2SoftMpeg2Dec::resetPlugin() {
+    mSignalledOutputEos = false;
+    gettimeofday(&mTimeStart, nullptr);
+    gettimeofday(&mTimeEnd, nullptr);
+}
+
+status_t C2SoftMpeg2Dec::deleteDecoder() {
+    if (mMemRecords) {
+        iv_mem_rec_t *ps_mem_rec = mMemRecords;
+
+        for (size_t i = 0; i < mNumMemRecords; i++, ps_mem_rec++) {
+            if (ps_mem_rec->pv_base) {
+                ivd_aligned_free(ps_mem_rec->pv_base);
+            }
+        }
+        ivd_aligned_free(mMemRecords);
+        mMemRecords = nullptr;
+    }
+    mDecHandle = nullptr;
+
+    return OK;
+}
+
+status_t C2SoftMpeg2Dec::reInitDecoder() {
+    deleteDecoder();
+
+    status_t ret = initDecoder();
+    if (OK != ret) {
+        ALOGE("Failed to initialize decoder");
+        deleteDecoder();
+        return ret;
+    }
+    return OK;
+}
+
+void fillEmptyWork(const std::unique_ptr<C2Work> &work) {
+    uint32_t flags = 0;
+    if (work->input.flags & C2FrameData::FLAG_END_OF_STREAM) {
+        flags |= C2FrameData::FLAG_END_OF_STREAM;
+        ALOGV("signalling eos");
+    }
+    work->worklets.front()->output.flags = (C2FrameData::flags_t)flags;
+    work->worklets.front()->output.buffers.clear();
+    work->worklets.front()->output.ordinal = work->input.ordinal;
+    work->workletsProcessed = 1u;
+}
+
+void C2SoftMpeg2Dec::finishWork(uint64_t index, const std::unique_ptr<C2Work> &work) {
+    std::shared_ptr<C2Buffer> buffer = createGraphicBuffer(std::move(mOutBlock),
+                                                           C2Rect(mWidth, mHeight));
+    mOutBlock = nullptr;
+    auto fillWork = [buffer, index](const std::unique_ptr<C2Work> &work) {
+        uint32_t flags = 0;
+        if ((work->input.flags & C2FrameData::FLAG_END_OF_STREAM) &&
+                (c2_cntr64_t(index) == work->input.ordinal.frameIndex)) {
+            flags |= C2FrameData::FLAG_END_OF_STREAM;
+            ALOGV("signalling eos");
+        }
+        work->worklets.front()->output.flags = (C2FrameData::flags_t)flags;
+        work->worklets.front()->output.buffers.clear();
+        work->worklets.front()->output.buffers.push_back(buffer);
+        work->worklets.front()->output.ordinal = work->input.ordinal;
+        work->workletsProcessed = 1u;
+    };
+    if (work && c2_cntr64_t(index) == work->input.ordinal.frameIndex) {
+        fillWork(work);
+    } else {
+        finish(index, fillWork);
+    }
+}
+
+c2_status_t C2SoftMpeg2Dec::ensureDecoderState(const std::shared_ptr<C2BlockPool> &pool) {
+    if (!mDecHandle) {
+        ALOGE("not supposed to be here, invalid decoder context");
+        return C2_CORRUPTED;
+    }
+    if (mStride != ALIGN64(mWidth)) {
+        mStride = ALIGN64(mWidth);
+        if (OK != setParams(mStride)) return C2_CORRUPTED;
+    }
+    if (mOutBlock &&
+            (mOutBlock->width() != mStride || mOutBlock->height() != mHeight)) {
+        mOutBlock.reset();
+    }
+    if (!mOutBlock) {
+        uint32_t format = HAL_PIXEL_FORMAT_YV12;
+        C2MemoryUsage usage = { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE };
+        c2_status_t err = pool->fetchGraphicBlock(mStride, mHeight, format, usage, &mOutBlock);
+        if (err != C2_OK) {
+            ALOGE("fetchGraphicBlock for Output failed with status %d", err);
+            return err;
+        }
+        ALOGV("provided (%dx%d) required (%dx%d)",
+              mOutBlock->width(), mOutBlock->height(), mStride, mHeight);
+    }
+
+    return C2_OK;
+}
+
+// TODO: can overall error checking be improved?
+// TODO: allow configuration of color format and usage for graphic buffers instead
+//       of hard coding them to HAL_PIXEL_FORMAT_YV12
+// TODO: pass coloraspects information to surface
+// TODO: test support for dynamic change in resolution
+// TODO: verify if the decoder sent back all frames
+void C2SoftMpeg2Dec::process(
+        const std::unique_ptr<C2Work> &work,
+        const std::shared_ptr<C2BlockPool> &pool) {
+    work->result = C2_OK;
+    work->workletsProcessed = 0u;
+    if (mSignalledError || mSignalledOutputEos) {
+        work->result = C2_BAD_VALUE;
+        return;
+    }
+
+    const C2ConstLinearBlock inBuffer = work->input.buffers[0]->data().linearBlocks().front();
+    size_t inOffset = inBuffer.offset();
+    size_t inSize = inBuffer.size();
+    uint32_t workIndex = work->input.ordinal.frameIndex.peeku() & 0xFFFFFFFF;
+    C2ReadView rView = work->input.buffers[0]->data().linearBlocks().front().map().get();
+    if (inSize && rView.error()) {
+        ALOGE("read view map failed %d", rView.error());
+        work->result = C2_CORRUPTED;
+        return;
+    }
+    bool eos = ((work->input.flags & C2FrameData::FLAG_END_OF_STREAM) != 0);
+    bool hasPicture = false;
+
+    ALOGV("in buffer attr. size %zu timestamp %d frameindex %d, flags %x",
+          inSize, (int)work->input.ordinal.timestamp.peeku(),
+          (int)work->input.ordinal.frameIndex.peeku(), work->input.flags);
+    while (inOffset < inSize) {
+        if (C2_OK != ensureDecoderState(pool)) {
+            mSignalledError = true;
+            work->result = C2_CORRUPTED;
+            return;
+        }
+        C2GraphicView wView = mOutBlock->map().get();
+        if (wView.error()) {
+            ALOGE("graphic view map failed %d", wView.error());
+            work->result = C2_CORRUPTED;
+            return;
+        }
+
+        ivd_video_decode_ip_t s_decode_ip;
+        ivd_video_decode_op_t s_decode_op;
+        if (!setDecodeArgs(&s_decode_ip, &s_decode_op, &rView, &wView,
+                           inOffset, inSize, workIndex)) {
+            mSignalledError = true;
+            work->result = C2_CORRUPTED;
+            return;
+        }
+        // If input dump is enabled, then write to file
+        DUMP_TO_FILE(mInFile, s_decode_ip.pv_stream_buffer, s_decode_ip.u4_num_Bytes);
+        WORD32 delay;
+        GETTIME(&mTimeStart, NULL);
+        TIME_DIFF(mTimeEnd, mTimeStart, delay);
+        (void) ivdec_api_function(mDecHandle, &s_decode_ip, &s_decode_op);
+        WORD32 decodeTime;
+        GETTIME(&mTimeEnd, nullptr);
+        TIME_DIFF(mTimeStart, mTimeEnd, decodeTime);
+        ALOGV("decodeTime=%6d delay=%6d numBytes=%6d ", decodeTime, delay,
+              s_decode_op.u4_num_bytes_consumed);
+        if (IMPEG2D_UNSUPPORTED_DIMENSIONS == s_decode_op.u4_error_code) {
+            ALOGV("unsupported resolution : %dx%d", s_decode_op.u4_pic_wd, s_decode_op.u4_pic_ht);
+            drainInternal(DRAIN_COMPONENT_NO_EOS, pool, work);
+            resetPlugin();
+            mWidth = s_decode_op.u4_pic_wd;
+            mHeight = s_decode_op.u4_pic_ht;
+            if (OK != reInitDecoder()) {
+                ALOGE("Failed to reinitialize decoder");
+                mSignalledError = true;
+                work->result = C2_CORRUPTED;
+                return;
+            }
+            continue;
+        } else if (IVD_RES_CHANGED == (s_decode_op.u4_error_code & 0xFF)) {
+            ALOGV("resolution changed");
+            drainInternal(DRAIN_COMPONENT_NO_EOS, pool, work);
+            resetDecoder();
+            resetPlugin();
+            mWidth = s_decode_op.u4_pic_wd;
+            mHeight = s_decode_op.u4_pic_ht;
+            continue;
+        }
+
+        (void) getSeqInfo();
+        if (mUpdateColorAspects) {
+            mUpdateColorAspects = false;
+        }
+        hasPicture |= (1 == s_decode_op.u4_frame_decoded_flag);
+        if (s_decode_op.u4_output_present) {
+            finishWork(s_decode_op.u4_ts, work);
+        }
+        inOffset += s_decode_op.u4_num_bytes_consumed;
+        if (hasPicture && (inSize - inOffset)) {
+            ALOGD("decoded frame in current access nal, ignoring further trailing bytes %d",
+                  (int)inSize - (int)inOffset);
+            break;
+        }
+    }
+
+    if (eos) {
+        drainInternal(DRAIN_COMPONENT_WITH_EOS, pool, work);
+        mSignalledOutputEos = true;
+    } else if (!hasPicture) {
+        fillEmptyWork(work);
+    }
+}
+
+c2_status_t C2SoftMpeg2Dec::drainInternal(
+        uint32_t drainMode,
+        const std::shared_ptr<C2BlockPool> &pool,
+        const std::unique_ptr<C2Work> &work) {
+    if (drainMode == NO_DRAIN) {
+        ALOGW("drain with NO_DRAIN: no-op");
+        return C2_OK;
+    }
+    if (drainMode == DRAIN_CHAIN) {
+        ALOGW("DRAIN_CHAIN not supported");
+        return C2_OMITTED;
+    }
+
+    if (OK != setFlushMode()) return C2_CORRUPTED;
+    while (true) {
+        if (C2_OK != ensureDecoderState(pool)) {
+            mSignalledError = true;
+            work->result = C2_CORRUPTED;
+            return C2_CORRUPTED;
+        }
+        C2GraphicView wView = mOutBlock->map().get();
+        if (wView.error()) {
+            ALOGE("graphic view map failed %d", wView.error());
+            return C2_CORRUPTED;
+        }
+        ivd_video_decode_ip_t s_decode_ip;
+        ivd_video_decode_op_t s_decode_op;
+        if (!setDecodeArgs(&s_decode_ip, &s_decode_op, nullptr, &wView, 0, 0, 0)) {
+            mSignalledError = true;
+            return C2_CORRUPTED;
+        }
+        (void) ivdec_api_function(mDecHandle, &s_decode_ip, &s_decode_op);
+        if (s_decode_op.u4_output_present) {
+            finishWork(s_decode_op.u4_ts, work);
+        } else {
+            break;
+        }
+    }
+    if (drainMode == DRAIN_COMPONENT_WITH_EOS &&
+            work && work->workletsProcessed == 0u) {
+        fillEmptyWork(work);
+    }
+
+    return C2_OK;
+}
+
+c2_status_t C2SoftMpeg2Dec::drain(
+        uint32_t drainMode,
+        const std::shared_ptr<C2BlockPool> &pool) {
+    return drainInternal(drainMode, pool, nullptr);
+}
+
+class C2SoftMpeg2DecFactory : public C2ComponentFactory {
+public:
+    virtual c2_status_t createComponent(
+            c2_node_id_t id,
+            std::shared_ptr<C2Component>* const component,
+            std::function<void(C2Component*)> deleter) override {
+        *component = std::shared_ptr<C2Component>(new C2SoftMpeg2Dec(kComponentName, id), deleter);
+        return C2_OK;
+    }
+
+    virtual c2_status_t createInterface(
+            c2_node_id_t id,
+            std::shared_ptr<C2ComponentInterface>* const interface,
+            std::function<void(C2ComponentInterface*)> deleter) override {
+        *interface = BuildIntf(kComponentName, id, deleter);
+        return C2_OK;
+    }
+
+    virtual ~C2SoftMpeg2DecFactory() override = default;
+};
+
+}  // namespace android
+
+extern "C" ::C2ComponentFactory* CreateCodec2Factory() {
+    ALOGV("in %s", __func__);
+    return new ::android::C2SoftMpeg2DecFactory();
+}
+
+extern "C" void DestroyCodec2Factory(::C2ComponentFactory* factory) {
+    ALOGV("in %s", __func__);
+    delete factory;
+}
diff --git a/media/libstagefright/codecs/mpeg2dec/C2SoftMpeg2Dec.h b/media/libstagefright/codecs/mpeg2dec/C2SoftMpeg2Dec.h
new file mode 100644
index 0000000..64e5b05
--- /dev/null
+++ b/media/libstagefright/codecs/mpeg2dec/C2SoftMpeg2Dec.h
@@ -0,0 +1,185 @@
+/*
+ * 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 C2_SOFT_MPEG2_DEC_H_
+#define C2_SOFT_MPEG2_DEC_H_
+
+#include <SimpleC2Component.h>
+
+#include <media/stagefright/foundation/ABase.h>
+#include <media/stagefright/foundation/ColorUtils.h>
+
+namespace android {
+
+#define ivdec_api_function              impeg2d_api_function
+#define ivdext_init_ip_t                impeg2d_init_ip_t
+#define ivdext_init_op_t                impeg2d_init_op_t
+#define ivdext_fill_mem_rec_ip_t        impeg2d_fill_mem_rec_ip_t
+#define ivdext_fill_mem_rec_op_t        impeg2d_fill_mem_rec_op_t
+#define ivdext_ctl_set_num_cores_ip_t   impeg2d_ctl_set_num_cores_ip_t
+#define ivdext_ctl_set_num_cores_op_t   impeg2d_ctl_set_num_cores_op_t
+#define ivdext_ctl_get_seq_info_ip_t    impeg2d_ctl_get_seq_info_ip_t
+#define ivdext_ctl_get_seq_info_op_t    impeg2d_ctl_get_seq_info_op_t
+#define ALIGN64(x)                      ((((x) + 63) >> 6) << 6)
+#define MAX_NUM_CORES                   4
+#define IVDEXT_CMD_CTL_SET_NUM_CORES    \
+        (IVD_CONTROL_API_COMMAND_TYPE_T)IMPEG2D_CMD_CTL_SET_NUM_CORES
+#define MIN(a, b)                       (((a) < (b)) ? (a) : (b))
+#define GETTIME(a, b)                   gettimeofday(a, b);
+#define TIME_DIFF(start, end, diff)     \
+    diff = (((end).tv_sec - (start).tv_sec) * 1000000) + \
+            ((end).tv_usec - (start).tv_usec);
+
+#ifdef FILE_DUMP_ENABLE
+    #define INPUT_DUMP_PATH     "/sdcard/clips/mpeg2d_input"
+    #define INPUT_DUMP_EXT      "m2v"
+    #define GENERATE_FILE_NAMES() {                         \
+        GETTIME(&mTimeStart, NULL);                         \
+        strcpy(mInFile, "");                                \
+        sprintf(mInFile, "%s_%ld.%ld.%s", INPUT_DUMP_PATH,  \
+                mTimeStart.tv_sec, mTimeStart.tv_usec,      \
+                INPUT_DUMP_EXT);                            \
+    }
+    #define CREATE_DUMP_FILE(m_filename) {                  \
+        FILE *fp = fopen(m_filename, "wb");                 \
+        if (fp != NULL) {                                   \
+            fclose(fp);                                     \
+        } else {                                            \
+            ALOGD("Could not open file %s", m_filename);    \
+        }                                                   \
+    }
+    #define DUMP_TO_FILE(m_filename, m_buf, m_size)         \
+    {                                                       \
+        FILE *fp = fopen(m_filename, "ab");                 \
+        if (fp != NULL && m_buf != NULL) {                  \
+            uint32_t i;                                     \
+            i = fwrite(m_buf, 1, m_size, fp);               \
+            ALOGD("fwrite ret %d to write %d", i, m_size);  \
+            if (i != (uint32_t)m_size) {                    \
+                ALOGD("Error in fwrite, returned %d", i);   \
+                perror("Error in write to file");           \
+            }                                               \
+            fclose(fp);                                     \
+        } else {                                            \
+            ALOGD("Could not write to file %s", m_filename);\
+        }                                                   \
+    }
+#else /* FILE_DUMP_ENABLE */
+    #define INPUT_DUMP_PATH
+    #define INPUT_DUMP_EXT
+    #define OUTPUT_DUMP_PATH
+    #define OUTPUT_DUMP_EXT
+    #define GENERATE_FILE_NAMES()
+    #define CREATE_DUMP_FILE(m_filename)
+    #define DUMP_TO_FILE(m_filename, m_buf, m_size)
+#endif /* FILE_DUMP_ENABLE */
+
+struct C2SoftMpeg2Dec : public SimpleC2Component {
+    C2SoftMpeg2Dec(const char *name, c2_node_id_t id);
+    virtual ~C2SoftMpeg2Dec();
+
+    // From SimpleC2Component
+    c2_status_t onInit() override;
+    c2_status_t onStop() override;
+    void onReset() override;
+    void onRelease() override;
+    c2_status_t onFlush_sm() override;
+    void process(
+            const std::unique_ptr<C2Work> &work,
+            const std::shared_ptr<C2BlockPool> &pool) override;
+    c2_status_t drain(
+            uint32_t drainMode,
+            const std::shared_ptr<C2BlockPool> &pool) override;
+ private:
+    status_t getNumMemRecords();
+    status_t fillMemRecords();
+    status_t createDecoder();
+    status_t setNumCores();
+    status_t setParams(size_t stride);
+    status_t getVersion();
+    status_t initDecoder();
+    bool setDecodeArgs(ivd_video_decode_ip_t *ps_decode_ip,
+                       ivd_video_decode_op_t *ps_decode_op,
+                       C2ReadView *inBuffer,
+                       C2GraphicView *outBuffer,
+                       size_t inOffset,
+                       size_t inSize,
+                       uint32_t tsMarker);
+    bool getSeqInfo();
+    // TODO:This is not the right place for colorAspects functions. These should
+    // be part of c2-vndk so that they can be accessed by all video plugins
+    // until then, make them feel at home
+    bool colorAspectsDiffer(const ColorAspects &a, const ColorAspects &b);
+    void updateFinalColorAspects(
+            const ColorAspects &otherAspects, const ColorAspects &preferredAspects);
+    status_t handleColorAspectsChange();
+    c2_status_t ensureDecoderState(const std::shared_ptr<C2BlockPool> &pool);
+    void finishWork(uint64_t index, const std::unique_ptr<C2Work> &work);
+    status_t setFlushMode();
+    c2_status_t drainInternal(
+            uint32_t drainMode,
+            const std::shared_ptr<C2BlockPool> &pool,
+            const std::unique_ptr<C2Work> &work);
+    status_t resetDecoder();
+    void resetPlugin();
+    status_t deleteDecoder();
+    status_t reInitDecoder();
+
+    // TODO:This is not the right place for this enum. These should
+    // be part of c2-vndk so that they can be accessed by all video plugins
+    // until then, make them feel at home
+    enum {
+        kNotSupported,
+        kPreferBitstream,
+        kPreferContainer,
+    };
+
+    iv_obj_t *mDecHandle;
+    iv_mem_rec_t *mMemRecords;
+    size_t mNumMemRecords;
+    std::shared_ptr<C2GraphicBlock> mOutBlock;
+    uint8_t *mOutBufferDrain;
+
+    size_t mNumCores;
+    IV_COLOR_FORMAT_T mIvColorformat;
+
+    uint32_t mWidth;
+    uint32_t mHeight;
+    uint32_t mStride;
+    bool mSignalledOutputEos;
+    bool mSignalledError;
+
+    // ColorAspects
+    Mutex mColorAspectsLock;
+    int mPreference;
+    ColorAspects mDefaultColorAspects;
+    ColorAspects mBitstreamColorAspects;
+    ColorAspects mFinalColorAspects;
+    bool mUpdateColorAspects;
+
+    // profile
+    struct timeval mTimeStart;
+    struct timeval mTimeEnd;
+#ifdef FILE_DUMP_ENABLE
+    char mInFile[200];
+#endif /* FILE_DUMP_ENABLE */
+
+    DISALLOW_EVIL_CONSTRUCTORS(C2SoftMpeg2Dec);
+};
+
+}  // namespace android
+
+#endif  // C2_SOFT_MPEG2_DEC_H_
diff --git a/media/libstagefright/codecs/on2/dec/Android.bp b/media/libstagefright/codecs/on2/dec/Android.bp
index 8a9399a..03f0c05 100644
--- a/media/libstagefright/codecs/on2/dec/Android.bp
+++ b/media/libstagefright/codecs/on2/dec/Android.bp
@@ -37,3 +37,82 @@
     },
     compile_multilib: "32",
 }
+
+cc_library_shared {
+    name: "libstagefright_soft_c2vp9dec",
+//    vendor_available: true,
+//    vndk: {
+//        enabled: true,
+//    },
+
+    srcs: ["C2SoftVpx.cpp"],
+
+    static_libs: ["libvpx"],
+
+    shared_libs: [
+        "liblog",
+        "libutils",
+        "libstagefright_codec2",
+        "libstagefright_codec2_vndk",
+        "libstagefright_foundation",
+        "libstagefright_simple_c2component",
+    ],
+
+    cflags: [
+        "-DVP9",
+        "-Wall",
+        "-Werror",
+    ],
+
+    sanitize: {
+        misc_undefined: [
+            "signed-integer-overflow",
+            "unsigned-integer-overflow",
+        ],
+        cfi: true,
+        diag: {
+            cfi: true,
+        },
+    },
+
+    ldflags: ["-Wl,-Bsymbolic"],
+}
+
+cc_library_shared {
+    name: "libstagefright_soft_c2vp8dec",
+//    vendor_available: true,
+//    vndk: {
+//        enabled: true,
+//    },
+
+    srcs: ["C2SoftVpx.cpp"],
+
+    static_libs: ["libvpx"],
+
+    shared_libs: [
+        "liblog",
+        "libutils",
+        "libstagefright_codec2",
+        "libstagefright_codec2_vndk",
+        "libstagefright_foundation",
+        "libstagefright_simple_c2component",
+    ],
+
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+
+    sanitize: {
+        misc_undefined: [
+            "signed-integer-overflow",
+            "unsigned-integer-overflow",
+        ],
+        cfi: true,
+        diag: {
+            cfi: true,
+        },
+    },
+
+    ldflags: ["-Wl,-Bsymbolic"],
+}
diff --git a/media/libstagefright/codecs/on2/dec/C2SoftVpx.cpp b/media/libstagefright/codecs/on2/dec/C2SoftVpx.cpp
new file mode 100644
index 0000000..8528f26
--- /dev/null
+++ b/media/libstagefright/codecs/on2/dec/C2SoftVpx.cpp
@@ -0,0 +1,411 @@
+/*
+ * 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
+#define LOG_TAG "C2SoftVpx"
+#include <utils/Log.h>
+
+#include "C2SoftVpx.h"
+
+#include <C2PlatformSupport.h>
+#include <SimpleC2Interface.h>
+
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AUtils.h>
+#include <media/stagefright/foundation/MediaDefs.h>
+
+namespace android {
+
+#ifdef VP9
+constexpr char kComponentName[] = "c2.google.vp9.decoder";
+#else
+constexpr char kComponentName[] = "c2.google.vp8.decoder";
+#endif
+
+static std::shared_ptr<C2ComponentInterface> BuildIntf(
+        const char *name, c2_node_id_t id,
+        std::function<void(C2ComponentInterface*)> deleter =
+            std::default_delete<C2ComponentInterface>()) {
+    return SimpleC2Interface::Builder(name, id, deleter)
+            .inputFormat(C2FormatCompressed)
+            .outputFormat(C2FormatVideo)
+            .inputMediaType(
+#ifdef VP9
+                    MEDIA_MIMETYPE_VIDEO_VP9
+#else
+                    MEDIA_MIMETYPE_VIDEO_VP8
+#endif
+            )
+            .outputMediaType(MEDIA_MIMETYPE_VIDEO_RAW)
+            .build();
+}
+
+C2SoftVpx::C2SoftVpx(const char *name, c2_node_id_t id)
+    : SimpleC2Component(BuildIntf(name, id)),
+      mCodecCtx(nullptr) {
+}
+
+C2SoftVpx::~C2SoftVpx() {
+    onRelease();
+}
+
+c2_status_t C2SoftVpx::onInit() {
+    status_t err = initDecoder();
+    return err == OK ? C2_OK : C2_CORRUPTED;
+}
+
+c2_status_t C2SoftVpx::onStop() {
+    (void) onFlush_sm();
+    destroyDecoder();
+
+    mSignalledError = false;
+    mSignalledOutputEos = false;
+
+    return C2_OK;
+}
+
+void C2SoftVpx::onReset() {
+    (void) onStop();
+    (void) initDecoder();
+}
+
+void C2SoftVpx::onRelease() {
+    destroyDecoder();
+}
+
+c2_status_t C2SoftVpx::onFlush_sm() {
+    if (mFrameParallelMode) {
+        // Flush decoder by passing nullptr data ptr and 0 size.
+        // Ideally, this should never fail.
+        if (vpx_codec_decode(mCodecCtx, nullptr, 0, nullptr, 0)) {
+            ALOGE("Failed to flush on2 decoder.");
+            return C2_CORRUPTED;
+        }
+    }
+
+    // Drop all the decoded frames in decoder.
+    vpx_codec_iter_t iter = nullptr;
+    while (vpx_codec_get_frame(mCodecCtx, &iter)) {
+    }
+
+    mSignalledError = false;
+    mSignalledOutputEos = false;
+    return C2_OK;
+}
+
+static int GetCPUCoreCount() {
+    int cpuCoreCount = 1;
+#if defined(_SC_NPROCESSORS_ONLN)
+    cpuCoreCount = sysconf(_SC_NPROCESSORS_ONLN);
+#else
+    // _SC_NPROC_ONLN must be defined...
+    cpuCoreCount = sysconf(_SC_NPROC_ONLN);
+#endif
+    CHECK(cpuCoreCount >= 1);
+    ALOGV("Number of CPU cores: %d", cpuCoreCount);
+    return cpuCoreCount;
+}
+
+status_t C2SoftVpx::initDecoder() {
+#ifdef VP9
+    mMode = MODE_VP9;
+#else
+    mMode = MODE_VP8;
+#endif
+
+    mWidth = 320;
+    mHeight = 240;
+    mFrameParallelMode = false;
+    mSignalledOutputEos = false;
+    mSignalledError = false;
+
+    mCodecCtx = new vpx_codec_ctx_t;
+
+    vpx_codec_dec_cfg_t cfg;
+    memset(&cfg, 0, sizeof(vpx_codec_dec_cfg_t));
+    cfg.threads = GetCPUCoreCount();
+
+    vpx_codec_flags_t flags;
+    memset(&flags, 0, sizeof(vpx_codec_flags_t));
+    if (mFrameParallelMode) flags |= VPX_CODEC_USE_FRAME_THREADING;
+
+    vpx_codec_err_t vpx_err;
+    if ((vpx_err = vpx_codec_dec_init(
+                 mCodecCtx, mMode == MODE_VP8 ? &vpx_codec_vp8_dx_algo : &vpx_codec_vp9_dx_algo,
+                 &cfg, flags))) {
+        ALOGE("on2 decoder failed to initialize. (%d)", vpx_err);
+        return UNKNOWN_ERROR;
+    }
+
+    return OK;
+}
+
+status_t C2SoftVpx::destroyDecoder() {
+    if  (mCodecCtx) {
+        vpx_codec_destroy(mCodecCtx);
+        delete mCodecCtx;
+        mCodecCtx = nullptr;
+    }
+
+    return OK;
+}
+
+void fillEmptyWork(const std::unique_ptr<C2Work> &work) {
+    uint32_t flags = 0;
+    if (work->input.flags & C2FrameData::FLAG_END_OF_STREAM) {
+        flags |= C2FrameData::FLAG_END_OF_STREAM;
+        ALOGV("signalling eos");
+    }
+    work->worklets.front()->output.flags = (C2FrameData::flags_t)flags;
+    work->worklets.front()->output.buffers.clear();
+    work->worklets.front()->output.ordinal = work->input.ordinal;
+    work->workletsProcessed = 1u;
+}
+
+void C2SoftVpx::finishWork(uint64_t index, const std::unique_ptr<C2Work> &work,
+                           const std::shared_ptr<C2GraphicBlock> &block) {
+    std::shared_ptr<C2Buffer> buffer = createGraphicBuffer(block,
+                                                           C2Rect(mWidth, mHeight));
+    auto fillWork = [buffer, index](const std::unique_ptr<C2Work> &work) {
+        uint32_t flags = 0;
+        if ((work->input.flags & C2FrameData::FLAG_END_OF_STREAM) &&
+                (c2_cntr64_t(index) == work->input.ordinal.frameIndex)) {
+            flags |= C2FrameData::FLAG_END_OF_STREAM;
+            ALOGV("signalling eos");
+        }
+        work->worklets.front()->output.flags = (C2FrameData::flags_t)flags;
+        work->worklets.front()->output.buffers.clear();
+        work->worklets.front()->output.buffers.push_back(buffer);
+        work->worklets.front()->output.ordinal = work->input.ordinal;
+        work->workletsProcessed = 1u;
+    };
+    if (work && c2_cntr64_t(index) == work->input.ordinal.frameIndex) {
+        fillWork(work);
+    } else {
+        finish(index, fillWork);
+    }
+}
+
+void C2SoftVpx::process(
+        const std::unique_ptr<C2Work> &work,
+        const std::shared_ptr<C2BlockPool> &pool) {
+    work->result = C2_OK;
+    work->workletsProcessed = 0u;
+    if (mSignalledError || mSignalledOutputEos) {
+        work->result = C2_BAD_VALUE;
+        return;
+    }
+
+    const C2ConstLinearBlock inBuffer = work->input.buffers[0]->data().linearBlocks().front();
+    size_t inOffset = inBuffer.offset();
+    size_t inSize = inBuffer.size();
+    C2ReadView rView = work->input.buffers[0]->data().linearBlocks().front().map().get();
+    if (inSize && rView.error()) {
+        ALOGE("read view map failed %d", rView.error());
+        work->result = C2_CORRUPTED;
+        return;
+    }
+
+    bool codecConfig = ((work->input.flags & C2FrameData::FLAG_CODEC_CONFIG) !=0);
+    bool eos = ((work->input.flags & C2FrameData::FLAG_END_OF_STREAM) != 0);
+
+    ALOGV("in buffer attr. size %zu timestamp %d frameindex %d, flags %x",
+          inSize, (int)work->input.ordinal.timestamp.peeku(),
+          (int)work->input.ordinal.frameIndex.peeku(), work->input.flags);
+
+    // Software VP9 Decoder does not need the Codec Specific Data (CSD)
+    // (specified in http://www.webmproject.org/vp9/profiles/). Ignore it if
+    // it was passed.
+    if (codecConfig) {
+        // Ignore CSD buffer for VP9.
+        if (mMode == MODE_VP9) {
+            fillEmptyWork(work);
+            return;
+        } else {
+            // Tolerate the CSD buffer for VP8. This is a workaround
+            // for b/28689536. continue
+            ALOGW("WARNING: Got CSD buffer for VP8. Continue");
+        }
+    }
+
+    uint8_t *bitstream = const_cast<uint8_t *>(rView.data() + inOffset);
+    int64_t frameIndex = work->input.ordinal.frameIndex.peekll();
+
+    if (inSize) {
+        vpx_codec_err_t err = vpx_codec_decode(
+                mCodecCtx, bitstream, inSize, &frameIndex, 0);
+        if (err != VPX_CODEC_OK) {
+            ALOGE("on2 decoder failed to decode frame. err: %d", err);
+            work->result = C2_CORRUPTED;
+            mSignalledError = true;
+            return;
+        }
+    }
+
+    (void)outputBuffer(pool, work);
+
+    if (eos) {
+        drainInternal(DRAIN_COMPONENT_WITH_EOS, pool, work);
+        mSignalledOutputEos = true;
+    } else if (!inSize) {
+        fillEmptyWork(work);
+    }
+}
+
+static void copyOutputBufferToYV12Frame(uint8_t *dst,
+        const uint8_t *srcY, const uint8_t *srcU, const uint8_t *srcV,
+        size_t srcYStride, size_t srcUStride, size_t srcVStride,
+        uint32_t width, uint32_t height, int32_t bpp) {
+    size_t dstYStride = align(width, 16) * bpp ;
+    size_t dstUVStride = align(dstYStride / 2, 16);
+    uint8_t *dstStart = dst;
+
+    for (size_t i = 0; i < height; ++i) {
+         memcpy(dst, srcY, width * bpp);
+         srcY += srcYStride;
+         dst += dstYStride;
+    }
+
+    dst = dstStart + dstYStride * height;
+    for (size_t i = 0; i < height / 2; ++i) {
+         memcpy(dst, srcV, width / 2 * bpp);
+         srcV += srcVStride;
+         dst += dstUVStride;
+    }
+
+    dst = dstStart + (dstYStride * height) + (dstUVStride * height / 2);
+    for (size_t i = 0; i < height / 2; ++i) {
+         memcpy(dst, srcU, width / 2 * bpp);
+         srcU += srcUStride;
+         dst += dstUVStride;
+    }
+}
+
+bool C2SoftVpx::outputBuffer(
+        const std::shared_ptr<C2BlockPool> &pool,
+        const std::unique_ptr<C2Work> &work)
+{
+    if (!(work && pool)) return false;
+
+    vpx_codec_iter_t iter = nullptr;
+    vpx_image_t *img = vpx_codec_get_frame(mCodecCtx, &iter);
+
+    if (!img) return false;
+
+    mWidth = img->d_w;
+    mHeight = img->d_h;
+
+    CHECK(img->fmt == VPX_IMG_FMT_I420 || img->fmt == VPX_IMG_FMT_I42016);
+    int32_t bpp = 1;
+    if (img->fmt == VPX_IMG_FMT_I42016) {
+        bpp = 2;
+    }
+
+    std::shared_ptr<C2GraphicBlock> block;
+    uint32_t format = HAL_PIXEL_FORMAT_YV12;
+    C2MemoryUsage usage = { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE };
+    c2_status_t err = pool->fetchGraphicBlock(align(mWidth, 16) * bpp, mHeight, format, usage, &block);
+    if (err != C2_OK) {
+        ALOGE("fetchGraphicBlock for Output failed with status %d", err);
+        work->result = err;
+        return false;
+    }
+
+    C2GraphicView wView = block->map().get();
+    if (wView.error()) {
+        ALOGE("graphic view map failed %d", wView.error());
+        work->result = C2_CORRUPTED;
+        return false;
+    }
+
+    ALOGV("provided (%dx%d) required (%dx%d), out frameindex %d",
+           block->width(), block->height(), mWidth, mHeight, (int)*(int64_t *)img->user_priv);
+
+    uint8_t *dst = const_cast<uint8_t *>(wView.data()[C2PlanarLayout::PLANE_Y]);
+    size_t srcYStride = img->stride[VPX_PLANE_Y];
+    size_t srcUStride = img->stride[VPX_PLANE_U];
+    size_t srcVStride = img->stride[VPX_PLANE_V];
+    const uint8_t *srcY = (const uint8_t *)img->planes[VPX_PLANE_Y];
+    const uint8_t *srcU = (const uint8_t *)img->planes[VPX_PLANE_U];
+    const uint8_t *srcV = (const uint8_t *)img->planes[VPX_PLANE_V];
+    copyOutputBufferToYV12Frame(dst, srcY, srcU, srcV,
+                                srcYStride, srcUStride, srcVStride, mWidth, mHeight, bpp);
+
+    finishWork(*(int64_t *)img->user_priv, work, std::move(block));
+    return true;
+}
+
+c2_status_t C2SoftVpx::drainInternal(
+        uint32_t drainMode,
+        const std::shared_ptr<C2BlockPool> &pool,
+        const std::unique_ptr<C2Work> &work) {
+    if (drainMode == NO_DRAIN) {
+        ALOGW("drain with NO_DRAIN: no-op");
+        return C2_OK;
+    }
+    if (drainMode == DRAIN_CHAIN) {
+        ALOGW("DRAIN_CHAIN not supported");
+        return C2_OMITTED;
+    }
+
+    while ((outputBuffer(pool, work))) {
+    }
+
+    if (drainMode == DRAIN_COMPONENT_WITH_EOS &&
+            work && work->workletsProcessed == 0u) {
+        fillEmptyWork(work);
+    }
+
+    return C2_OK;
+}
+c2_status_t C2SoftVpx::drain(
+        uint32_t drainMode,
+        const std::shared_ptr<C2BlockPool> &pool) {
+    return drainInternal(drainMode, pool, nullptr);
+}
+
+class C2SoftVpxFactory : public C2ComponentFactory {
+public:
+    virtual c2_status_t createComponent(
+            c2_node_id_t id,
+            std::shared_ptr<C2Component>* const component,
+            std::function<void(C2Component*)> deleter) override {
+        *component = std::shared_ptr<C2Component>(new C2SoftVpx(kComponentName, id), deleter);
+        return C2_OK;
+    }
+
+    virtual c2_status_t createInterface(
+            c2_node_id_t id,
+            std::shared_ptr<C2ComponentInterface>* const interface,
+            std::function<void(C2ComponentInterface*)> deleter) override {
+        *interface = BuildIntf(kComponentName, id, deleter);
+        return C2_OK;
+    }
+
+    virtual ~C2SoftVpxFactory() override = default;
+};
+
+}  // namespace android
+
+extern "C" ::C2ComponentFactory* CreateCodec2Factory() {
+    ALOGV("in %s", __func__);
+    return new ::android::C2SoftVpxFactory();
+}
+
+extern "C" void DestroyCodec2Factory(::C2ComponentFactory* factory) {
+    ALOGV("in %s", __func__);
+    delete factory;
+}
diff --git a/media/libstagefright/codecs/on2/dec/C2SoftVpx.h b/media/libstagefright/codecs/on2/dec/C2SoftVpx.h
new file mode 100644
index 0000000..b5d4e21
--- /dev/null
+++ b/media/libstagefright/codecs/on2/dec/C2SoftVpx.h
@@ -0,0 +1,76 @@
+/*
+ * 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 C2_SOFT_VPX_H_
+#define C2_SOFT_VPX_H_
+
+#include <SimpleC2Component.h>
+
+#include <media/stagefright/foundation/ABase.h>
+
+#include "vpx/vpx_decoder.h"
+#include "vpx/vp8dx.h"
+
+namespace android {
+
+struct C2SoftVpx : public SimpleC2Component {
+    C2SoftVpx(const char *name, c2_node_id_t id);
+    virtual ~C2SoftVpx();
+
+    // From SimpleC2Component
+    c2_status_t onInit() override;
+    c2_status_t onStop() override;
+    void onReset() override;
+    void onRelease() override;
+    c2_status_t onFlush_sm() override;
+    void process(
+            const std::unique_ptr<C2Work> &work,
+            const std::shared_ptr<C2BlockPool> &pool) override;
+    c2_status_t drain(
+            uint32_t drainMode,
+            const std::shared_ptr<C2BlockPool> &pool) override;
+ private:
+    enum {
+        MODE_VP8,
+        MODE_VP9,
+    } mMode;
+
+    vpx_codec_ctx_t *mCodecCtx;
+    bool mFrameParallelMode;  // Frame parallel is only supported by VP9 decoder.
+
+    uint32_t mWidth;
+    uint32_t mHeight;
+    bool mSignalledOutputEos;
+    bool mSignalledError;
+
+    status_t initDecoder();
+    status_t destroyDecoder();
+    void finishWork(uint64_t index, const std::unique_ptr<C2Work> &work,
+                    const std::shared_ptr<C2GraphicBlock> &block);
+    bool outputBuffer(
+            const std::shared_ptr<C2BlockPool> &pool,
+            const std::unique_ptr<C2Work> &work);
+    c2_status_t drainInternal(
+            uint32_t drainMode,
+            const std::shared_ptr<C2BlockPool> &pool,
+            const std::unique_ptr<C2Work> &work);
+
+    DISALLOW_EVIL_CONSTRUCTORS(C2SoftVpx);
+};
+
+}  // namespace android
+
+#endif  // C2_SOFT_VPX_H_
diff --git a/media/libstagefright/codecs/opus/dec/Android.bp b/media/libstagefright/codecs/opus/dec/Android.bp
index 43318f2..38d72e6 100644
--- a/media/libstagefright/codecs/opus/dec/Android.bp
+++ b/media/libstagefright/codecs/opus/dec/Android.bp
@@ -1,4 +1,40 @@
 cc_library_shared {
+    name: "libstagefright_soft_c2opusdec",
+//    vendor_available: true,
+//    vndk: {
+//        enabled: true,
+//    },
+
+    srcs: ["C2SoftOpus.cpp"],
+
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+
+    sanitize: {
+        misc_undefined: [
+            "signed-integer-overflow",
+            "unsigned-integer-overflow",
+        ],
+        cfi: true,
+        diag: {
+            cfi: true,
+        },
+    },
+
+    shared_libs: [
+        "liblog",
+        "libutils",
+        "libstagefright_codec2",
+        "libstagefright_codec2_vndk",
+        "libstagefright_foundation",
+        "libstagefright_simple_c2component",
+        "libopus",
+    ],
+}
+
+cc_library_shared {
     name: "libstagefright_soft_opusdec",
     vendor_available: true,
     vndk: {
diff --git a/media/libstagefright/codecs/opus/dec/C2SoftOpus.cpp b/media/libstagefright/codecs/opus/dec/C2SoftOpus.cpp
new file mode 100644
index 0000000..47fb6de
--- /dev/null
+++ b/media/libstagefright/codecs/opus/dec/C2SoftOpus.cpp
@@ -0,0 +1,448 @@
+/*
+ * 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
+#define LOG_TAG "C2SoftOpus"
+#include <utils/Log.h>
+
+#include "C2SoftOpus.h"
+
+#include <C2PlatformSupport.h>
+#include <SimpleC2Interface.h>
+
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/MediaDefs.h>
+
+extern "C" {
+    #include <opus.h>
+    #include <opus_multistream.h>
+}
+
+namespace android {
+
+constexpr char kComponentName[] = "c2.google.opus.decoder";
+
+static std::shared_ptr<C2ComponentInterface> BuildIntf(
+        const char *name, c2_node_id_t id,
+        std::function<void(C2ComponentInterface*)> deleter =
+            std::default_delete<C2ComponentInterface>()) {
+    return SimpleC2Interface::Builder(name, id, deleter)
+            .inputFormat(C2FormatCompressed)
+            .outputFormat(C2FormatAudio)
+            .inputMediaType(MEDIA_MIMETYPE_AUDIO_OPUS)
+            .outputMediaType(MEDIA_MIMETYPE_AUDIO_RAW)
+            .build();
+}
+
+C2SoftOpus::C2SoftOpus(const char *name, c2_node_id_t id)
+    : SimpleC2Component(BuildIntf(name, id)),
+      mDecoder(nullptr) {
+}
+
+C2SoftOpus::~C2SoftOpus() {
+    onRelease();
+}
+
+c2_status_t C2SoftOpus::onInit() {
+    status_t err = initDecoder();
+    return err == OK ? C2_OK : C2_NO_MEMORY;
+}
+
+c2_status_t C2SoftOpus::onStop() {
+    if (mDecoder) {
+        opus_multistream_decoder_destroy(mDecoder);
+        mDecoder = nullptr;
+    }
+    memset(&mHeader, 0, sizeof(mHeader));
+    mCodecDelay = 0;
+    mSeekPreRoll = 0;
+    mSamplesToDiscard = 0;
+    mInputBufferCount = 0;
+    mSignalledError = false;
+    mSignalledOutputEos = false;
+
+    return C2_OK;
+}
+
+void C2SoftOpus::onReset() {
+    (void)onStop();
+}
+
+void C2SoftOpus::onRelease() {
+    if (mDecoder) {
+        opus_multistream_decoder_destroy(mDecoder);
+        mDecoder = nullptr;
+    }
+}
+
+status_t C2SoftOpus::initDecoder() {
+    memset(&mHeader, 0, sizeof(mHeader));
+    mCodecDelay = 0;
+    mSeekPreRoll = 0;
+    mSamplesToDiscard = 0;
+    mInputBufferCount = 0;
+    mSignalledError = false;
+    mSignalledOutputEos = false;
+
+    return OK;
+}
+
+c2_status_t C2SoftOpus::onFlush_sm() {
+    if (mDecoder) {
+        opus_multistream_decoder_ctl(mDecoder, OPUS_RESET_STATE);
+        mSamplesToDiscard = mSeekPreRoll;
+        mSignalledOutputEos = false;
+    }
+    return C2_OK;
+}
+
+c2_status_t C2SoftOpus::drain(
+        uint32_t drainMode,
+        const std::shared_ptr<C2BlockPool> &pool) {
+    (void) pool;
+    if (drainMode == NO_DRAIN) {
+        ALOGW("drain with NO_DRAIN: no-op");
+        return C2_OK;
+    }
+    if (drainMode == DRAIN_CHAIN) {
+        ALOGW("DRAIN_CHAIN not supported");
+        return C2_OMITTED;
+    }
+
+    return C2_OK;
+}
+
+static void fillEmptyWork(const std::unique_ptr<C2Work> &work) {
+    work->worklets.front()->output.flags = work->input.flags;
+    work->worklets.front()->output.buffers.clear();
+    work->worklets.front()->output.ordinal = work->input.ordinal;
+    work->workletsProcessed = 1u;
+}
+
+static uint16_t ReadLE16(const uint8_t *data, size_t data_size,
+                         uint32_t read_offset) {
+    if (read_offset + 1 > data_size)
+        return 0;
+    uint16_t val;
+    val = data[read_offset];
+    val |= data[read_offset + 1] << 8;
+    return val;
+}
+
+static const int kRate = 48000;
+
+// Opus uses Vorbis channel mapping, and Vorbis channel mapping specifies
+// mappings for up to 8 channels. This information is part of the Vorbis I
+// Specification:
+// http://www.xiph.org/vorbis/doc/Vorbis_I_spec.html
+static const int kMaxChannels = 8;
+
+// Maximum packet size used in Xiph's opusdec.
+static const int kMaxOpusOutputPacketSizeSamples = 960 * 6;
+
+// Default audio output channel layout. Used to initialize |stream_map| in
+// OpusHeader, and passed to opus_multistream_decoder_create() when the header
+// does not contain mapping information. The values are valid only for mono and
+// stereo output: Opus streams with more than 2 channels require a stream map.
+static const int kMaxChannelsWithDefaultLayout = 2;
+static const uint8_t kDefaultOpusChannelLayout[kMaxChannelsWithDefaultLayout] = { 0, 1 };
+
+// Parses Opus Header. Header spec: http://wiki.xiph.org/OggOpus#ID_Header
+static bool ParseOpusHeader(const uint8_t *data, size_t data_size,
+                            OpusHeader* header) {
+    // Size of the Opus header excluding optional mapping information.
+    const size_t kOpusHeaderSize = 19;
+
+    // Offset to the channel count byte in the Opus header.
+    const size_t kOpusHeaderChannelsOffset = 9;
+
+    // Offset to the pre-skip value in the Opus header.
+    const size_t kOpusHeaderSkipSamplesOffset = 10;
+
+    // Offset to the gain value in the Opus header.
+    const size_t kOpusHeaderGainOffset = 16;
+
+    // Offset to the channel mapping byte in the Opus header.
+    const size_t kOpusHeaderChannelMappingOffset = 18;
+
+    // Opus Header contains a stream map. The mapping values are in the header
+    // beyond the always present |kOpusHeaderSize| bytes of data. The mapping
+    // data contains stream count, coupling information, and per channel mapping
+    // values:
+    //   - Byte 0: Number of streams.
+    //   - Byte 1: Number coupled.
+    //   - Byte 2: Starting at byte 2 are |header->channels| uint8 mapping
+    //             values.
+    const size_t kOpusHeaderNumStreamsOffset = kOpusHeaderSize;
+    const size_t kOpusHeaderNumCoupledOffset = kOpusHeaderNumStreamsOffset + 1;
+    const size_t kOpusHeaderStreamMapOffset = kOpusHeaderNumStreamsOffset + 2;
+
+    if (data_size < kOpusHeaderSize) {
+        ALOGE("Header size is too small.");
+        return false;
+    }
+    header->channels = *(data + kOpusHeaderChannelsOffset);
+    if (header->channels <= 0 || header->channels > kMaxChannels) {
+        ALOGE("Invalid Header, wrong channel count: %d", header->channels);
+        return false;
+    }
+
+    header->skip_samples = ReadLE16(data,
+                                    data_size,
+                                    kOpusHeaderSkipSamplesOffset);
+
+    header->gain_db = static_cast<int16_t>(ReadLE16(data,
+                                                    data_size,
+                                                    kOpusHeaderGainOffset));
+
+    header->channel_mapping = *(data + kOpusHeaderChannelMappingOffset);
+    if (!header->channel_mapping) {
+        if (header->channels > kMaxChannelsWithDefaultLayout) {
+            ALOGE("Invalid Header, missing stream map.");
+            return false;
+        }
+        header->num_streams = 1;
+        header->num_coupled = header->channels > 1;
+        header->stream_map[0] = 0;
+        header->stream_map[1] = 1;
+        return true;
+    }
+    if (data_size < kOpusHeaderStreamMapOffset + header->channels) {
+        ALOGE("Invalid stream map; insufficient data for current channel "
+              "count: %d", header->channels);
+        return false;
+    }
+    header->num_streams = *(data + kOpusHeaderNumStreamsOffset);
+    header->num_coupled = *(data + kOpusHeaderNumCoupledOffset);
+    if (header->num_streams + header->num_coupled != header->channels) {
+        ALOGE("Inconsistent channel mapping.");
+        return false;
+    }
+    for (int i = 0; i < header->channels; ++i)
+        header->stream_map[i] = *(data + kOpusHeaderStreamMapOffset + i);
+    return true;
+}
+
+// Convert nanoseconds to number of samples.
+static uint64_t ns_to_samples(uint64_t ns, int rate) {
+    return static_cast<double>(ns) * rate / 1000000000;
+}
+
+void C2SoftOpus::process(
+        const std::unique_ptr<C2Work> &work,
+        const std::shared_ptr<C2BlockPool> &pool) {
+    work->result = C2_OK;
+    work->workletsProcessed = 0u;
+    if (mSignalledError || mSignalledOutputEos) {
+        work->result = C2_BAD_VALUE;
+        return;
+    }
+
+    const C2ConstLinearBlock inBuffer = work->input.buffers[0]->data().linearBlocks().front();
+    bool eos = ((work->input.flags & C2FrameData::FLAG_END_OF_STREAM) != 0);
+    size_t inOffset = inBuffer.offset();
+    size_t inSize = inBuffer.size();
+    C2ReadView rView = work->input.buffers[0]->data().linearBlocks().front().map().get();
+    if (inSize && rView.error()) {
+        ALOGE("read view map failed %d", rView.error());
+        work->result = C2_CORRUPTED;
+        return;
+    }
+    if (inSize == 0) {
+        fillEmptyWork(work);
+        if (eos) {
+            mSignalledOutputEos = true;
+            ALOGV("signalled EOS");
+        }
+        return;
+    }
+
+    ALOGV("in buffer attr. size %zu timestamp %d frameindex %d", inSize,
+          (int)work->input.ordinal.timestamp.peeku(), (int)work->input.ordinal.frameIndex.peeku());
+    const uint8_t *data = rView.data() + inOffset;
+    if (mInputBufferCount < 3) {
+        if (mInputBufferCount == 0) {
+            if (!ParseOpusHeader(data, inSize, &mHeader)) {
+                ALOGE("Encountered error while Parsing Opus Header.");
+                mSignalledError = true;
+                work->result = C2_CORRUPTED;
+                return;
+            }
+            uint8_t channel_mapping[kMaxChannels] = {0};
+            if (mHeader.channels <= kMaxChannelsWithDefaultLayout) {
+                memcpy(&channel_mapping,
+                       kDefaultOpusChannelLayout,
+                       kMaxChannelsWithDefaultLayout);
+            } else {
+                memcpy(&channel_mapping,
+                       mHeader.stream_map,
+                       mHeader.channels);
+            }
+            int status = OPUS_INVALID_STATE;
+            mDecoder = opus_multistream_decoder_create(kRate,
+                                                       mHeader.channels,
+                                                       mHeader.num_streams,
+                                                       mHeader.num_coupled,
+                                                       channel_mapping,
+                                                       &status);
+            if (!mDecoder || status != OPUS_OK) {
+                ALOGE("opus_multistream_decoder_create failed status = %s",
+                      opus_strerror(status));
+                mSignalledError = true;
+                work->result = C2_CORRUPTED;
+                return;
+            }
+            status = opus_multistream_decoder_ctl(mDecoder,
+                                                  OPUS_SET_GAIN(mHeader.gain_db));
+            if (status != OPUS_OK) {
+                ALOGE("Failed to set OPUS header gain; status = %s",
+                      opus_strerror(status));
+                mSignalledError = true;
+                work->result = C2_CORRUPTED;
+                return;
+            }
+        } else {
+            if (inSize < 8) {
+                ALOGE("Input sample size is too small.");
+                mSignalledError = true;
+                work->result = C2_CORRUPTED;
+                return;
+            }
+            int64_t samples = ns_to_samples( *(reinterpret_cast<int64_t*>
+                              (const_cast<uint8_t *> (data))), kRate);
+            if (mInputBufferCount == 1) {
+                mCodecDelay = samples;
+                mSamplesToDiscard = mCodecDelay;
+            }
+            else {
+                mSeekPreRoll = samples;
+            }
+        }
+
+        ++mInputBufferCount;
+        fillEmptyWork(work);
+        if (eos) {
+            mSignalledOutputEos = true;
+            ALOGV("signalled EOS");
+        }
+        return;
+    }
+
+    // Ignore CSD re-submissions.
+    if ((work->input.flags & C2FrameData::FLAG_CODEC_CONFIG)) {
+        fillEmptyWork(work);
+        return;
+    }
+
+    // When seeking to zero, |mCodecDelay| samples has to be discarded
+    // instead of |mSeekPreRoll| samples (as we would when seeking to any
+    // other timestamp).
+    if (work->input.ordinal.timestamp.peeku() == 0) mSamplesToDiscard = mCodecDelay;
+
+    std::shared_ptr<C2LinearBlock> block;
+    C2MemoryUsage usage = { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE };
+    c2_status_t err = pool->fetchLinearBlock(
+                          kMaxNumSamplesPerBuffer * kMaxChannels * sizeof(int16_t),
+                          usage, &block);
+    if (err != C2_OK) {
+        ALOGE("fetchLinearBlock for Output failed with status %d", err);
+        work->result = C2_NO_MEMORY;
+        return;
+    }
+    C2WriteView wView = block->map().get();
+    if (wView.error()) {
+        ALOGE("write view map failed %d", wView.error());
+        work->result = C2_CORRUPTED;
+        return;
+    }
+
+    int numSamples = opus_multistream_decode(mDecoder,
+                                             data,
+                                             inSize,
+                                             reinterpret_cast<int16_t *> (wView.data()),
+                                             kMaxOpusOutputPacketSizeSamples,
+                                             0);
+    if (numSamples < 0) {
+        ALOGE("opus_multistream_decode returned numSamples %d", numSamples);
+        numSamples = 0;
+        mSignalledError = true;
+        work->result = C2_CORRUPTED;
+        return;
+    }
+
+    int outOffset = 0;
+    if (mSamplesToDiscard > 0) {
+        if (mSamplesToDiscard > numSamples) {
+            mSamplesToDiscard -= numSamples;
+            numSamples = 0;
+        } else {
+            numSamples -= mSamplesToDiscard;
+            outOffset = mSamplesToDiscard * sizeof(int16_t) * mHeader.channels;
+            mSamplesToDiscard = 0;
+        }
+    }
+
+    if (numSamples) {
+        int outSize = numSamples * sizeof(int16_t) * mHeader.channels;
+        ALOGV("out buffer attr. offset %d size %d ", outOffset, outSize);
+
+        work->worklets.front()->output.flags = work->input.flags;
+        work->worklets.front()->output.buffers.clear();
+        work->worklets.front()->output.buffers.push_back(createLinearBuffer(block, outOffset, outSize));
+        work->worklets.front()->output.ordinal = work->input.ordinal;
+        work->workletsProcessed = 1u;
+    } else {
+        fillEmptyWork(work);
+        block.reset();
+    }
+    if (eos) {
+        mSignalledOutputEos = true;
+        ALOGV("signalled EOS");
+    }
+}
+
+class C2SoftOpusDecFactory : public C2ComponentFactory {
+public:
+    virtual c2_status_t createComponent(
+            c2_node_id_t id,
+            std::shared_ptr<C2Component>* const component,
+            std::function<void(C2Component*)> deleter) override {
+        *component = std::shared_ptr<C2Component>(new C2SoftOpus(kComponentName, id), deleter);
+        return C2_OK;
+    }
+
+    virtual c2_status_t createInterface(
+            c2_node_id_t id,
+            std::shared_ptr<C2ComponentInterface>* const interface,
+            std::function<void(C2ComponentInterface*)> deleter) override {
+        *interface = BuildIntf(kComponentName, id, deleter);
+        return C2_OK;
+    }
+
+    virtual ~C2SoftOpusDecFactory() override = default;
+};
+
+}  // namespace android
+
+extern "C" ::C2ComponentFactory* CreateCodec2Factory() {
+    ALOGV("in %s", __func__);
+    return new ::android::C2SoftOpusDecFactory();
+}
+
+extern "C" void DestroyCodec2Factory(::C2ComponentFactory* factory) {
+    ALOGV("in %s", __func__);
+    delete factory;
+}
diff --git a/media/libstagefright/codecs/opus/dec/C2SoftOpus.h b/media/libstagefright/codecs/opus/dec/C2SoftOpus.h
new file mode 100644
index 0000000..70ad2de
--- /dev/null
+++ b/media/libstagefright/codecs/opus/dec/C2SoftOpus.h
@@ -0,0 +1,76 @@
+/*
+ * 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 C2_SOFT_OPUS_H_
+#define C2_SOFT_OPUS_H_
+
+#include <SimpleC2Component.h>
+
+#include <media/stagefright/foundation/ABase.h>
+
+struct OpusMSDecoder;
+
+namespace android {
+
+struct OpusHeader {
+  int channels;
+  int skip_samples;
+  int channel_mapping;
+  int num_streams;
+  int num_coupled;
+  int16_t gain_db;
+  uint8_t stream_map[8];
+};
+
+struct C2SoftOpus : public SimpleC2Component {
+    C2SoftOpus(const char *name, c2_node_id_t id);
+    virtual ~C2SoftOpus();
+
+    // From SimpleC2Component
+    c2_status_t onInit() override;
+    c2_status_t onStop() override;
+    void onReset() override;
+    void onRelease() override;
+    c2_status_t onFlush_sm() override;
+    void process(
+            const std::unique_ptr<C2Work> &work,
+            const std::shared_ptr<C2BlockPool> &pool) override;
+    c2_status_t drain(
+            uint32_t drainMode,
+            const std::shared_ptr<C2BlockPool> &pool) override;
+private:
+    enum {
+        kMaxNumSamplesPerBuffer = 960 * 6
+    };
+
+    OpusMSDecoder *mDecoder;
+    OpusHeader mHeader;
+
+    int64_t mCodecDelay;
+    int64_t mSeekPreRoll;
+    int64_t mSamplesToDiscard;
+    size_t mInputBufferCount;
+    bool mSignalledError;
+    bool mSignalledOutputEos;
+
+    status_t initDecoder();
+
+    DISALLOW_EVIL_CONSTRUCTORS(C2SoftOpus);
+};
+
+}  // namespace android
+
+#endif  // C2_SOFT_OPUS_H_
diff --git a/media/libstagefright/flac/dec/FLACDecoder.cpp b/media/libstagefright/flac/dec/FLACDecoder.cpp
index 8c7137c..e0e9211 100644
--- a/media/libstagefright/flac/dec/FLACDecoder.cpp
+++ b/media/libstagefright/flac/dec/FLACDecoder.cpp
@@ -220,9 +220,10 @@
 }
 
 // static
-sp<FLACDecoder> FLACDecoder::Create() {
-    sp<FLACDecoder> decoder = new FLACDecoder();
-    if (decoder->init() != OK) {
+FLACDecoder *FLACDecoder::Create() {
+    FLACDecoder *decoder = new (std::nothrow) FLACDecoder();
+    if (decoder == NULL || decoder->init() != OK) {
+        delete decoder;
         return NULL;
     }
     return decoder;
diff --git a/media/libstagefright/flac/dec/FLACDecoder.h b/media/libstagefright/flac/dec/FLACDecoder.h
index 36282a8..1a33cae 100644
--- a/media/libstagefright/flac/dec/FLACDecoder.h
+++ b/media/libstagefright/flac/dec/FLACDecoder.h
@@ -26,14 +26,14 @@
 namespace android {
 
 // packet based FLAC decoder, wrapps libFLAC stream decoder.
-class FLACDecoder : public RefBase {
+class FLACDecoder {
 
 public:
     enum {
         kMaxChannels = 8,
     };
 
-    static sp<FLACDecoder> Create();
+    static FLACDecoder *Create();
 
     FLAC__StreamMetadata_StreamInfo getStreamInfo() const {
         return mStreamInfo;
@@ -43,10 +43,10 @@
     status_t decodeOneFrame(const uint8_t *inBuffer, size_t inBufferLen,
             short *outBuffer, size_t *outBufferLen);
     void flush();
+    virtual ~FLACDecoder();
 
 protected:
     FLACDecoder();
-    virtual ~FLACDecoder() override;
 
 private:
     // stream properties
diff --git a/media/libstagefright/include/CCodecBufferChannel.h b/media/libstagefright/include/CCodecBufferChannel.h
index eb3255f..51eee10 100644
--- a/media/libstagefright/include/CCodecBufferChannel.h
+++ b/media/libstagefright/include/CCodecBufferChannel.h
@@ -88,7 +88,7 @@
      * Start queueing buffers to the component. This object should never queue
      * buffers before this call.
      */
-    void start(const sp<AMessage> &inputFormat, const sp<AMessage> &outputFormat);
+    status_t start(const sp<AMessage> &inputFormat, const sp<AMessage> &outputFormat);
 
     /**
      * Stop queueing buffers to the component. This object should never queue
diff --git a/media/libstagefright/include/Codec2Buffer.h b/media/libstagefright/include/Codec2Buffer.h
index 9766b41..eeb889d 100644
--- a/media/libstagefright/include/Codec2Buffer.h
+++ b/media/libstagefright/include/Codec2Buffer.h
@@ -20,6 +20,7 @@
 
 #include <C2Buffer.h>
 
+#include <media/hardware/VideoAPI.h>
 #include <media/MediaCodecBuffer.h>
 
 namespace android {
@@ -109,6 +110,11 @@
 public:
     /**
      * Allocate a new LinearBufferBlock wrapping around C2LinearBlock object.
+     *
+     * \param   format  mandatory buffer format for MediaCodecBuffer
+     * \param   block   C2LinearBlock object to wrap around.
+     * \return          LinearBlockBuffer object with writable mapping.
+     *                  nullptr if unsuccessful.
      */
     static sp<LinearBlockBuffer> Allocate(
             const sp<AMessage> &format, const std::shared_ptr<C2LinearBlock> &block);
@@ -137,6 +143,11 @@
 public:
     /**
      * Allocate a new ConstLinearBlockBuffer wrapping around C2Buffer object.
+     *
+     * \param   format  mandatory buffer format for MediaCodecBuffer
+     * \param   buffer  linear C2Buffer object to wrap around.
+     * \return          ConstLinearBlockBuffer object with readable mapping.
+     *                  nullptr if unsuccessful.
      */
     static sp<ConstLinearBlockBuffer> Allocate(
             const sp<AMessage> &format, const std::shared_ptr<C2Buffer> &buffer);
@@ -156,6 +167,110 @@
     std::shared_ptr<C2Buffer> mBufferRef;
 };
 
+/**
+ * MediaCodecBuffer implementation wraps around C2GraphicBlock.
+ *
+ * This object exposes the underlying bits via accessor APIs and "image-data"
+ * metadata, created automatically at allocation time.
+ */
+class GraphicBlockBuffer : public Codec2Buffer {
+public:
+    /**
+     * Allocate a new GraphicBlockBuffer wrapping around C2GraphicBlock object.
+     * If |block| is not in good color formats, it allocates YV12 local buffer
+     * and copies the content over at asC2Buffer().
+     *
+     * \param   format  mandatory buffer format for MediaCodecBuffer
+     * \param   block   C2GraphicBlock object to wrap around.
+     * \param   alloc   a function to allocate backing ABuffer if needed.
+     * \return          GraphicBlockBuffer object with writable mapping.
+     *                  nullptr if unsuccessful.
+     */
+    static sp<GraphicBlockBuffer> Allocate(
+            const sp<AMessage> &format,
+            const std::shared_ptr<C2GraphicBlock> &block,
+            std::function<sp<ABuffer>(size_t)> alloc);
+
+    std::shared_ptr<C2Buffer> asC2Buffer() override;
+
+    virtual ~GraphicBlockBuffer() = default;
+
+private:
+    GraphicBlockBuffer(
+            const sp<AMessage> &format,
+            const sp<ABuffer> &buffer,
+            C2GraphicView &&view,
+            const std::shared_ptr<C2GraphicBlock> &block,
+            const sp<ABuffer> &imageData,
+            bool wrapped);
+    GraphicBlockBuffer() = delete;
+
+    inline MediaImage2 *imageData() { return (MediaImage2 *)mImageData->data(); }
+
+    C2GraphicView mView;
+    std::shared_ptr<C2GraphicBlock> mBlock;
+    sp<ABuffer> mImageData;
+    const bool mWrapped;
+};
+
+/**
+ * MediaCodecBuffer implementation wraps around graphic C2Buffer object.
+ *
+ * This object exposes the underlying bits via accessor APIs and "image-data"
+ * metadata, created automatically at allocation time.
+ */
+class ConstGraphicBlockBuffer : public Codec2Buffer {
+public:
+    /**
+     * Allocate a new ConstGraphicBlockBuffer wrapping around C2Buffer object.
+     * If |buffer| is not in good color formats, it allocates YV12 local buffer
+     * and copies the content of |buffer| over to expose.
+     *
+     * \param   format  mandatory buffer format for MediaCodecBuffer
+     * \param   buffer  graphic C2Buffer object to wrap around.
+     * \param   alloc   a function to allocate backing ABuffer if needed.
+     * \return          ConstGraphicBlockBuffer object with readable mapping.
+     *                  nullptr if unsuccessful.
+     */
+    static sp<ConstGraphicBlockBuffer> Allocate(
+            const sp<AMessage> &format,
+            const std::shared_ptr<C2Buffer> &buffer,
+            std::function<sp<ABuffer>(size_t)> alloc);
+
+    /**
+     * Allocate a new ConstGraphicBlockBuffer which allocates YV12 local buffer
+     * and copies the content of |buffer| over to expose.
+     *
+     * \param   format  mandatory buffer format for MediaCodecBuffer
+     * \param   alloc   a function to allocate backing ABuffer if needed.
+     * \return          ConstGraphicBlockBuffer object with no wrapping buffer.
+     */
+    static sp<ConstGraphicBlockBuffer> AllocateEmpty(
+            const sp<AMessage> &format,
+            std::function<sp<ABuffer>(size_t)> alloc);
+
+    std::shared_ptr<C2Buffer> asC2Buffer() override;
+    bool canCopy(const std::shared_ptr<C2Buffer> &buffer) const override;
+    bool copy(const std::shared_ptr<C2Buffer> &buffer) override;
+
+    virtual ~ConstGraphicBlockBuffer() = default;
+
+private:
+    ConstGraphicBlockBuffer(
+            const sp<AMessage> &format,
+            const sp<ABuffer> &aBuffer,
+            std::unique_ptr<const C2GraphicView> &&view,
+            const std::shared_ptr<C2Buffer> &buffer,
+            const sp<ABuffer> &imageData,
+            bool wrapped);
+    ConstGraphicBlockBuffer() = delete;
+
+    sp<ABuffer> mImageData;
+    std::unique_ptr<const C2GraphicView> mView;
+    std::shared_ptr<C2Buffer> mBufferRef;
+    const bool mWrapped;
+};
+
 }  // namespace android
 
 #endif  // CODEC2_BUFFER_H_
diff --git a/media/libstagefright/include/media/stagefright/ACodec.h b/media/libstagefright/include/media/stagefright/ACodec.h
index fa22003..1a5304b 100644
--- a/media/libstagefright/include/media/stagefright/ACodec.h
+++ b/media/libstagefright/include/media/stagefright/ACodec.h
@@ -253,6 +253,7 @@
 
     sp<AMessage> mLastOutputFormat;
     bool mIsVideo;
+    bool mIsImage;
     bool mIsEncoder;
     bool mFatalError;
     bool mShutdownInProgress;
@@ -489,11 +490,12 @@
     status_t setupMPEG4EncoderParameters(const sp<AMessage> &msg);
     status_t setupH263EncoderParameters(const sp<AMessage> &msg);
     status_t setupAVCEncoderParameters(const sp<AMessage> &msg);
-    status_t setupHEVCEncoderParameters(const sp<AMessage> &msg);
+    status_t setupHEVCEncoderParameters(const sp<AMessage> &msg, sp<AMessage> &outputFormat);
     status_t setupVPXEncoderParameters(const sp<AMessage> &msg, sp<AMessage> &outputFormat);
 
     status_t verifySupportForProfileAndLevel(int32_t profile, int32_t level);
 
+    status_t configureImageGrid(const sp<AMessage> &msg, sp<AMessage> &outputFormat);
     status_t configureBitrate(
             OMX_VIDEO_CONTROLRATETYPE bitrateMode, int32_t bitrate, int32_t quality = 0);
     void configureEncoderLatency(const sp<AMessage> &msg);
diff --git a/media/libstagefright/include/media/stagefright/CCodec.h b/media/libstagefright/include/media/stagefright/CCodec.h
index 3a2670d..078b03e 100644
--- a/media/libstagefright/include/media/stagefright/CCodec.h
+++ b/media/libstagefright/include/media/stagefright/CCodec.h
@@ -69,6 +69,8 @@
 private:
     typedef std::chrono::time_point<std::chrono::steady_clock> TimePoint;
 
+    status_t tryAndReportOnError(std::function<status_t()> job);
+
     void initiateStop();
     void initiateRelease(bool sendCallback = true);
 
diff --git a/media/libstagefright/include/media/stagefright/CodecBase.h b/media/libstagefright/include/media/stagefright/CodecBase.h
index 268662f..9606fd9 100644
--- a/media/libstagefright/include/media/stagefright/CodecBase.h
+++ b/media/libstagefright/include/media/stagefright/CodecBase.h
@@ -95,8 +95,11 @@
          *
          * @param componentName the unique name of the component specified in
          *                      MediaCodecList.
+         * @param codecInfo the associated codec info if available.
          */
-        virtual void onComponentAllocated(const char *componentName) = 0;
+        virtual void onComponentAllocated(
+                const char *componentName,
+                const sp<MediaCodecInfo> &codecInfo = nullptr) = 0;
         /**
          * Notify MediaCodec that the underlying component is configured.
          *
diff --git a/media/libstagefright/include/media/stagefright/MediaCodec.h b/media/libstagefright/include/media/stagefright/MediaCodec.h
index e7faea5..ef8de1f 100644
--- a/media/libstagefright/include/media/stagefright/MediaCodec.h
+++ b/media/libstagefright/include/media/stagefright/MediaCodec.h
@@ -184,6 +184,8 @@
 
     status_t getName(AString *componentName) const;
 
+    status_t getCodecInfo(sp<MediaCodecInfo> *codecInfo) const;
+
     status_t getMetrics(MediaAnalyticsItem * &reply);
 
     status_t setParameters(const sp<AMessage> &params);
@@ -248,6 +250,7 @@
         kWhatRequestIDRFrame                = 'ridr',
         kWhatRequestActivityNotification    = 'racN',
         kWhatGetName                        = 'getN',
+        kWhatGetCodecInfo                   = 'gCoI',
         kWhatSetParameters                  = 'setP',
         kWhatSetCallback                    = 'setC',
         kWhatSetNotification                = 'setN',
@@ -308,6 +311,7 @@
     sp<ALooper> mCodecLooper;
     sp<CodecBase> mCodec;
     AString mComponentName;
+    sp<MediaCodecInfo> mCodecInfo;
     sp<AReplyToken> mReplyID;
     uint32_t mFlags;
     status_t mStickyError;
diff --git a/media/libstagefright/include/media/stagefright/MediaCodecList.h b/media/libstagefright/include/media/stagefright/MediaCodecList.h
index d46fe85..bb4da09 100644
--- a/media/libstagefright/include/media/stagefright/MediaCodecList.h
+++ b/media/libstagefright/include/media/stagefright/MediaCodecList.h
@@ -74,8 +74,7 @@
             const char *mime,
             bool createEncoder,
             uint32_t flags,
-            Vector<AString> *matchingCodecs,
-            Vector<AString> *owners = nullptr);
+            Vector<AString> *matchingCodecs);
 
     static bool isSoftwareCodec(const AString &componentName);
 
diff --git a/media/libstagefright/include/media/stagefright/MetaDataUtils.h b/media/libstagefright/include/media/stagefright/MetaDataUtils.h
index 3af2218..d5a8080 100644
--- a/media/libstagefright/include/media/stagefright/MetaDataUtils.h
+++ b/media/libstagefright/include/media/stagefright/MetaDataUtils.h
@@ -23,7 +23,7 @@
 namespace android {
 
 struct ABuffer;
-bool MakeAVCCodecSpecificData(MetaDataBase &meta, const sp<ABuffer> &accessUnit);
+bool MakeAVCCodecSpecificData(MetaDataBase &meta, const uint8_t *data, size_t size);
 bool MakeAACCodecSpecificData(MetaDataBase &meta, unsigned profile, unsigned sampling_freq_index,
         unsigned channel_configuration);
 
diff --git a/media/libstagefright/mpeg2ts/ATSParser.cpp b/media/libstagefright/mpeg2ts/ATSParser.cpp
index bef2db4..5cc5093 100644
--- a/media/libstagefright/mpeg2ts/ATSParser.cpp
+++ b/media/libstagefright/mpeg2ts/ATSParser.cpp
@@ -27,6 +27,7 @@
 #include <binder/MemoryDealer.h>
 #include <cutils/native_handle.h>
 #include <hidlmemory/FrameworkUtils.h>
+#include <media/cas/DescramblerAPI.h>
 #include <media/stagefright/foundation/ABitReader.h>
 #include <media/stagefright/foundation/ABuffer.h>
 #include <media/stagefright/foundation/ADebug.h>
@@ -1387,6 +1388,9 @@
 
     uint32_t sctrl = tsScramblingControl != 0 ?
             tsScramblingControl : pesScramblingControl;
+    if (mQueue->isScrambled()) {
+        sctrl |= DescramblerPlugin::kScrambling_Flag_PesHeader;
+    }
 
     // Perform the 1st pass descrambling if needed
     if (descrambleBytes > 0) {
diff --git a/media/libstagefright/mpeg2ts/AnotherPacketSource.cpp b/media/libstagefright/mpeg2ts/AnotherPacketSource.cpp
index 8488d10..ece0692 100644
--- a/media/libstagefright/mpeg2ts/AnotherPacketSource.cpp
+++ b/media/libstagefright/mpeg2ts/AnotherPacketSource.cpp
@@ -218,7 +218,7 @@
         }
 
         sp<ABuffer> mpegUserData;
-        if (buffer->meta()->findBuffer("mpegUserData", &mpegUserData) && mpegUserData != NULL) {
+        if (buffer->meta()->findBuffer("mpeg-user-data", &mpegUserData) && mpegUserData != NULL) {
             bufmeta.setData(
                     kKeyMpegUserData, 0, mpegUserData->data(), mpegUserData->size());
         }
diff --git a/media/libstagefright/mpeg2ts/ESQueue.cpp b/media/libstagefright/mpeg2ts/ESQueue.cpp
index 59fba1a..0fa9fcb 100644
--- a/media/libstagefright/mpeg2ts/ESQueue.cpp
+++ b/media/libstagefright/mpeg2ts/ESQueue.cpp
@@ -634,7 +634,7 @@
 
         if (mFormat == NULL) {
             mFormat = new MetaData;
-            if (!MakeAVCCodecSpecificData(*mFormat, accessUnit)) {
+            if (!MakeAVCCodecSpecificData(*mFormat, accessUnit->data(), accessUnit->size())) {
                 mFormat.clear();
             }
         }
@@ -865,6 +865,7 @@
             }
             bits.skipBits(2);  // original_copy, home
 
+            mFormat = new MetaData;
             MakeAACCodecSpecificData(*mFormat,
                     profile, sampling_freq_index, channel_configuration);
 
@@ -1009,7 +1010,7 @@
         }
         if (mFormat == NULL) {
             mFormat = new MetaData;
-            if (!MakeAVCCodecSpecificData(*mFormat, mBuffer)) {
+            if (!MakeAVCCodecSpecificData(*mFormat, mBuffer->data(), mBuffer->size())) {
                 ALOGW("Creating dummy AVC format for scrambled content");
                 mFormat = new MetaData;
                 mFormat->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_AVC);
@@ -1171,7 +1172,9 @@
 
             if (mFormat == NULL) {
                 mFormat = new MetaData;
-                if (!MakeAVCCodecSpecificData(*mFormat, accessUnit)) {
+                if (!MakeAVCCodecSpecificData(*mFormat,
+                        accessUnit->data(),
+                        accessUnit->size())) {
                     mFormat.clear();
                 }
             }
@@ -1474,7 +1477,7 @@
                                     mpegUserData->data() + i * sizeof(size_t),
                                     &userDataPositions[i], sizeof(size_t));
                         }
-                        accessUnit->meta()->setBuffer("mpegUserData", mpegUserData);
+                        accessUnit->meta()->setBuffer("mpeg-user-data", mpegUserData);
                     }
                 }
 
@@ -1492,7 +1495,9 @@
         const uint8_t *data, size_t size) {
     static const char kStartCode[] = "\x00\x00\x01";
 
-    if (size < 3) {
+    // per ISO/IEC 14496-2 6.2.1, a chunk has a 3-byte prefix + 1-byte start code
+    // we need at least <prefix><start><next prefix> to successfully scan
+    if (size < 3 + 1 + 3) {
         return -EAGAIN;
     }
 
@@ -1500,7 +1505,7 @@
         return -EAGAIN;
     }
 
-    size_t offset = 3;
+    size_t offset = 4;
     while (offset + 2 < size) {
         if (!memcmp(&data[offset], kStartCode, 3)) {
             return offset;
@@ -1551,6 +1556,9 @@
                     state = EXPECT_VISUAL_OBJECT_START;
                 } else {
                     discard = true;
+                    offset += chunkSize;
+                    ALOGW("b/74114680, advance to next chunk");
+                    android_errorWriteLog(0x534e4554, "74114680");
                 }
                 break;
             }
diff --git a/media/libstagefright/omx/OMXUtils.cpp b/media/libstagefright/omx/OMXUtils.cpp
index f597e02..f7b569d 100644
--- a/media/libstagefright/omx/OMXUtils.cpp
+++ b/media/libstagefright/omx/OMXUtils.cpp
@@ -163,6 +163,8 @@
             "audio_decoder.ac3", "audio_encoder.ac3" },
         { MEDIA_MIMETYPE_AUDIO_EAC3,
             "audio_decoder.eac3", "audio_encoder.eac3" },
+        { MEDIA_MIMETYPE_IMAGE_ANDROID_HEIC,
+            "image_decoder.heic", "image_encoder.heic" },
     };
 
     static const size_t kNumMimeToRole =
diff --git a/media/ndk/NdkMediaExtractor.cpp b/media/ndk/NdkMediaExtractor.cpp
index ea43d2e..ac837a3 100644
--- a/media/ndk/NdkMediaExtractor.cpp
+++ b/media/ndk/NdkMediaExtractor.cpp
@@ -43,7 +43,12 @@
 static media_status_t translate_error(status_t err) {
     if (err == OK) {
         return AMEDIA_OK;
+    } else if (err == ERROR_END_OF_STREAM) {
+        return AMEDIA_ERROR_END_OF_STREAM;
+    } else if (err == ERROR_IO) {
+        return AMEDIA_ERROR_IO;
     }
+
     ALOGE("sf error code: %d", err);
     return AMEDIA_ERROR_UNKNOWN;
 }
@@ -411,5 +416,64 @@
     return -1;
 }
 
+EXPORT
+media_status_t AMediaExtractor_getSampleFormat(AMediaExtractor *ex, AMediaFormat *fmt) {
+    if (fmt == NULL) {
+        return AMEDIA_ERROR_INVALID_PARAMETER;
+    }
+
+    sp<MetaData> sampleMeta;
+    status_t err = ex->mImpl->getSampleMeta(&sampleMeta);
+    if (err != OK) {
+        return translate_error(err);
+    }
+
+    sp<AMessage> meta;
+    AMediaFormat_getFormat(fmt, &meta);
+    meta->clear();
+
+    int32_t layerId;
+    if (sampleMeta->findInt32(kKeyTemporalLayerId, &layerId)) {
+        meta->setInt32(AMEDIAFORMAT_KEY_TEMPORAL_LAYER_ID, layerId);
+    }
+
+    size_t trackIndex;
+    err = ex->mImpl->getSampleTrackIndex(&trackIndex);
+    if (err == OK) {
+        meta->setInt32(AMEDIAFORMAT_KEY_TRACK_INDEX, trackIndex);
+        sp<AMessage> trackFormat;
+        AString mime;
+        err = ex->mImpl->getTrackFormat(trackIndex, &trackFormat);
+        if (err == OK
+                && trackFormat != NULL
+                && trackFormat->findString(AMEDIAFORMAT_KEY_MIME, &mime)) {
+            meta->setString(AMEDIAFORMAT_KEY_MIME, mime);
+        }
+    }
+
+    int64_t durationUs;
+    if (sampleMeta->findInt64(kKeyDuration, &durationUs)) {
+        meta->setInt64(AMEDIAFORMAT_KEY_DURATION, durationUs);
+    }
+
+    uint32_t dataType; // unused
+    const void *seiData;
+    size_t seiLength;
+    if (sampleMeta->findData(kKeySEI, &dataType, &seiData, &seiLength)) {
+        sp<ABuffer> sei = ABuffer::CreateAsCopy(seiData, seiLength);;
+        meta->setBuffer(AMEDIAFORMAT_KEY_SEI, sei);
+    }
+
+    const void *mpegUserDataPointer;
+    size_t mpegUserDataLength;
+    if (sampleMeta->findData(
+            kKeyMpegUserData, &dataType, &mpegUserDataPointer, &mpegUserDataLength)) {
+        sp<ABuffer> mpegUserData = ABuffer::CreateAsCopy(mpegUserDataPointer, mpegUserDataLength);
+        meta->setBuffer(AMEDIAFORMAT_KEY_MPEG_USER_DATA, mpegUserData);
+    }
+
+    return AMEDIA_OK;
+}
+
 } // extern "C"
 
diff --git a/media/ndk/NdkMediaFormat.cpp b/media/ndk/NdkMediaFormat.cpp
index b86876b..9bf450c 100644
--- a/media/ndk/NdkMediaFormat.cpp
+++ b/media/ndk/NdkMediaFormat.cpp
@@ -286,7 +286,13 @@
 EXPORT const char* AMEDIAFORMAT_KEY_COLOR_STANDARD = "color-standard";
 EXPORT const char* AMEDIAFORMAT_KEY_COLOR_TRANSFER = "color-transfer";
 EXPORT const char* AMEDIAFORMAT_KEY_COMPLEXITY = "complexity";
+EXPORT const char* AMEDIAFORMAT_KEY_CSD = "csd";
+EXPORT const char* AMEDIAFORMAT_KEY_CSD_0 = "csd-0";
+EXPORT const char* AMEDIAFORMAT_KEY_CSD_1 = "csd-1";
+EXPORT const char* AMEDIAFORMAT_KEY_CSD_2 = "csd-2";
 EXPORT const char* AMEDIAFORMAT_KEY_DISPLAY_CROP = "crop";
+EXPORT const char* AMEDIAFORMAT_KEY_DISPLAY_HEIGHT = "display-height";
+EXPORT const char* AMEDIAFORMAT_KEY_DISPLAY_WIDTH = "display-width";
 EXPORT const char* AMEDIAFORMAT_KEY_DURATION = "durationUs";
 EXPORT const char* AMEDIAFORMAT_KEY_FLAC_COMPRESSION_LEVEL = "flac-compression-level";
 EXPORT const char* AMEDIAFORMAT_KEY_FRAME_RATE = "frame-rate";
@@ -309,6 +315,7 @@
 EXPORT const char* AMEDIAFORMAT_KEY_MAX_INPUT_SIZE = "max-input-size";
 EXPORT const char* AMEDIAFORMAT_KEY_MAX_WIDTH = "max-width";
 EXPORT const char* AMEDIAFORMAT_KEY_MIME = "mime";
+EXPORT const char* AMEDIAFORMAT_KEY_MPEG_USER_DATA = "mpeg-user-data";
 EXPORT const char* AMEDIAFORMAT_KEY_OPERATING_RATE = "operating-rate";
 EXPORT const char* AMEDIAFORMAT_KEY_PCM_ENCODING = "pcm-encoding";
 EXPORT const char* AMEDIAFORMAT_KEY_PRIORITY = "priority";
@@ -317,10 +324,14 @@
 EXPORT const char* AMEDIAFORMAT_KEY_REPEAT_PREVIOUS_FRAME_AFTER = "repeat-previous-frame-after";
 EXPORT const char* AMEDIAFORMAT_KEY_ROTATION = "rotation-degrees";
 EXPORT const char* AMEDIAFORMAT_KEY_SAMPLE_RATE = "sample-rate";
+EXPORT const char* AMEDIAFORMAT_KEY_SEI = "sei";
 EXPORT const char* AMEDIAFORMAT_KEY_SLICE_HEIGHT = "slice-height";
 EXPORT const char* AMEDIAFORMAT_KEY_STRIDE = "stride";
+EXPORT const char* AMEDIAFORMAT_KEY_TEMPORAL_LAYER_ID = "temporal-layer-id";
 EXPORT const char* AMEDIAFORMAT_KEY_TEMPORAL_LAYERING = "ts-schema";
+EXPORT const char* AMEDIAFORMAT_KEY_TIME_US = "timeUs";
 EXPORT const char* AMEDIAFORMAT_KEY_TRACK_ID = "track-id";
+EXPORT const char* AMEDIAFORMAT_KEY_TRACK_INDEX = "track-index";
 EXPORT const char* AMEDIAFORMAT_KEY_WIDTH = "width";
 
 
diff --git a/media/ndk/include/media/NdkMediaError.h b/media/ndk/include/media/NdkMediaError.h
index e48fcbe..13aacc9 100644
--- a/media/ndk/include/media/NdkMediaError.h
+++ b/media/ndk/include/media/NdkMediaError.h
@@ -53,6 +53,8 @@
     AMEDIA_ERROR_INVALID_OBJECT        = AMEDIA_ERROR_BASE - 3,
     AMEDIA_ERROR_INVALID_PARAMETER     = AMEDIA_ERROR_BASE - 4,
     AMEDIA_ERROR_INVALID_OPERATION     = AMEDIA_ERROR_BASE - 5,
+    AMEDIA_ERROR_END_OF_STREAM         = AMEDIA_ERROR_BASE - 6,
+    AMEDIA_ERROR_IO                    = AMEDIA_ERROR_BASE - 7,
 
     AMEDIA_DRM_ERROR_BASE              = -20000,
     AMEDIA_DRM_NOT_PROVISIONED         = AMEDIA_DRM_ERROR_BASE - 1,
diff --git a/media/ndk/include/media/NdkMediaExtractor.h b/media/ndk/include/media/NdkMediaExtractor.h
index 3c9e23d..f7b9cfd 100644
--- a/media/ndk/include/media/NdkMediaExtractor.h
+++ b/media/ndk/include/media/NdkMediaExtractor.h
@@ -203,6 +203,17 @@
  */
 int64_t AMediaExtractor_getCachedDuration(AMediaExtractor *);
 
+/**
+ * Read the current sample's metadata format into |fmt|. Examples of sample metadata are
+ * SEI (supplemental enhancement information) and MPEG user data, both of which can embed
+ * closed-caption data.
+ *
+ * Returns AMEDIA_OK on success or AMEDIA_ERROR_* to indicate failure reason.
+ * Existing key-value pairs in |fmt| would be removed if this API returns AMEDIA_OK.
+ * The contents of |fmt| is undefined if this API returns AMEDIA_ERROR_*.
+ */
+media_status_t AMediaExtractor_getSampleFormat(AMediaExtractor *ex, AMediaFormat *fmt);
+
 #endif /* __ANDROID_API__ >= 28 */
 
 #endif /* __ANDROID_API__ >= 21 */
diff --git a/media/ndk/include/media/NdkMediaFormat.h b/media/ndk/include/media/NdkMediaFormat.h
index b6489c7..1da9197 100644
--- a/media/ndk/include/media/NdkMediaFormat.h
+++ b/media/ndk/include/media/NdkMediaFormat.h
@@ -100,7 +100,13 @@
 extern const char* AMEDIAFORMAT_KEY_COLOR_STANDARD;
 extern const char* AMEDIAFORMAT_KEY_COLOR_TRANSFER;
 extern const char* AMEDIAFORMAT_KEY_COMPLEXITY;
+extern const char* AMEDIAFORMAT_KEY_CSD;
+extern const char* AMEDIAFORMAT_KEY_CSD_0;
+extern const char* AMEDIAFORMAT_KEY_CSD_1;
+extern const char* AMEDIAFORMAT_KEY_CSD_2;
 extern const char* AMEDIAFORMAT_KEY_DISPLAY_CROP;
+extern const char* AMEDIAFORMAT_KEY_DISPLAY_HEIGHT;
+extern const char* AMEDIAFORMAT_KEY_DISPLAY_WIDTH;
 extern const char* AMEDIAFORMAT_KEY_DURATION;
 extern const char* AMEDIAFORMAT_KEY_FLAC_COMPRESSION_LEVEL;
 extern const char* AMEDIAFORMAT_KEY_FRAME_RATE;
@@ -123,6 +129,7 @@
 extern const char* AMEDIAFORMAT_KEY_MAX_INPUT_SIZE;
 extern const char* AMEDIAFORMAT_KEY_MAX_WIDTH;
 extern const char* AMEDIAFORMAT_KEY_MIME;
+extern const char* AMEDIAFORMAT_KEY_MPEG_USER_DATA;
 extern const char* AMEDIAFORMAT_KEY_OPERATING_RATE;
 extern const char* AMEDIAFORMAT_KEY_PCM_ENCODING;
 extern const char* AMEDIAFORMAT_KEY_PRIORITY;
@@ -131,10 +138,14 @@
 extern const char* AMEDIAFORMAT_KEY_REPEAT_PREVIOUS_FRAME_AFTER;
 extern const char* AMEDIAFORMAT_KEY_ROTATION;
 extern const char* AMEDIAFORMAT_KEY_SAMPLE_RATE;
+extern const char* AMEDIAFORMAT_KEY_SEI;
 extern const char* AMEDIAFORMAT_KEY_SLICE_HEIGHT;
 extern const char* AMEDIAFORMAT_KEY_STRIDE;
+extern const char* AMEDIAFORMAT_KEY_TEMPORAL_LAYER_ID;
 extern const char* AMEDIAFORMAT_KEY_TEMPORAL_LAYERING;
+extern const char* AMEDIAFORMAT_KEY_TIME_US;
 extern const char* AMEDIAFORMAT_KEY_TRACK_ID;
+extern const char* AMEDIAFORMAT_KEY_TRACK_INDEX;
 extern const char* AMEDIAFORMAT_KEY_WIDTH;
 
 #endif /* __ANDROID_API__ >= 21 */
diff --git a/media/ndk/libmediandk.map.txt b/media/ndk/libmediandk.map.txt
index 17c1a0d..d8c41d2 100644
--- a/media/ndk/libmediandk.map.txt
+++ b/media/ndk/libmediandk.map.txt
@@ -159,6 +159,7 @@
     AMediaExtractor_getPsshInfo;
     AMediaExtractor_getSampleCryptoInfo;
     AMediaExtractor_getSampleFlags;
+    AMediaExtractor_getSampleFormat;   # introduced=28
     AMediaExtractor_getSampleSize;     # introduced=28
     AMediaExtractor_getSampleTime;
     AMediaExtractor_getSampleTrackIndex;
diff --git a/packages/MediaComponents/Android.mk b/packages/MediaComponents/Android.mk
index 22eea25..19e4b24 100644
--- a/packages/MediaComponents/Android.mk
+++ b/packages/MediaComponents/Android.mk
@@ -32,6 +32,7 @@
 
 # TODO: Use System SDK once public APIs are approved
 # LOCAL_SDK_VERSION := system_current
+LOCAL_PRIVATE_PLATFORM_APIS := true
 
 LOCAL_SRC_FILES := \
     $(call all-java-files-under, src) \
diff --git a/packages/MediaComponents/res/layout/media_controller.xml b/packages/MediaComponents/res/layout/media_controller.xml
index 38f139d..8488c84 100644
--- a/packages/MediaComponents/res/layout/media_controller.xml
+++ b/packages/MediaComponents/res/layout/media_controller.xml
@@ -104,7 +104,7 @@
             <view class="com.android.support.mediarouter.app.MediaRouteButton"
                 android:id="@+id/cast"
                 android:layout_centerVertical="true"
-                android:visibility="visible"
+                android:visibility="gone"
                 style="@style/TitleBarButton" />
         </LinearLayout>
 
@@ -194,6 +194,9 @@
                 android:visibility="gone" />
 
             <ImageButton
+                android:id="@+id/mute"
+                style="@style/BottomBarButton.Mute" />
+            <ImageButton
                 android:id="@+id/subtitle"
                 android:scaleType="fitCenter"
                 style="@style/BottomBarButton.CC" />
@@ -221,12 +224,12 @@
                 android:layout_height="wrap_content" />
 
             <ImageButton
-                android:id="@+id/mute"
-                style="@style/BottomBarButton.Mute" />
-            <ImageButton
                 android:id="@+id/aspect_ratio"
                 style="@style/BottomBarButton.AspectRatio" />
             <ImageButton
+                android:id="@+id/video_quality"
+                style="@style/BottomBarButton.VideoQuality" />
+            <ImageButton
                 android:id="@+id/settings"
                 style="@style/BottomBarButton.Settings" />
             <ImageButton
diff --git a/packages/MediaComponents/res/values/strings.xml b/packages/MediaComponents/res/values/strings.xml
index 5f9c78d..305cef4 100644
--- a/packages/MediaComponents/res/values/strings.xml
+++ b/packages/MediaComponents/res/values/strings.xml
@@ -105,11 +105,27 @@
     </string>
     <!-- Placeholder text indicating that the user can press the button to go to an external website. -->
     <string name="MediaControlView2_ad_text">Visit Advertiser</string>
-    <string name="MediaControlView2_cc_text">Closed caption</string>
+    <string name="MediaControlView2_subtitle_text">Closed caption</string>
+    <string name="MediaControlView2_subtitle_off_text">Off</string>
     <string name="MediaControlView2_audio_track_text">Audio track</string>
+    <string name="MediaControlView2_audio_track_none_text">None</string>
     <string name="MediaControlView2_video_quality_text">Video quality</string>
     <string name="MediaControlView2_video_quality_auto_text">Auto</string>
     <string name="MediaControlView2_playback_speed_text">Playback speed</string>
-    <string name="MediaControlView2_playback_speed_normal_text">Normal</string>
+    <string name="MediaControlView2_playback_speed_0_25x_text">0.25x</string>
+    <string name="MediaControlView2_playback_speed_0_5x_text">0.5x</string>
+    <string name="MediaControlView2_playback_speed_0_75x_text">0.75x</string>
+    <string name="MediaControlView2_playback_speed_1x_text">Normal</string>
+    <string name="MediaControlView2_playback_speed_1_25x_text">1.25x</string>
+    <string name="MediaControlView2_playback_speed_1_5x_text">1.5x</string>
+    <string name="MediaControlView2_playback_speed_2x_text">2x</string>
     <string name="MediaControlView2_help_text">Help &amp; feedback</string>
+    <!-- Text for displaying subtitle track number. -->
+    <string name="MediaControlView2_subtitle_track_number_text">
+        Track <xliff:g id="track_number" example="1">%1$s</xliff:g>
+    </string>
+    <!-- Text for displaying audio track number. -->
+    <string name="MediaControlView2_audio_track_number_text">
+        Track <xliff:g id="audio_number" example="1">%1$s</xliff:g>
+    </string>
 </resources>
diff --git a/packages/MediaComponents/res/values/style.xml b/packages/MediaComponents/res/values/style.xml
index 299f16b..23c7bc9 100644
--- a/packages/MediaComponents/res/values/style.xml
+++ b/packages/MediaComponents/res/values/style.xml
@@ -81,4 +81,8 @@
     <style name="BottomBarButton.Mute">
         <item name="android:src">@drawable/ic_mute</item>
     </style>
+
+    <style name="BottomBarButton.VideoQuality">
+        <item name="android:src">@drawable/ic_high_quality</item>
+    </style>
 </resources>
diff --git a/packages/MediaComponents/runcts.sh b/packages/MediaComponents/runcts.sh
new file mode 100644
index 0000000..0cf0e44
--- /dev/null
+++ b/packages/MediaComponents/runcts.sh
@@ -0,0 +1,212 @@
+#!/bin/bash
+# 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.
+
+# Usage '. runcts.sh'
+
+function _runtest_cts_mediacomponent_usage() {
+  echo 'runtest-cts-MediaComponents [option]: Build, flash device,'
+  echo '              and run subset of CtsMediaTestCases that MediaComponents covers.'
+  echo '          *Warning* This bypasses CTS setup (e.g. download media contents from server)'
+  echo '          For running CTS in official way, use atest or cts-tradefed '
+  echo '     -h|--help: This help'
+  echo '     --skip: Skip build and flash. Just rerun-tests'
+  echo '     --min: Only rebuild tests and updatable library.'
+  echo '     -s [device_id]: Specify a device name to run test against.'
+  echo '                     You can define ${ADBHOST} instead.'
+  echo '     -r [count]: Repeat tests for given count. It will stop when fails.'
+  echo '     --ignore: Keep repeating tests even when it fails.'
+  echo '     -t [test]: Only run the specific test. Can be either a class or a method.'
+}
+
+function runtest-cts-MediaComponents() {
+  # Edit here if you want to support other tests.
+  # List up libs and apks in the media_api needed for tests, and place test target at the last.
+  local TEST_PACKAGE_DIR=("frameworks/av/packages/MediaComponents/test")
+  local TEST_PACKAGE=("android.media.cts")
+  local BUILD_TARGETS=("MediaComponents" "CtsMediaTestCases")
+  # Don't include MediaComponents -- if we simply install it, system server
+  # wouldn't use the installed one.
+  local INSTALL_TARGETS=("CtsMediaTestCases")
+  local TEST_RUNNER="android.support.test.runner.AndroidJUnitRunner"
+  local DEPENDENCIES=("mockito-target-minus-junit4" "android-support-test" "compatibility-device-util")
+  local DEFAULT_TEST_TARGET=""
+  DEFAULT_TEST_TARGET+="android.media.cts.MediaBrowser2Test"
+  DEFAULT_TEST_TARGET+=",android.media.cts.MediaController2Test"
+  DEFAULT_TEST_TARGET+=",android.media.cts.MediaMetadata2Test"
+  DEFAULT_TEST_TARGET+=",android.media.cts.MediaSession2Test"
+  DEFAULT_TEST_TARGET+=",android.media.cts.MediaSession2_PermissionTest"
+  DEFAULT_TEST_TARGET+=",android.media.cts.MediaSessionManager_MediaSession2Test"
+  DEFAULT_TEST_TARGET+=",android.media.cts.SessionToken2Test"
+  if [[ -z "${ANDROID_BUILD_TOP}" ]]; then
+    echo "Needs to lunch a target first"
+    return
+  fi
+
+  local old_path=${OLDPWD}
+  while true; do
+    local OPTION_SKIP="false"
+    local OPTION_MIN="false"
+    local OPTION_REPEAT_COUNT="1"
+    local OPTION_IGNORE="false"
+    local OPTION_TEST_TARGET="${DEFAULT_TEST_TARGET}"
+    local adbhost_local
+    while (( "$#" )); do
+      case "${1}" in
+        -h|--help)
+          _runtest_cts_mediacomponent_usage
+          return
+          ;;
+        --skip)
+          OPTION_SKIP="true"
+          ;;
+        --min)
+          OPTION_MIN="true"
+          ;;
+        -s)
+          shift
+          adbhost_local=${1}
+          ;;
+        -r)
+          shift
+          OPTION_REPEAT_COUNT="${1}"
+          ;;
+        --ignore)
+          OPTION_IGNORE="true"
+          ;;
+        -t)
+          shift
+          OPTION_TEST_TARGET="${1}"
+      esac
+      shift
+    done
+
+    # Build adb command.
+    local adb
+    if [[ -z "${adbhost_local}" ]]; then
+      adbhost_local=${ADBHOST}
+    fi
+    if [[ -z "${adbhost_local}" ]]; then
+      local device_count=$(adb devices | sed '/^[[:space:]]*$/d' | wc -l)
+      if [[ "${device_count}" != "2" ]]; then
+        echo "Too many devices. Specify a device." && break
+      fi
+      adb="adb"
+    else
+      adb="adb -s ${adbhost_local}"
+    fi
+
+    local target_dir="${ANDROID_BUILD_TOP}/${TEST_PACKAGE_DIR}"
+    #local TEST_PACKAGE=$(sed -n 's/^.*\bpackage\b="\([a-z0-9\.]*\)".*$/\1/p' ${target_dir}/AndroidManifest.xml)
+
+    if [[ "${OPTION_SKIP}" != "true" ]]; then
+      # Build dependencies if needed.
+      local dependency
+      local build_dependency=""
+      for dependency in ${DEPENDENCIES[@]}; do
+        if [[ "${dependency}" == "out/"* ]]; then
+          if [[ ! -f ${ANDROID_BUILD_TOP}/${dependency} ]]; then
+            build_dependency="true"
+            break
+          fi
+        else
+          if [[ "$(find ${OUT} -name ${dependency}_intermediates | wc -l)" == "0" ]]; then
+            build_dependency="true"
+            break
+          fi
+        fi
+      done
+      if [[ "${build_dependency}" == "true" ]]; then
+        echo "Building dependencies. Will only print stderr."
+        m ${DEPENDENCIES[@]} -j > /dev/null
+      fi
+
+      # Build test apk and required apk.
+      local build_targets="${BUILD_TARGETS[@]}"
+      if [[ "${OPTION_MIN}" != "true" ]]; then
+        build_targets="${build_targets} droid"
+      fi
+      m ${build_targets} -j || break
+
+      local device_build_type="$(${adb} shell getprop ro.build.type)"
+      if [[ "${device_build_type}" == "user" ]]; then
+        # User build. Cannot adb sync
+        ${adb} reboot bootloader
+        fastboot flashall
+      else
+        ${adb} root
+        local device_verity_mode="$(${adb} shell getprop ro.boot.veritymode)"
+        if [[ "${device_verity_mode}" != "disabled" ]]; then
+          ${adb} disable-verity
+          ${adb} reboot
+          ${adb} wait-for-device || break
+          ${adb} root
+        fi
+        ${adb} remount
+        ${adb} shell stop
+        ${adb} shell setprop log.tag.MediaSessionService DEBUG
+        ${adb} sync
+        ${adb} shell start
+      fi
+      ${adb} wait-for-device || break
+      # Ensure package manager is loaded.
+      # TODO(jaewan): Find better way to wait
+      sleep 15
+
+      # Install apks
+      local install_failed="false"
+      for target in ${INSTALL_TARGETS[@]}; do
+        local apk_path=$(find ${OUT}/system ${OUT}/data -name ${target}.apk)
+        local apk_num=$(find ${OUT}/system ${OUT}/data -name ${target}.apk | wc -l)
+        if [[ "${apk_num}" != "1" ]]; then
+          echo "Cannot locate a ${target}.apk. Found ${apk_num} apks" && break
+        fi
+        echo "Installing ${target}.apk. path=${apk_path}"
+        ${adb} install -r ${apk_path}
+        if [[ "${?}" != "0" ]]; then
+          install_failed="true"
+          break
+        fi
+      done
+      if [[ "${install_failed}" == "true" ]]; then
+        echo "Failed to install. Test wouldn't run."
+        break
+      fi
+    fi
+
+    local test_target=""
+    if [[ -n "${OPTION_TEST_TARGET}" ]]; then
+      test_target="-e class ${OPTION_TEST_TARGET}"
+    fi
+
+    local i
+    local tmpfile=$(tempfile)
+    for ((i=1; i <= ${OPTION_REPEAT_COUNT}; i++)); do
+      echo "Run test ${i}/${OPTION_REPEAT_COUNT}"
+      ${adb} shell am instrument ${test_target} -w ${TEST_PACKAGE}/${TEST_RUNNER} >& ${tmpfile}
+      cat ${tmpfile}
+      if [[ "${OPTION_IGNORE}" != "true" ]]; then
+        if [[ -n "$(grep ${tmpfile} -e 'FAILURE\|crashed')" ]]; then
+          # am instrument doesn't return error code so need to grep result message instead
+          break
+        fi
+      fi
+    done
+    rm ${tmpfile}
+    break
+  done
+}
+
+echo "Following functions are added to your environment:"
+_runtest_cts_mediacomponent_usage
diff --git a/packages/MediaComponents/src/com/android/media/IMediaSession2.aidl b/packages/MediaComponents/src/com/android/media/IMediaSession2.aidl
index 63c0697..34251fb 100644
--- a/packages/MediaComponents/src/com/android/media/IMediaSession2.aidl
+++ b/packages/MediaComponents/src/com/android/media/IMediaSession2.aidl
@@ -46,7 +46,6 @@
     //////////////////////////////////////////////////////////////////////////////////////////////
     // send command
     //////////////////////////////////////////////////////////////////////////////////////////////
-    void sendCommand(IMediaSession2Callback caller, in Bundle command, in Bundle args);
     void sendTransportControlCommand(IMediaSession2Callback caller,
             int commandCode, in Bundle args);
     void sendCustomCommand(IMediaSession2Callback caller, in Bundle command, in Bundle args,
diff --git a/packages/MediaComponents/src/com/android/media/IMediaSession2Callback.aidl b/packages/MediaComponents/src/com/android/media/IMediaSession2Callback.aidl
index 6a03989..3d812f8 100644
--- a/packages/MediaComponents/src/com/android/media/IMediaSession2Callback.aidl
+++ b/packages/MediaComponents/src/com/android/media/IMediaSession2Callback.aidl
@@ -41,8 +41,9 @@
     void onDisconnected();
 
     void onCustomLayoutChanged(in List<Bundle> commandButtonlist);
+    void onAllowedCommandsChanged(in Bundle commands);
 
-    void sendCustomCommand(in Bundle command, in Bundle args, in ResultReceiver receiver);
+    void onCustomCommand(in Bundle command, in Bundle args, in ResultReceiver receiver);
 
     //////////////////////////////////////////////////////////////////////////////////////////////
     // Browser sepcific
diff --git a/packages/MediaComponents/src/com/android/media/MediaBrowser2Impl.java b/packages/MediaComponents/src/com/android/media/MediaBrowser2Impl.java
index 9190dfc..122ebe7 100644
--- a/packages/MediaComponents/src/com/android/media/MediaBrowser2Impl.java
+++ b/packages/MediaComponents/src/com/android/media/MediaBrowser2Impl.java
@@ -19,6 +19,7 @@
 import android.content.Context;
 import android.media.MediaBrowser2;
 import android.media.MediaBrowser2.BrowserCallback;
+import android.media.MediaController2;
 import android.media.MediaItem2;
 import android.media.SessionToken2;
 import android.media.update.MediaBrowser2Provider;
@@ -44,6 +45,10 @@
         mCallback = callback;
     }
 
+    @Override MediaBrowser2 getInstance() {
+        return (MediaBrowser2) super.getInstance();
+    }
+
     @Override
     public void getLibraryRoot_impl(Bundle rootHints) {
         final IMediaSession2 binder = getSessionBinder();
@@ -63,6 +68,10 @@
 
     @Override
     public void subscribe_impl(String parentId, Bundle extras) {
+        if (parentId == null) {
+            throw new IllegalArgumentException("parentId shouldn't be null");
+        }
+
         final IMediaSession2 binder = getSessionBinder();
         if (binder != null) {
             try {
@@ -80,6 +89,10 @@
 
     @Override
     public void unsubscribe_impl(String parentId) {
+        if (parentId == null) {
+            throw new IllegalArgumentException("parentId shouldn't be null");
+        }
+
         final IMediaSession2 binder = getSessionBinder();
         if (binder != null) {
             try {
@@ -165,6 +178,9 @@
         if (TextUtils.isEmpty(query)) {
             throw new IllegalArgumentException("query shouldn't be empty");
         }
+        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 {
@@ -183,39 +199,39 @@
     public void onGetLibraryRootDone(
             final Bundle rootHints, final String rootMediaId, final Bundle rootExtra) {
         getCallbackExecutor().execute(() -> {
-            mCallback.onGetLibraryRootDone(rootHints, rootMediaId, rootExtra);
+            mCallback.onGetLibraryRootDone(getInstance(), rootHints, rootMediaId, rootExtra);
         });
     }
 
     public void onGetItemDone(String mediaId, MediaItem2 item) {
         getCallbackExecutor().execute(() -> {
-            mCallback.onGetItemDone(mediaId, item);
+            mCallback.onGetItemDone(getInstance(), mediaId, item);
         });
     }
 
     public void onGetChildrenDone(String parentId, int page, int pageSize, List<MediaItem2> result,
             Bundle extras) {
         getCallbackExecutor().execute(() -> {
-            mCallback.onGetChildrenDone(parentId, page, pageSize, result, extras);
+            mCallback.onGetChildrenDone(getInstance(), parentId, page, pageSize, result, extras);
         });
     }
 
     public void onSearchResultChanged(String query, int itemCount, Bundle extras) {
         getCallbackExecutor().execute(() -> {
-            mCallback.onSearchResultChanged(query, itemCount, extras);
+            mCallback.onSearchResultChanged(getInstance(), query, itemCount, extras);
         });
     }
 
     public void onGetSearchResultDone(String query, int page, int pageSize, List<MediaItem2> result,
             Bundle extras) {
         getCallbackExecutor().execute(() -> {
-            mCallback.onGetSearchResultDone(query, page, pageSize, result, extras);
+            mCallback.onGetSearchResultDone(getInstance(), query, page, pageSize, result, extras);
         });
     }
 
     public void onChildrenChanged(final String parentId, int itemCount, final Bundle extras) {
         getCallbackExecutor().execute(() -> {
-            mCallback.onChildrenChanged(parentId, itemCount, extras);
+            mCallback.onChildrenChanged(getInstance(), parentId, itemCount, extras);
         });
     }
 }
diff --git a/packages/MediaComponents/src/com/android/media/MediaController2Impl.java b/packages/MediaComponents/src/com/android/media/MediaController2Impl.java
index 4e29d37..96fdda0 100644
--- a/packages/MediaComponents/src/com/android/media/MediaController2Impl.java
+++ b/packages/MediaComponents/src/com/android/media/MediaController2Impl.java
@@ -16,6 +16,8 @@
 
 package com.android.media;
 
+import static android.media.MediaSession2.*;
+
 import android.app.PendingIntent;
 import android.content.ComponentName;
 import android.content.Context;
@@ -39,9 +41,12 @@
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.IBinder;
+import android.os.Process;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
+import android.os.UserHandle;
 import android.support.annotation.GuardedBy;
+import android.text.TextUtils;
 import android.util.Log;
 
 import java.util.ArrayList;
@@ -77,7 +82,7 @@
     @GuardedBy("mLock")
     private PendingIntent mSessionActivity;
     @GuardedBy("mLock")
-    private CommandGroup mCommandGroup;
+    private CommandGroup mAllowedCommands;
 
     // Assignment should be used with the lock hold, but should be used without a lock to prevent
     // potential deadlock.
@@ -125,6 +130,24 @@
             connectToSession(SessionToken2Impl.from(mToken).getSessionBinder());
         } else {
             // Session service
+            if (Process.myUid() == Process.SYSTEM_UID) {
+                // It's system server (MediaSessionService) that wants to monitor session.
+                // Don't bind if able..
+                IMediaSession2 binder = SessionToken2Impl.from(mToken).getSessionBinder();
+                if (binder != null) {
+                    // Use binder in the session token instead of bind by its own.
+                    // Otherwise server will holds the binding to the service *forever* and service
+                    // will never stop.
+                    mServiceConnection = null;
+                    connectToSession(SessionToken2Impl.from(mToken).getSessionBinder());
+                    return;
+                } else if (DEBUG) {
+                    // Should happen only when system server wants to dispatch media key events to
+                    // a dead service.
+                    Log.d(TAG, "System server binds to a session service. Should unbind"
+                            + " immediately after the use.");
+                }
+            }
             mServiceConnection = new SessionServiceConnection();
             connectToService();
         }
@@ -150,8 +173,15 @@
         //    If a service wants to keep running, it should be either foreground service or
         //    bounded service. But there had been request for the feature for system apps
         //    and using bindService() will be better fit with it.
-        // TODO(jaewan): Use bindServiceAsUser()??
-        boolean result = mContext.bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
+        boolean result;
+        if (Process.myUid() == Process.SYSTEM_UID) {
+            // Use bindServiceAsUser() for binding from system service to avoid following warning.
+            // ContextImpl: Calling a method in the system process without a qualified user
+            result = mContext.bindServiceAsUser(intent, mServiceConnection, Context.BIND_AUTO_CREATE,
+                    UserHandle.getUserHandleForUid(mToken.getUid()));
+        } else {
+            result = mContext.bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
+        }
         if (!result) {
             Log.w(TAG, "bind to " + mToken + " failed");
         } else if (DEBUG) {
@@ -197,7 +227,7 @@
             }
         }
         mCallbackExecutor.execute(() -> {
-            mCallback.onDisconnected();
+            mCallback.onDisconnected(mInstance);
         });
     }
 
@@ -221,6 +251,42 @@
         return mInstance;
     }
 
+    // Returns session binder if the controller can send the command.
+    IMediaSession2 getSessionBinderIfAble(int commandCode) {
+        synchronized (mLock) {
+            if (!mAllowedCommands.hasCommand(commandCode)) {
+                // Cannot send because isn't allowed to.
+                Log.w(TAG, "Controller isn't allowed to call command, commandCode="
+                        + commandCode);
+                return null;
+            }
+        }
+        // TODO(jaewan): Should we do this with the lock hold?
+        final IMediaSession2 binder = mSessionBinder;
+        if (binder == null) {
+            // Cannot send because disconnected.
+            Log.w(TAG, "Session is disconnected");
+        }
+        return binder;
+    }
+
+    // Returns session binder if the controller can send the command.
+    IMediaSession2 getSessionBinderIfAble(Command command) {
+        synchronized (mLock) {
+            if (!mAllowedCommands.hasCommand(command)) {
+                Log.w(TAG, "Controller isn't allowed to call command, command=" + command);
+                return null;
+            }
+        }
+        // TODO(jaewan): Should we do this with the lock hold?
+        final IMediaSession2 binder = mSessionBinder;
+        if (binder == null) {
+            // Cannot send because disconnected.
+            Log.w(TAG, "Session is disconnected");
+        }
+        return binder;
+    }
+
     @Override
     public SessionToken2 getSessionToken_impl() {
         return mToken;
@@ -248,12 +314,12 @@
     }
 
     @Override
-    public void skipToPrevious_impl() {
+    public void skipToPreviousItem_impl() {
         sendTransportControlCommand(MediaSession2.COMMAND_CODE_PLAYBACK_SKIP_PREV_ITEM);
     }
 
     @Override
-    public void skipToNext_impl() {
+    public void skipToNextItem_impl() {
         sendTransportControlCommand(MediaSession2.COMMAND_CODE_PLAYBACK_SKIP_NEXT_ITEM);
     }
 
@@ -285,7 +351,7 @@
     @Override
     public void setVolumeTo_impl(int value, int flags) {
         // TODO(hdmoon): sanity check
-        final IMediaSession2 binder = mSessionBinder;
+        final IMediaSession2 binder = getSessionBinderIfAble(COMMAND_CODE_PLAYBACK_SET_VOLUME);
         if (binder != null) {
             try {
                 binder.setVolumeTo(mSessionCallbackStub, value, flags);
@@ -300,7 +366,7 @@
     @Override
     public void adjustVolume_impl(int direction, int flags) {
         // TODO(hdmoon): sanity check
-        final IMediaSession2 binder = mSessionBinder;
+        final IMediaSession2 binder = getSessionBinderIfAble(COMMAND_CODE_PLAYBACK_SET_VOLUME);
         if (binder != null) {
             try {
                 binder.adjustVolume(mSessionCallbackStub, direction, flags);
@@ -314,7 +380,10 @@
 
     @Override
     public void prepareFromUri_impl(Uri uri, Bundle extras) {
-        final IMediaSession2 binder = mSessionBinder;
+        final IMediaSession2 binder = getSessionBinderIfAble(COMMAND_CODE_PREPARE_FROM_URI);
+        if (uri == null) {
+            throw new IllegalArgumentException("uri shouldn't be null");
+        }
         if (binder != null) {
             try {
                 binder.prepareFromUri(mSessionCallbackStub, uri, extras);
@@ -328,7 +397,10 @@
 
     @Override
     public void prepareFromSearch_impl(String query, Bundle extras) {
-        final IMediaSession2 binder = mSessionBinder;
+        final IMediaSession2 binder = getSessionBinderIfAble(COMMAND_CODE_PREPARE_FROM_SEARCH);
+        if (TextUtils.isEmpty(query)) {
+            throw new IllegalArgumentException("query shouldn't be empty");
+        }
         if (binder != null) {
             try {
                 binder.prepareFromSearch(mSessionCallbackStub, query, extras);
@@ -341,8 +413,11 @@
     }
 
     @Override
-    public void prepareMediaId_impl(String mediaId, Bundle extras) {
-        final IMediaSession2 binder = mSessionBinder;
+    public void prepareFromMediaId_impl(String mediaId, Bundle extras) {
+        final IMediaSession2 binder = getSessionBinderIfAble(COMMAND_CODE_PREPARE_FROM_MEDIA_ID);
+        if (mediaId == null) {
+            throw new IllegalArgumentException("mediaId shouldn't be null");
+        }
         if (binder != null) {
             try {
                 binder.prepareFromMediaId(mSessionCallbackStub, mediaId, extras);
@@ -356,7 +431,10 @@
 
     @Override
     public void playFromUri_impl(Uri uri, Bundle extras) {
-        final IMediaSession2 binder = mSessionBinder;
+        final IMediaSession2 binder = getSessionBinderIfAble(COMMAND_CODE_PLAY_FROM_URI);
+        if (uri == null) {
+            throw new IllegalArgumentException("uri shouldn't be null");
+        }
         if (binder != null) {
             try {
                 binder.playFromUri(mSessionCallbackStub, uri, extras);
@@ -370,7 +448,10 @@
 
     @Override
     public void playFromSearch_impl(String query, Bundle extras) {
-        final IMediaSession2 binder = mSessionBinder;
+        final IMediaSession2 binder = getSessionBinderIfAble(COMMAND_CODE_PLAY_FROM_SEARCH);
+        if (TextUtils.isEmpty(query)) {
+            throw new IllegalArgumentException("query shouldn't be empty");
+        }
         if (binder != null) {
             try {
                 binder.playFromSearch(mSessionCallbackStub, query, extras);
@@ -384,7 +465,10 @@
 
     @Override
     public void playFromMediaId_impl(String mediaId, Bundle extras) {
-        final IMediaSession2 binder = mSessionBinder;
+        final IMediaSession2 binder = getSessionBinderIfAble(COMMAND_CODE_PLAY_FROM_MEDIA_ID);
+        if (mediaId == null) {
+            throw new IllegalArgumentException("mediaId shouldn't be null");
+        }
         if (binder != null) {
             try {
                 binder.playFromMediaId(mSessionCallbackStub, mediaId, extras);
@@ -422,8 +506,7 @@
         if (command == null) {
             throw new IllegalArgumentException("command shouldn't be null");
         }
-        // TODO(jaewan): Also check if the command is allowed.
-        final IMediaSession2 binder = mSessionBinder;
+        final IMediaSession2 binder = getSessionBinderIfAble(command);
         if (binder != null) {
             try {
                 binder.sendCustomCommand(mSessionCallbackStub, command.toBundle(), args, cb);
@@ -459,6 +542,9 @@
 
     @Override
     public void seekTo_impl(long pos) {
+        if (pos < 0) {
+            throw new IllegalArgumentException("position shouldn't be negative");
+        }
         Bundle args = new Bundle();
         args.putLong(MediaSession2Stub.ARGUMENT_KEY_POSITION, pos);
         sendTransportControlCommand(MediaSession2.COMMAND_CODE_PLAYBACK_SEEK_TO, args);
@@ -466,12 +552,16 @@
 
     @Override
     public void skipToPlaylistItem_impl(MediaItem2 item) {
+        if (item == null) {
+            throw new IllegalArgumentException("item shouldn't be null");
+        }
+
         // TODO(jaewan): Implement this
         /*
         Bundle args = new Bundle();
         args.putInt(MediaSession2Stub.ARGUMENT_KEY_ITEM_INDEX, item);
         sendTransportControlCommand(
-                MediaSession2.COMMAND_CODE_PLAYBACK_SKIP_TO_PLAYLIST_ITEM, args);
+                MediaSession2.COMMAND_CODE_PLAYLIST_SKIP_TO_PLAYLIST_ITEM, args);
         */
     }
 
@@ -484,17 +574,32 @@
 
     @Override
     public void addPlaylistItem_impl(int index, MediaItem2 item) {
-        // TODO(jaewan): Implement
+        // TODO(jaewan): Implement (b/73149584)
+        if (index < 0) {
+            throw new IllegalArgumentException("index shouldn't be negative");
+        }
+        if (item == null) {
+            throw new IllegalArgumentException("item shouldn't be null");
+        }
     }
 
     @Override
     public void removePlaylistItem_impl(MediaItem2 item) {
-        // TODO(jaewan): Implement
+        // TODO(jaewan): Implement (b/73149584)
+        if (item == null) {
+            throw new IllegalArgumentException("item shouldn't be null");
+        }
     }
 
     @Override
     public void replacePlaylistItem_impl(int index, MediaItem2 item) {
-        // TODO: Implement this
+        // TODO: Implement this (b/73149407)
+        if (index < 0) {
+            throw new IllegalArgumentException("index shouldn't be negative");
+        }
+        if (item == null) {
+            throw new IllegalArgumentException("item shouldn't be null");
+        }
     }
 
     @Override
@@ -514,7 +619,7 @@
     @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");
         }
         Bundle args = new Bundle();
         args.putBundle(MediaSession2Stub.ARGUMENT_KEY_PLAYLIST_PARAMS, params.toBundle());
@@ -546,7 +651,7 @@
     }
 
     @Override
-    public MediaItem2 getCurrentPlaylistItem_impl() {
+    public MediaItem2 getCurrentMediaItem_impl() {
         // TODO(jaewan): Implement
         return null;
     }
@@ -559,7 +664,7 @@
             if (!mInstance.isConnected()) {
                 return;
             }
-            mCallback.onPlaybackStateChanged(state);
+            mCallback.onPlaybackStateChanged(mInstance, state);
         });
     }
 
@@ -571,7 +676,7 @@
             if (!mInstance.isConnected()) {
                 return;
             }
-            mCallback.onPlaylistParamsChanged(params);
+            mCallback.onPlaylistParamsChanged(mInstance, params);
         });
     }
 
@@ -583,42 +688,34 @@
             if (!mInstance.isConnected()) {
                 return;
             }
-            mCallback.onPlaybackInfoChanged(info);
+            mCallback.onPlaybackInfoChanged(mInstance, info);
         });
     }
 
-    void pushPlaylistChanges(final List<Bundle> list) {
-        final List<MediaItem2> playlist = new ArrayList<>();
-        for (int i = 0; i < list.size(); i++) {
-            MediaItem2 item = MediaItem2.fromBundle(mContext, list.get(i));
-            if (item != null) {
-                playlist.add(item);
-            }
-        }
-
+    void pushPlaylistChanges(final List<MediaItem2> playlist) {
         synchronized (mLock) {
             mPlaylist = playlist;
             mCallbackExecutor.execute(() -> {
                 if (!mInstance.isConnected()) {
                     return;
                 }
-                mCallback.onPlaylistChanged(playlist);
+                mCallback.onPlaylistChanged(mInstance, playlist);
             });
         }
     }
 
     // Should be used without a lock to prevent potential deadlock.
     void onConnectedNotLocked(IMediaSession2 sessionBinder,
-            final CommandGroup commandGroup, final PlaybackState2 state, final PlaybackInfo info,
+            final CommandGroup allowedCommands, final PlaybackState2 state, final PlaybackInfo info,
             final PlaylistParams params, final List<MediaItem2> playlist,
             final PendingIntent sessionActivity) {
         if (DEBUG) {
             Log.d(TAG, "onConnectedNotLocked sessionBinder=" + sessionBinder
-                    + ", commands=" + commandGroup);
+                    + ", allowedCommands=" + allowedCommands);
         }
         boolean close = false;
         try {
-            if (sessionBinder == null || commandGroup == null) {
+            if (sessionBinder == null || allowedCommands == null) {
                 // Connection rejected.
                 close = true;
                 return;
@@ -633,7 +730,7 @@
                     close = true;
                     return;
                 }
-                mCommandGroup = commandGroup;
+                mAllowedCommands = allowedCommands;
                 mPlaybackState = state;
                 mPlaybackInfo = info;
                 mPlaylistParams = params;
@@ -657,7 +754,7 @@
                 // Note: We may trigger ControllerCallbacks with the initial values
                 // But it's hard to define the order of the controller callbacks
                 // Only notify about the
-                mCallback.onConnected(commandGroup);
+                mCallback.onConnected(mInstance, allowedCommands);
             });
         } finally {
             if (close) {
@@ -675,13 +772,19 @@
         }
         mCallbackExecutor.execute(() -> {
             // TODO(jaewan): Double check if the controller exists.
-            mCallback.onCustomCommand(command, args, receiver);
+            mCallback.onCustomCommand(mInstance, command, args, receiver);
+        });
+    }
+
+    void onAllowedCommandsChanged(final CommandGroup commands) {
+        mCallbackExecutor.execute(() -> {
+            mCallback.onAllowedCommandsChanged(mInstance, commands);
         });
     }
 
     void onCustomLayoutChanged(final List<CommandButton> layout) {
         mCallbackExecutor.execute(() -> {
-            mCallback.onCustomLayoutChanged(layout);
+            mCallback.onCustomLayoutChanged(mInstance, layout);
         });
     }
 
diff --git a/packages/MediaComponents/src/com/android/media/MediaItem2Impl.java b/packages/MediaComponents/src/com/android/media/MediaItem2Impl.java
index 4c4ef24..039ff8f 100644
--- a/packages/MediaComponents/src/com/android/media/MediaItem2Impl.java
+++ b/packages/MediaComponents/src/com/android/media/MediaItem2Impl.java
@@ -24,6 +24,7 @@
 import android.content.Context;
 import android.media.DataSourceDesc;
 import android.media.MediaItem2;
+import android.media.MediaItem2.Builder;
 import android.media.MediaItem2.Flags;
 import android.media.MediaMetadata2;
 import android.media.update.MediaItem2Provider;
@@ -43,25 +44,22 @@
     private DataSourceDesc mDataSourceDesc;
 
     // From the public API
-    public MediaItem2Impl(Context context, MediaItem2 instance, String mediaId,
-            DataSourceDesc dsd, MediaMetadata2 metadata, @Flags int flags) {
+    public MediaItem2Impl(@NonNull Context context, @NonNull String mediaId,
+            @Nullable DataSourceDesc dsd, @Nullable MediaMetadata2 metadata, @Flags int flags) {
         if (mediaId == null) {
             throw new IllegalArgumentException("mediaId shouldn't be null");
         }
-        if (dsd == null) {
-            throw new IllegalArgumentException("dsd shouldn't be null");
-        }
         if (metadata != null && !TextUtils.equals(mediaId, metadata.getMediaId())) {
             throw new IllegalArgumentException("metadata's id should be matched with the mediaid");
         }
 
         mContext = context;
-        mInstance = instance;
-
         mId = mediaId;
         mDataSourceDesc = dsd;
         mMetadata = metadata;
         mFlags = flags;
+
+        mInstance = new MediaItem2(this);
     }
 
     // Create anonymized version
@@ -157,4 +155,49 @@
     public @Nullable DataSourceDesc getDataSourceDesc_impl() {
         return mDataSourceDesc;
     }
+
+    public static class BuilderImpl implements MediaItem2Provider.BuilderProvider {
+        private Context mContext;
+        private Builder mInstance;
+        private @Flags int mFlags;
+        private String mMediaId;
+        private MediaMetadata2 mMetadata;
+        private DataSourceDesc mDataSourceDesc;
+
+        public BuilderImpl(Context context, Builder instance, int flags) {
+            mContext = context;
+            mInstance = instance;
+            mFlags = flags;
+        }
+
+        @Override
+        public Builder setMediaId_impl(@Nullable String mediaId) {
+            mMediaId = mediaId;
+            return mInstance;
+        }
+
+        @Override
+        public Builder setMetadata_impl(@Nullable MediaMetadata2 metadata) {
+            mMetadata = metadata;
+            return mInstance;
+        }
+
+        @Override
+        public Builder setDataSourceDesc_impl(@Nullable DataSourceDesc dataSourceDesc) {
+            mDataSourceDesc = dataSourceDesc;
+            return mInstance;
+        }
+
+        @Override
+        public MediaItem2 build_impl() {
+            String id = (mMetadata != null)
+                    ? mMetadata.getString(MediaMetadata2.METADATA_KEY_MEDIA_ID) : null;
+            if (id == null) {
+                //  TODO(jaewan): Double check if its sufficient (e.g. Use UUID instead?)
+                id = (mMediaId != null) ? mMediaId : toString();
+            }
+            return new MediaItem2Impl(mContext, id, mDataSourceDesc, mMetadata, mFlags)
+                    .getInstance();
+        }
+    }
 }
diff --git a/packages/MediaComponents/src/com/android/media/MediaLibraryService2Impl.java b/packages/MediaComponents/src/com/android/media/MediaLibraryService2Impl.java
index a21fda6..5351394 100644
--- a/packages/MediaComponents/src/com/android/media/MediaLibraryService2Impl.java
+++ b/packages/MediaComponents/src/com/android/media/MediaLibraryService2Impl.java
@@ -24,6 +24,7 @@
 import android.media.MediaLibraryService2.MediaLibrarySession.Builder;
 import android.media.MediaLibraryService2.MediaLibrarySession.MediaLibrarySessionCallback;
 import android.media.MediaPlayerBase;
+import android.media.MediaPlaylistAgent;
 import android.media.MediaSession2;
 import android.media.MediaSession2.ControllerInfo;
 import android.media.MediaSessionService2;
@@ -67,10 +68,12 @@
     public static class MediaLibrarySessionImpl extends MediaSession2Impl
             implements MediaLibrarySessionProvider {
         public MediaLibrarySessionImpl(Context context,
-                MediaPlayerBase player, String id, VolumeProvider2 volumeProvider,
+                MediaPlayerBase player, String id, MediaPlaylistAgent playlistAgent,
+                VolumeProvider2 volumeProvider,
                 PendingIntent sessionActivity, Executor callbackExecutor,
                 MediaLibrarySessionCallback callback) {
-            super(context, player, id, volumeProvider, sessionActivity, callbackExecutor, callback);
+            super(context, player, id, playlistAgent, volumeProvider, sessionActivity, callbackExecutor,
+                    callback);
             // Don't put any extra initialization here. Here's the reason.
             // System service will recognize this session inside of the super constructor and would
             // connect to this session assuming that initialization is finished. However, if any
@@ -138,8 +141,8 @@
 
         @Override
         public MediaLibrarySession build_impl() {
-            return new MediaLibrarySessionImpl(mContext, mPlayer, mId, mVolumeProvider,
-                    mSessionActivity, mCallbackExecutor, mCallback).getInstance();
+            return new MediaLibrarySessionImpl(mContext, mPlayer, mId, mPlaylistAgent,
+                    mVolumeProvider, mSessionActivity, mCallbackExecutor, mCallback).getInstance();
         }
     }
 
diff --git a/packages/MediaComponents/src/com/android/media/MediaPlaylistAgentImpl.java b/packages/MediaComponents/src/com/android/media/MediaPlaylistAgentImpl.java
new file mode 100644
index 0000000..b3c17f9
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/media/MediaPlaylistAgentImpl.java
@@ -0,0 +1,154 @@
+/*
+ * 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.
+ */
+
+package com.android.media;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.media.MediaItem2;
+import android.media.MediaMetadata2;
+import android.media.MediaPlaylistAgent;
+import android.media.MediaPlaylistAgent.PlaylistEventCallback;
+import android.media.update.MediaPlaylistAgentProvider;
+
+import java.util.List;
+import java.util.concurrent.Executor;
+
+public class MediaPlaylistAgentImpl implements MediaPlaylistAgentProvider {
+    private final Context mContext;
+    private final MediaPlaylistAgent mInstance;
+
+    public MediaPlaylistAgentImpl(Context context, MediaPlaylistAgent instance) {
+        mContext = context;
+        mInstance = instance;
+    }
+
+    final public void registerPlaylistEventCallback_impl(
+            @NonNull @CallbackExecutor Executor executor, @NonNull PlaylistEventCallback callback) {
+        if (executor == null) {
+            throw new IllegalArgumentException("executor shouldn't be null");
+        }
+        if (callback == null) {
+            throw new IllegalArgumentException("callback shouldn't be null");
+        }
+        // TODO(jaewan): implement this (b/74090741)
+    }
+
+    final public void unregisterPlaylistEventCallback_impl(
+            @NonNull PlaylistEventCallback callback) {
+        if (callback == null) {
+            throw new IllegalArgumentException("callback shouldn't be null");
+        }
+        // TODO(jaewan): implement this (b/74090741)
+    }
+
+    final public void notifyPlaylistChanged_impl() {
+        // TODO(jaewan): implement this (b/74090741)
+    }
+
+    final public void notifyPlaylistMetadataChanged_impl() {
+        // TODO(jaewan): implement this (b/74090741)
+    }
+
+    final public void notifyShuffleModeChanged_impl() {
+        // TODO(jaewan): implement this (b/74090741)
+    }
+
+    final public void notifyRepeatModeChanged_impl() {
+        // TODO(jaewan): implement this (b/74090741)
+    }
+
+    public @Nullable List<MediaItem2> getPlaylist_impl() {
+        // TODO(jaewan): implement this (b/74090741)
+        return null;
+    }
+
+    public void setPlaylist_impl(@NonNull List<MediaItem2> list,
+            @Nullable MediaMetadata2 metadata) {
+        if (list == null) {
+            throw new IllegalArgumentException("list shouldn't be null");
+        }
+        // TODO(jaewan): implement this (b/74090741)
+    }
+
+    public @Nullable MediaMetadata2 getPlaylistMetadata_impl() {
+        // TODO(jaewan): implement this (b/74090741)
+        return null;
+    }
+
+    public void updatePlaylistMetadata_impl(@Nullable MediaMetadata2 metadata) {
+        // TODO(jaewan): implement this (b/74090741)
+    }
+
+    public void addPlaylistItem_impl(int index, @NonNull MediaItem2 item) {
+        if (item == null) {
+            throw new IllegalArgumentException("item shouldn't be null");
+        }
+        // TODO(jaewan): implement this (b/74090741)
+    }
+
+    public void removePlaylistItem_impl(@NonNull MediaItem2 item) {
+        if (item == null) {
+            throw new IllegalArgumentException("item shouldn't be null");
+        }
+        // TODO(jaewan): implement this (b/74090741)
+    }
+
+    public void replacePlaylistItem_impl(int index, @NonNull MediaItem2 item) {
+        if (index < 0) {
+            throw new IllegalArgumentException("index can not have a negative value");
+        }
+        if (item == null) {
+            throw new IllegalArgumentException("item shouldn't be null");
+        }
+        // TODO(jaewan): implement this (b/74090741)
+    }
+
+    public void skipToPlaylistItem_impl(@NonNull MediaItem2 item) {
+        if (item == null) {
+            throw new IllegalArgumentException("item shouldn't be null");
+        }
+        // TODO(jaewan): implement this (b/74090741)
+    }
+
+    public void skipToPreviousItem_impl() {
+        // TODO(jaewan): implement this (b/74090741)
+    }
+
+    public void skipToNextItem_impl() {
+        // TODO(jaewan): implement this (b/74090741)
+    }
+
+    public int getRepeatMode_impl() {
+        // TODO(jaewan): implement this (b/74090741)
+        return MediaPlaylistAgent.REPEAT_MODE_NONE;
+    }
+
+    public void setRepeatMode_impl(int repeatMode) {
+        // TODO(jaewan): implement this (b/74090741)
+    }
+
+    public int getShuffleMode_impl() {
+        // TODO(jaewan): implement this (b/74090741)
+        return MediaPlaylistAgent.SHUFFLE_MODE_NONE;
+    }
+
+    public void setShuffleMode_impl(int shuffleMode) {
+        // TODO(jaewan): implement this (b/74090741)
+    }
+}
diff --git a/packages/MediaComponents/src/com/android/media/MediaSession2CallbackStub.java b/packages/MediaComponents/src/com/android/media/MediaSession2CallbackStub.java
index 2178ccd..451368f 100644
--- a/packages/MediaComponents/src/com/android/media/MediaSession2CallbackStub.java
+++ b/packages/MediaComponents/src/com/android/media/MediaSession2CallbackStub.java
@@ -18,6 +18,7 @@
 
 import android.app.PendingIntent;
 import android.content.Context;
+import android.media.MediaController2;
 import android.media.MediaItem2;
 import android.media.MediaSession2.Command;
 import android.media.MediaSession2.CommandButton;
@@ -26,6 +27,7 @@
 import android.media.PlaybackState2;
 import android.os.Bundle;
 import android.os.ResultReceiver;
+import android.text.TextUtils;
 import android.util.Log;
 
 import com.android.media.MediaController2Impl.PlaybackInfoImpl;
@@ -79,7 +81,7 @@
     }
 
     @Override
-    public void onPlaylistChanged(List<Bundle> playlist) throws RuntimeException {
+    public void onPlaylistChanged(List<Bundle> playlistBundle) throws RuntimeException {
         final MediaController2Impl controller;
         try {
             controller = getController();
@@ -87,14 +89,24 @@
             Log.w(TAG, "Don't fail silently here. Highly likely a bug");
             return;
         }
-        if (playlist == null) {
+        if (playlistBundle == null) {
+            Log.w(TAG, "onPlaylistChanged(): Ignoring null playlist");
             return;
         }
+        List<MediaItem2> playlist = new ArrayList<>();
+        for (Bundle bundle : playlistBundle) {
+            MediaItem2 item = MediaItem2.fromBundle(controller.getContext(), bundle);
+            if (item == null) {
+                Log.w(TAG, "onPlaylistChanged(): Ignoring null item in playlist");
+            } else {
+                playlist.add(item);
+            }
+        }
         controller.pushPlaylistChanges(playlist);
     }
 
     @Override
-    public void onPlaylistParamsChanged(Bundle params) throws RuntimeException {
+    public void onPlaylistParamsChanged(Bundle paramsBundle) throws RuntimeException {
         final MediaController2Impl controller;
         try {
             controller = getController();
@@ -102,8 +114,12 @@
             Log.w(TAG, "Don't fail silently here. Highly likely a bug");
             return;
         }
-        controller.pushPlaylistParamsChanges(
-                PlaylistParams.fromBundle(controller.getContext(), params));
+        PlaylistParams params = PlaylistParams.fromBundle(controller.getContext(), paramsBundle);
+        if (params == null) {
+            Log.w(TAG, "onPlaylistParamsChanged(): Ignoring null playlistParams");
+            return;
+        }
+        controller.pushPlaylistParamsChanges(params);
     }
 
     @Override
@@ -118,6 +134,12 @@
             Log.w(TAG, "Don't fail silently here. Highly likely a bug");
             return;
         }
+        MediaController2.PlaybackInfo info =
+                PlaybackInfoImpl.fromBundle(controller.getContext(), playbackInfo);
+        if (info == null) {
+            Log.w(TAG, "onPlaybackInfoChanged(): Ignoring null playbackInfo");
+            return;
+        }
         controller.pushPlaybackInfoChanges(
                 PlaybackInfoImpl.fromBundle(controller.getContext(), playbackInfo));
     }
@@ -125,7 +147,7 @@
     @Override
     public void onConnected(IMediaSession2 sessionBinder, Bundle commandGroup,
             Bundle playbackState, Bundle playbackInfo, Bundle playlistParams, List<Bundle>
-            playlist, PendingIntent sessionActivity) {
+            itemBundleList, PendingIntent sessionActivity) {
         final MediaController2Impl controller = mController.get();
         if (controller == null) {
             if (DEBUG) {
@@ -134,11 +156,14 @@
             return;
         }
         final Context context = controller.getContext();
-        List<MediaItem2> list = new ArrayList<>();
-        for (int i = 0; i < playlist.size(); i++) {
-            MediaItem2 item = MediaItem2.fromBundle(context, playlist.get(i));
-            if (item != null) {
-                list.add(item);
+        List<MediaItem2> itemList = null;
+        if (itemBundleList != null) {
+            itemList = new ArrayList<>();
+            for (int i = 0; i < itemBundleList.size(); i++) {
+                MediaItem2 item = MediaItem2.fromBundle(context, itemBundleList.get(i));
+                if (item != null) {
+                    itemList.add(item);
+                }
             }
         }
         controller.onConnectedNotLocked(sessionBinder,
@@ -146,7 +171,7 @@
                 PlaybackState2.fromBundle(context, playbackState),
                 PlaybackInfoImpl.fromBundle(context, playbackInfo),
                 PlaylistParams.fromBundle(context, playlistParams),
-                list, sessionActivity);
+                itemList, sessionActivity);
     }
 
     @Override
@@ -164,7 +189,7 @@
     @Override
     public void onCustomLayoutChanged(List<Bundle> commandButtonlist) {
         if (commandButtonlist == null) {
-            // Illegal call. Ignore
+            Log.w(TAG, "onCustomLayoutChanged(): Ignoring null commandButtonlist");
             return;
         }
         final MediaController2Impl controller;
@@ -190,7 +215,28 @@
     }
 
     @Override
-    public void sendCustomCommand(Bundle commandBundle, Bundle args, ResultReceiver receiver) {
+    public void onAllowedCommandsChanged(Bundle commandsBundle) {
+        final MediaController2Impl controller;
+        try {
+            controller = getController();
+        } catch (IllegalStateException e) {
+            Log.w(TAG, "Don't fail silently here. Highly likely a bug");
+            return;
+        }
+        if (controller == null) {
+            // TODO(jaewan): Revisit here. Could be a bug
+            return;
+        }
+        CommandGroup commands = CommandGroup.fromBundle(controller.getContext(), commandsBundle);
+        if (commands == null) {
+            Log.w(TAG, "onAllowedCommandsChanged(): Ignoring null commands");
+            return;
+        }
+        controller.onAllowedCommandsChanged(commands);
+    }
+
+    @Override
+    public void onCustomCommand(Bundle commandBundle, Bundle args, ResultReceiver receiver) {
         final MediaController2Impl controller;
         try {
             controller = getController();
@@ -200,6 +246,7 @@
         }
         Command command = Command.fromBundle(controller.getContext(), commandBundle);
         if (command == null) {
+            Log.w(TAG, "onCustomCommand(): Ignoring null command");
             return;
         }
         controller.onCustomCommand(command, args, receiver);
@@ -228,6 +275,10 @@
 
     @Override
     public void onGetItemDone(String mediaId, Bundle itemBundle) throws RuntimeException {
+        if (mediaId == null) {
+            Log.w(TAG, "onGetItemDone(): Ignoring null mediaId");
+            return;
+        }
         final MediaBrowser2Impl browser;
         try {
             browser = getBrowser();
@@ -246,6 +297,10 @@
     @Override
     public void onGetChildrenDone(String parentId, int page, int pageSize,
             List<Bundle> itemBundleList, Bundle extras) throws RuntimeException {
+        if (parentId == null) {
+            Log.w(TAG, "onGetChildrenDone(): Ignoring null parentId");
+            return;
+        }
         final MediaBrowser2Impl browser;
         try {
             browser = getBrowser();
@@ -271,6 +326,10 @@
     @Override
     public void onSearchResultChanged(String query, int itemCount, Bundle extras)
             throws RuntimeException {
+        if (TextUtils.isEmpty(query)) {
+            Log.w(TAG, "onSearchResultChanged(): Ignoring empty query");
+            return;
+        }
         final MediaBrowser2Impl browser;
         try {
             browser = getBrowser();
@@ -288,6 +347,10 @@
     @Override
     public void onGetSearchResultDone(String query, int page, int pageSize,
             List<Bundle> itemBundleList, Bundle extras) throws RuntimeException {
+        if (TextUtils.isEmpty(query)) {
+            Log.w(TAG, "onGetSearchResultDone(): Ignoring empty query");
+            return;
+        }
         final MediaBrowser2Impl browser;
         try {
             browser = getBrowser();
@@ -312,6 +375,10 @@
 
     @Override
     public void onChildrenChanged(String parentId, int itemCount, Bundle extras) {
+        if (parentId == null) {
+            Log.w(TAG, "onChildrenChanged(): Ignoring null parentId");
+            return;
+        }
         final MediaBrowser2Impl browser;
         try {
             browser = getBrowser();
diff --git a/packages/MediaComponents/src/com/android/media/MediaSession2Impl.java b/packages/MediaComponents/src/com/android/media/MediaSession2Impl.java
index 5e7af3b..4c6b0a4 100644
--- a/packages/MediaComponents/src/com/android/media/MediaSession2Impl.java
+++ b/packages/MediaComponents/src/com/android/media/MediaSession2Impl.java
@@ -21,14 +21,12 @@
 import static android.media.SessionToken2.TYPE_SESSION;
 import static android.media.SessionToken2.TYPE_SESSION_SERVICE;
 
-import android.Manifest.permission;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ResolveInfo;
 import android.media.AudioAttributes;
 import android.media.AudioManager;
@@ -39,7 +37,7 @@
 import android.media.MediaMetadata2;
 import android.media.MediaPlayerBase;
 import android.media.MediaPlayerBase.PlayerEventCallback;
-import android.media.MediaPlaylistController;
+import android.media.MediaPlaylistAgent;
 import android.media.MediaSession2;
 import android.media.MediaSession2.Builder;
 import android.media.MediaSession2.Command;
@@ -64,7 +62,6 @@
 import android.support.annotation.GuardedBy;
 import android.text.TextUtils;
 import android.util.ArrayMap;
-import android.util.ArraySet;
 import android.util.Log;
 
 import java.lang.ref.WeakReference;
@@ -108,6 +105,8 @@
     @GuardedBy("mLock")
     private MediaPlayerBase mPlayer;
     @GuardedBy("mLock")
+    private MediaPlaylistAgent mPlaylistAgent;
+    @GuardedBy("mLock")
     private VolumeProvider2 mVolumeProvider;
     @GuardedBy("mLock")
     private PlaybackInfo mPlaybackInfo;
@@ -116,17 +115,18 @@
 
     /**
      * Can be only called by the {@link Builder#build()}.
-     *
      * @param context
      * @param player
      * @param id
+     * @param playlistAgent
      * @param volumeProvider
      * @param sessionActivity
      * @param callbackExecutor
      * @param callback
      */
     public MediaSession2Impl(Context context, MediaPlayerBase player, String id,
-            VolumeProvider2 volumeProvider, PendingIntent sessionActivity,
+            MediaPlaylistAgent playlistAgent, VolumeProvider2 volumeProvider,
+            PendingIntent sessionActivity,
             Executor callbackExecutor, SessionCallback callback) {
         // TODO(jaewan): Keep other params.
         mInstance = createInstance();
@@ -140,6 +140,7 @@
         mSessionActivity = sessionActivity;
         mSessionStub = new MediaSession2Stub(this);
         mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+        mPlaylistAgent = playlistAgent;
 
         // Infer type from the id and package name.
         String libraryService = getServiceName(context, MediaLibraryService2.SERVICE_INTERFACE, id);
@@ -149,10 +150,10 @@
                     + " session services define the same id=" + id);
         } else if (libraryService != null) {
             mSessionToken = new SessionToken2Impl(context, Process.myUid(), TYPE_LIBRARY_SERVICE,
-                    mContext.getPackageName(), libraryService, id, null).getInstance();
+                    mContext.getPackageName(), libraryService, id, mSessionStub).getInstance();
         } else if (sessionService != null) {
             mSessionToken = new SessionToken2Impl(context, Process.myUid(), TYPE_SESSION_SERVICE,
-                    mContext.getPackageName(), sessionService, id, null).getInstance();
+                    mContext.getPackageName(), sessionService, id, mSessionStub).getInstance();
         } else {
             mSessionToken = new SessionToken2Impl(context, Process.myUid(), TYPE_SESSION,
                     mContext.getPackageName(), null, id, mSessionStub).getInstance();
@@ -203,13 +204,13 @@
     }
 
     @Override
-    public void setPlayer_impl(MediaPlayerBase player, MediaPlaylistController mpcl,
+    public void updatePlayer_impl(MediaPlayerBase player, MediaPlaylistAgent playlistAgent,
             VolumeProvider2 volumeProvider) throws IllegalArgumentException {
         ensureCallingThread();
         if (player == null) {
             throw new IllegalArgumentException("player shouldn't be null");
         }
-
+        mPlaylistAgent = playlistAgent;
         setPlayer(player, volumeProvider);
     }
 
@@ -294,7 +295,11 @@
         return getPlayer();
     }
 
-    // TODO(jaewan): Change this to @NonNull
+    @Override
+    public VolumeProvider2 getVolumeProvider_impl() {
+        return mVolumeProvider;
+    }
+
     @Override
     public SessionToken2 getToken_impl() {
         return mSessionToken;
@@ -337,18 +342,16 @@
         ensureCallingThread();
         final MediaPlayerBase player = mPlayer;
         if (player != null) {
-            // TODO: Uncomment or remove
-            //player.stop();
-            player.pause();
+            player.reset();
         } else if (DEBUG) {
             Log.d(TAG, "API calls after the close()", new IllegalStateException());
         }
     }
 
     @Override
-    public void skipToPrevious_impl() {
+    public void skipToPreviousItem_impl() {
         ensureCallingThread();
-        // TODO: Uncomment or remove
+        // TODO(jaewan): Implement this (b/74175632)
         /*
         final MediaPlayerBase player = mPlayer;
         if (player != null) {
@@ -361,14 +364,17 @@
     }
 
     @Override
-    public void skipToNext_impl() {
+    public void skipToNextItem_impl() {
         ensureCallingThread();
+        // TODO(jaewan): Implement this (b/74175632)
+        /*
         final MediaPlayerBase player = mPlayer;
         if (player != null) {
             player.skipToNext();
         } else if (DEBUG) {
             Log.d(TAG, "API calls after the close()", new IllegalStateException());
         }
+        */
     }
 
     @Override
@@ -424,7 +430,13 @@
 
     @Override
     public void setAllowedCommands_impl(ControllerInfo controller, CommandGroup commands) {
-        // TODO(jaewan): Implement
+        if (controller == null) {
+            throw new IllegalArgumentException("controller shouldn't be null");
+        }
+        if (commands == null) {
+            throw new IllegalArgumentException("commands shouldn't be null");
+        }
+        mSessionStub.setAllowedCommands(controller, commands);
     }
 
     @Override
@@ -448,7 +460,7 @@
         /*
         final MediaPlayerBase player = mPlayer;
         if (player != null) {
-            // TODO implement, use the SessionPlaylistController itf
+            // TODO implement and use SessionPlaylistAgent
             //player.setPlaylist(playlist);
             mSessionStub.notifyPlaylistChanged(playlist);
         } else if (DEBUG) {
@@ -807,7 +819,7 @@
         }
 
         /**
-         * @ 7return a new Bundle instance from the Command
+         * @return a new Bundle instance from the Command
          */
         public Bundle toBundle_impl() {
             Bundle bundle = new Bundle();
@@ -859,7 +871,7 @@
     public static class CommandGroupImpl implements CommandGroupProvider {
         private static final String KEY_COMMANDS =
                 "android.media.mediasession2.commandgroup.commands";
-        private ArraySet<Command> mCommands = new ArraySet<>();
+        private List<Command> mCommands = new ArrayList<>();
         private final Context mContext;
         private final CommandGroup mInstance;
 
@@ -878,8 +890,7 @@
 
         @Override
         public void addAllPredefinedCommands_impl() {
-            final int COMMAND_CODE_MAX = 22;
-            for (int i = 1; i <= COMMAND_CODE_MAX; i++) {
+            for (int i = 1; i <= MediaSession2.COMMAND_CODE_MAX; i++) {
                 mCommands.add(new Command(mContext, i));
             }
         }
@@ -900,13 +911,18 @@
                 throw new IllegalArgumentException("Use hasCommand(Command) for custom command");
             }
             for (int i = 0; i < mCommands.size(); i++) {
-                if (mCommands.valueAt(i).getCommandCode() == code) {
+                if (mCommands.get(i).getCommandCode() == code) {
                     return true;
                 }
             }
             return false;
         }
 
+        @Override
+        public List<Command> getCommands_impl() {
+            return mCommands;
+        }
+
         /**
          * @return new bundle from the CommandGroup
          * @hide
@@ -915,7 +931,7 @@
         public Bundle toBundle_impl() {
             ArrayList<Bundle> list = new ArrayList<>();
             for (int i = 0; i < mCommands.size(); i++) {
-                list.add(mCommands.valueAt(i).toBundle());
+                list.add(mCommands.get(i).toBundle());
             }
             Bundle bundle = new Bundle();
             bundle.putParcelableArrayList(KEY_COMMANDS, list);
@@ -962,31 +978,13 @@
             mInstance = instance;
             mUid = uid;
             mPackageName = packageName;
-
-            // TODO(jaewan): Remove this workaround
-            if ("com.android.server.media".equals(packageName)) {
-                mIsTrusted = true;
-            } else if (context.checkPermission(permission.MEDIA_CONTENT_CONTROL, pid, uid) ==
-                    PackageManager.PERMISSION_GRANTED) {
-                mIsTrusted = true;
-            } else {
-                // TODO(jaewan): Also consider enabled notification listener.
-                mIsTrusted = false;
-                // System apps may bind across the user so uid can be differ.
-                // Skip sanity check for the system app.
-                try {
-                    int uidForPackage = context.getPackageManager().getPackageUid(packageName, 0);
-                    if (uid != uidForPackage) {
-                        throw new IllegalArgumentException("Illegal call from uid=" + uid +
-                                ", pkg=" + packageName + ". Expected uid" + uidForPackage);
-                    }
-                } catch (NameNotFoundException e) {
-                    // Rethrow exception with different name because binder methods only accept
-                    // RemoteException.
-                    throw new IllegalArgumentException(e);
-                }
-            }
             mControllerBinder = callback;
+            MediaSessionManager manager =
+                  (MediaSessionManager) context.getSystemService(Context.MEDIA_SESSION_SERVICE);
+            // Ask server whether the controller is trusted.
+            // App cannot know this because apps cannot query enabled notification listener for
+            // another package, but system server can do.
+            mIsTrusted = manager.isTrusted(uid, packageName);
         }
 
         @Override
@@ -1010,8 +1008,17 @@
         }
 
         @Override
-        public boolean equals_impl(ControllerInfoProvider obj) {
-            return equals(obj);
+        public boolean equals_impl(Object obj) {
+            if (!(obj instanceof ControllerInfo)) {
+                return false;
+            }
+            return equals(((ControllerInfo) obj).getProvider());
+        }
+
+        @Override
+        public String toString_impl() {
+            return "ControllerInfo {pkg=" + mPackageName + ", uid=" + mUid + ", trusted="
+                    + mIsTrusted + "}";
         }
 
         @Override
@@ -1269,6 +1276,7 @@
         String mId;
         Executor mCallbackExecutor;
         C mCallback;
+        MediaPlaylistAgent mPlaylistAgent;
         VolumeProvider2 mVolumeProvider;
         PendingIntent mSessionActivity;
 
@@ -1276,7 +1284,6 @@
          * Constructor.
          *
          * @param context a context
-         * @param player a player to handle incoming command from any controller.
          * @throws IllegalArgumentException if any parameter is null, or the player is a
          *      {@link MediaSession2} or {@link MediaController2}.
          */
@@ -1290,17 +1297,25 @@
             mId = "";
         }
 
-        public void setPlayer_impl(MediaPlayerBase player, MediaPlaylistController mplc,
-                VolumeProvider2 volumeProvider) {
-            // TODO: Use MediaPlaylistController
+        public void setPlayer_impl(MediaPlayerBase player) {
             if (player == null) {
                 throw new IllegalArgumentException("player shouldn't be null");
             }
             mPlayer = player;
-            mVolumeProvider = volumeProvider;
+        }
+
+        @Override
+        public void setPlaylistAgent_impl(MediaPlaylistAgent playlistAgent) {
+            if (playlistAgent == null) {
+                throw new IllegalArgumentException("playlistAgent shouldn't be null");
+            }
+            mPlaylistAgent = playlistAgent;
         }
 
         public void setVolumeProvider_impl(VolumeProvider2 volumeProvider) {
+            if (volumeProvider == null) {
+                throw new IllegalArgumentException("volumeProvider shouldn't be null");
+            }
             mVolumeProvider = volumeProvider;
         }
 
@@ -1343,8 +1358,8 @@
                 mCallback = new SessionCallback(mContext) {};
             }
 
-            return new MediaSession2Impl(mContext, mPlayer, mId, mVolumeProvider,
-                    mSessionActivity, mCallbackExecutor, mCallback).getInstance();
+            return new MediaSession2Impl(mContext, mPlayer, mId, mPlaylistAgent,
+                    mVolumeProvider, mSessionActivity, mCallbackExecutor, mCallback).getInstance();
         }
     }
 }
diff --git a/packages/MediaComponents/src/com/android/media/MediaSession2Stub.java b/packages/MediaComponents/src/com/android/media/MediaSession2Stub.java
index b1e71e2..d4164f6 100644
--- a/packages/MediaComponents/src/com/android/media/MediaSession2Stub.java
+++ b/packages/MediaComponents/src/com/android/media/MediaSession2Stub.java
@@ -47,9 +47,9 @@
 
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
-import java.util.HashSet;
 
 public class MediaSession2Stub extends IMediaSession2.Stub {
 
@@ -67,6 +67,10 @@
     @GuardedBy("mLock")
     private final ArrayMap<IBinder, ControllerInfo> mControllers = new ArrayMap<>();
     @GuardedBy("mLock")
+    private final Set<IBinder> mConnectingControllers = new HashSet<>();
+    @GuardedBy("mLock")
+    private final ArrayMap<ControllerInfo, CommandGroup> mAllowedCommandGroupMap = new ArrayMap<>();
+    @GuardedBy("mLock")
     private final ArrayMap<ControllerInfo, Set<String>> mSubscriptions = new ArrayMap<>();
 
     public MediaSession2Stub(MediaSession2Impl session) {
@@ -92,10 +96,10 @@
         }
     }
 
-    private MediaSession2Impl getSession() throws IllegalStateException {
+    private MediaSession2Impl getSession() {
         final MediaSession2Impl session = mSession.get();
-        if (session == null) {
-            throw new IllegalStateException("Session is died");
+        if (session == null && DEBUG) {
+            Log.d(TAG, "Session is closed", new IllegalStateException());
         }
         return session;
     }
@@ -108,49 +112,146 @@
         return (MediaLibrarySessionImpl) session;
     }
 
-    private ControllerInfo getController(IMediaSession2Callback caller) {
-        // TODO(jaewan): Find a way to return connection-in-progress-controller
-        //               to be included here, because session owner may want to send some datas
-        //               while onConnected() hasn't returned.
+    // Get controller if the command from caller to session is able to be handled.
+    private ControllerInfo getControllerIfAble(IMediaSession2Callback caller) {
         synchronized (mLock) {
-            return mControllers.get(caller.asBinder());
+            final ControllerInfo controllerInfo = mControllers.get(caller.asBinder());
+            if (controllerInfo == null && DEBUG) {
+                Log.d(TAG, "Controller is disconnected", new IllegalStateException());
+            }
+            return controllerInfo;
+        }
+    }
+
+    // Get controller if the command from caller to session is able to be handled.
+    private ControllerInfo getControllerIfAble(IMediaSession2Callback caller, int commandCode) {
+        synchronized (mLock) {
+            final ControllerInfo controllerInfo = getControllerIfAble(caller);
+            if (controllerInfo == null) {
+                return null;
+            }
+            CommandGroup allowedCommands = mAllowedCommandGroupMap.get(controllerInfo);
+            if (allowedCommands == null) {
+                Log.w(TAG, "Controller with null allowed commands. Ignoring",
+                        new IllegalStateException());
+                return null;
+            }
+            if (!allowedCommands.hasCommand(commandCode)) {
+                if (DEBUG) {
+                    Log.d(TAG, "Controller isn't allowed for command " + commandCode);
+                }
+                return null;
+            }
+            return controllerInfo;
+        }
+    }
+
+    // Get controller if the command from caller to session is able to be handled.
+    private ControllerInfo getControllerIfAble(IMediaSession2Callback caller, Command command) {
+        synchronized (mLock) {
+            final ControllerInfo controllerInfo = getControllerIfAble(caller);
+            if (controllerInfo == null) {
+                return null;
+            }
+            CommandGroup allowedCommands = mAllowedCommandGroupMap.get(controllerInfo);
+            if (allowedCommands == null) {
+                Log.w(TAG, "Controller with null allowed commands. Ignoring",
+                        new IllegalStateException());
+                return null;
+            }
+            if (!allowedCommands.hasCommand(command)) {
+                if (DEBUG) {
+                    Log.d(TAG, "Controller isn't allowed for command " + command);
+                }
+                return null;
+            }
+            return controllerInfo;
+        }
+    }
+
+    // Return binder if the session is able to send a command to the controller.
+    private IMediaSession2Callback getControllerBinderIfAble(ControllerInfo controller) {
+        if (getSession() == null) {
+            // getSession() already logged if session is closed.
+            return null;
+        }
+        final ControllerInfoImpl impl = ControllerInfoImpl.from(controller);
+        synchronized (mLock) {
+            if (mControllers.get(impl.getId()) != null
+                    || mConnectingControllers.contains(impl.getId())) {
+                return impl.getControllerBinder();
+            }
+            if (DEBUG) {
+                Log.d(TAG, controller + " isn't connected nor connecting",
+                        new IllegalArgumentException());
+            }
+            return null;
+        }
+    }
+
+    // Return binder if the session is able to send a command to the controller.
+    private IMediaSession2Callback getControllerBinderIfAble(ControllerInfo controller,
+            int commandCode) {
+        synchronized (mLock) {
+            CommandGroup allowedCommands = mAllowedCommandGroupMap.get(controller);
+            if (allowedCommands == null) {
+                Log.w(TAG, "Controller with null allowed commands. Ignoring");
+                return null;
+            }
+            if (!allowedCommands.hasCommand(commandCode)) {
+                if (DEBUG) {
+                    Log.d(TAG, "Controller isn't allowed for command " + commandCode);
+                }
+                return null;
+            }
+            return getControllerBinderIfAble(controller);
         }
     }
 
     //////////////////////////////////////////////////////////////////////////////////////////////
     // AIDL methods for session overrides
     //////////////////////////////////////////////////////////////////////////////////////////////
-
     @Override
-    public void connect(final IMediaSession2Callback caller, String callingPackage)
+    public void connect(final IMediaSession2Callback caller, final String callingPackage)
             throws RuntimeException {
-        final MediaSession2Impl sessionImpl = getSession();
-        final Context context = sessionImpl.getContext();
+        final MediaSession2Impl session = getSession();
+        if (session == null) {
+            return;
+        }
+        final Context context = session.getContext();
         final ControllerInfo controllerInfo = new ControllerInfo(context,
                 Binder.getCallingUid(), Binder.getCallingPid(), callingPackage, caller);
-        sessionImpl.getCallbackExecutor().execute(() -> {
-            final MediaSession2Impl session = mSession.get();
-            if (session == null) {
+        session.getCallbackExecutor().execute(() -> {
+            if (getSession() == null) {
                 return;
             }
-            CommandGroup allowedCommands = session.getCallback().onConnect(controllerInfo);
-            // Don't reject connection for the controllerInfo from trusted app.
+            synchronized (mLock) {
+                // Keep connecting controllers.
+                // This helps sessions to call APIs in the onConnect() (e.g. setCustomLayout())
+                // instead of pending them.
+                mConnectingControllers.add(ControllerInfoImpl.from(controllerInfo).getId());
+            }
+            CommandGroup allowedCommands = session.getCallback().onConnect(
+                    session.getInstance(), controllerInfo);
+            // Don't reject connection for the request from trusted app.
             // Otherwise server will fail to retrieve session's information to dispatch
             // media keys to.
             boolean accept = allowedCommands != null || controllerInfo.isTrusted();
-            ControllerInfoImpl impl = ControllerInfoImpl.from(controllerInfo);
             if (accept) {
+                ControllerInfoImpl controllerImpl = ControllerInfoImpl.from(controllerInfo);
                 if (DEBUG) {
                     Log.d(TAG, "Accepting connection, controllerInfo=" + controllerInfo
                             + " allowedCommands=" + allowedCommands);
                 }
-                synchronized (mLock) {
-                    mControllers.put(impl.getId(), controllerInfo);
-                }
                 if (allowedCommands == null) {
                     // For trusted apps, send non-null allowed commands to keep connection.
                     allowedCommands = new CommandGroup(context);
                 }
+                synchronized (mLock) {
+                    mConnectingControllers.remove(controllerImpl.getId());
+                    mControllers.put(controllerImpl.getId(),  controllerInfo);
+                    mAllowedCommandGroupMap.put(controllerInfo, allowedCommands);
+                }
                 // If connection is accepted, notify the current state to the controller.
                 // It's needed because we cannot call synchronous calls between session/controller.
                 // Note: We're doing this after the onConnectionChanged(), but there's no guarantee
@@ -165,9 +266,12 @@
                 final PlaylistParams params = session.getInstance().getPlaylistParams();
                 final Bundle paramsBundle = (params != null) ? params.toBundle() : null;
                 final PendingIntent sessionActivity = session.getSessionActivity();
-                final List<MediaItem2> playlist = session.getInstance().getPlaylist();
-                final List<Bundle> playlistBundle = new ArrayList<>();
+                final List<MediaItem2> playlist =
+                        allowedCommands.hasCommand(MediaSession2.COMMAND_CODE_PLAYLIST_GET_LIST)
+                                ? session.getInstance().getPlaylist() : null;
+                final List<Bundle> playlistBundle;
                 if (playlist != null) {
+                    playlistBundle = new ArrayList<>();
                     // TODO(jaewan): Find a way to avoid concurrent modification exception.
                     for (int i = 0; i < playlist.size(); i++) {
                         final MediaItem2 item = playlist.get(i);
@@ -178,11 +282,13 @@
                             }
                         }
                     }
+                } else {
+                    playlistBundle = null;
                 }
 
                 // Double check if session is still there, because close() can be called in another
                 // thread.
-                if (mSession.get() == null) {
+                if (getSession() == null) {
                     return;
                 }
                 try {
@@ -194,6 +300,9 @@
                     // TODO(jaewan): Handle here.
                 }
             } else {
+                synchronized (mLock) {
+                    mConnectingControllers.remove(ControllerInfoImpl.from(controllerInfo).getId());
+                }
                 if (DEBUG) {
                     Log.d(TAG, "Rejecting connection, controllerInfo=" + controllerInfo);
                 }
@@ -208,7 +317,7 @@
     }
 
     @Override
-    public void release(IMediaSession2Callback caller) throws RemoteException {
+    public void release(final IMediaSession2Callback caller) throws RemoteException {
         synchronized (mLock) {
             ControllerInfo controllerInfo = mControllers.remove(caller.asBinder());
             if (DEBUG) {
@@ -219,29 +328,27 @@
     }
 
     @Override
-    public void setVolumeTo(IMediaSession2Callback caller, int value, int flags)
+    public void setVolumeTo(final IMediaSession2Callback caller, final int value, final int flags)
             throws RuntimeException {
-        final MediaSession2Impl sessionImpl = getSession();
-        final ControllerInfo controller = getController(caller);
-        if (controller == null) {
-            if (DEBUG) {
-                Log.d(TAG, "Command from a controller that hasn't connected. Ignore");
-            }
+        final MediaSession2Impl session = getSession();
+        final ControllerInfo controller = getControllerIfAble(
+                caller, MediaSession2.COMMAND_CODE_PLAYBACK_SET_VOLUME);
+        if (session == null || controller == null) {
             return;
         }
-        sessionImpl.getCallbackExecutor().execute(() -> {
-            final MediaSession2Impl session = mSession.get();
-            if (session == null) {
+        session.getCallbackExecutor().execute(() -> {
+            if (getControllerIfAble(caller, MediaSession2.COMMAND_CODE_PLAYBACK_SET_VOLUME) == null) {
                 return;
             }
             // TODO(jaewan): Sanity check.
             Command command = new Command(
-                    session.getContext(), MediaSession2.COMMAND_CODE_SET_VOLUME);
-            boolean accepted = session.getCallback().onCommandRequest(controller, command);
+                    session.getContext(), MediaSession2.COMMAND_CODE_PLAYBACK_SET_VOLUME);
+            boolean accepted = session.getCallback().onCommandRequest(session.getInstance(),
+                    controller, command);
             if (!accepted) {
                 // Don't run rejected command.
                 if (DEBUG) {
-                    Log.d(TAG, "Command " + MediaSession2.COMMAND_CODE_SET_VOLUME + " from "
+                    Log.d(TAG, "Command " + MediaSession2.COMMAND_CODE_PLAYBACK_SET_VOLUME + " from "
                             + controller + " was rejected by " + session);
                 }
                 return;
@@ -259,27 +366,25 @@
     @Override
     public void adjustVolume(IMediaSession2Callback caller, int direction, int flags)
             throws RuntimeException {
-        final MediaSession2Impl sessionImpl = getSession();
-        final ControllerInfo controller = getController(caller);
-        if (controller == null) {
-            if (DEBUG) {
-                Log.d(TAG, "Command from a controller that hasn't connected. Ignore");
-            }
+        final MediaSession2Impl session = getSession();
+        final ControllerInfo controller = getControllerIfAble(
+                caller, MediaSession2.COMMAND_CODE_PLAYBACK_SET_VOLUME);
+        if (session == null || controller == null) {
             return;
         }
-        sessionImpl.getCallbackExecutor().execute(() -> {
-            final MediaSession2Impl session = mSession.get();
-            if (session == null) {
+        session.getCallbackExecutor().execute(() -> {
+            if (getControllerIfAble(caller, MediaSession2.COMMAND_CODE_PLAYBACK_SET_VOLUME) == null) {
                 return;
             }
             // TODO(jaewan): Sanity check.
             Command command = new Command(
-                    session.getContext(), MediaSession2.COMMAND_CODE_SET_VOLUME);
-            boolean accepted = session.getCallback().onCommandRequest(controller, command);
+                    session.getContext(), MediaSession2.COMMAND_CODE_PLAYBACK_SET_VOLUME);
+            boolean accepted = session.getCallback().onCommandRequest(session.getInstance(),
+                    controller, command);
             if (!accepted) {
                 // Don't run rejected command.
                 if (DEBUG) {
-                    Log.d(TAG, "Command " + MediaSession2.COMMAND_CODE_SET_VOLUME + " from "
+                    Log.d(TAG, "Command " + MediaSession2.COMMAND_CODE_PLAYBACK_SET_VOLUME + " from "
                             + controller + " was rejected by " + session);
                 }
                 return;
@@ -295,30 +400,21 @@
     }
 
     @Override
-    public void sendCommand(IMediaSession2Callback caller, Bundle command, Bundle args)
-            throws RuntimeException {
-        // TODO(jaewan): Generic command
-    }
-
-    @Override
     public void sendTransportControlCommand(IMediaSession2Callback caller,
             int commandCode, Bundle args) throws RuntimeException {
-        final MediaSession2Impl sessionImpl = getSession();
-        final ControllerInfo controller = getController(caller);
-        if (controller == null) {
-            if (DEBUG) {
-                Log.d(TAG, "Command from a controller that hasn't connected. Ignore");
-            }
+        final MediaSession2Impl session = getSession();
+        final ControllerInfo controller = getControllerIfAble(caller, commandCode);
+        if (session == null || controller == null) {
             return;
         }
-        sessionImpl.getCallbackExecutor().execute(() -> {
-            final MediaSession2Impl session = mSession.get();
-            if (session == null) {
+        session.getCallbackExecutor().execute(() -> {
+            if (getControllerIfAble(caller, commandCode) == null) {
                 return;
             }
             // TODO(jaewan): Sanity check.
             Command command = new Command(session.getContext(), commandCode);
-            boolean accepted = session.getCallback().onCommandRequest(controller, command);
+            boolean accepted = session.getCallback().onCommandRequest(session.getInstance(),
+                    controller, command);
             if (!accepted) {
                 // Don't run rejected command.
                 if (DEBUG) {
@@ -339,10 +435,10 @@
                     session.getInstance().stop();
                     break;
                 case MediaSession2.COMMAND_CODE_PLAYBACK_SKIP_PREV_ITEM:
-                    session.getInstance().skipToPrevious();
+                    session.getInstance().skipToPreviousItem();
                     break;
                 case MediaSession2.COMMAND_CODE_PLAYBACK_SKIP_NEXT_ITEM:
-                    session.getInstance().skipToNext();
+                    session.getInstance().skipToNextItem();
                     break;
                 case MediaSession2.COMMAND_CODE_PLAYBACK_PREPARE:
                     session.getInstance().prepare();
@@ -356,18 +452,21 @@
                 case MediaSession2.COMMAND_CODE_PLAYBACK_SEEK_TO:
                     session.getInstance().seekTo(args.getLong(ARGUMENT_KEY_POSITION));
                     break;
-                case MediaSession2.COMMAND_CODE_PLAYBACK_SKIP_TO_PLAYLIST_ITEM:
+                case MediaSession2.COMMAND_CODE_PLAYLIST_SKIP_TO_PLAYLIST_ITEM:
                     // TODO(jaewan): Implement
                     /*
                     session.getInstance().skipToPlaylistItem(
                             args.getInt(ARGUMENT_KEY_ITEM_INDEX));
                     */
                     break;
+                    // TODO(jaewan): Remove (b/74116823)
+                    /*
                 case MediaSession2.COMMAND_CODE_PLAYBACK_SET_PLAYLIST_PARAMS:
                     session.getInstance().setPlaylistParams(
                             PlaylistParams.fromBundle(session.getContext(),
                                     args.getBundle(ARGUMENT_KEY_PLAYLIST_PARAMS)));
                     break;
+                    */
                 default:
                     // TODO(jaewan): Resend unknown (new) commands through the custom command.
             }
@@ -377,141 +476,133 @@
     @Override
     public void sendCustomCommand(final IMediaSession2Callback caller, final Bundle commandBundle,
             final Bundle args, final ResultReceiver receiver) {
-        final MediaSession2Impl sessionImpl = getSession();
-        final ControllerInfo controller = getController(caller);
-        if (controller == null) {
-            if (DEBUG) {
-                Log.d(TAG, "Command from a controller that hasn't connected. Ignore");
-            }
+        final MediaSession2Impl session = getSession();
+        if (session == null) {
             return;
         }
-        sessionImpl.getCallbackExecutor().execute(() -> {
-            final MediaSession2Impl session = mSession.get();
-            if (session == null) {
+        final Command command = Command.fromBundle(session.getContext(), commandBundle);
+        final ControllerInfo controller = getControllerIfAble(caller, command);
+        if (controller == null) {
+            return;
+        }
+        session.getCallbackExecutor().execute(() -> {
+            if (getControllerIfAble(caller, command) == null) {
                 return;
             }
-            final Command command = Command.fromBundle(session.getContext(), commandBundle);
-            session.getCallback().onCustomCommand(controller, command, args, receiver);
+            session.getCallback().onCustomCommand(session.getInstance(),
+                    controller, command, args, receiver);
         });
     }
 
     @Override
     public void prepareFromUri(final IMediaSession2Callback caller, final Uri uri,
             final Bundle extras) {
-        final MediaSession2Impl sessionImpl = getSession();
-        final ControllerInfo controller = getController(caller);
-        if (controller == null) {
-            if (DEBUG) {
-                Log.d(TAG, "Command from a controller that hasn't connected. Ignore");
-            }
+        final MediaSession2Impl session = getSession();
+        final ControllerInfo controller = getControllerIfAble(
+                caller, MediaSession2.COMMAND_CODE_PREPARE_FROM_URI);
+        if (session == null || controller == null) {
             return;
         }
-        sessionImpl.getCallbackExecutor().execute(() -> {
-            final MediaSession2Impl session = mSession.get();
-            if (session == null) {
+        session.getCallbackExecutor().execute(() -> {
+            if (getControllerIfAble(
+                    caller, MediaSession2.COMMAND_CODE_PREPARE_FROM_URI) == null) {
                 return;
             }
-            session.getCallback().onPrepareFromUri(controller, uri, extras);
+            session.getCallback().onPrepareFromUri(session.getInstance(),
+                    controller, uri, extras);
         });
     }
 
     @Override
     public void prepareFromSearch(final IMediaSession2Callback caller, final String query,
             final Bundle extras) {
-        final MediaSession2Impl sessionImpl = getSession();
-        final ControllerInfo controller = getController(caller);
-        if (controller == null) {
-            if (DEBUG) {
-                Log.d(TAG, "Command from a controller that hasn't connected. Ignore");
-            }
+        final MediaSession2Impl session = getSession();
+        final ControllerInfo controller = getControllerIfAble(
+                caller, MediaSession2.COMMAND_CODE_PREPARE_FROM_SEARCH);
+        if (session == null || controller == null) {
             return;
         }
-        sessionImpl.getCallbackExecutor().execute(() -> {
-            final MediaSession2Impl session = mSession.get();
-            if (session == null) {
+        session.getCallbackExecutor().execute(() -> {
+            if (getControllerIfAble(
+                    caller, MediaSession2.COMMAND_CODE_PREPARE_FROM_SEARCH) == null) {
                 return;
             }
-            session.getCallback().onPrepareFromSearch(controller, query, extras);
+            session.getCallback().onPrepareFromSearch(session.getInstance(),
+                    controller, query, extras);
         });
     }
 
     @Override
     public void prepareFromMediaId(final IMediaSession2Callback caller, final String mediaId,
             final Bundle extras) {
-        final MediaSession2Impl sessionImpl = getSession();
-        final ControllerInfo controller = getController(caller);
-        if (controller == null) {
-            if (DEBUG) {
-                Log.d(TAG, "Command from a controller that hasn't connected. Ignore");
-            }
+        final MediaSession2Impl session = getSession();
+        final ControllerInfo controller = getControllerIfAble(
+                caller, MediaSession2.COMMAND_CODE_PREPARE_FROM_MEDIA_ID);
+        if (session == null || controller == null) {
             return;
         }
-        sessionImpl.getCallbackExecutor().execute(() -> {
-            final MediaSession2Impl session = mSession.get();
-            if (session == null) {
+        session.getCallbackExecutor().execute(() -> {
+            if (getControllerIfAble(
+                    caller, MediaSession2.COMMAND_CODE_PREPARE_FROM_MEDIA_ID) == null) {
                 return;
             }
-            session.getCallback().onPrepareFromMediaId(controller, mediaId, extras);
+            session.getCallback().onPrepareFromMediaId(session.getInstance(),
+                    controller, mediaId, extras);
         });
     }
 
     @Override
     public void playFromUri(final IMediaSession2Callback caller, final Uri uri,
             final Bundle extras) {
-        final MediaSession2Impl sessionImpl = getSession();
-        final ControllerInfo controller = getController(caller);
-        if (controller == null) {
-            if (DEBUG) {
-                Log.d(TAG, "Command from a controller that hasn't connected. Ignore");
-            }
+        final MediaSession2Impl session = getSession();
+        final ControllerInfo controller = getControllerIfAble(
+                caller, MediaSession2.COMMAND_CODE_PLAY_FROM_URI);
+        if (session == null || controller == null) {
             return;
         }
-        sessionImpl.getCallbackExecutor().execute(() -> {
-            final MediaSession2Impl session = mSession.get();
-            if (session == null) {
+        session.getCallbackExecutor().execute(() -> {
+            if (getControllerIfAble(
+                    caller, MediaSession2.COMMAND_CODE_PLAY_FROM_URI) == null) {
                 return;
             }
-            session.getCallback().onPlayFromUri(controller, uri, extras);
+            session.getCallback().onPlayFromUri(session.getInstance(), controller, uri, extras);
         });
     }
 
     @Override
     public void playFromSearch(final IMediaSession2Callback caller, final String query,
             final Bundle extras) {
-        final MediaSession2Impl sessionImpl = getSession();
-        final ControllerInfo controller = getController(caller);
-        if (controller == null) {
-            if (DEBUG) {
-                Log.d(TAG, "Command from a controller that hasn't connected. Ignore");
-            }
+        final MediaSession2Impl session = getSession();
+        final ControllerInfo controller = getControllerIfAble(
+                caller, MediaSession2.COMMAND_CODE_PLAY_FROM_SEARCH);
+        if (session == null || controller == null) {
             return;
         }
-        sessionImpl.getCallbackExecutor().execute(() -> {
-            final MediaSession2Impl session = mSession.get();
-            if (session == null) {
+        session.getCallbackExecutor().execute(() -> {
+            if (getControllerIfAble(
+                    caller, MediaSession2.COMMAND_CODE_PLAY_FROM_SEARCH) == null) {
                 return;
             }
-            session.getCallback().onPlayFromSearch(controller, query, extras);
+            session.getCallback().onPlayFromSearch(session.getInstance(),
+                    controller, query, extras);
         });
     }
 
     @Override
     public void playFromMediaId(final IMediaSession2Callback caller, final String mediaId,
             final Bundle extras) {
-        final MediaSession2Impl sessionImpl = getSession();
-        final ControllerInfo controller = getController(caller);
-        if (controller == null) {
-            if (DEBUG) {
-                Log.d(TAG, "Command from a controller that hasn't connected. Ignore");
-            }
+        final MediaSession2Impl session = getSession();
+        final ControllerInfo controller = getControllerIfAble(
+                caller, MediaSession2.COMMAND_CODE_PLAY_FROM_MEDIA_ID);
+        if (session == null || controller == null) {
             return;
         }
-        sessionImpl.getCallbackExecutor().execute(() -> {
-            final MediaSession2Impl session = mSession.get();
+        session.getCallbackExecutor().execute(() -> {
             if (session == null) {
                 return;
             }
-            session.getCallback().onPlayFromMediaId(controller, mediaId, extras);
+            session.getCallback().onPlayFromMediaId(session.getInstance(),
+                    controller, mediaId, extras);
         });
     }
 
@@ -519,7 +610,7 @@
     public void setRating(final IMediaSession2Callback caller, final String mediaId,
             final Bundle ratingBundle) {
         final MediaSession2Impl sessionImpl = getSession();
-        final ControllerInfo controller = getController(caller);
+        final ControllerInfo controller = getControllerIfAble(caller);
         if (controller == null) {
             if (DEBUG) {
                 Log.d(TAG, "Command from a controller that hasn't connected. Ignore");
@@ -532,7 +623,8 @@
                 return;
             }
             Rating2 rating = Rating2Impl.fromBundle(session.getContext(), ratingBundle);
-            session.getCallback().onSetRating(controller, mediaId, rating);
+            session.getCallback().onSetRating(session.getInstance(),
+                    controller, mediaId, rating);
         });
     }
 
@@ -541,25 +633,22 @@
     //////////////////////////////////////////////////////////////////////////////////////////////
 
     @Override
-    public void getBrowserRoot(IMediaSession2Callback caller, Bundle rootHints)
+    public void getBrowserRoot(final IMediaSession2Callback caller, final Bundle rootHints)
             throws RuntimeException {
-        final MediaLibrarySessionImpl sessionImpl = getLibrarySession();
-        final ControllerInfo controller = getController(caller);
-        if (controller == null) {
-            if (DEBUG) {
-                Log.d(TAG, "getBrowerRoot() from a controller that hasn't connected. Ignore");
-            }
+        final MediaLibrarySessionImpl session = getLibrarySession();
+        final ControllerInfo controller = getControllerIfAble(
+                caller, MediaSession2.COMMAND_CODE_BROWSER);
+        if (session == null || controller == null) {
             return;
         }
-        sessionImpl.getCallbackExecutor().execute(() -> {
-            final MediaLibrarySessionImpl session = getLibrarySession();
-            if (session == null) {
+        session.getCallbackExecutor().execute(() -> {
+            if (getControllerIfAble(caller, MediaSession2.COMMAND_CODE_BROWSER) == null) {
                 return;
             }
-            final ControllerInfoImpl controllerImpl = ControllerInfoImpl.from(controller);
-            LibraryRoot root = session.getCallback().onGetLibraryRoot(controller, rootHints);
+            LibraryRoot root = session.getCallback().onGetLibraryRoot(session.getInstance(),
+                    controller, rootHints);
             try {
-                controllerImpl.getControllerBinder().onGetLibraryRootDone(rootHints,
+                caller.onGetLibraryRootDone(rootHints,
                         root == null ? null : root.getRootId(),
                         root == null ? null : root.getExtras());
             } catch (RemoteException e) {
@@ -570,31 +659,28 @@
     }
 
     @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;
-        }
+    public void getItem(final IMediaSession2Callback caller, final String mediaId)
+            throws RuntimeException {
         if (mediaId == null) {
             if (DEBUG) {
                 Log.d(TAG, "mediaId shouldn't be null");
             }
             return;
         }
-        sessionImpl.getCallbackExecutor().execute(() -> {
-            final MediaLibrarySessionImpl session = getLibrarySession();
-            if (session == null) {
+        final MediaLibrarySessionImpl session = getLibrarySession();
+        final ControllerInfo controller = getControllerIfAble(
+                caller, MediaSession2.COMMAND_CODE_BROWSER);
+        if (session == null || controller == null) {
+            return;
+        }
+        session.getCallbackExecutor().execute(() -> {
+            if (getControllerIfAble(caller, MediaSession2.COMMAND_CODE_BROWSER) == null) {
                 return;
             }
-            final ControllerInfoImpl controllerImpl = ControllerInfoImpl.from(controller);
-            MediaItem2 result = session.getCallback().onGetItem(controller, mediaId);
+            MediaItem2 result = session.getCallback().onGetItem(session.getInstance(),
+                    controller, mediaId);
             try {
-                controllerImpl.getControllerBinder().onGetItemDone(
-                        mediaId, result == null ? null : result.toBundle());
+                caller.onGetItemDone(mediaId, result == null ? null : result.toBundle());
             } catch (RemoteException e) {
                 // Controller may be died prematurely.
                 // TODO(jaewan): Handle this.
@@ -603,16 +689,8 @@
     }
 
     @Override
-    public void getChildren(IMediaSession2Callback caller, String parentId, int page,
-            int pageSize, Bundle extras) 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;
-        }
+    public void getChildren(final IMediaSession2Callback caller, final String parentId,
+            final int page, final int pageSize, final Bundle extras) throws RuntimeException {
         if (parentId == null) {
             if (DEBUG) {
                 Log.d(TAG, "parentId shouldn't be null");
@@ -625,21 +703,23 @@
             }
             return;
         }
-
-        sessionImpl.getCallbackExecutor().execute(() -> {
-            final MediaLibrarySessionImpl session = getLibrarySession();
-            if (session == null) {
+        final MediaLibrarySessionImpl session = getLibrarySession();
+        final ControllerInfo controller = getControllerIfAble(
+                caller, MediaSession2.COMMAND_CODE_BROWSER);
+        if (session == null || controller == null) {
+            return;
+        }
+        session.getCallbackExecutor().execute(() -> {
+            if (getControllerIfAble(caller, MediaSession2.COMMAND_CODE_BROWSER) == null) {
                 return;
             }
-            final ControllerInfoImpl controllerImpl = ControllerInfoImpl.from(controller);
-            List<MediaItem2> result = session.getCallback().onGetChildren(
+            List<MediaItem2> result = session.getCallback().onGetChildren(session.getInstance(),
                     controller, parentId, page, pageSize, extras);
             if (result != null && result.size() > pageSize) {
                 throw new IllegalArgumentException("onGetChildren() shouldn't return media items "
                         + "more than pageSize. result.size()=" + result.size() + " pageSize="
                         + pageSize);
             }
-
             List<Bundle> bundleList = null;
             if (result != null) {
                 bundleList = new ArrayList<>();
@@ -647,10 +727,8 @@
                     bundleList.add(item == null ? null : item.toBundle());
                 }
             }
-
             try {
-                controllerImpl.getControllerBinder().onGetChildrenDone(
-                        parentId, page, pageSize, bundleList, extras);
+                caller.onGetChildrenDone(parentId, page, pageSize, bundleList, extras);
             } catch (RemoteException e) {
                 // Controller may be died prematurely.
                 // TODO(jaewan): Handle this.
@@ -660,42 +738,24 @@
 
     @Override
     public void search(IMediaSession2Callback caller, String query, Bundle extras) {
-        final MediaLibrarySessionImpl sessionImpl = getLibrarySession();
-        final ControllerInfo controller = getController(caller);
-        if (controller == null) {
-            if (DEBUG) {
-                Log.d(TAG, "search() from a controller that hasn't connected. Ignore");
-            }
+        final MediaLibrarySessionImpl session = getLibrarySession();
+        final ControllerInfo controller = getControllerIfAble(
+                caller, MediaSession2.COMMAND_CODE_BROWSER);
+        if (session == null || controller == null) {
             return;
         }
-        if (TextUtils.isEmpty(query)) {
-            if (DEBUG) {
-                Log.d(TAG, "query shouldn't be empty");
-            }
-            return;
-        }
-
-        sessionImpl.getCallbackExecutor().execute(() -> {
-            final MediaLibrarySessionImpl session = getLibrarySession();
-            if (session == null) {
+        session.getCallbackExecutor().execute(() -> {
+            if (getControllerIfAble(caller, MediaSession2.COMMAND_CODE_BROWSER) == null) {
                 return;
             }
-            final ControllerInfoImpl controllerImpl = ControllerInfoImpl.from(controller);
-            session.getCallback().onSearch(controller, query, extras);
+            session.getCallback().onSearch(session.getInstance(),
+                    controller, query, extras);
         });
     }
 
     @Override
-    public void getSearchResult(IMediaSession2Callback caller, String query, int page,
-            int pageSize, Bundle extras) {
-        final MediaLibrarySessionImpl sessionImpl = getLibrarySession();
-        final ControllerInfo controller = getController(caller);
-        if (controller == null) {
-            if (DEBUG) {
-                Log.d(TAG, "getSearchResult() from a controller that hasn't connected. Ignore");
-            }
-            return;
-        }
+    public void getSearchResult(final IMediaSession2Callback caller, final String query,
+            final int page, final int pageSize, final Bundle extras) {
         if (TextUtils.isEmpty(query)) {
             if (DEBUG) {
                 Log.d(TAG, "query shouldn't be empty");
@@ -708,21 +768,23 @@
             }
             return;
         }
-
-        sessionImpl.getCallbackExecutor().execute(() -> {
-            final MediaLibrarySessionImpl session = getLibrarySession();
-            if (session == null) {
+        final MediaLibrarySessionImpl session = getLibrarySession();
+        final ControllerInfo controller = getControllerIfAble(
+                caller, MediaSession2.COMMAND_CODE_BROWSER);
+        if (session == null || controller == null) {
+            return;
+        }
+        session.getCallbackExecutor().execute(() -> {
+            if (getControllerIfAble(caller, MediaSession2.COMMAND_CODE_BROWSER) == null) {
                 return;
             }
-            final ControllerInfoImpl controllerImpl = ControllerInfoImpl.from(controller);
-            List<MediaItem2> result = session.getCallback().onGetSearchResult(
+            List<MediaItem2> result = session.getCallback().onGetSearchResult(session.getInstance(),
                     controller, query, page, pageSize, extras);
             if (result != null && result.size() > pageSize) {
                 throw new IllegalArgumentException("onGetSearchResult() shouldn't return media "
                         + "items more than pageSize. result.size()=" + result.size() + " pageSize="
                         + pageSize);
             }
-
             List<Bundle> bundleList = null;
             if (result != null) {
                 bundleList = new ArrayList<>();
@@ -732,8 +794,7 @@
             }
 
             try {
-                controllerImpl.getControllerBinder().onGetSearchResultDone(
-                        query, page, pageSize, bundleList, extras);
+                caller.onGetSearchResultDone(query, page, pageSize, bundleList, extras);
             } catch (RemoteException e) {
                 // Controller may be died prematurely.
                 // TODO(jaewan): Handle this.
@@ -744,20 +805,18 @@
     @Override
     public void subscribe(final IMediaSession2Callback caller, final String parentId,
             final Bundle option) {
-        final MediaLibrarySessionImpl sessionImpl = getLibrarySession();
-        final ControllerInfo controller = getController(caller);
-        if (controller == null) {
-            if (DEBUG) {
-                Log.d(TAG, "subscribe() from a browser that hasn't connected. Ignore");
-            }
+        final MediaLibrarySessionImpl session = getLibrarySession();
+        final ControllerInfo controller = getControllerIfAble(
+                caller, MediaSession2.COMMAND_CODE_BROWSER);
+        if (session == null || controller == null) {
             return;
         }
-        sessionImpl.getCallbackExecutor().execute(() -> {
-            final MediaLibrarySessionImpl session = getLibrarySession();
-            if (session == null) {
+        session.getCallbackExecutor().execute(() -> {
+            if (getControllerIfAble(caller, MediaSession2.COMMAND_CODE_BROWSER) == null) {
                 return;
             }
-            session.getCallback().onSubscribe(controller, parentId, option);
+            session.getCallback().onSubscribe(session.getInstance(),
+                    controller, parentId, option);
             synchronized (mLock) {
                 Set<String> subscription = mSubscriptions.get(controller);
                 if (subscription == null) {
@@ -771,20 +830,17 @@
 
     @Override
     public void unsubscribe(final IMediaSession2Callback caller, final String parentId) {
-        final MediaLibrarySessionImpl sessionImpl = getLibrarySession();
-        final ControllerInfo controller = getController(caller);
-        if (controller == null) {
-            if (DEBUG) {
-                Log.d(TAG, "unsubscribe() from a browser that hasn't connected. Ignore");
-            }
+        final MediaLibrarySessionImpl session = getLibrarySession();
+        final ControllerInfo controller = getControllerIfAble(
+                caller, MediaSession2.COMMAND_CODE_BROWSER);
+        if (session == null || controller == null) {
             return;
         }
-        sessionImpl.getCallbackExecutor().execute(() -> {
-            final MediaLibrarySessionImpl session = getLibrarySession();
-            if (session == null) {
+        session.getCallbackExecutor().execute(() -> {
+            if (getControllerIfAble(caller, MediaSession2.COMMAND_CODE_BROWSER) == null) {
                 return;
             }
-            session.getCallback().onUnsubscribe(controller, parentId);
+            session.getCallback().onUnsubscribe(session.getInstance(), controller, parentId);
             synchronized (mLock) {
                 mSubscriptions.remove(controller);
             }
@@ -810,8 +866,10 @@
     public void notifyPlaybackStateChangedNotLocked(PlaybackState2 state) {
         final List<ControllerInfo> list = getControllers();
         for (int i = 0; i < list.size(); i++) {
-            IMediaSession2Callback controllerBinder =
-                    ControllerInfoImpl.from(list.get(i)).getControllerBinder();
+            final IMediaSession2Callback controllerBinder = getControllerBinderIfAble(list.get(i));
+            if (controllerBinder == null) {
+                return;
+            }
             try {
                 final Bundle bundle = state != null ? state.toBundle() : null;
                 controllerBinder.onPlaybackStateChanged(bundle);
@@ -823,10 +881,10 @@
     }
 
     public void notifyCustomLayoutNotLocked(ControllerInfo controller, List<CommandButton> layout) {
-        // TODO(jaewan): It's OK to be called while it's connecting, but not OK if the connection
-        //               is rejected. Handle the case.
-        IMediaSession2Callback controllerBinder =
-                ControllerInfoImpl.from(controller).getControllerBinder();
+        final IMediaSession2Callback controllerBinder = getControllerBinderIfAble(controller);
+        if (controllerBinder == null) {
+            return;
+        }
         try {
             List<Bundle> layoutBundles = new ArrayList<>();
             for (int i = 0; i < layout.size(); i++) {
@@ -843,9 +901,6 @@
     }
 
     public void notifyPlaylistChanged(List<MediaItem2> playlist) {
-        if (playlist == null) {
-            return;
-        }
         final List<Bundle> bundleList = new ArrayList<>();
         for (int i = 0; i < playlist.size(); i++) {
             if (playlist.get(i) != null) {
@@ -857,13 +912,15 @@
         }
         final List<ControllerInfo> list = getControllers();
         for (int i = 0; i < list.size(); i++) {
-            IMediaSession2Callback controllerBinder =
-                    ControllerInfoImpl.from(list.get(i)).getControllerBinder();
-            try {
-                controllerBinder.onPlaylistChanged(bundleList);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Controller is gone", e);
-                // TODO(jaewan): What to do when the controller is gone?
+            final IMediaSession2Callback controllerBinder = getControllerBinderIfAble(
+                    list.get(i), MediaSession2.COMMAND_CODE_PLAYLIST_GET_LIST);
+            if (controllerBinder != null) {
+                try {
+                    controllerBinder.onPlaylistChanged(bundleList);
+                } catch (RemoteException e) {
+                    Log.w(TAG, "Controller is gone", e);
+                    // TODO(jaewan): What to do when the controller is gone?
+                }
             }
         }
     }
@@ -871,8 +928,10 @@
     public void notifyPlaylistParamsChanged(MediaSession2.PlaylistParams params) {
         final List<ControllerInfo> list = getControllers();
         for (int i = 0; i < list.size(); i++) {
-            IMediaSession2Callback controllerBinder =
-                    ControllerInfoImpl.from(list.get(i)).getControllerBinder();
+            final IMediaSession2Callback controllerBinder = getControllerBinderIfAble(list.get(i));
+            if (controllerBinder == null) {
+                return;
+            }
             try {
                 controllerBinder.onPlaylistParamsChanged(params.toBundle());
             } catch (RemoteException e) {
@@ -885,8 +944,10 @@
     public void notifyPlaybackInfoChanged(MediaController2.PlaybackInfo playbackInfo) {
         final List<ControllerInfo> list = getControllers();
         for (int i = 0; i < list.size(); i++) {
-            IMediaSession2Callback controllerBinder =
-                    ControllerInfoImpl.from(list.get(i)).getControllerBinder();
+            final IMediaSession2Callback controllerBinder = getControllerBinderIfAble(list.get(i));
+            if (controllerBinder == null) {
+                return;
+            }
             try {
                 controllerBinder.onPlaybackInfoChanged(((MediaController2Impl.PlaybackInfoImpl)
                         playbackInfo.getProvider()).toBundle());
@@ -897,6 +958,22 @@
         }
     }
 
+    public void setAllowedCommands(ControllerInfo controller, CommandGroup commands) {
+        synchronized (mLock) {
+            mAllowedCommandGroupMap.put(controller, commands);
+        }
+        final IMediaSession2Callback controllerBinder = getControllerBinderIfAble(controller);
+        if (controllerBinder == null) {
+            return;
+        }
+        try {
+            controllerBinder.onAllowedCommandsChanged(commands.toBundle());
+        } catch (RemoteException e) {
+            Log.w(TAG, "Controller is gone", e);
+            // TODO(jaewan): What to do when the controller is gone?
+        }
+    }
+
     public void sendCustomCommand(ControllerInfo controller, Command command, Bundle args,
             ResultReceiver receiver) {
         if (receiver != null && controller == null) {
@@ -906,11 +983,6 @@
         if (command == null) {
             throw new IllegalArgumentException("command shouldn't be null");
         }
-        final IMediaSession2Callback controllerBinder =
-                ControllerInfoImpl.from(controller).getControllerBinder();
-        if (getController(controllerBinder) == null) {
-            throw new IllegalArgumentException("Controller is gone");
-        }
         sendCustomCommandInternal(controller, command, args, receiver);
     }
 
@@ -926,11 +998,13 @@
 
     private void sendCustomCommandInternal(ControllerInfo controller, Command command, Bundle args,
             ResultReceiver receiver) {
-        final IMediaSession2Callback controllerBinder =
-                ControllerInfoImpl.from(controller).getControllerBinder();
+        final IMediaSession2Callback controllerBinder = getControllerBinderIfAble(controller);
+        if (controllerBinder == null) {
+            return;
+        }
         try {
             Bundle commandBundle = command.toBundle();
-            controllerBinder.sendCustomCommand(commandBundle, args, receiver);
+            controllerBinder.onCustomCommand(commandBundle, args, receiver);
         } catch (RemoteException e) {
             Log.w(TAG, "Controller is gone", e);
             // TODO(jaewan): What to do when the controller is gone?
@@ -943,10 +1017,12 @@
 
     public void notifySearchResultChanged(ControllerInfo controller, String query, int itemCount,
             Bundle extras) {
-        final IMediaSession2Callback callbackBinder =
-                ControllerInfoImpl.from(controller).getControllerBinder();
+        final IMediaSession2Callback controllerBinder = getControllerBinderIfAble(controller);
+        if (controllerBinder == null) {
+            return;
+        }
         try {
-            callbackBinder.onSearchResultChanged(query, itemCount, extras);
+            controllerBinder.onSearchResultChanged(query, itemCount, extras);
         } catch (RemoteException e) {
             Log.w(TAG, "Controller is gone", e);
             // TODO(jaewan): What to do when the controller is gone?
@@ -955,9 +1031,6 @@
 
     public void notifyChildrenChangedNotLocked(ControllerInfo controller, String parentId,
             int itemCount, Bundle extras) {
-        // TODO(jaewan): Handle when controller is disconnected and no longer valid.
-        //               Note: Commands may be sent while onConnected() is running. Should we also
-        //                     consider it as error?
         notifyChildrenChangedInternalNotLocked(controller, parentId, itemCount, extras);
     }
 
@@ -978,10 +1051,12 @@
                 return;
             }
         }
-        final IMediaSession2Callback callbackBinder =
-                ControllerInfoImpl.from(controller).getControllerBinder();
+        final IMediaSession2Callback controllerBinder = getControllerBinderIfAble(controller);
+        if (controller == null) {
+            return;
+        }
         try {
-            callbackBinder.onChildrenChanged(parentId, itemCount, extras);
+            controllerBinder.onChildrenChanged(parentId, itemCount, extras);
         } catch (RemoteException e) {
             // TODO(jaewan): Handle controller removed?
         }
diff --git a/packages/MediaComponents/src/com/android/media/MediaSessionService2Impl.java b/packages/MediaComponents/src/com/android/media/MediaSessionService2Impl.java
index 5bf67d2..6bd2b2a 100644
--- a/packages/MediaComponents/src/com/android/media/MediaSessionService2Impl.java
+++ b/packages/MediaComponents/src/com/android/media/MediaSessionService2Impl.java
@@ -40,7 +40,7 @@
 public class MediaSessionService2Impl implements MediaSessionService2Provider {
 
     private static final String TAG = "MPSessionService"; // to meet 23 char limit in Log tag
-    private static final boolean DEBUG = true; // TODO(jaewan): Change this.
+    private static final boolean DEBUG = true; // TODO(jaewan): Change this. (b/74094611)
 
     private final MediaSessionService2 mInstance;
     private final PlayerEventCallback mCallback = new SessionServiceEventCallback();
@@ -138,7 +138,7 @@
 
     private class SessionServiceEventCallback extends PlayerEventCallback {
         @Override
-        public void onPlayerStateChanged(MediaPlayerBase mpb, int state) {
+        public void onPlayerStateChanged(MediaPlayerBase player, int state) {
             // TODO: Implement this
             return;
         }
diff --git a/packages/MediaComponents/src/com/android/media/RoutePlayer.java b/packages/MediaComponents/src/com/android/media/RoutePlayer.java
new file mode 100644
index 0000000..9450d34
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/media/RoutePlayer.java
@@ -0,0 +1,169 @@
+/*
+ * 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.
+ */
+
+package com.android.media;
+
+import android.content.Context;
+import android.media.DataSourceDesc;
+import android.media.session.MediaSession;
+import android.media.session.PlaybackState;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.support.annotation.RequiresApi;
+
+import com.android.support.mediarouter.media.MediaItemStatus;
+import com.android.support.mediarouter.media.MediaRouter;
+import com.android.support.mediarouter.media.MediaSessionStatus;
+import com.android.support.mediarouter.media.RemotePlaybackClient;
+import com.android.support.mediarouter.media.RemotePlaybackClient.ItemActionCallback;
+import com.android.support.mediarouter.media.RemotePlaybackClient.SessionActionCallback;
+import com.android.support.mediarouter.media.RemotePlaybackClient.StatusCallback;
+
+import java.util.Map;
+
+@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
+public class RoutePlayer extends MediaSession.Callback {
+    public static final long PLAYBACK_ACTIONS = PlaybackState.ACTION_PAUSE
+            | PlaybackState.ACTION_PLAY | PlaybackState.ACTION_SEEK_TO
+            | PlaybackState.ACTION_FAST_FORWARD | PlaybackState.ACTION_REWIND;
+
+    private RemotePlaybackClient mClient;
+    private String mSessionId;
+    private String mItemId;
+    private PlayerEventCallback mCallback;
+
+    private StatusCallback mStatusCallback = new StatusCallback() {
+        @Override
+        public void onItemStatusChanged(Bundle data,
+                String sessionId, MediaSessionStatus sessionStatus,
+                String itemId, MediaItemStatus itemStatus) {
+            updateSessionStatus(sessionId, sessionStatus);
+            updateItemStatus(itemId, itemStatus);
+        }
+    };
+
+    public RoutePlayer(Context context, MediaRouter.RouteInfo route) {
+        mClient = new RemotePlaybackClient(context, route);
+        mClient.setStatusCallback(mStatusCallback);
+        mClient.startSession(null, new SessionActionCallback() {
+            @Override
+            public void onResult(Bundle data,
+                    String sessionId, MediaSessionStatus sessionStatus) {
+                updateSessionStatus(sessionId, sessionStatus);
+            }
+        });
+    }
+
+    @Override
+    public void onPlay() {
+        mClient.resume(null, new SessionActionCallback() {
+            @Override
+            public void onResult(Bundle data,
+                    String sessionId, MediaSessionStatus sessionStatus) {
+                updateSessionStatus(sessionId, sessionStatus);
+            }
+        });
+    }
+
+    @Override
+    public void onPause() {
+        mClient.pause(null, new SessionActionCallback() {
+            @Override
+            public void onResult(Bundle data,
+                    String sessionId, MediaSessionStatus sessionStatus) {
+                updateSessionStatus(sessionId, sessionStatus);
+            }
+        });
+    }
+
+    @Override
+    public void onSeekTo(long pos) {
+        mClient.seek(mItemId, pos, null, new ItemActionCallback() {
+            @Override
+            public void onResult(Bundle data,
+                    String sessionId, MediaSessionStatus sessionStatus,
+                    String itemId, MediaItemStatus itemStatus) {
+                updateSessionStatus(sessionId, sessionStatus);
+                updateItemStatus(itemId, itemStatus);
+            }
+        });
+    }
+
+    @Override
+    public void onStop() {
+        mClient.stop(null, new SessionActionCallback() {
+            @Override
+            public void onResult(Bundle data,
+                    String sessionId, MediaSessionStatus sessionStatus) {
+                updateSessionStatus(sessionId, sessionStatus);
+            }
+        });
+    }
+
+    public void setPlayerEventCallback(PlayerEventCallback callback) {
+        mCallback = callback;
+    }
+
+    public void openVideo(DataSourceDesc dsd) {
+        mClient.play(dsd.getUri(), "video/mp4", null, 0, null, new ItemActionCallback() {
+            @Override
+            public void onResult(Bundle data,
+                    String sessionId, MediaSessionStatus sessionStatus,
+                    String itemId, MediaItemStatus itemStatus) {
+                updateSessionStatus(sessionId, sessionStatus);
+                updateItemStatus(itemId, itemStatus);
+                playInternal(dsd.getUri());
+            }
+        });
+    }
+
+    public void release() {
+        if (mClient != null) {
+            mClient.release();
+            mClient = null;
+        }
+        if (mCallback != null) {
+            mCallback = null;
+        }
+    }
+
+    private void playInternal(Uri uri) {
+        mClient.play(uri, "video/mp4", null, 0, null, new ItemActionCallback() {
+            @Override
+            public void onResult(Bundle data,
+                    String sessionId, MediaSessionStatus sessionStatus,
+                    String itemId, MediaItemStatus itemStatus) {
+                updateSessionStatus(sessionId, sessionStatus);
+                updateItemStatus(itemId, itemStatus);
+            }
+        });
+    }
+
+    private void updateSessionStatus(String sessionId, MediaSessionStatus sessionStatus) {
+        mSessionId = sessionId;
+    }
+
+    private void updateItemStatus(String itemId, MediaItemStatus itemStatus) {
+        mItemId = itemId;
+        if (itemStatus == null || mCallback == null) return;
+        mCallback.onPlayerStateChanged(itemStatus);
+    }
+
+    public static abstract class PlayerEventCallback {
+        public void onPlayerStateChanged(MediaItemStatus itemStatus) { }
+    }
+}
diff --git a/packages/MediaComponents/src/com/android/media/SessionToken2Impl.java b/packages/MediaComponents/src/com/android/media/SessionToken2Impl.java
index b1ba8aa..3965ccb 100644
--- a/packages/MediaComponents/src/com/android/media/SessionToken2Impl.java
+++ b/packages/MediaComponents/src/com/android/media/SessionToken2Impl.java
@@ -100,7 +100,7 @@
 
     public SessionToken2Impl(Context context, int uid, int type,
             String packageName, String serviceName, String id, IMediaSession2 sessionBinder) {
-        // TODO(jaewan): Add sanity check
+        // TODO(jaewan): Add sanity check (b/73863865)
         mUid = uid;
         mType = type;
         mPackageName = packageName;
@@ -207,9 +207,6 @@
         if (TextUtils.isEmpty(packageName) || id == null) {
             throw new IllegalArgumentException("Package name nor ID cannot be null.");
         }
-        // TODO(jaewan): Revisit here when we add connection callback to the session for individual
-        //               controller's permission check. With it, sessionBinder should be available
-        //               if and only if for session, not session service.
         return new SessionToken2Impl(context, uid, type, packageName, serviceName, id,
                 sessionBinder != null ? IMediaSession2.Stub.asInterface(sessionBinder) : null)
                 .getInstance();
diff --git a/packages/MediaComponents/src/com/android/media/update/ApiFactory.java b/packages/MediaComponents/src/com/android/media/update/ApiFactory.java
index b0d435d..07b8788 100644
--- a/packages/MediaComponents/src/com/android/media/update/ApiFactory.java
+++ b/packages/MediaComponents/src/com/android/media/update/ApiFactory.java
@@ -32,6 +32,7 @@
 import android.media.MediaLibraryService2.MediaLibrarySession.MediaLibrarySessionCallback;
 import android.media.MediaMetadata2;
 import android.media.MediaPlayerBase;
+import android.media.MediaPlaylistAgent;
 import android.media.MediaSession2;
 import android.media.MediaSession2.Command;
 import android.media.MediaSession2.CommandGroup;
@@ -50,6 +51,7 @@
 import android.media.update.MediaItem2Provider;
 import android.media.update.MediaLibraryService2Provider.LibraryRootProvider;
 import android.media.update.MediaMetadata2Provider;
+import android.media.update.MediaPlaylistAgentProvider;
 import android.media.update.MediaSession2Provider;
 import android.media.update.MediaSession2Provider.BuilderBaseProvider;
 import android.media.update.MediaSession2Provider.CommandButtonProvider.BuilderProvider;
@@ -76,6 +78,7 @@
 import com.android.media.MediaLibraryService2Impl;
 import com.android.media.MediaLibraryService2Impl.LibraryRootImpl;
 import com.android.media.MediaMetadata2Impl;
+import com.android.media.MediaPlaylistAgentImpl;
 import com.android.media.MediaSession2Impl;
 import com.android.media.MediaSession2Impl.PlaylistParamsImpl;
 import com.android.media.MediaSessionService2Impl;
@@ -226,9 +229,9 @@
     }
 
     @Override
-    public MediaItem2Provider createMediaItem2(Context context, MediaItem2 instance,
-            String mediaId, DataSourceDesc dsd, MediaMetadata2 metadata, int flags) {
-        return new MediaItem2Impl(context, instance, mediaId, dsd, metadata, flags);
+    public MediaItem2Provider.BuilderProvider createMediaItem2Builder(
+            Context context, MediaItem2.Builder instance, int flags) {
+        return new MediaItem2Impl.BuilderImpl(context, instance, flags);
     }
 
     @Override
@@ -249,14 +252,14 @@
 
     @Override
     public MediaMetadata2Provider.BuilderProvider createMediaMetadata2Builder(
-            Context context, MediaMetadata2.Builder builder) {
-        return new MediaMetadata2Impl.BuilderImpl(context, builder);
+            Context context, MediaMetadata2.Builder instance) {
+        return new MediaMetadata2Impl.BuilderImpl(context, instance);
     }
 
     @Override
     public MediaMetadata2Provider.BuilderProvider createMediaMetadata2Builder(
-            Context context, MediaMetadata2.Builder builder, MediaMetadata2 source) {
-        return new MediaMetadata2Impl.BuilderImpl(context, builder, source);
+            Context context, MediaMetadata2.Builder instance, MediaMetadata2 source) {
+        return new MediaMetadata2Impl.BuilderImpl(context, instance, source);
     }
 
     @Override
@@ -301,4 +304,10 @@
     public PlaybackState2 fromBundle_PlaybackState2(Context context, Bundle bundle) {
         return PlaybackState2Impl.fromBundle(context, bundle);
     }
+
+    @Override
+    public MediaPlaylistAgentProvider createMediaPlaylistAgent(Context context,
+            MediaPlaylistAgent instance) {
+        return new MediaPlaylistAgentImpl(context, instance);
+    }
 }
diff --git a/packages/MediaComponents/src/com/android/widget/MediaControlView2Impl.java b/packages/MediaComponents/src/com/android/widget/MediaControlView2Impl.java
index c4787d1..69febc2 100644
--- a/packages/MediaComponents/src/com/android/widget/MediaControlView2Impl.java
+++ b/packages/MediaComponents/src/com/android/widget/MediaControlView2Impl.java
@@ -31,6 +31,7 @@
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
+import android.widget.AdapterView;
 import android.widget.BaseAdapter;
 import android.widget.Button;
 import android.widget.ImageButton;
@@ -64,19 +65,30 @@
     static final String ARGUMENT_KEY_FULLSCREEN = "fullScreen";
 
     // TODO: Move these constants to public api to support custom video view.
-    static final String KEY_STATE_CONTAINS_SUBTITLE = "StateContainsSubtitle";
-    static final String EVENT_UPDATE_SUBTITLE_STATUS = "UpdateSubtitleStatus";
+    // TODO: Combine these constants into one regarding TrackInfo.
+    static final String KEY_VIDEO_TRACK_COUNT = "VideoTrackCount";
+    static final String KEY_AUDIO_TRACK_COUNT = "AudioTrackCount";
+    static final String KEY_SUBTITLE_TRACK_COUNT = "SubtitleTrackCount";
+    static final String EVENT_UPDATE_TRACK_STATUS = "UpdateTrackStatus";
 
     // TODO: Remove this once integrating with MediaSession2 & MediaMetadata2
     static final String KEY_STATE_IS_ADVERTISEMENT = "MediaTypeAdvertisement";
     static final String EVENT_UPDATE_MEDIA_TYPE_STATUS = "UpdateMediaTypeStatus";
 
+    // String for receiving command to show subtitle from MediaSession.
+    static final String COMMAND_SHOW_SUBTITLE = "showSubtitle";
+    // String for receiving command to hide subtitle from MediaSession.
+    static final String COMMAND_HIDE_SUBTITLE = "hideSubtitle";
+    // TODO: remove once the implementation is revised
+    public static final String COMMAND_SET_FULLSCREEN = "setFullscreen";
+
     private static final int MAX_PROGRESS = 1000;
     private static final int DEFAULT_PROGRESS_UPDATE_TIME_MS = 1000;
     private static final int REWIND_TIME_MS = 10000;
     private static final int FORWARD_TIME_MS = 30000;
     private static final int AD_SKIP_WAIT_TIME_MS = 5000;
     private static final int RESOURCE_NON_EXISTENT = -1;
+    private static final String RESOURCE_EMPTY = "";
 
     private Resources mResources;
     private MediaController mController;
@@ -92,13 +104,15 @@
     private int mDuration;
     private int mPrevState;
     private int mPrevLeftBarWidth;
+    private int mVideoTrackCount;
+    private int mAudioTrackCount;
+    private int mSubtitleTrackCount;
     private long mPlaybackActions;
     private boolean mDragging;
     private boolean mIsFullScreen;
     private boolean mOverflowExpanded;
     private boolean mIsStopped;
     private boolean mSubtitleIsEnabled;
-    private boolean mContainsSubtitle;
     private boolean mSeekAvailable;
     private boolean mIsAdvertisement;
     private ImageButton mPlayPauseButton;
@@ -119,11 +133,17 @@
     private ImageButton mAspectRationButton;
     private ImageButton mSettingsButton;
 
+    private ListView mSettingsListView;
     private PopupWindow mSettingsWindow;
     private SettingsAdapter mSettingsAdapter;
-    private List<Integer> mSettingsMainTextIdsList;
-    private List<Integer> mSettingsSubTextIdsList;
+
+    private List<String> mSettingsMainTextsList;
+    private List<String> mSettingsSubTextsList;
     private List<Integer> mSettingsIconIdsList;
+    private List<String> mSubtitleDescriptionsList;
+    private List<String> mAudioTrackList;
+    private List<String> mVideoQualityList;
+    private List<String> mPlaybackSpeedTextIdsList;
 
     private CharSequence mPlayDescription;
     private CharSequence mPauseDescription;
@@ -211,7 +231,7 @@
                 }
                 break;
             case MediaControlView2.BUTTON_SUBTITLE:
-                if (mSubtitleButton != null && mContainsSubtitle) {
+                if (mSubtitleButton != null && mSubtitleTrackCount > 0) {
                     mSubtitleButton.setVisibility(visibility);
                 }
                 break;
@@ -470,17 +490,17 @@
         mAdRemainingView = v.findViewById(R.id.ad_remaining);
         mAdExternalLink = v.findViewById(R.id.ad_external_link);
 
-        populateResourceIds();
-        ListView settingsListView = (ListView) ApiHelper.inflateLibLayout(mInstance.getContext(),
+        initializeSettingsLists();
+        mSettingsListView = (ListView) ApiHelper.inflateLibLayout(mInstance.getContext(),
                 R.layout.settings_list);
-        mSettingsAdapter = new SettingsAdapter(mSettingsMainTextIdsList, mSettingsSubTextIdsList,
-                mSettingsIconIdsList, true);
-        settingsListView.setAdapter(mSettingsAdapter);
-
+        mSettingsAdapter = new SettingsAdapter(mSettingsMainTextsList, mSettingsSubTextsList,
+                mSettingsIconIdsList, false);
+        mSettingsListView.setAdapter(mSettingsAdapter);
+        mSettingsListView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
+        mSettingsListView.setOnItemClickListener(mSettingsItemClickListener);
         int width = mResources.getDimensionPixelSize(R.dimen.MediaControlView2_settings_width);
-        mSettingsWindow = new PopupWindow(settingsListView, width,
+        mSettingsWindow = new PopupWindow(mSettingsListView, width,
                 ViewGroup.LayoutParams.WRAP_CONTENT, true);
-        // TODO: add listener to list view to allow each item to be selected.
     }
 
     /**
@@ -730,12 +750,12 @@
             if (!mSubtitleIsEnabled) {
                 mSubtitleButton.setImageDrawable(
                         mResources.getDrawable(R.drawable.ic_media_subtitle_enabled, null));
-                mController.sendCommand(MediaControlView2.COMMAND_SHOW_SUBTITLE, null, null);
+                mController.sendCommand(MediaControlView2Impl.COMMAND_SHOW_SUBTITLE, null, null);
                 mSubtitleIsEnabled = true;
             } else {
                 mSubtitleButton.setImageDrawable(
                         mResources.getDrawable(R.drawable.ic_media_subtitle_disabled, null));
-                mController.sendCommand(MediaControlView2.COMMAND_HIDE_SUBTITLE, null, null);
+                mController.sendCommand(MediaControlView2Impl.COMMAND_HIDE_SUBTITLE, null, null);
                 mSubtitleIsEnabled = false;
             }
         }
@@ -755,7 +775,7 @@
             }
             Bundle args = new Bundle();
             args.putBoolean(ARGUMENT_KEY_FULLSCREEN, isEnteringFullScreen);
-            mController.sendCommand(MediaControlView2.COMMAND_SET_FULLSCREEN, args, null);
+            mController.sendCommand(MediaControlView2Impl.COMMAND_SET_FULLSCREEN, args, null);
 
             mIsFullScreen = isEnteringFullScreen;
         }
@@ -780,11 +800,72 @@
     private final View.OnClickListener mSettingsButtonListener = new View.OnClickListener() {
         @Override
         public void onClick(View v) {
+            mSettingsAdapter = new SettingsAdapter(mSettingsMainTextsList,
+                    mSettingsSubTextsList, mSettingsIconIdsList, false);
+            mSettingsListView.setAdapter(mSettingsAdapter);
             int itemHeight = mResources.getDimensionPixelSize(
                     R.dimen.MediaControlView2_settings_height);
             int totalHeight = mSettingsAdapter.getCount() * itemHeight;
             int margin = (-1) * mResources.getDimensionPixelSize(
                     R.dimen.MediaControlView2_settings_offset);
+            mSettingsWindow.dismiss();
+            mSettingsWindow.showAsDropDown(mInstance, margin, margin - totalHeight,
+                    Gravity.BOTTOM | Gravity.RIGHT);
+        }
+    };
+
+    private final AdapterView.OnItemClickListener mSettingsItemClickListener
+            = new AdapterView.OnItemClickListener() {
+        @Override
+        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+            switch (position) {
+                // change to identifiers
+                case 0:
+                    // TODO: add additional subtitle track details
+                    mSubtitleDescriptionsList = new ArrayList<String>();
+                    mSubtitleDescriptionsList.add(mResources.getString(
+                            R.string.MediaControlView2_subtitle_off_text));
+                    for (int i = 0; i < mSubtitleTrackCount; i++) {
+                        String track = mResources.getString(
+                                R.string.MediaControlView2_subtitle_track_number_text, i + 1);
+                        mSubtitleDescriptionsList.add(track);
+                    }
+                    mSettingsAdapter = new SettingsAdapter(mSubtitleDescriptionsList, null,
+                            null, true);
+                    break;
+                case 1:
+                    // TODO: add additional audio track details
+                    mAudioTrackList = new ArrayList<String>();
+                    mAudioTrackList.add(mResources.getString(
+                            R.string.MediaControlView2_audio_track_none_text));
+                    for (int i = 0; i < mAudioTrackCount; i++) {
+                        String track = mResources.getString(
+                                R.string.MediaControlView2_audio_track_number_text, i + 1);
+                        mAudioTrackList.add(track);
+                    }
+                    mSettingsAdapter = new SettingsAdapter(mAudioTrackList, null,
+                            null, true);
+                    break;
+                case 2:
+                    // TODO: add support for multiple quality video tracks
+                    mSettingsAdapter = new SettingsAdapter(mVideoQualityList, null,
+                            null, true);
+                    break;
+                case 3:
+                    // TODO: implement code to reflect change in speed.
+                    mSettingsAdapter = new SettingsAdapter(mPlaybackSpeedTextIdsList, null,
+                            null, true);
+                    break;
+                default:
+                    return;
+            }
+            mSettingsListView.setAdapter(mSettingsAdapter);
+            int itemHeight = mResources.getDimensionPixelSize(
+                    R.dimen.MediaControlView2_settings_height);
+            int totalHeight = mSettingsAdapter.getCount() * itemHeight;
+            int margin = (-1) * mResources.getDimensionPixelSize(
+                    R.dimen.MediaControlView2_settings_offset);
+            mSettingsWindow.dismiss();
             mSettingsWindow.showAsDropDown(mInstance, margin, margin - totalHeight,
                     Gravity.BOTTOM | Gravity.RIGHT);
         }
@@ -871,29 +952,79 @@
         }
     }
 
-    private void populateResourceIds() {
-        // TODO: create record class for storing this info
-        mSettingsMainTextIdsList = new ArrayList<Integer>();
-        mSettingsMainTextIdsList.add(R.string.MediaControlView2_cc_text);
-        mSettingsMainTextIdsList.add(R.string.MediaControlView2_audio_track_text);
-        mSettingsMainTextIdsList.add(R.string.MediaControlView2_video_quality_text);
-        mSettingsMainTextIdsList.add(R.string.MediaControlView2_playback_speed_text);
-        mSettingsMainTextIdsList.add(R.string.MediaControlView2_help_text);
+    private void initializeSettingsLists() {
+        if (mSettingsMainTextsList == null) {
+            mSettingsMainTextsList = new ArrayList<String>();
+            mSettingsMainTextsList.add(
+                    mResources.getString(R.string.MediaControlView2_subtitle_text));
+            mSettingsMainTextsList.add(
+                    mResources.getString(R.string.MediaControlView2_audio_track_text));
+            mSettingsMainTextsList.add(
+                    mResources.getString(R.string.MediaControlView2_video_quality_text));
+            mSettingsMainTextsList.add(
+                    mResources.getString(R.string.MediaControlView2_playback_speed_text));
+            mSettingsMainTextsList.add(
+                    mResources.getString(R.string.MediaControlView2_help_text));
+        }
 
         // TODO: Update the following code to be dynamic.
-        mSettingsSubTextIdsList = new ArrayList<Integer>();
-        mSettingsSubTextIdsList.add(R.string.MediaControlView2_cc_text);
-        mSettingsSubTextIdsList.add(R.string.MediaControlView2_audio_track_text);
-        mSettingsSubTextIdsList.add(R.string.MediaControlView2_video_quality_text);
-        mSettingsSubTextIdsList.add(R.string.MediaControlView2_playback_speed_text);
-        mSettingsSubTextIdsList.add(RESOURCE_NON_EXISTENT);
+        if (mSettingsSubTextsList == null) {
+            mSettingsSubTextsList = new ArrayList<String>();
+            mSettingsSubTextsList.add(
+                    mResources.getString(R.string.MediaControlView2_subtitle_off_text));
+            mSettingsSubTextsList.add(
+                    mResources.getString(R.string.MediaControlView2_audio_track_none_text));
+            mSettingsSubTextsList.add(
+                    mResources.getString(R.string.MediaControlView2_video_quality_auto_text));
+            mSettingsSubTextsList.add(
+                    mResources.getString(R.string.MediaControlView2_playback_speed_1x_text));
+            mSettingsSubTextsList.add(RESOURCE_EMPTY);
+        }
 
-        mSettingsIconIdsList = new ArrayList<Integer>();
-        mSettingsIconIdsList.add(R.drawable.ic_closed_caption_off);
-        mSettingsIconIdsList.add(R.drawable.ic_audiotrack);
-        mSettingsIconIdsList.add(R.drawable.ic_high_quality);
-        mSettingsIconIdsList.add(R.drawable.ic_play_circle_filled);
-        mSettingsIconIdsList.add(R.drawable.ic_help);
+        if (mSettingsIconIdsList == null) {
+            mSettingsIconIdsList = new ArrayList<Integer>();
+            mSettingsIconIdsList.add(R.drawable.ic_closed_caption_off);
+            mSettingsIconIdsList.add(R.drawable.ic_audiotrack);
+            mSettingsIconIdsList.add(R.drawable.ic_high_quality);
+            mSettingsIconIdsList.add(R.drawable.ic_play_circle_filled);
+            mSettingsIconIdsList.add(R.drawable.ic_help);
+        }
+
+        if (mSubtitleDescriptionsList == null) {
+            mSubtitleDescriptionsList = new ArrayList<String>();
+            mSubtitleDescriptionsList.add(
+                    mResources.getString(R.string.MediaControlView2_subtitle_off_text));
+        }
+
+        if (mAudioTrackList == null) {
+            mAudioTrackList = new ArrayList<String>();
+            mAudioTrackList.add(
+                    mResources.getString(R.string.MediaControlView2_audio_track_none_text));
+        }
+
+        if (mVideoQualityList == null) {
+            mVideoQualityList = new ArrayList<String>();
+            mVideoQualityList.add(
+                    mResources.getString(R.string.MediaControlView2_video_quality_auto_text));
+        }
+
+        if (mPlaybackSpeedTextIdsList == null) {
+            mPlaybackSpeedTextIdsList = new ArrayList<String>();
+            mPlaybackSpeedTextIdsList.add(
+                    mResources.getString(R.string.MediaControlView2_playback_speed_0_25x_text));
+            mPlaybackSpeedTextIdsList.add(
+                    mResources.getString(R.string.MediaControlView2_playback_speed_0_5x_text));
+            mPlaybackSpeedTextIdsList.add(
+                    mResources.getString(R.string.MediaControlView2_playback_speed_0_75x_text));
+            mPlaybackSpeedTextIdsList.add(
+                    mResources.getString(R.string.MediaControlView2_playback_speed_1x_text));
+            mPlaybackSpeedTextIdsList.add(
+                    mResources.getString(R.string.MediaControlView2_playback_speed_1_25x_text));
+            mPlaybackSpeedTextIdsList.add(
+                    mResources.getString(R.string.MediaControlView2_playback_speed_1_5x_text));
+            mPlaybackSpeedTextIdsList.add(
+                    mResources.getString(R.string.MediaControlView2_playback_speed_2x_text));
+        }
     }
 
     private class MediaControllerCallback extends MediaController.Callback {
@@ -988,45 +1119,48 @@
 
         @Override
         public void onSessionEvent(String event, Bundle extras) {
-            if (event.equals(EVENT_UPDATE_SUBTITLE_STATUS)) {
-                boolean newSubtitleStatus = extras.getBoolean(KEY_STATE_CONTAINS_SUBTITLE);
-                if (newSubtitleStatus != mContainsSubtitle) {
-                    if (newSubtitleStatus) {
+            switch (event) {
+                case EVENT_UPDATE_TRACK_STATUS:
+                    mVideoTrackCount = extras.getInt(KEY_VIDEO_TRACK_COUNT);
+                    mAudioTrackCount = extras.getInt(KEY_AUDIO_TRACK_COUNT);
+                    int newSubtitleTrackCount = extras.getInt(KEY_SUBTITLE_TRACK_COUNT);
+                    if (newSubtitleTrackCount > 0) {
                         mSubtitleButton.clearColorFilter();
                         mSubtitleButton.setEnabled(true);
                     } else {
                         mSubtitleButton.setColorFilter(R.color.gray);
                         mSubtitleButton.setEnabled(false);
                     }
-                    mContainsSubtitle = newSubtitleStatus;
-                }
-            } else if (event.equals(EVENT_UPDATE_MEDIA_TYPE_STATUS)) {
-                boolean newStatus = extras.getBoolean(KEY_STATE_IS_ADVERTISEMENT);
-                if (newStatus != mIsAdvertisement) {
-                    mIsAdvertisement = newStatus;
-                    updateLayout();
-                }
+                    mSubtitleTrackCount = newSubtitleTrackCount;
+                    break;
+                case EVENT_UPDATE_MEDIA_TYPE_STATUS:
+                    boolean newStatus = extras.getBoolean(KEY_STATE_IS_ADVERTISEMENT);
+                    if (newStatus != mIsAdvertisement) {
+                        mIsAdvertisement = newStatus;
+                        updateLayout();
+                    }
+                    break;
             }
         }
     }
 
     private class SettingsAdapter extends BaseAdapter {
-        List<Integer> mMainTextIds;
-        List<Integer> mSubTextIds;
         List<Integer> mIconIds;
+        List<String> mMainTexts;
+        List<String> mSubTexts;
         boolean mIsCheckable;
 
-        public SettingsAdapter(List<Integer> mainTextIds, @Nullable List<Integer> subTextIds,
+        public SettingsAdapter(List<String> mainTexts, @Nullable List<String> subTexts,
                 @Nullable List<Integer> iconIds, boolean isCheckable) {
-            mMainTextIds = mainTextIds;
-            mSubTextIds = subTextIds;
+            mMainTexts = mainTexts;
+            mSubTexts = subTexts;
             mIconIds = iconIds;
             mIsCheckable = isCheckable;
         }
 
         @Override
         public int getCount() {
-            return (mMainTextIds == null) ? 0 : mMainTextIds.size();
+            return (mMainTexts == null) ? 0 : mMainTexts.size();
         }
 
         @Override
@@ -1053,15 +1187,15 @@
             ImageView checkView = (ImageView) row.findViewById(R.id.check);
 
             // Set main text
-            mainTextView.setText(mResources.getString(mMainTextIds.get(position)));
+            mainTextView.setText(mMainTexts.get(position));
 
             // Remove sub text and center the main text if sub texts do not exist at all or the sub
-            // text at this particular position is set to RESOURCE_NON_EXISTENT.
-            if (mSubTextIds == null || mSubTextIds.get(position) == RESOURCE_NON_EXISTENT) {
+            // text at this particular position is empty.
+            if (mSubTexts == null || mSubTexts.get(position) == RESOURCE_EMPTY) {
                 subTextView.setVisibility(View.GONE);
             } else {
                 // Otherwise, set sub text.
-                subTextView.setText(mResources.getString(mSubTextIds.get(position)));
+                subTextView.setText(mSubTexts.get(position));
             }
 
             // Remove main icon and set visibility to gone if icons are set to null or the icon at
diff --git a/packages/MediaComponents/src/com/android/widget/VideoSurfaceView.java b/packages/MediaComponents/src/com/android/widget/VideoSurfaceView.java
index 8577123..fc92e85 100644
--- a/packages/MediaComponents/src/com/android/widget/VideoSurfaceView.java
+++ b/packages/MediaComponents/src/com/android/widget/VideoSurfaceView.java
@@ -18,7 +18,7 @@
 
 import android.content.Context;
 import android.graphics.Rect;
-import android.media.MediaPlayer;
+import android.media.MediaPlayer2;
 import android.support.annotation.NonNull;
 import android.util.AttributeSet;
 import android.util.Log;
@@ -33,7 +33,7 @@
     private static final boolean DEBUG = true; // STOPSHIP: Log.isLoggable(TAG, Log.DEBUG);
     private SurfaceHolder mSurfaceHolder = null;
     private SurfaceListener mSurfaceListener = null;
-    private MediaPlayer mMediaPlayer;
+    private MediaPlayer2 mMediaPlayer;
     // A flag to indicate taking over other view should be proceed.
     private boolean mIsTakingOverOldView;
     private VideoViewInterface mOldView;
@@ -62,7 +62,7 @@
     ////////////////////////////////////////////////////
 
     @Override
-    public boolean assignSurfaceToMediaPlayer(MediaPlayer mp) {
+    public boolean assignSurfaceToMediaPlayer(MediaPlayer2 mp) {
         Log.d(TAG, "assignSurfaceToMediaPlayer(): mSurfaceHolder: " + mSurfaceHolder);
         if (mp == null || !hasAvailableSurface()) {
             return false;
@@ -82,7 +82,7 @@
     }
 
     @Override
-    public void setMediaPlayer(MediaPlayer mp) {
+    public void setMediaPlayer(MediaPlayer2 mp) {
         mMediaPlayer = mp;
         if (mIsTakingOverOldView) {
             takeOver(mOldView);
diff --git a/packages/MediaComponents/src/com/android/widget/VideoTextureView.java b/packages/MediaComponents/src/com/android/widget/VideoTextureView.java
index 69a4ebe..024a3aa 100644
--- a/packages/MediaComponents/src/com/android/widget/VideoTextureView.java
+++ b/packages/MediaComponents/src/com/android/widget/VideoTextureView.java
@@ -18,7 +18,7 @@
 
 import android.content.Context;
 import android.graphics.SurfaceTexture;
-import android.media.MediaPlayer;
+import android.media.MediaPlayer2;
 import android.support.annotation.NonNull;
 import android.support.annotation.RequiresApi;
 import android.util.AttributeSet;
@@ -38,7 +38,7 @@
     private SurfaceTexture mSurfaceTexture;
     private Surface mSurface;
     private SurfaceListener mSurfaceListener;
-    private MediaPlayer mMediaPlayer;
+    private MediaPlayer2 mMediaPlayer;
     // A flag to indicate taking over other view should be proceed.
     private boolean mIsTakingOverOldView;
     private VideoViewInterface mOldView;
@@ -66,7 +66,7 @@
     ////////////////////////////////////////////////////
 
     @Override
-    public boolean assignSurfaceToMediaPlayer(MediaPlayer mp) {
+    public boolean assignSurfaceToMediaPlayer(MediaPlayer2 mp) {
         Log.d(TAG, "assignSurfaceToMediaPlayer(): mSurfaceTexture: " + mSurfaceTexture);
         if (mp == null || !hasAvailableSurface()) {
             // Surface is not ready.
@@ -87,7 +87,7 @@
     }
 
     @Override
-    public void setMediaPlayer(MediaPlayer mp) {
+    public void setMediaPlayer(MediaPlayer2 mp) {
         mMediaPlayer = mp;
         if (mIsTakingOverOldView) {
             takeOver(mOldView);
diff --git a/packages/MediaComponents/src/com/android/widget/VideoView2Impl.java b/packages/MediaComponents/src/com/android/widget/VideoView2Impl.java
index cdb1470..0bb659d 100644
--- a/packages/MediaComponents/src/com/android/widget/VideoView2Impl.java
+++ b/packages/MediaComponents/src/com/android/widget/VideoView2Impl.java
@@ -16,16 +16,15 @@
 
 package com.android.widget;
 
-import android.annotation.NonNull;
 import android.content.Context;
-import android.content.res.Resources;
 import android.media.AudioAttributes;
 import android.media.AudioFocusRequest;
 import android.media.AudioManager;
 import android.media.DataSourceDesc;
 import android.media.MediaMetadata;
-import android.media.MediaPlayer;
-import android.media.MediaPlayerBase;
+import android.media.MediaPlayer2;
+import android.media.MediaPlayer2.MediaPlayer2EventCallback;
+import android.media.MediaPlayer2Impl;
 import android.media.Cea708CaptionRenderer;
 import android.media.ClosedCaptionRenderer;
 import android.media.MediaItem2;
@@ -45,6 +44,7 @@
 import android.media.update.VideoView2Provider;
 import android.media.update.ViewGroupProvider;
 import android.net.Uri;
+import android.os.AsyncTask;
 import android.os.Bundle;
 import android.os.ResultReceiver;
 import android.support.annotation.Nullable;
@@ -58,15 +58,12 @@
 import android.widget.MediaControlView2;
 import android.widget.VideoView2;
 
-import com.android.media.update.ApiHelper;
-import com.android.media.update.R;
+import com.android.media.RoutePlayer;
+import com.android.support.mediarouter.media.MediaItemStatus;
+import com.android.support.mediarouter.media.MediaControlIntent;
 import com.android.support.mediarouter.media.MediaRouter;
 import com.android.support.mediarouter.media.MediaRouteSelector;
-import com.android.support.mediarouter.media.RemotePlaybackClient;
-import com.android.support.mediarouter.media.MediaItemStatus;
-import com.android.support.mediarouter.media.MediaSessionStatus;
 
-import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
@@ -103,13 +100,11 @@
     private VideoTextureView mTextureView;
     private VideoSurfaceView mSurfaceView;
 
-    private MediaPlayer mMediaPlayer;
+    private MediaPlayer2 mMediaPlayer;
+    private DataSourceDesc mDsd;
     private MediaControlView2 mMediaControlView;
     private MediaSession mMediaSession;
     private MediaController mMediaController;
-    private MediaSession.Callback mRouteSessionCallback = new RouteSessionCallback();
-    private MediaRouter mMediaRouter;
-    private MediaRouteSelector mRouteSelector;
     private Metadata mMetadata;
     private MediaMetadata2 mMediaMetadata;
     private boolean mNeedUpdateMediaType;
@@ -126,18 +121,96 @@
     private int mVideoWidth;
     private int mVideoHeight;
 
+    private ArrayList<Integer> mVideoTrackIndices;
+    private ArrayList<Integer> mAudioTrackIndices;
     private ArrayList<Integer> mSubtitleTrackIndices;
+
+    // selected video/audio/subtitle track index as MediaPlayer2 returns
+    private int mSelectedVideoTrackIndex;
+    private int mSelectedAudioTrackIndex;
+    private int mSelectedSubtitleTrackIndex;
+
     private SubtitleView mSubtitleView;
     private boolean mSubtitleEnabled;
-    private int mSelectedTrackIndex;  // selected subtitle track index as MediaPlayer2 returns
 
     private float mSpeed;
     // TODO: Remove mFallbackSpeed when integration with MediaPlayer2's new setPlaybackParams().
     // Refer: https://docs.google.com/document/d/1nzAfns6i2hJ3RkaUre3QMT6wsDedJ5ONLiA_OOBFFX8/edit
     private float mFallbackSpeed;  // keep the original speed before 'pause' is called.
-
     private long mShowControllerIntervalMs;
 
+    private MediaRouter mMediaRouter;
+    private MediaRouteSelector mRouteSelector;
+    private MediaRouter.RouteInfo mRoute;
+    private RoutePlayer mRoutePlayer;
+
+    private final MediaRouter.Callback mRouterCallback = new MediaRouter.Callback() {
+        @Override
+        public void onRouteSelected(MediaRouter router, MediaRouter.RouteInfo route) {
+            if (route.supportsControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)) {
+                // Stop local playback (if necessary)
+                resetPlayer();
+                mRoute = route;
+                mRoutePlayer = new RoutePlayer(mInstance.getContext(), route);
+                mRoutePlayer.setPlayerEventCallback(new RoutePlayer.PlayerEventCallback() {
+                    @Override
+                    public void onPlayerStateChanged(MediaItemStatus itemStatus) {
+                        PlaybackState.Builder psBuilder = new PlaybackState.Builder();
+                        psBuilder.setActions(RoutePlayer.PLAYBACK_ACTIONS);
+                        long position = itemStatus.getContentPosition();
+                        switch (itemStatus.getPlaybackState()) {
+                            case MediaItemStatus.PLAYBACK_STATE_PENDING:
+                                psBuilder.setState(PlaybackState.STATE_NONE, position, 0);
+                                mCurrentState = STATE_IDLE;
+                                break;
+                            case MediaItemStatus.PLAYBACK_STATE_PLAYING:
+                                psBuilder.setState(PlaybackState.STATE_PLAYING, position, 1);
+                                mCurrentState = STATE_PLAYING;
+                                break;
+                            case MediaItemStatus.PLAYBACK_STATE_PAUSED:
+                                psBuilder.setState(PlaybackState.STATE_PAUSED, position, 0);
+                                mCurrentState = STATE_PAUSED;
+                                break;
+                            case MediaItemStatus.PLAYBACK_STATE_BUFFERING:
+                                psBuilder.setState(PlaybackState.STATE_BUFFERING, position, 0);
+                                mCurrentState = STATE_PAUSED;
+                                break;
+                            case MediaItemStatus.PLAYBACK_STATE_FINISHED:
+                                psBuilder.setState(PlaybackState.STATE_STOPPED, position, 0);
+                                mCurrentState = STATE_PLAYBACK_COMPLETED;
+                                break;
+                        }
+
+                        PlaybackState pbState = psBuilder.build();
+                        mMediaSession.setPlaybackState(pbState);
+
+                        MediaMetadata.Builder mmBuilder = new MediaMetadata.Builder();
+                        mmBuilder.putLong(MediaMetadata.METADATA_KEY_DURATION,
+                                itemStatus.getContentDuration());
+                        mMediaSession.setMetadata(mmBuilder.build());
+                    }
+                });
+                // Start remote playback (if necessary)
+                mRoutePlayer.openVideo(mDsd);
+            }
+        }
+
+        @Override
+        public void onRouteUnselected(MediaRouter router, MediaRouter.RouteInfo route, int reason) {
+            if (mRoute != null && mRoutePlayer != null) {
+                mRoutePlayer.release();
+                mRoutePlayer = null;
+            }
+            if (mRoute == route) {
+                mRoute = null;
+            }
+            if (reason != MediaRouter.UNSELECT_REASON_ROUTE_CHANGED) {
+                // TODO: Resume local playback  (if necessary)
+                openVideo(mDsd);
+            }
+        }
+    };
+
     public VideoView2Impl(VideoView2 instance,
             ViewGroupProvider superProvider, ViewGroupProvider privateProvider) {
         super(instance, superProvider, privateProvider);
@@ -150,7 +223,7 @@
         mVideoHeight = 0;
         mSpeed = 1.0f;
         mFallbackSpeed = mSpeed;
-        mSelectedTrackIndex = INVALID_TRACK_INDEX;
+        mSelectedSubtitleTrackIndex = INVALID_TRACK_INDEX;
         // TODO: add attributes to get this value.
         mShowControllerIntervalMs = DEFAULT_SHOW_CONTROLLER_INTERVAL_MS;
 
@@ -189,7 +262,6 @@
         mSubtitleView.setBackgroundColor(0);
         mInstance.addView(mSubtitleView);
 
-        // TODO: Need a common namespace for attributes those are defined in updatable library.
         boolean enableControlView = (attrs == null) || attrs.getAttributeBooleanValue(
                 "http://schemas.android.com/apk/res/android",
                 "enableControlView", true);
@@ -212,16 +284,20 @@
             Log.d(TAG, "viewType attribute is textureView.");
             // TODO: implement
         }
+
+        MediaRouteSelector.Builder builder = new MediaRouteSelector.Builder();
+        builder.addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
+        builder.addControlCategory(MediaControlIntent.CATEGORY_LIVE_AUDIO);
+        builder.addControlCategory(MediaControlIntent.CATEGORY_LIVE_VIDEO);
+        mRouteSelector = builder.build();
     }
 
     @Override
     public void setMediaControlView2_impl(MediaControlView2 mediaControlView, long intervalMs) {
         mMediaControlView = mediaControlView;
         mShowControllerIntervalMs = intervalMs;
-        if (mRouteSelector != null) {
-            ((MediaControlView2Impl) mMediaControlView.getProvider())
-                    .setRouteSelector(mRouteSelector);
-        }
+        // TODO: Call MediaControlView2.setRouteSelector only when cast availalbe.
+        ((MediaControlView2Impl) mMediaControlView.getProvider()).setRouteSelector(mRouteSelector);
 
         if (mInstance.isAttachedToWindow()) {
             attachMediaControlView();
@@ -321,30 +397,6 @@
     }
 
     @Override
-    public void setRouteAttributes_impl(List<String> routeCategories, MediaPlayerBase player) {
-        // TODO: implement this.
-    }
-
-    @Override
-    public void setRouteAttributes_impl(@NonNull List<String> routeCategories,
-            MediaSession.Callback sessionPlayer) {
-        MediaRouteSelector.Builder builder = new MediaRouteSelector.Builder();
-        for (String category : routeCategories) {
-            builder.addControlCategory(category);
-        }
-        mRouteSelector = builder.build();
-        if (mMediaControlView != null) {
-            ((MediaControlView2Impl) mMediaControlView.getProvider())
-                    .setRouteSelector(mRouteSelector);
-        }
-        mMediaRouter = MediaRouter.getInstance(mInstance.getContext());
-        mRouteSessionCallback = sessionPlayer;
-        if (mMediaSession != null) {
-            mMediaRouter.setMediaSession(mMediaSession);
-        }
-    }
-
-    @Override
     public void setVideoPath_impl(String path) {
         mInstance.setVideoUri(Uri.parse(path));
     }
@@ -356,8 +408,9 @@
 
     @Override
     public void setVideoUri_impl(Uri uri, Map<String, String> headers) {
-        mSeekWhenPrepared = 0;
-        openVideo(uri, headers);
+        DataSourceDesc.Builder builder = new DataSourceDesc.Builder();
+        builder.setDataSource(mInstance.getContext(), uri, headers, null);
+        mInstance.setDataSource(builder.build());
     }
 
     @Override
@@ -367,7 +420,9 @@
 
     @Override
     public void setDataSource_impl(DataSourceDesc dsd) {
-        // TODO: implement this
+        mDsd = dsd;
+        mSeekWhenPrepared = 0;
+        openVideo(dsd);
     }
 
     @Override
@@ -424,12 +479,16 @@
         // Create MediaSession
         mMediaSession = new MediaSession(mInstance.getContext(), "VideoView2MediaSession");
         mMediaSession.setCallback(new MediaSessionCallback());
+        mMediaSession.setActive(true);
         mMediaController = mMediaSession.getController();
-        if (mMediaRouter != null) {
-            mMediaRouter.setMediaSession(mMediaSession);
-        }
-
+        mMediaRouter = MediaRouter.getInstance(mInstance.getContext());
+        mMediaRouter.setMediaSession(mMediaSession);
+        mMediaRouter.addCallback(mRouteSelector, mRouterCallback);
         attachMediaControlView();
+        // TODO: remove this after moving MediaSession creating code inside initializing VideoView2
+        if (mCurrentState == STATE_PREPARED) {
+            extractTracks();
+        }
     }
 
     @Override
@@ -535,21 +594,27 @@
     }
 
     private boolean isInPlaybackState() {
-        return (mMediaPlayer != null
+        return (mMediaPlayer != null || mRoutePlayer != null)
                 && mCurrentState != STATE_ERROR
                 && mCurrentState != STATE_IDLE
-                && mCurrentState != STATE_PREPARING);
+                && mCurrentState != STATE_PREPARING;
     }
 
     private boolean needToStart() {
-        return (mMediaPlayer != null
+        return (mMediaPlayer != null || mRoutePlayer != null)
                 && mCurrentState != STATE_PLAYING
-                && mTargetState == STATE_PLAYING);
+                && mTargetState == STATE_PLAYING;
     }
 
-    // Creates a MediaPlayer instance and prepare playback.
-    private void openVideo(Uri uri, Map<String, String> headers) {
+    // Creates a MediaPlayer2 instance and prepare playback.
+    private void openVideo(DataSourceDesc dsd) {
+        Uri uri = dsd.getUri();
+        Map<String, String> headers = dsd.getUriHeaders();
         resetPlayer();
+        if (isRemotePlayback()) {
+            mRoutePlayer.openVideo(dsd);
+            return;
+        }
         if (mAudioFocusType != AudioManager.AUDIOFOCUS_NONE) {
             // TODO this should have a focus listener
             AudioFocusRequest focusRequest;
@@ -560,13 +625,13 @@
         }
 
         try {
-            Log.d(TAG, "openVideo(): creating new MediaPlayer instance.");
-            mMediaPlayer = new MediaPlayer();
+            Log.d(TAG, "openVideo(): creating new MediaPlayer2 instance.");
+            mMediaPlayer = new MediaPlayer2Impl();
             mSurfaceView.setMediaPlayer(mMediaPlayer);
             mTextureView.setMediaPlayer(mMediaPlayer);
             mCurrentView.assignSurfaceToMediaPlayer(mMediaPlayer);
 
-            // TODO: create SubtitleController in MediaPlayer, but we need
+            // TODO: create SubtitleController in MediaPlayer2, but we need
             // a context for the subtitle renderers
             final Context context = mInstance.getContext();
             final SubtitleController controller = new SubtitleController(
@@ -578,23 +643,21 @@
             controller.registerRenderer(new SRTRenderer(context));
             mMediaPlayer.setSubtitleAnchor(
                     controller, (SubtitleController.Anchor) mSubtitleView);
-            // TODO: Remove timed text related code later once relevant Renderer is defined.
-            // This is just for debugging purpose.
-            mMediaPlayer.setOnTimedTextListener(mTimedTextListener);
+            Executor executor = new Executor() {
+                @Override
+                public void execute(Runnable runnable) {
+                    runnable.run();
+                }
+            };
+            mMediaPlayer.setMediaPlayer2EventCallback(executor, mMediaPlayer2Callback);
 
-            mMediaPlayer.setOnPreparedListener(mPreparedListener);
-            mMediaPlayer.setOnVideoSizeChangedListener(mSizeChangedListener);
-            mMediaPlayer.setOnCompletionListener(mCompletionListener);
-            mMediaPlayer.setOnErrorListener(mErrorListener);
-            mMediaPlayer.setOnInfoListener(mInfoListener);
-            mMediaPlayer.setOnBufferingUpdateListener(mBufferingUpdateListener);
             mCurrentBufferPercentage = 0;
-            mMediaPlayer.setDataSource(mInstance.getContext(), uri, headers);
+            mMediaPlayer.setDataSource(dsd);
             mMediaPlayer.setAudioAttributes(mAudioAttributes);
             // we don't set the target state here either, but preserve the
             // target state that was there before.
             mCurrentState = STATE_PREPARING;
-            mMediaPlayer.prepareAsync();
+            mMediaPlayer.prepare();
 
             // Save file name as title since the file may not have a title Metadata.
             mTitle = uri.getPath();
@@ -607,24 +670,12 @@
                 Log.d(TAG, "openVideo(). mCurrentState=" + mCurrentState
                         + ", mTargetState=" + mTargetState);
             }
-            /*
-            for (Pair<InputStream, MediaFormat> pending: mPendingSubtitleTracks) {
-                try {
-                    mMediaPlayer.addSubtitleSource(pending.first, pending.second);
-                } catch (IllegalStateException e) {
-                    mInfoListener.onInfo(
-                            mMediaPlayer, MediaPlayer.MEDIA_INFO_UNSUPPORTED_SUBTITLE, 0);
-                }
-            }
-            */
-        } catch (IOException | IllegalArgumentException ex) {
+        } catch (IllegalArgumentException ex) {
             Log.w(TAG, "Unable to open content: " + uri, ex);
             mCurrentState = STATE_ERROR;
             mTargetState = STATE_ERROR;
-            mErrorListener.onError(mMediaPlayer,
-                    MediaPlayer.MEDIA_ERROR_UNKNOWN, MediaPlayer.MEDIA_ERROR_IO);
-        } finally {
-            //mPendingSubtitleTracks.clear();
+            mMediaPlayer2Callback.onError(mMediaPlayer, dsd,
+                    MediaPlayer2.MEDIA_ERROR_UNKNOWN, MediaPlayer2.MEDIA_ERROR_IO);
         }
     }
 
@@ -633,9 +684,18 @@
      */
     private void resetPlayer() {
         if (mMediaPlayer != null) {
-            mMediaPlayer.reset();
-            mMediaPlayer.release();
+            final MediaPlayer2 player = mMediaPlayer;
+            new AsyncTask<MediaPlayer2, Void, Void>() {
+                @Override
+                protected Void doInBackground(MediaPlayer2... players) {
+                    // TODO: Fix NPE while MediaPlayer2.close()
+                    //players[0].close();
+                    return null;
+                }
+            }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, player);
             mMediaPlayer = null;
+            mTextureView.setMediaPlayer(null);
+            mSurfaceView.setMediaPlayer(null);
             //mPendingSubtitleTracks.clear();
             mCurrentState = STATE_IDLE;
             mTargetState = STATE_IDLE;
@@ -650,8 +710,8 @@
     private void updatePlaybackState() {
         if (mStateBuilder == null) {
             // Get the capabilities of the player for this stream
-            mMetadata = mMediaPlayer.getMetadata(MediaPlayer.METADATA_ALL,
-                    MediaPlayer.BYPASS_METADATA_FILTER);
+            mMetadata = mMediaPlayer.getMetadata(MediaPlayer2.METADATA_ALL,
+                    MediaPlayer2.BYPASS_METADATA_FILTER);
 
             // Add Play action as default
             long playbackActions = PlaybackState.ACTION_PLAY;
@@ -691,6 +751,8 @@
         if (mCurrentState != STATE_ERROR
             && mCurrentState != STATE_IDLE
             && mCurrentState != STATE_PREPARING) {
+            // TODO: this should be replaced with MediaPlayer2.getBufferedPosition() once it is
+            // implemented.
             mStateBuilder.setBufferedPosition(
                     (long) (mCurrentBufferPercentage / 100.0) * mMediaPlayer.getDuration());
         }
@@ -782,8 +844,8 @@
             return false;
         }
         PlaybackInfo playbackInfo = mMediaController.getPlaybackInfo();
-        return (playbackInfo != null)
-                && (playbackInfo.getPlaybackType() == PlaybackInfo.PLAYBACK_TYPE_REMOTE);
+        return playbackInfo != null
+                && playbackInfo.getPlaybackType() == PlaybackInfo.PLAYBACK_TYPE_REMOTE;
     }
 
     private void selectOrDeselectSubtitle(boolean select) {
@@ -792,166 +854,90 @@
         }
         if (select) {
             if (mSubtitleTrackIndices.size() > 0) {
-                // Select first subtitle track
-                mSelectedTrackIndex = mSubtitleTrackIndices.get(0);
-                mMediaPlayer.selectTrack(mSelectedTrackIndex);
+                // TODO: make this selection dynamic
+                mSelectedSubtitleTrackIndex = mSubtitleTrackIndices.get(0);
+                mMediaPlayer.selectTrack(mSelectedSubtitleTrackIndex);
                 mSubtitleView.setVisibility(View.VISIBLE);
             }
         } else {
-            if (mSelectedTrackIndex != INVALID_TRACK_INDEX) {
-                mMediaPlayer.deselectTrack(mSelectedTrackIndex);
-                mSelectedTrackIndex = INVALID_TRACK_INDEX;
+            if (mSelectedSubtitleTrackIndex != INVALID_TRACK_INDEX) {
+                mMediaPlayer.deselectTrack(mSelectedSubtitleTrackIndex);
+                mSelectedSubtitleTrackIndex = INVALID_TRACK_INDEX;
                 mSubtitleView.setVisibility(View.GONE);
             }
         }
     }
 
-    private void extractSubtitleTracks() {
-        MediaPlayer.TrackInfo[] trackInfos = mMediaPlayer.getTrackInfo();
-        boolean previouslyNoTracks = mSubtitleTrackIndices == null
-                || mSubtitleTrackIndices.size() == 0;
+    private void extractTracks() {
+        List<MediaPlayer2.TrackInfo> trackInfos = mMediaPlayer.getTrackInfo();
+        mVideoTrackIndices = new ArrayList<>();
+        mAudioTrackIndices = new ArrayList<>();
         mSubtitleTrackIndices = new ArrayList<>();
-        for (int i = 0; i < trackInfos.length; ++i) {
-            int trackType = trackInfos[i].getTrackType();
-            if (trackType == MediaPlayer.TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE
-                    || trackType == MediaPlayer.TrackInfo.MEDIA_TRACK_TYPE_TIMEDTEXT) {
+        for (int i = 0; i < trackInfos.size(); ++i) {
+            int trackType = trackInfos.get(i).getTrackType();
+            if (trackType == MediaPlayer2.TrackInfo.MEDIA_TRACK_TYPE_VIDEO) {
+                mVideoTrackIndices.add(i);
+            } else if (trackType == MediaPlayer2.TrackInfo.MEDIA_TRACK_TYPE_AUDIO) {
+                mAudioTrackIndices.add(i);
+            } else if (trackType == MediaPlayer2.TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE
+                    || trackType == MediaPlayer2.TrackInfo.MEDIA_TRACK_TYPE_TIMEDTEXT) {
                   mSubtitleTrackIndices.add(i);
             }
         }
+        Bundle data = new Bundle();
+        data.putInt(MediaControlView2Impl.KEY_VIDEO_TRACK_COUNT, mVideoTrackIndices.size());
+        data.putInt(MediaControlView2Impl.KEY_AUDIO_TRACK_COUNT, mAudioTrackIndices.size());
+        data.putInt(MediaControlView2Impl.KEY_SUBTITLE_TRACK_COUNT, mSubtitleTrackIndices.size());
         if (mSubtitleTrackIndices.size() > 0) {
-            if (previouslyNoTracks) {
-                selectOrDeselectSubtitle(mSubtitleEnabled);
-                // Notify MediaControlView that subtitle track exists
-                // TODO: Send the subtitle track list to MediaSession for MCV2.
-                Bundle data = new Bundle();
-                data.putBoolean(MediaControlView2Impl.KEY_STATE_CONTAINS_SUBTITLE, true);
-                mMediaSession.sendSessionEvent(
-                        MediaControlView2Impl.EVENT_UPDATE_SUBTITLE_STATUS, data);
-            }
-        } else {
-            Bundle data = new Bundle();
-            data.putBoolean(MediaControlView2Impl.KEY_STATE_CONTAINS_SUBTITLE, false);
-            mMediaSession.sendSessionEvent(
-                    MediaControlView2Impl.EVENT_UPDATE_SUBTITLE_STATUS, data);
+            selectOrDeselectSubtitle(mSubtitleEnabled);
         }
+        mMediaSession.sendSessionEvent(MediaControlView2Impl.EVENT_UPDATE_TRACK_STATUS, data);
     }
 
-    MediaPlayer.OnVideoSizeChangedListener mSizeChangedListener =
-            new MediaPlayer.OnVideoSizeChangedListener() {
-                public void onVideoSizeChanged(MediaPlayer mp, int width, int height) {
+    MediaPlayer2EventCallback mMediaPlayer2Callback =
+            new MediaPlayer2EventCallback() {
+                @Override
+                public void onVideoSizeChanged(
+                        MediaPlayer2 mp, DataSourceDesc dsd, int width, int height) {
                     if (DEBUG) {
-                        Log.d(TAG, "OnVideoSizeChanged(): size: " + width + "/" + height);
+                        Log.d(TAG, "onVideoSizeChanged(): size: " + width + "/" + height);
                     }
                     mVideoWidth = mp.getVideoWidth();
                     mVideoHeight = mp.getVideoHeight();
                     if (DEBUG) {
-                        Log.d(TAG, "OnVideoSizeChanged(): mVideoSize:" + mVideoWidth + "/"
+                        Log.d(TAG, "onVideoSizeChanged(): mVideoSize:" + mVideoWidth + "/"
                                 + mVideoHeight);
                     }
-
                     if (mVideoWidth != 0 && mVideoHeight != 0) {
                         mInstance.requestLayout();
                     }
                 }
-            };
 
-    MediaPlayer.OnPreparedListener mPreparedListener = new MediaPlayer.OnPreparedListener() {
-        public void onPrepared(MediaPlayer mp) {
-            if (DEBUG) {
-                Log.d(TAG, "OnPreparedListener(). mCurrentState=" + mCurrentState
-                        + ", mTargetState=" + mTargetState);
-            }
-            mCurrentState = STATE_PREPARED;
-            // Create and set playback state for MediaControlView2
-            updatePlaybackState();
-            extractSubtitleTracks();
-
-            if (mMediaControlView != null) {
-                mMediaControlView.setEnabled(true);
-            }
-            int videoWidth = mp.getVideoWidth();
-            int videoHeight = mp.getVideoHeight();
-
-            // mSeekWhenPrepared may be changed after seekTo() call
-            long seekToPosition = mSeekWhenPrepared;
-            if (seekToPosition != 0) {
-                mMediaController.getTransportControls().seekTo(seekToPosition);
-            }
-
-            if (videoWidth != 0 && videoHeight != 0) {
-                if (videoWidth != mVideoWidth || videoHeight != mVideoHeight) {
-                    if (DEBUG) {
-                        Log.i(TAG, "OnPreparedListener() : ");
-                        Log.i(TAG, " video size: " + videoWidth + "/" + videoHeight);
-                        Log.i(TAG, " measuredSize: " + mInstance.getMeasuredWidth() + "/"
-                                + mInstance.getMeasuredHeight());
-                        Log.i(TAG, " viewSize: " + mInstance.getWidth() + "/"
-                                + mInstance.getHeight());
-                    }
-
-                    mVideoWidth = videoWidth;
-                    mVideoHeight = videoHeight;
-                    mInstance.requestLayout();
+                // TODO: Remove timed text related code later once relevant Renderer is defined.
+                // This is just for debugging purpose.
+                @Override
+                public void onTimedText(
+                        MediaPlayer2 mp, DataSourceDesc dsd, TimedText text) {
+                        Log.d(TAG, "TimedText: " + text.getText());
                 }
 
-                if (needToStart()) {
-                    mMediaController.getTransportControls().play();
-                }
-            } else {
-                // We don't know the video size yet, but should start anyway.
-                // The video size might be reported to us later.
-                if (needToStart()) {
-                    mMediaController.getTransportControls().play();
-                }
-            }
-
-            // Get and set duration and title values as MediaMetadata for MediaControlView2
-            MediaMetadata.Builder builder = new MediaMetadata.Builder();
-            if (mMetadata != null && mMetadata.has(Metadata.TITLE)) {
-                mTitle = mMetadata.getString(Metadata.TITLE);
-            }
-            builder.putString(MediaMetadata.METADATA_KEY_TITLE, mTitle);
-            builder.putLong(MediaMetadata.METADATA_KEY_DURATION, mMediaPlayer.getDuration());
-
-            if (mMediaSession != null) {
-                mMediaSession.setMetadata(builder.build());
-
-                // TODO: merge this code with the above code when integrating with MediaSession2.
-                if (mNeedUpdateMediaType) {
-                    mMediaSession.sendSessionEvent(
-                            MediaControlView2Impl.EVENT_UPDATE_MEDIA_TYPE_STATUS, mMediaTypeData);
-                    mNeedUpdateMediaType = false;
-                }
-            }
-        }
-    };
-
-    private MediaPlayer.OnCompletionListener mCompletionListener =
-            new MediaPlayer.OnCompletionListener() {
-                public void onCompletion(MediaPlayer mp) {
-                    mCurrentState = STATE_PLAYBACK_COMPLETED;
-                    mTargetState = STATE_PLAYBACK_COMPLETED;
-                    updatePlaybackState();
-
-                    if (mAudioFocusType != AudioManager.AUDIOFOCUS_NONE) {
-                        mAudioManager.abandonAudioFocus(null);
+                @Override
+                public void onInfo(
+                        MediaPlayer2 mp, DataSourceDesc dsd, int what, int extra) {
+                    if (what == MediaPlayer2.MEDIA_INFO_METADATA_UPDATE) {
+                        extractTracks();
+                    } else if (what == MediaPlayer2.MEDIA_INFO_PREPARED) {
+                        this.onPrepared(mp, dsd);
+                    } else if (what == MediaPlayer2.MEDIA_INFO_PLAYBACK_COMPLETE) {
+                        this.onCompletion(mp, dsd);
+                    } else if (what == MediaPlayer2.MEDIA_INFO_BUFFERING_UPDATE) {
+                        this.onBufferingUpdate(mp, dsd, extra);
                     }
                 }
-            };
 
-    private MediaPlayer.OnInfoListener mInfoListener =
-            new MediaPlayer.OnInfoListener() {
-                public boolean onInfo(MediaPlayer mp, int what, int extra) {
-                    if (what == MediaPlayer.MEDIA_INFO_METADATA_UPDATE) {
-                        extractSubtitleTracks();
-                    }
-                    return true;
-                }
-            };
-
-    private MediaPlayer.OnErrorListener mErrorListener =
-            new MediaPlayer.OnErrorListener() {
-                public boolean onError(MediaPlayer mp, int frameworkErr, int implErr) {
+                @Override
+                public void onError(
+                        MediaPlayer2 mp, DataSourceDesc dsd, int frameworkErr, int implErr) {
                     if (DEBUG) {
                         Log.d(TAG, "Error: " + frameworkErr + "," + implErr);
                     }
@@ -962,41 +948,112 @@
                     if (mMediaControlView != null) {
                         mMediaControlView.setVisibility(View.GONE);
                     }
-                    return true;
                 }
-            };
 
-    private MediaPlayer.OnBufferingUpdateListener mBufferingUpdateListener =
-            new MediaPlayer.OnBufferingUpdateListener() {
-                public void onBufferingUpdate(MediaPlayer mp, int percent) {
+                private void onPrepared(MediaPlayer2 mp, DataSourceDesc dsd) {
+                    if (DEBUG) {
+                        Log.d(TAG, "OnPreparedListener(). mCurrentState=" + mCurrentState
+                                + ", mTargetState=" + mTargetState);
+                    }
+                    mCurrentState = STATE_PREPARED;
+                    // Create and set playback state for MediaControlView2
+                    updatePlaybackState();
+
+                    // TODO: change this to send TrackInfos to MediaControlView2
+                    // TODO: create MediaSession when initializing VideoView2
+                    if (mMediaSession != null) {
+                        extractTracks();
+                    }
+
+                    if (mMediaControlView != null) {
+                        mMediaControlView.setEnabled(true);
+                    }
+                    int videoWidth = mp.getVideoWidth();
+                    int videoHeight = mp.getVideoHeight();
+
+                    // mSeekWhenPrepared may be changed after seekTo() call
+                    long seekToPosition = mSeekWhenPrepared;
+                    if (seekToPosition != 0) {
+                        mMediaController.getTransportControls().seekTo(seekToPosition);
+                    }
+
+                    if (videoWidth != 0 && videoHeight != 0) {
+                        if (videoWidth != mVideoWidth || videoHeight != mVideoHeight) {
+                            if (DEBUG) {
+                                Log.i(TAG, "OnPreparedListener() : ");
+                                Log.i(TAG, " video size: " + videoWidth + "/" + videoHeight);
+                                Log.i(TAG, " measuredSize: " + mInstance.getMeasuredWidth() + "/"
+                                        + mInstance.getMeasuredHeight());
+                                Log.i(TAG, " viewSize: " + mInstance.getWidth() + "/"
+                                        + mInstance.getHeight());
+                            }
+                            mVideoWidth = videoWidth;
+                            mVideoHeight = videoHeight;
+                            mInstance.requestLayout();
+                        }
+
+                        if (needToStart()) {
+                            mMediaController.getTransportControls().play();
+                        }
+                    } else {
+                        // We don't know the video size yet, but should start anyway.
+                        // The video size might be reported to us later.
+                        if (needToStart()) {
+                            mMediaController.getTransportControls().play();
+                        }
+                    }
+                    // Get and set duration and title values as MediaMetadata for MediaControlView2
+                    MediaMetadata.Builder builder = new MediaMetadata.Builder();
+                    if (mMetadata != null && mMetadata.has(Metadata.TITLE)) {
+                        mTitle = mMetadata.getString(Metadata.TITLE);
+                    }
+                    builder.putString(MediaMetadata.METADATA_KEY_TITLE, mTitle);
+                    builder.putLong(
+                            MediaMetadata.METADATA_KEY_DURATION, mMediaPlayer.getDuration());
+
+                    if (mMediaSession != null) {
+                        mMediaSession.setMetadata(builder.build());
+
+                        // TODO: merge this code with the above code when integrating with
+                        // MediaSession2.
+                        if (mNeedUpdateMediaType) {
+                            mMediaSession.sendSessionEvent(
+                                    MediaControlView2Impl.EVENT_UPDATE_MEDIA_TYPE_STATUS,
+                                    mMediaTypeData);
+                            mNeedUpdateMediaType = false;
+                        }
+                    }
+                }
+
+                private void onCompletion(MediaPlayer2 mp, DataSourceDesc dsd) {
+                    mCurrentState = STATE_PLAYBACK_COMPLETED;
+                    mTargetState = STATE_PLAYBACK_COMPLETED;
+                    updatePlaybackState();
+                    if (mAudioFocusType != AudioManager.AUDIOFOCUS_NONE) {
+                        mAudioManager.abandonAudioFocus(null);
+                    }
+                }
+
+                private void onBufferingUpdate(MediaPlayer2 mp, DataSourceDesc dsd, int percent) {
                     mCurrentBufferPercentage = percent;
                     updatePlaybackState();
                 }
             };
 
-    // TODO: Remove timed text related code later once relevant Renderer is defined.
-    // This is just for debugging purpose.
-    private MediaPlayer.OnTimedTextListener mTimedTextListener =
-            new MediaPlayer.OnTimedTextListener() {
-                public void onTimedText(MediaPlayer mp, TimedText text) {
-                    Log.d(TAG, "TimedText: " + text.getText());
-                }
-            };
-
     private class MediaSessionCallback extends MediaSession.Callback {
         @Override
         public void onCommand(String command, Bundle args, ResultReceiver receiver) {
             if (isRemotePlayback()) {
-                mRouteSessionCallback.onCommand(command, args, receiver);
+                mRoutePlayer.onCommand(command, args, receiver);
             } else {
                 switch (command) {
-                    case MediaControlView2.COMMAND_SHOW_SUBTITLE:
+                    case MediaControlView2Impl.COMMAND_SHOW_SUBTITLE:
                         mInstance.setSubtitleEnabled(true);
                         break;
-                    case MediaControlView2.COMMAND_HIDE_SUBTITLE:
+                    case MediaControlView2Impl.COMMAND_HIDE_SUBTITLE:
                         mInstance.setSubtitleEnabled(false);
                         break;
-                    case MediaControlView2.COMMAND_SET_FULLSCREEN:
+                    case MediaControlView2Impl.COMMAND_SET_FULLSCREEN:
                         if (mFullScreenRequestListener != null) {
                             mFullScreenRequestListener.onFullScreenRequest(
                                     mInstance,
@@ -1017,57 +1074,57 @@
 
         @Override
         public void onPlay() {
-            if (isRemotePlayback()) {
-                mRouteSessionCallback.onPlay();
-            } else {
-                if (isInPlaybackState() && mCurrentView.hasAvailableSurface()) {
+            if (isInPlaybackState() && mCurrentView.hasAvailableSurface()) {
+                if (isRemotePlayback()) {
+                    mRoutePlayer.onPlay();
+                } else {
                     applySpeed();
-                    mMediaPlayer.start();
+                    mMediaPlayer.play();
                     mCurrentState = STATE_PLAYING;
                     updatePlaybackState();
                 }
-                mTargetState = STATE_PLAYING;
-                if (DEBUG) {
-                    Log.d(TAG, "onPlay(). mCurrentState=" + mCurrentState
-                            + ", mTargetState=" + mTargetState);
-                }
+                mCurrentState = STATE_PLAYING;
+            }
+            mTargetState = STATE_PLAYING;
+            if (DEBUG) {
+                Log.d(TAG, "onPlay(). mCurrentState=" + mCurrentState
+                        + ", mTargetState=" + mTargetState);
             }
             showController();
         }
 
         @Override
         public void onPause() {
-            if (isRemotePlayback()) {
-                mRouteSessionCallback.onPause();
-            } else {
-                if (isInPlaybackState()) {
-                    if (mMediaPlayer.isPlaying()) {
-                        mMediaPlayer.pause();
-                        mCurrentState = STATE_PAUSED;
-                        updatePlaybackState();
-                    }
+            if (isInPlaybackState()) {
+                if (isRemotePlayback()) {
+                    mRoutePlayer.onPause();
+                    mCurrentState = STATE_PAUSED;
+                } else if (mMediaPlayer.isPlaying()) {
+                    mMediaPlayer.pause();
+                    mCurrentState = STATE_PAUSED;
+                    updatePlaybackState();
                 }
-                mTargetState = STATE_PAUSED;
-                if (DEBUG) {
-                    Log.d(TAG, "onPause(). mCurrentState=" + mCurrentState
-                            + ", mTargetState=" + mTargetState);
-                }
+            }
+            mTargetState = STATE_PAUSED;
+            if (DEBUG) {
+                Log.d(TAG, "onPause(). mCurrentState=" + mCurrentState
+                        + ", mTargetState=" + mTargetState);
             }
             showController();
         }
 
         @Override
         public void onSeekTo(long pos) {
-            if (isRemotePlayback()) {
-                mRouteSessionCallback.onSeekTo(pos);
-            } else {
-                if (isInPlaybackState()) {
-                    mMediaPlayer.seekTo(pos, MediaPlayer.SEEK_PREVIOUS_SYNC);
+            if (isInPlaybackState()) {
+                if (isRemotePlayback()) {
+                    mRoutePlayer.onSeekTo(pos);
+                } else {
+                    mMediaPlayer.seekTo(pos, MediaPlayer2.SEEK_PREVIOUS_SYNC);
                     mSeekWhenPrepared = 0;
                     updatePlaybackState();
-                } else {
-                    mSeekWhenPrepared = pos;
                 }
+            } else {
+                mSeekWhenPrepared = pos;
             }
             showController();
         }
@@ -1075,74 +1132,11 @@
         @Override
         public void onStop() {
             if (isRemotePlayback()) {
-                mRouteSessionCallback.onStop();
+                mRoutePlayer.onStop();
             } else {
                 resetPlayer();
             }
             showController();
         }
     }
-
-    private class RouteSessionCallback extends MediaSession.Callback {
-        RemotePlaybackClient mClient;
-
-        RemotePlaybackClient.StatusCallback mStatusCallback =
-                new RemotePlaybackClient.StatusCallback() {
-            @Override
-            public void onItemStatusChanged(Bundle data,
-                    String sessionId, MediaSessionStatus sessionStatus,
-                    String itemId, MediaItemStatus itemStatus) {
-                // TODO: implement this
-            }
-
-            @Override
-            public void onSessionStatusChanged(Bundle data,
-                    String sessionId, MediaSessionStatus sessionStatus) {
-                // TODO: implement this
-            }
-
-            @Override
-            public void onSessionChanged(String sessionId) {
-                // TODO: implement this
-            }
-        };
-
-        @Override
-        public void onCommand(String command, Bundle args, ResultReceiver receiver) {
-            ensureClient();
-            // TODO: implement this
-        }
-
-        @Override
-        public void onPlay() {
-            ensureClient();
-            // TODO: implement this
-        }
-
-        @Override
-        public void onPause() {
-            ensureClient();
-            // TODO: implement this
-        }
-
-        @Override
-        public void onSeekTo(long pos) {
-            ensureClient();
-            // TODO: implement this
-        }
-
-        @Override
-        public void onStop() {
-            ensureClient();
-            // TODO: implement this
-        }
-
-        private void ensureClient() {
-            if (mClient == null) {
-                mClient = new RemotePlaybackClient(
-                        mInstance.getContext(), mMediaRouter.getSelectedRoute());
-                mClient.setStatusCallback(mStatusCallback);
-            }
-        }
-    }
 }
diff --git a/packages/MediaComponents/src/com/android/widget/VideoViewInterface.java b/packages/MediaComponents/src/com/android/widget/VideoViewInterface.java
index 2a5eb94..854d47e 100644
--- a/packages/MediaComponents/src/com/android/widget/VideoViewInterface.java
+++ b/packages/MediaComponents/src/com/android/widget/VideoViewInterface.java
@@ -17,31 +17,31 @@
 package com.android.widget;
 
 import android.annotation.NonNull;
-import android.media.MediaPlayer;
+import android.media.MediaPlayer2;
 import android.view.View;
 
 interface VideoViewInterface {
     /**
-     * Assigns the view's surface to the given MediaPlayer instance.
+     * Assigns the view's surface to the given MediaPlayer2 instance.
      *
-     * @param mp MediaPlayer
+     * @param mp MediaPlayer2
      * @return true if the surface is successfully assigned, false if not. It will fail to assign
-     *         if any of MediaPlayer or surface is unavailable.
+     *         if any of MediaPlayer2 or surface is unavailable.
      */
-    boolean assignSurfaceToMediaPlayer(MediaPlayer mp);
+    boolean assignSurfaceToMediaPlayer(MediaPlayer2 mp);
     void setSurfaceListener(SurfaceListener l);
     int getViewType();
-    void setMediaPlayer(MediaPlayer mp);
+    void setMediaPlayer(MediaPlayer2 mp);
 
     /**
-     * Takes over oldView. It means that the MediaPlayer will start rendering on this view.
+     * Takes over oldView. It means that the MediaPlayer2 will start rendering on this view.
      * The visibility of oldView will be set as {@link View.GONE}. If the view doesn't have a
-     * MediaPlayer instance or its surface is not available, the actual execution is deferred until
-     * a MediaPlayer instance is set by {@link #setMediaPlayer} or its surface becomes available.
+     * MediaPlayer2 instance or its surface is not available, the actual execution is deferred until
+     * a MediaPlayer2 instance is set by {@link #setMediaPlayer} or its surface becomes available.
      * {@link SurfaceListener.onSurfaceTakeOverDone} will be called when the actual execution is
      * done.
      *
-     * @param oldView The view that MediaPlayer is currently rendering on.
+     * @param oldView The view that MediaPlayer2 is currently rendering on.
      */
     void takeOver(@NonNull VideoViewInterface oldView);
 
diff --git a/packages/MediaComponents/test/Android.mk b/packages/MediaComponents/test/Android.mk
index 8703b9f..ea053e0 100644
--- a/packages/MediaComponents/test/Android.mk
+++ b/packages/MediaComponents/test/Android.mk
@@ -25,4 +25,5 @@
     compatibility-device-util
 
 LOCAL_PACKAGE_NAME := MediaComponentsTest
+LOCAL_PRIVATE_PLATFORM_APIS := true
 include $(BUILD_PACKAGE)
diff --git a/packages/MediaComponents/test/src/android/media/MediaBrowser2Test.java b/packages/MediaComponents/test/src/android/media/MediaBrowser2Test.java
index 27822e6..e58bd02 100644
--- a/packages/MediaComponents/test/src/android/media/MediaBrowser2Test.java
+++ b/packages/MediaComponents/test/src/android/media/MediaBrowser2Test.java
@@ -339,7 +339,9 @@
         final CountDownLatch latch = new CountDownLatch(1);
         final SessionCallbackProxy callbackProxy = new SessionCallbackProxy(mContext) {
             @Override
-            public void onSubscribe(ControllerInfo info, String parentId, Bundle extras) {
+            public void onSubscribe(@NonNull MediaLibrarySession session,
+                    @NonNull ControllerInfo info, @NonNull String parentId,
+                    @Nullable Bundle extras) {
                 if (Process.myUid() == info.getUid()) {
                     assertEquals(testParentId, parentId);
                     assertTrue(TestUtils.equals(testExtras, extras));
@@ -361,7 +363,8 @@
         final CountDownLatch latch = new CountDownLatch(1);
         final SessionCallbackProxy callbackProxy = new SessionCallbackProxy(mContext) {
             @Override
-            public void onUnsubscribe(ControllerInfo info, String parentId) {
+            public void onUnsubscribe(@NonNull MediaLibrarySession session,
+                    @NonNull ControllerInfo info, @NonNull String parentId) {
                 if (Process.myUid() == info.getUid()) {
                     assertEquals(testParentId, parentId);
                     latch.countDown();
@@ -387,21 +390,29 @@
         final CountDownLatch latch = new CountDownLatch(3);
         final SessionCallbackProxy sessionCallbackProxy = new SessionCallbackProxy(mContext) {
             @Override
-            public CommandGroup onConnect(ControllerInfo controller) {
-                final MockMediaLibraryService2 service = (MockMediaLibraryService2)
-                        TestServiceRegistry.getInstance().getServiceInstance();
-                final MediaLibrarySession session = (MediaLibrarySession) service.getSession();
-                // Shouldn't trigger onChildrenChanged() for the browser, because it hasn't
-                // subscribed.
-                session.notifyChildrenChanged(testParentId1, testChildrenCount, null);
-                session.notifyChildrenChanged(controller, testParentId1, testChildrenCount, null);
-                return super.onConnect(controller);
+            public CommandGroup onConnect(@NonNull MediaSession2 session,
+                    @NonNull ControllerInfo controller) {
+                if (Process.myUid() == controller.getUid()) {
+                    assertTrue(session instanceof MediaLibrarySession);
+                    if (mSession != null) {
+                        mSession.close();
+                    }
+                    mSession = session;
+                    // Shouldn't trigger onChildrenChanged() for the browser, because it hasn't
+                    // subscribed.
+                    ((MediaLibrarySession) session).notifyChildrenChanged(
+                            testParentId1, testChildrenCount, null);
+                    ((MediaLibrarySession) session).notifyChildrenChanged(
+                            controller, testParentId1, testChildrenCount, null);
+                }
+                return super.onConnect(session, controller);
             }
 
             @Override
-            public void onSubscribe(ControllerInfo info, String parentId, Bundle extras) {
+            public void onSubscribe(@NonNull MediaLibrarySession session,
+                    @NonNull ControllerInfo info, @NonNull String parentId,
+                    @Nullable Bundle extras) {
                 if (Process.myUid() == info.getUid()) {
-                    final MediaLibrarySession session =  (MediaLibrarySession) mSession;
                     session.notifyChildrenChanged(testParentId2, testChildrenCount, null);
                     session.notifyChildrenChanged(info, testParentId2, testChildrenCount,
                             testExtras);
@@ -435,12 +446,6 @@
         final SessionToken2 token = MockMediaLibraryService2.getToken(mContext);
         final MediaBrowser2 browser = (MediaBrowser2) createController(
                 token, true, controllerCallbackProxy);
-        final MockMediaLibraryService2 service =
-                (MockMediaLibraryService2) TestServiceRegistry.getInstance().getServiceInstance();
-        if (mSession != null) {
-            mSession.close();
-        }
-        mSession = service.getSession();
         assertTrue(mSession instanceof MediaLibrarySession);
         browser.subscribe(testParentId2, null);
         // This ensures that onChildrenChanged() is only called for the expected reasons.
@@ -462,45 +467,52 @@
 
         @CallSuper
         @Override
-        public void onConnected(CommandGroup commands) {
+        public void onConnected(MediaController2 controller, CommandGroup commands) {
             connectLatch.countDown();
         }
 
         @CallSuper
         @Override
-        public void onDisconnected() {
+        public void onDisconnected(MediaController2 controller) {
             disconnectLatch.countDown();
         }
 
         @Override
-        public void onPlaybackStateChanged(PlaybackState2 state) {
+        public void onPlaybackStateChanged(MediaController2 controller, PlaybackState2 state) {
             mCallbackProxy.onPlaybackStateChanged(state);
         }
 
         @Override
-        public void onPlaylistParamsChanged(PlaylistParams params) {
+        public void onPlaylistParamsChanged(MediaController2 controller, PlaylistParams params) {
             mCallbackProxy.onPlaylistParamsChanged(params);
         }
 
         @Override
-        public void onPlaybackInfoChanged(MediaController2.PlaybackInfo info) {
+        public void onPlaybackInfoChanged(MediaController2 controller,
+                MediaController2.PlaybackInfo info) {
             mCallbackProxy.onPlaybackInfoChanged(info);
         }
 
         @Override
-        public void onCustomCommand(Command command, Bundle args, ResultReceiver receiver) {
+        public void onCustomCommand(MediaController2 controller, Command command, Bundle args,
+                ResultReceiver receiver) {
             mCallbackProxy.onCustomCommand(command, args, receiver);
         }
 
-
         @Override
-        public void onCustomLayoutChanged(List<CommandButton> layout) {
+        public void onCustomLayoutChanged(MediaController2 controller, List<CommandButton> layout) {
             mCallbackProxy.onCustomLayoutChanged(layout);
         }
 
         @Override
-        public void onGetLibraryRootDone(Bundle rootHints, String rootMediaId, Bundle rootExtra) {
-            super.onGetLibraryRootDone(rootHints, rootMediaId, rootExtra);
+        public void onAllowedCommandsChanged(MediaController2 controller, CommandGroup commands) {
+            mCallbackProxy.onAllowedCommandsChanged(commands);
+        }
+
+        @Override
+        public void onGetLibraryRootDone(MediaBrowser2 browser, Bundle rootHints,
+                String rootMediaId, Bundle rootExtra) {
+            super.onGetLibraryRootDone(browser, rootHints, rootMediaId, rootExtra);
             if (mCallbackProxy instanceof TestBrowserCallbackInterface) {
                 ((TestBrowserCallbackInterface) mCallbackProxy)
                         .onGetLibraryRootDone(rootHints, rootMediaId, rootExtra);
@@ -508,17 +520,17 @@
         }
 
         @Override
-        public void onGetItemDone(String mediaId, MediaItem2 result) {
-            super.onGetItemDone(mediaId, result);
+        public void onGetItemDone(MediaBrowser2 browser, String mediaId, MediaItem2 result) {
+            super.onGetItemDone(browser, mediaId, result);
             if (mCallbackProxy instanceof TestBrowserCallbackInterface) {
                 ((TestBrowserCallbackInterface) mCallbackProxy).onGetItemDone(mediaId, result);
             }
         }
 
         @Override
-        public void onGetChildrenDone(String parentId, int page, int pageSize,
-                List<MediaItem2> result, Bundle extras) {
-            super.onGetChildrenDone(parentId, page, pageSize, result, extras);
+        public void onGetChildrenDone(MediaBrowser2 browser, String parentId, int page,
+                int pageSize, List<MediaItem2> result, Bundle extras) {
+            super.onGetChildrenDone(browser, parentId, page, pageSize, result, extras);
             if (mCallbackProxy instanceof TestBrowserCallbackInterface) {
                 ((TestBrowserCallbackInterface) mCallbackProxy)
                         .onGetChildrenDone(parentId, page, pageSize, result, extras);
@@ -526,8 +538,9 @@
         }
 
         @Override
-        public void onSearchResultChanged(String query, int itemCount, Bundle extras) {
-            super.onSearchResultChanged(query, itemCount, extras);
+        public void onSearchResultChanged(MediaBrowser2 browser, String query, int itemCount,
+                Bundle extras) {
+            super.onSearchResultChanged(browser, query, itemCount, extras);
             if (mCallbackProxy instanceof TestBrowserCallbackInterface) {
                 ((TestBrowserCallbackInterface) mCallbackProxy)
                         .onSearchResultChanged(query, itemCount, extras);
@@ -535,9 +548,9 @@
         }
 
         @Override
-        public void onGetSearchResultDone(String query, int page, int pageSize,
-                List<MediaItem2> result, Bundle extras) {
-            super.onGetSearchResultDone(query, page, pageSize, result, extras);
+        public void onGetSearchResultDone(MediaBrowser2 browser, String query, int page,
+                int pageSize, List<MediaItem2> result, Bundle extras) {
+            super.onGetSearchResultDone(browser, query, page, pageSize, result, extras);
             if (mCallbackProxy instanceof TestBrowserCallbackInterface) {
                 ((TestBrowserCallbackInterface) mCallbackProxy)
                         .onGetSearchResultDone(query, page, pageSize, result, extras);
@@ -545,8 +558,9 @@
         }
 
         @Override
-        public void onChildrenChanged(String parentId, int itemCount, Bundle extras) {
-            super.onChildrenChanged(parentId, itemCount, extras);
+        public void onChildrenChanged(MediaBrowser2 browser, String parentId, int itemCount,
+                Bundle extras) {
+            super.onChildrenChanged(browser, parentId, itemCount, extras);
             if (mCallbackProxy instanceof TestBrowserCallbackInterface) {
                 ((TestBrowserCallbackInterface) mCallbackProxy)
                         .onChildrenChanged(parentId, itemCount, extras);
diff --git a/packages/MediaComponents/test/src/android/media/MediaController2Test.java b/packages/MediaComponents/test/src/android/media/MediaController2Test.java
index 0efb84a..2237d18 100644
--- a/packages/MediaComponents/test/src/android/media/MediaController2Test.java
+++ b/packages/MediaComponents/test/src/android/media/MediaController2Test.java
@@ -19,7 +19,6 @@
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
-import android.media.MediaPlayerBase.PlayerEventCallback;
 import android.media.MediaSession2.Command;
 import android.media.MediaSession2.CommandGroup;
 import android.media.MediaSession2.ControllerInfo;
@@ -33,6 +32,7 @@
 import android.os.HandlerThread;
 import android.os.Process;
 import android.os.ResultReceiver;
+import android.support.annotation.NonNull;
 import android.support.test.filters.FlakyTest;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
@@ -119,8 +119,8 @@
 
     @Ignore
     @Test
-    public void testSkipToPrevious() throws InterruptedException {
-        mController.skipToPrevious();
+    public void testSkipToPreviousItem() throws InterruptedException {
+        mController.skipToPreviousItem();
         try {
             assertTrue(mPlayer.mCountDownLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
         } catch (InterruptedException e) {
@@ -129,9 +129,10 @@
         assertTrue(mPlayer.mSkipToPreviousCalled);
     }
 
+    @Ignore
     @Test
-    public void testSkipToNext() throws InterruptedException {
-        mController.skipToNext();
+    public void testSkipToNextItem() throws InterruptedException {
+        mController.skipToNextItem();
         try {
             assertTrue(mPlayer.mCountDownLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
         } catch (InterruptedException e) {
@@ -212,7 +213,7 @@
         } catch (InterruptedException e) {
             fail(e.getMessage());
         }
-        assertTrue(mPlayer.mSetCurrentPlaylistItemCalled);
+        assertTrue(mPlayer.mSetCurrentPlaylistCalled);
         assertEquals(itemIndex, mPlayer.mCurrentItem);
     }
     */
@@ -322,9 +323,9 @@
         final CountDownLatch latch = new CountDownLatch(1);
         final SessionCallback callback = new SessionCallback(mContext) {
             @Override
-            public void onCustomCommand(ControllerInfo controller, Command customCommand,
-                    Bundle args, ResultReceiver cb) {
-                super.onCustomCommand(controller, customCommand, args, cb);
+            public void onCustomCommand(MediaSession2 session, ControllerInfo controller,
+                    Command customCommand, Bundle args, ResultReceiver cb) {
+                super.onCustomCommand(session, controller, customCommand, args, cb);
                 assertEquals(mContext.getPackageName(), controller.getPackageName());
                 assertEquals(testCommand, customCommand);
                 assertTrue(TestUtils.equals(testArgs, args));
@@ -352,7 +353,8 @@
     public void testControllerCallback_sessionRejects() throws InterruptedException {
         final MediaSession2.SessionCallback sessionCallback = new SessionCallback(mContext) {
             @Override
-            public MediaSession2.CommandGroup onConnect(ControllerInfo controller) {
+            public MediaSession2.CommandGroup onConnect(MediaSession2 session,
+                    ControllerInfo controller) {
                 return null;
             }
         };
@@ -390,7 +392,9 @@
         final CountDownLatch latch = new CountDownLatch(1);
         final SessionCallback callback = new SessionCallback(mContext) {
             @Override
-            public void onPlayFromSearch(ControllerInfo controller, String query, Bundle extras) {
+            public void onPlayFromSearch(MediaSession2 session, ControllerInfo controller,
+                    String query, Bundle extras) {
+                super.onPlayFromSearch(session, controller, query, extras);
                 assertEquals(mContext.getPackageName(), controller.getPackageName());
                 assertEquals(request, query);
                 assertTrue(TestUtils.equals(bundle, extras));
@@ -415,7 +419,8 @@
         final CountDownLatch latch = new CountDownLatch(1);
         final SessionCallback callback = new SessionCallback(mContext) {
             @Override
-            public void onPlayFromUri(ControllerInfo controller, Uri uri, Bundle extras) {
+            public void onPlayFromUri(MediaSession2 session, ControllerInfo controller, Uri uri,
+                    Bundle extras) {
                 assertEquals(mContext.getPackageName(), controller.getPackageName());
                 assertEquals(request, uri);
                 assertTrue(TestUtils.equals(bundle, extras));
@@ -440,9 +445,10 @@
         final CountDownLatch latch = new CountDownLatch(1);
         final SessionCallback callback = new SessionCallback(mContext) {
             @Override
-            public void onPlayFromMediaId(ControllerInfo controller, String id, Bundle extras) {
+            public void onPlayFromMediaId(MediaSession2 session, ControllerInfo controller,
+                    String mediaId, Bundle extras) {
                 assertEquals(mContext.getPackageName(), controller.getPackageName());
-                assertEquals(request, id);
+                assertEquals(request, mediaId);
                 assertTrue(TestUtils.equals(bundle, extras));
                 latch.countDown();
             }
@@ -466,8 +472,8 @@
         final CountDownLatch latch = new CountDownLatch(1);
         final SessionCallback callback = new SessionCallback(mContext) {
             @Override
-            public void onPrepareFromSearch(ControllerInfo controller, String query,
-                    Bundle extras) {
+            public void onPrepareFromSearch(MediaSession2 session, ControllerInfo controller,
+                    String query, Bundle extras) {
                 assertEquals(mContext.getPackageName(), controller.getPackageName());
                 assertEquals(request, query);
                 assertTrue(TestUtils.equals(bundle, extras));
@@ -492,7 +498,8 @@
         final CountDownLatch latch = new CountDownLatch(1);
         final SessionCallback callback = new SessionCallback(mContext) {
             @Override
-            public void onPrepareFromUri(ControllerInfo controller, Uri uri, Bundle extras) {
+            public void onPrepareFromUri(MediaSession2 session, ControllerInfo controller, Uri uri,
+                    Bundle extras) {
                 assertEquals(mContext.getPackageName(), controller.getPackageName());
                 assertEquals(request, uri);
                 assertTrue(TestUtils.equals(bundle, extras));
@@ -517,9 +524,10 @@
         final CountDownLatch latch = new CountDownLatch(1);
         final SessionCallback callback = new SessionCallback(mContext) {
             @Override
-            public void onPrepareFromMediaId(ControllerInfo controller, String id, Bundle extras) {
+            public void onPrepareFromMediaId(MediaSession2 session, ControllerInfo controller,
+                    String mediaId, Bundle extras) {
                 assertEquals(mContext.getPackageName(), controller.getPackageName());
-                assertEquals(request, id);
+                assertEquals(request, mediaId);
                 assertTrue(TestUtils.equals(bundle, extras));
                 latch.countDown();
             }
@@ -544,8 +552,8 @@
         final CountDownLatch latch = new CountDownLatch(1);
         final SessionCallback callback = new SessionCallback(mContext) {
             @Override
-            public void onSetRating(ControllerInfo controller, String mediaIdOut,
-                    Rating2 ratingOut) {
+            public void onSetRating(MediaSession2 session, ControllerInfo controller,
+                    String mediaIdOut, Rating2 ratingOut) {
                 assertEquals(mContext.getPackageName(), controller.getPackageName());
                 assertEquals(mediaId, mediaIdOut);
                 assertEquals(rating, ratingOut);
@@ -616,9 +624,9 @@
                     player.notifyPlaybackState(state);
                     controller.stop();
                     player.notifyPlaybackState(state);
-                    controller.skipToNext();
+                    controller.skipToNextItem();
                     player.notifyPlaybackState(state);
-                    controller.skipToPrevious();
+                    controller.skipToPreviousItem();
                 }
                 // This may hang if deadlock happens.
                 latch.countDown();
@@ -650,15 +658,6 @@
         assertEquals(SessionToken2.TYPE_SESSION_SERVICE, token.getType());
     }
 
-    private void connectToService(SessionToken2 token) throws InterruptedException {
-        if (mSession != null) {
-            mSession.close();
-        }
-        mController = createController(token);
-        mSession = TestServiceRegistry.getInstance().getServiceInstance().getSession();
-        mPlayer = (MockPlayer) mSession.getPlayer();
-    }
-
     @Test
     public void testConnectToService_sessionService() throws InterruptedException {
         testConnectToService(MockMediaSessionService2.ID);
@@ -674,17 +673,24 @@
         final CountDownLatch latch = new CountDownLatch(1);
         final SessionCallbackProxy proxy = new SessionCallbackProxy(mContext) {
             @Override
-            public CommandGroup onConnect(ControllerInfo controller) {
+            public CommandGroup onConnect(@NonNull MediaSession2 session,
+                    @NonNull ControllerInfo controller) {
                 if (Process.myUid() == controller.getUid()) {
+                    if (mSession != null) {
+                        mSession.close();
+                    }
+                    mSession = session;
+                    mPlayer = (MockPlayer) session.getPlayer();
                     assertEquals(mContext.getPackageName(), controller.getPackageName());
                     assertFalse(controller.isTrusted());
                     latch.countDown();
                 }
-                return super.onConnect(controller);
+                return super.onConnect(session, controller);
             }
         };
         TestServiceRegistry.getInstance().setSessionCallbackProxy(proxy);
-        connectToService(TestUtils.getServiceToken(mContext, id));
+
+        mController = createController(TestUtils.getServiceToken(mContext, id));
         assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
 
         // Test command from controller to session service
@@ -712,11 +718,14 @@
         testControllerAfterSessionIsGone(mSession.getToken().getId());
     }
 
+    // TODO(jaewan): Re-enable this test
     @Ignore
     @Test
     public void testControllerAfterSessionIsGone_sessionService() throws InterruptedException {
+        /*
         connectToService(TestUtils.getServiceToken(mContext, MockMediaSessionService2.ID));
         testControllerAfterSessionIsGone(MockMediaSessionService2.ID);
+        */
     }
 
     @Test
@@ -744,13 +753,11 @@
         testControllerAfterSessionIsGone(id);
     }
 
-    @Ignore
     @Test
     public void testClose_sessionService() throws InterruptedException {
         testCloseFromService(MockMediaSessionService2.ID);
     }
 
-    @Ignore
     @Test
     public void testClose_libraryService() throws InterruptedException {
         testCloseFromService(MockMediaLibraryService2.ID);
diff --git a/packages/MediaComponents/test/src/android/media/MediaSession2Test.java b/packages/MediaComponents/test/src/android/media/MediaSession2Test.java
index 3c72e7d..a0b1f18 100644
--- a/packages/MediaComponents/test/src/android/media/MediaSession2Test.java
+++ b/packages/MediaComponents/test/src/android/media/MediaSession2Test.java
@@ -29,7 +29,6 @@
 
 import android.content.Context;
 import android.media.MediaController2.PlaybackInfo;
-import android.media.MediaPlayerBase.PlayerEventCallback;
 import android.media.MediaSession2.Builder;
 import android.media.MediaSession2.Command;
 import android.media.MediaSession2.CommandButton;
@@ -38,13 +37,11 @@
 import android.media.MediaSession2.PlaylistParams;
 import android.media.MediaSession2.SessionCallback;
 import android.os.Bundle;
-import android.os.Looper;
 import android.os.Process;
 import android.os.ResultReceiver;
 import android.support.annotation.NonNull;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
-import android.text.TextUtils;
 
 import org.junit.After;
 import org.junit.Before;
@@ -190,19 +187,20 @@
         });
     }
 
+    @Ignore
     @Test
-    public void testSkipToNext() throws Exception {
+    public void testSkipToNextItem() throws Exception {
         sHandler.postAndSync(() -> {
-            mSession.skipToNext();
+            mSession.skipToNextItem();
             assertTrue(mPlayer.mSkipToNextCalled);
         });
     }
 
     @Ignore
     @Test
-    public void testSkipToPrevious() throws Exception {
+    public void testSkipToPreviousItem() throws Exception {
         sHandler.postAndSync(() -> {
-            mSession.skipToPrevious();
+            mSession.skipToPreviousItem();
             assertTrue(mPlayer.mSkipToPreviousCalled);
         });
     }
@@ -358,13 +356,14 @@
         assertEquals(1, callback.commands.size());
         assertEquals(MediaSession2.COMMAND_CODE_PLAYBACK_PAUSE,
                 (long) callback.commands.get(0).getCommandCode());
-        controller.skipToNext();
-        assertTrue(mPlayer.mCountDownLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-        assertTrue(mPlayer.mSkipToNextCalled);
-        assertFalse(mPlayer.mPauseCalled);
-        assertEquals(2, callback.commands.size());
-        assertEquals(MediaSession2.COMMAND_CODE_PLAYBACK_SKIP_NEXT_ITEM,
-                (long) callback.commands.get(1).getCommandCode());
+        // TODO(jaewan): uncomment followings once skipToNextItem is implemented (b/74090741)
+//        controller.skipToNextItem();
+//        assertTrue(mPlayer.mCountDownLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+//        assertTrue(mPlayer.mSkipToNextCalled);
+//        assertFalse(mPlayer.mPauseCalled);
+//        assertEquals(2, callback.commands.size());
+//        assertEquals(MediaSession2.COMMAND_CODE_PLAYBACK_SKIP_NEXT_ITEM,
+//                (long) callback.commands.get(1).getCommandCode());
     }
 
     @Test
@@ -391,11 +390,12 @@
         final CountDownLatch latch = new CountDownLatch(1);
         final SessionCallback sessionCallback = new SessionCallback(mContext) {
             @Override
-            public CommandGroup onConnect(ControllerInfo controller) {
+            public CommandGroup onConnect(MediaSession2 session,
+                    ControllerInfo controller) {
                 if (mContext.getPackageName().equals(controller.getPackageName())) {
                     mSession.setCustomLayout(controller, buttons);
                 }
-                return super.onConnect(controller);
+                return super.onConnect(session, controller);
             }
         };
 
@@ -404,6 +404,10 @@
                 .setId("testSetCustomLayout")
                 .setSessionCallback(sHandlerExecutor, sessionCallback)
                 .build()) {
+            if (mSession != null) {
+                mSession.close();
+                mSession = session;
+            }
             final TestControllerCallbackInterface callback = new TestControllerCallbackInterface() {
                 @Override
                 public void onCustomLayoutChanged(List<CommandButton> layout) {
@@ -423,6 +427,38 @@
     }
 
     @Test
+    public void testSetAllowedCommands() throws InterruptedException {
+        final CommandGroup commands = new CommandGroup(mContext);
+        commands.addCommand(new Command(mContext, MediaSession2.COMMAND_CODE_PLAYBACK_PLAY));
+        commands.addCommand(new Command(mContext, MediaSession2.COMMAND_CODE_PLAYBACK_PAUSE));
+        commands.addCommand(new Command(mContext, MediaSession2.COMMAND_CODE_PLAYBACK_STOP));
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        final TestControllerCallbackInterface callback = new TestControllerCallbackInterface() {
+            @Override
+            public void onAllowedCommandsChanged(CommandGroup commandsOut) {
+                assertNotNull(commandsOut);
+                List<Command> expected = commands.getCommands();
+                List<Command> actual = commandsOut.getCommands();
+
+                assertNotNull(actual);
+                assertEquals(expected.size(), actual.size());
+                for (int i = 0; i < expected.size(); i++) {
+                    assertEquals(expected.get(i), actual.get(i));
+                }
+                latch.countDown();
+            }
+        };
+
+        final MediaController2 controller = createController(mSession.getToken(), true, callback);
+        ControllerInfo controllerInfo = getTestControllerInfo();
+        assertNotNull(controllerInfo);
+
+        mSession.setAllowedCommands(controllerInfo, commands);
+        assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+    }
+
+    @Test
     public void testSendCustomAction() throws InterruptedException {
         final Command testCommand =
                 new Command(mContext, MediaSession2.COMMAND_CODE_PLAYBACK_PREPARE);
@@ -454,13 +490,12 @@
     private ControllerInfo getTestControllerInfo() {
         List<ControllerInfo> controllers = mSession.getConnectedControllers();
         assertNotNull(controllers);
-        final String packageName = mContext.getPackageName();
         for (int i = 0; i < controllers.size(); i++) {
-            if (TextUtils.equals(packageName, controllers.get(i).getPackageName())) {
+            if (Process.myUid() == controllers.get(i).getUid()) {
                 return controllers.get(i);
             }
         }
-        fail("Fails to get custom command");
+        fail("Failed to get test controller info");
         return null;
     }
 
@@ -470,7 +505,8 @@
         }
 
         @Override
-        public MediaSession2.CommandGroup onConnect(ControllerInfo controllerInfo) {
+        public MediaSession2.CommandGroup onConnect(MediaSession2 session,
+                ControllerInfo controllerInfo) {
             if (Process.myUid() != controllerInfo.getUid()) {
                 return null;
             }
@@ -490,7 +526,7 @@
         }
 
         @Override
-        public boolean onCommandRequest(ControllerInfo controllerInfo,
+        public boolean onCommandRequest(MediaSession2 session, ControllerInfo controllerInfo,
                 MediaSession2.Command command) {
             assertEquals(mContext.getPackageName(), controllerInfo.getPackageName());
             assertEquals(Process.myUid(), controllerInfo.getUid());
diff --git a/packages/MediaComponents/test/src/android/media/MediaSession2TestBase.java b/packages/MediaComponents/test/src/android/media/MediaSession2TestBase.java
index c30b9a6..83706c0 100644
--- a/packages/MediaComponents/test/src/android/media/MediaSession2TestBase.java
+++ b/packages/MediaComponents/test/src/android/media/MediaSession2TestBase.java
@@ -69,6 +69,7 @@
         default void onPlaybackInfoChanged(MediaController2.PlaybackInfo info) {}
         default void onPlaybackStateChanged(PlaybackState2 state) {}
         default void onCustomLayoutChanged(List<CommandButton> layout) {}
+        default void onAllowedCommandsChanged(CommandGroup commands) {}
         default void onCustomCommand(Command command, Bundle args, ResultReceiver receiver) {}
     }
 
@@ -187,23 +188,24 @@
 
         @CallSuper
         @Override
-        public void onConnected(CommandGroup commands) {
+        public void onConnected(MediaController2 controller, CommandGroup commands) {
             connectLatch.countDown();
         }
 
         @CallSuper
         @Override
-        public void onDisconnected() {
+        public void onDisconnected(MediaController2 controller) {
             disconnectLatch.countDown();
         }
 
         @Override
-        public void onPlaybackStateChanged(PlaybackState2 state) {
+        public void onPlaybackStateChanged(MediaController2 controller, PlaybackState2 state) {
             mCallbackProxy.onPlaybackStateChanged(state);
         }
 
         @Override
-        public void onCustomCommand(Command command, Bundle args, ResultReceiver receiver) {
+        public void onCustomCommand(MediaController2 controller, Command command, Bundle args,
+                ResultReceiver receiver) {
             mCallbackProxy.onCustomCommand(command, args, receiver);
         }
 
@@ -226,24 +228,31 @@
         }
 
         @Override
-        public void onPlaylistChanged(List<MediaItem2> params) {
+        public void onPlaylistChanged(MediaController2 controller, List<MediaItem2> params) {
             mCallbackProxy.onPlaylistChanged(params);
         }
 
         @Override
-        public void onPlaylistParamsChanged(MediaSession2.PlaylistParams params) {
+        public void onPlaylistParamsChanged(MediaController2 controller,
+                MediaSession2.PlaylistParams params) {
             mCallbackProxy.onPlaylistParamsChanged(params);
         }
 
         @Override
-        public void onPlaybackInfoChanged(MediaController2.PlaybackInfo info) {
+        public void onPlaybackInfoChanged(MediaController2 controller,
+                MediaController2.PlaybackInfo info) {
             mCallbackProxy.onPlaybackInfoChanged(info);
         }
 
         @Override
-        public void onCustomLayoutChanged(List<CommandButton> layout) {
+        public void onCustomLayoutChanged(MediaController2 controller, List<CommandButton> layout) {
             mCallbackProxy.onCustomLayoutChanged(layout);
         }
+
+        @Override
+        public void onAllowedCommandsChanged(MediaController2 controller, CommandGroup commands) {
+            mCallbackProxy.onAllowedCommandsChanged(commands);
+        }
     }
 
     public class TestMediaController extends MediaController2 implements TestControllerInterface {
diff --git a/packages/MediaComponents/test/src/android/media/MediaSession2_PermissionTest.java b/packages/MediaComponents/test/src/android/media/MediaSession2_PermissionTest.java
new file mode 100644
index 0000000..ca36513
--- /dev/null
+++ b/packages/MediaComponents/test/src/android/media/MediaSession2_PermissionTest.java
@@ -0,0 +1,424 @@
+/*
+ * 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.
+ */
+
+package android.media;
+
+import static android.media.MediaSession2.COMMAND_CODE_PLAYBACK_FAST_FORWARD;
+import static android.media.MediaSession2.COMMAND_CODE_PLAYBACK_PAUSE;
+import static android.media.MediaSession2.COMMAND_CODE_PLAYBACK_PLAY;
+import static android.media.MediaSession2.COMMAND_CODE_PLAYBACK_REWIND;
+import static android.media.MediaSession2.COMMAND_CODE_PLAYBACK_SEEK_TO;
+import static android.media.MediaSession2.COMMAND_CODE_PLAYBACK_SET_PLAYLIST_PARAMS;
+import static android.media.MediaSession2.COMMAND_CODE_PLAYBACK_SET_VOLUME;
+import static android.media.MediaSession2.COMMAND_CODE_PLAYBACK_SKIP_NEXT_ITEM;
+import static android.media.MediaSession2.COMMAND_CODE_PLAYBACK_SKIP_PREV_ITEM;
+import static android.media.MediaSession2.COMMAND_CODE_PLAYBACK_STOP;
+import static android.media.MediaSession2.COMMAND_CODE_PLAY_FROM_MEDIA_ID;
+import static android.media.MediaSession2.COMMAND_CODE_PLAY_FROM_SEARCH;
+import static android.media.MediaSession2.COMMAND_CODE_PLAY_FROM_URI;
+import static android.media.MediaSession2.COMMAND_CODE_PREPARE_FROM_MEDIA_ID;
+import static android.media.MediaSession2.COMMAND_CODE_PREPARE_FROM_SEARCH;
+import static android.media.MediaSession2.COMMAND_CODE_PREPARE_FROM_URI;
+import static android.media.MediaSession2.ControllerInfo;
+import static android.media.MediaSession2.PlaylistParams;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.after;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.media.MediaSession2.Command;
+import android.media.MediaSession2.CommandGroup;
+import android.media.MediaSession2.SessionCallback;
+import android.net.Uri;
+import android.os.Process;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+
+/**
+ * Tests whether {@link MediaSession2} receives commands that hasn't allowed.
+ */
+@RunWith(AndroidJUnit4.class)
+@MediumTest
+public class MediaSession2_PermissionTest extends MediaSession2TestBase {
+    private static final String SESSION_ID = "MediaSession2Test_permission";
+
+    private MockPlayer mPlayer;
+    private MediaSession2 mSession;
+    private MediaSession2.SessionCallback mCallback;
+
+    private MediaSession2 matchesSession() {
+        return argThat((session) -> session == mSession);
+    }
+
+    private static ControllerInfo matchesCaller() {
+        return argThat((controllerInfo) -> controllerInfo.getUid() == Process.myUid());
+    }
+
+    private static Command matches(final int commandCode) {
+        return argThat((command) -> command.getCommandCode() == commandCode);
+    }
+
+    @Before
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+    }
+
+    @After
+    @Override
+    public void cleanUp() throws Exception {
+        super.cleanUp();
+        if (mSession != null) {
+            mSession.close();
+            mSession = null;
+        }
+        mPlayer = null;
+        mCallback = null;
+    }
+
+    private MediaSession2 createSessionWithAllowedActions(CommandGroup commands) {
+        mPlayer = new MockPlayer(0);
+        if (commands == null) {
+            commands = new CommandGroup(mContext);
+        }
+        mCallback = mock(SessionCallback.class);
+        when(mCallback.onConnect(any(), any())).thenReturn(commands);
+        if (mSession != null) {
+            mSession.close();
+        }
+        mSession = new MediaSession2.Builder(mContext).setPlayer(mPlayer).setId(SESSION_ID)
+                .setSessionCallback(sHandlerExecutor, mCallback).build();
+        return mSession;
+    }
+
+    private CommandGroup createCommandGroupWith(int commandCode) {
+        CommandGroup commands = new CommandGroup(mContext);
+        commands.addCommand(new Command(mContext, commandCode));
+        return commands;
+    }
+
+    private CommandGroup createCommandGroupWithout(int commandCode) {
+        CommandGroup commands = new CommandGroup(mContext);
+        commands.addAllPredefinedCommands();
+        commands.removeCommand(new Command(mContext, commandCode));
+        return commands;
+    }
+
+    @Test
+    public void testPlay() throws InterruptedException {
+        createSessionWithAllowedActions(createCommandGroupWith(COMMAND_CODE_PLAYBACK_PLAY));
+        createController(mSession.getToken()).play();
+        verify(mCallback, timeout(TIMEOUT_MS).atLeastOnce()).onCommandRequest(
+                matchesSession(), matchesCaller(), matches(COMMAND_CODE_PLAYBACK_PLAY));
+
+        createSessionWithAllowedActions(createCommandGroupWithout(COMMAND_CODE_PLAYBACK_PLAY));
+        createController(mSession.getToken()).play();
+        verify(mCallback, after(WAIT_TIME_MS).never()).onCommandRequest(any(), any(), any());
+    }
+
+    @Test
+    public void testPause() throws InterruptedException {
+        createSessionWithAllowedActions(createCommandGroupWith(COMMAND_CODE_PLAYBACK_PAUSE));
+        createController(mSession.getToken()).pause();
+        verify(mCallback, timeout(TIMEOUT_MS).atLeastOnce()).onCommandRequest(
+                matchesSession(), matchesCaller(), matches(COMMAND_CODE_PLAYBACK_PAUSE));
+
+        createSessionWithAllowedActions(createCommandGroupWithout(COMMAND_CODE_PLAYBACK_PAUSE));
+        createController(mSession.getToken()).pause();
+        verify(mCallback, after(WAIT_TIME_MS).never()).onCommandRequest(any(), any(), any());
+    }
+
+    @Test
+    public void testStop() throws InterruptedException {
+        createSessionWithAllowedActions(createCommandGroupWith(COMMAND_CODE_PLAYBACK_STOP));
+        createController(mSession.getToken()).stop();
+        verify(mCallback, timeout(TIMEOUT_MS).atLeastOnce()).onCommandRequest(
+                matchesSession(), matchesCaller(), matches(COMMAND_CODE_PLAYBACK_STOP));
+
+        createSessionWithAllowedActions(createCommandGroupWithout(COMMAND_CODE_PLAYBACK_STOP));
+        createController(mSession.getToken()).stop();
+        verify(mCallback, after(WAIT_TIME_MS).never()).onCommandRequest(any(), any(), any());
+    }
+
+    @Test
+    public void testSkipToNext() throws InterruptedException {
+        createSessionWithAllowedActions(
+                createCommandGroupWith(COMMAND_CODE_PLAYBACK_SKIP_NEXT_ITEM));
+        createController(mSession.getToken()).skipToNextItem();
+        verify(mCallback, timeout(TIMEOUT_MS).atLeastOnce()).onCommandRequest(
+                matchesSession(), matchesCaller(), matches(COMMAND_CODE_PLAYBACK_SKIP_NEXT_ITEM));
+
+        createSessionWithAllowedActions(
+                createCommandGroupWithout(COMMAND_CODE_PLAYBACK_SKIP_NEXT_ITEM));
+        createController(mSession.getToken()).skipToNextItem();
+        verify(mCallback, after(WAIT_TIME_MS).never()).onCommandRequest(any(), any(), any());
+    }
+
+    @Test
+    public void testSkipToPrevious() throws InterruptedException {
+        createSessionWithAllowedActions(
+                createCommandGroupWith(COMMAND_CODE_PLAYBACK_SKIP_PREV_ITEM));
+        createController(mSession.getToken()).skipToPreviousItem();
+        verify(mCallback, timeout(TIMEOUT_MS).atLeastOnce()).onCommandRequest(
+                matchesSession(), matchesCaller(), matches(COMMAND_CODE_PLAYBACK_SKIP_PREV_ITEM));
+
+        createSessionWithAllowedActions(
+                createCommandGroupWithout(COMMAND_CODE_PLAYBACK_SKIP_PREV_ITEM));
+        createController(mSession.getToken()).skipToPreviousItem();
+        verify(mCallback, after(WAIT_TIME_MS).never()).onCommandRequest(any(), any(), any());
+    }
+
+    @Test
+    public void testFastForward() throws InterruptedException {
+        createSessionWithAllowedActions(
+                createCommandGroupWith(COMMAND_CODE_PLAYBACK_FAST_FORWARD));
+        createController(mSession.getToken()).fastForward();
+        verify(mCallback, timeout(TIMEOUT_MS).atLeastOnce()).onCommandRequest(
+                matchesSession(), matchesCaller(), matches(COMMAND_CODE_PLAYBACK_FAST_FORWARD));
+
+        createSessionWithAllowedActions(
+                createCommandGroupWithout(COMMAND_CODE_PLAYBACK_FAST_FORWARD));
+        createController(mSession.getToken()).fastForward();
+        verify(mCallback, after(WAIT_TIME_MS).never()).onCommandRequest(any(), any(), any());
+    }
+
+    @Test
+    public void testRewind() throws InterruptedException {
+        createSessionWithAllowedActions(
+                createCommandGroupWith(COMMAND_CODE_PLAYBACK_REWIND));
+        createController(mSession.getToken()).rewind();
+        verify(mCallback, timeout(TIMEOUT_MS).atLeastOnce()).onCommandRequest(
+                matchesSession(), matchesCaller(), matches(COMMAND_CODE_PLAYBACK_REWIND));
+
+        createSessionWithAllowedActions(createCommandGroupWithout(COMMAND_CODE_PLAYBACK_REWIND));
+        createController(mSession.getToken()).rewind();
+        verify(mCallback, after(WAIT_TIME_MS).never()).onCommandRequest(any(), any(), any());
+    }
+
+    @Test
+    public void testSeekTo() throws InterruptedException {
+        final long position = 10;
+        createSessionWithAllowedActions(
+                createCommandGroupWith(COMMAND_CODE_PLAYBACK_SEEK_TO));
+        createController(mSession.getToken()).seekTo(position);
+        verify(mCallback, timeout(TIMEOUT_MS).atLeastOnce()).onCommandRequest(
+                matchesSession(), matchesCaller(), matches(COMMAND_CODE_PLAYBACK_SEEK_TO));
+
+        createSessionWithAllowedActions(createCommandGroupWithout(COMMAND_CODE_PLAYBACK_SEEK_TO));
+        createController(mSession.getToken()).seekTo(position);
+        verify(mCallback, after(WAIT_TIME_MS).never()).onCommandRequest(any(), any(), any());
+    }
+
+    // TODO(jaewan): Uncomment when we implement skipToPlaylistItem()
+    /*
+    @Test
+    public void testSkipToPlaylistItem() throws InterruptedException {
+        final Uri uri = Uri.parse("set://current.playlist.item");
+        final DataSourceDesc dsd = new DataSourceDesc.Builder()
+                .setDataSource(mContext, uri).build();
+        final MediaItem2 item = new MediaItem2.Builder(mContext, MediaItem2.FLAG_PLAYABLE)
+                .setDataSourceDesc(dsd).build();
+        createSessionWithAllowedActions(
+                createCommandGroupWith(COMMAND_CODE_PLAYBACK_SET_CURRENT_PLAYLIST_ITEM));
+        createController(mSession.getToken()).skipToPlaylistItem(item);
+        verify(mCallback, timeout(TIMEOUT_MS).atLeastOnce()).onCommandRequest(matchesCaller(),
+                matches(COMMAND_CODE_PLAYBACK_SET_CURRENT_PLAYLIST_ITEM));
+
+        createSessionWithAllowedActions(
+                createCommandGroupWithout(COMMAND_CODE_PLAYBACK_SET_CURRENT_PLAYLIST_ITEM));
+        createController(mSession.getToken()).skipToPlaylistItem(item);
+        verify(mCallback, after(WAIT_TIME_MS).never()).onCommandRequest(any(), any());
+    }
+    */
+
+    @Test
+    public void testSetPlaylistParams() throws InterruptedException {
+        final PlaylistParams param = new PlaylistParams(mContext,
+                PlaylistParams.REPEAT_MODE_ALL, PlaylistParams.SHUFFLE_MODE_ALL, null);
+        createSessionWithAllowedActions(
+                createCommandGroupWith(COMMAND_CODE_PLAYBACK_SET_PLAYLIST_PARAMS));
+        createController(mSession.getToken()).setPlaylistParams(param);
+        verify(mCallback, timeout(TIMEOUT_MS).atLeastOnce()).onCommandRequest(
+                matchesSession(), matchesCaller(),
+                matches(COMMAND_CODE_PLAYBACK_SET_PLAYLIST_PARAMS));
+
+        createSessionWithAllowedActions(
+                createCommandGroupWithout(COMMAND_CODE_PLAYBACK_SET_PLAYLIST_PARAMS));
+        createController(mSession.getToken()).setPlaylistParams(param);
+        verify(mCallback, after(WAIT_TIME_MS).never()).onCommandRequest(any(), any(), any());
+    }
+
+    @Test
+    public void testSetVolume() throws InterruptedException {
+        createSessionWithAllowedActions(createCommandGroupWith(COMMAND_CODE_PLAYBACK_SET_VOLUME));
+        createController(mSession.getToken()).setVolumeTo(0, 0);
+        verify(mCallback, timeout(TIMEOUT_MS).atLeastOnce()).onCommandRequest(
+                matchesSession(), matchesCaller(), matches(COMMAND_CODE_PLAYBACK_SET_VOLUME));
+
+        createSessionWithAllowedActions(
+                createCommandGroupWithout(COMMAND_CODE_PLAYBACK_SET_VOLUME));
+        createController(mSession.getToken()).setVolumeTo(0, 0);
+        verify(mCallback, after(WAIT_TIME_MS).never()).onCommandRequest(any(), any(), any());
+    }
+
+    @Test
+    public void testPlayFromMediaId() throws InterruptedException {
+        final String mediaId = "testPlayFromMediaId";
+        createSessionWithAllowedActions(
+                createCommandGroupWith(COMMAND_CODE_PLAY_FROM_MEDIA_ID));
+        createController(mSession.getToken()).playFromMediaId(mediaId, null);
+        verify(mCallback, timeout(TIMEOUT_MS).atLeastOnce()).onPlayFromMediaId(
+                matchesSession(), matchesCaller(), eq(mediaId), isNull());
+
+        createSessionWithAllowedActions(
+                createCommandGroupWithout(COMMAND_CODE_PLAY_FROM_MEDIA_ID));
+        createController(mSession.getToken()).playFromMediaId(mediaId, null);
+        verify(mCallback, after(WAIT_TIME_MS).never()).onPlayFromMediaId(
+                any(), any(), any(), any());
+    }
+
+    @Test
+    public void testPlayFromUri() throws InterruptedException {
+        final Uri uri = Uri.parse("play://from.uri");
+        createSessionWithAllowedActions(
+                createCommandGroupWith(COMMAND_CODE_PLAY_FROM_URI));
+        createController(mSession.getToken()).playFromUri(uri, null);
+        verify(mCallback, timeout(TIMEOUT_MS).atLeastOnce()).onPlayFromUri(
+                matchesSession(), matchesCaller(), eq(uri), isNull());
+
+        createSessionWithAllowedActions(
+                createCommandGroupWithout(COMMAND_CODE_PLAY_FROM_URI));
+        createController(mSession.getToken()).playFromUri(uri, null);
+        verify(mCallback, after(WAIT_TIME_MS).never()).onPlayFromUri(any(), any(), any(), any());
+    }
+
+    @Test
+    public void testPlayFromSearch() throws InterruptedException {
+        final String query = "testPlayFromSearch";
+        createSessionWithAllowedActions(
+                createCommandGroupWith(COMMAND_CODE_PLAY_FROM_SEARCH));
+        createController(mSession.getToken()).playFromSearch(query, null);
+        verify(mCallback, timeout(TIMEOUT_MS).atLeastOnce()).onPlayFromSearch(
+                matchesSession(), matchesCaller(), eq(query), isNull());
+
+        createSessionWithAllowedActions(
+                createCommandGroupWithout(COMMAND_CODE_PLAY_FROM_SEARCH));
+        createController(mSession.getToken()).playFromSearch(query, null);
+        verify(mCallback, after(WAIT_TIME_MS).never()).onPlayFromSearch(any(), any(), any(), any());
+    }
+
+    @Test
+    public void testPrepareFromMediaId() throws InterruptedException {
+        final String mediaId = "testPrepareFromMediaId";
+        createSessionWithAllowedActions(
+                createCommandGroupWith(COMMAND_CODE_PREPARE_FROM_MEDIA_ID));
+        createController(mSession.getToken()).prepareFromMediaId(mediaId, null);
+        verify(mCallback, timeout(TIMEOUT_MS).atLeastOnce()).onPrepareFromMediaId(
+                matchesSession(), matchesCaller(), eq(mediaId), isNull());
+
+        createSessionWithAllowedActions(
+                createCommandGroupWithout(COMMAND_CODE_PREPARE_FROM_MEDIA_ID));
+        createController(mSession.getToken()).prepareFromMediaId(mediaId, null);
+        verify(mCallback, after(WAIT_TIME_MS).never()).onPrepareFromMediaId(
+                any(), any(), any(), any());
+    }
+
+    @Test
+    public void testPrepareFromUri() throws InterruptedException {
+        final Uri uri = Uri.parse("prepare://from.uri");
+        createSessionWithAllowedActions(
+                createCommandGroupWith(COMMAND_CODE_PREPARE_FROM_URI));
+        createController(mSession.getToken()).prepareFromUri(uri, null);
+        verify(mCallback, timeout(TIMEOUT_MS).atLeastOnce()).onPrepareFromUri(
+                matchesSession(), matchesCaller(), eq(uri), isNull());
+
+        createSessionWithAllowedActions(
+                createCommandGroupWithout(COMMAND_CODE_PREPARE_FROM_URI));
+        createController(mSession.getToken()).prepareFromUri(uri, null);
+        verify(mCallback, after(WAIT_TIME_MS).never()).onPrepareFromUri(any(), any(), any(), any());
+    }
+
+    @Test
+    public void testPrepareFromSearch() throws InterruptedException {
+        final String query = "testPrepareFromSearch";
+        createSessionWithAllowedActions(
+                createCommandGroupWith(COMMAND_CODE_PREPARE_FROM_SEARCH));
+        createController(mSession.getToken()).prepareFromSearch(query, null);
+        verify(mCallback, timeout(TIMEOUT_MS).atLeastOnce()).onPrepareFromSearch(
+                matchesSession(), matchesCaller(), eq(query), isNull());
+
+        createSessionWithAllowedActions(
+                createCommandGroupWithout(COMMAND_CODE_PREPARE_FROM_SEARCH));
+        createController(mSession.getToken()).prepareFromSearch(query, null);
+        verify(mCallback, after(WAIT_TIME_MS).never()).onPrepareFromSearch(
+                any(), any(), any(), any());
+    }
+
+    @Test
+    public void testChangingPermissionWithSetAllowedCommands() throws InterruptedException {
+        final String query = "testChangingPermissionWithSetAllowedCommands";
+        createSessionWithAllowedActions(
+                createCommandGroupWith(COMMAND_CODE_PREPARE_FROM_SEARCH));
+
+        TestControllerCallbackInterface controllerCallback =
+                mock(TestControllerCallbackInterface.class);
+        MediaController2 controller =
+                createController(mSession.getToken(), true, controllerCallback);
+
+        controller.prepareFromSearch(query, null);
+        verify(mCallback, timeout(TIMEOUT_MS).atLeastOnce()).onPrepareFromSearch(
+                matchesSession(), matchesCaller(), eq(query), isNull());
+        clearInvocations(mCallback);
+
+        // Change allowed commands.
+        mSession.setAllowedCommands(getTestControllerInfo(),
+                createCommandGroupWithout(COMMAND_CODE_PREPARE_FROM_SEARCH));
+        verify(controllerCallback, timeout(TIMEOUT_MS).atLeastOnce())
+                .onAllowedCommandsChanged(any());
+
+        controller.prepareFromSearch(query, null);
+        verify(mCallback, after(WAIT_TIME_MS).never()).onPrepareFromSearch(
+                any(), any(), any(), any());
+    }
+
+    private ControllerInfo getTestControllerInfo() {
+        List<ControllerInfo> controllers = mSession.getConnectedControllers();
+        assertNotNull(controllers);
+        for (int i = 0; i < controllers.size(); i++) {
+            if (Process.myUid() == controllers.get(i).getUid()) {
+                return controllers.get(i);
+            }
+        }
+        fail("Failed to get test controller info");
+        return null;
+    }
+}
diff --git a/packages/MediaComponents/test/src/android/media/MediaSessionManager_MediaSession2.java b/packages/MediaComponents/test/src/android/media/MediaSessionManager_MediaSession2.java
index 4cdd140..17b200f 100644
--- a/packages/MediaComponents/test/src/android/media/MediaSessionManager_MediaSession2.java
+++ b/packages/MediaComponents/test/src/android/media/MediaSessionManager_MediaSession2.java
@@ -20,6 +20,7 @@
 import android.media.MediaSession2.ControllerInfo;
 import android.media.MediaSession2.SessionCallback;
 import android.media.session.MediaSessionManager;
+import android.media.session.MediaSessionManager.OnSessionTokensChangedListener;
 import android.media.session.PlaybackState;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
@@ -30,7 +31,9 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.UUID;
 import java.util.List;
+import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
 import static org.junit.Assert.*;
@@ -111,7 +114,8 @@
             mSession = new MediaSession2.Builder(mContext).setPlayer(new MockPlayer(0))
                     .setId(TAG).setSessionCallback(sHandlerExecutor, new SessionCallback(mContext) {
                         @Override
-                        public MediaSession2.CommandGroup onConnect(ControllerInfo controller) {
+                        public MediaSession2.CommandGroup onConnect(
+                                MediaSession2 session, ControllerInfo controller) {
                             // Reject all connection request.
                             return null;
                         }
@@ -209,9 +213,128 @@
         assertTrue(foundTestLibraryService);
     }
 
+    @Test
+    public void testAddOnSessionTokensChangedListener() throws InterruptedException {
+        TokensChangedListener listener = new TokensChangedListener();
+        mManager.addOnSessionTokensChangedListener(sHandlerExecutor, listener);
+
+        listener.reset();
+        MediaSession2 session1 = new MediaSession2.Builder(mContext)
+                .setPlayer(new MockPlayer(0))
+                .setId(UUID.randomUUID().toString())
+                .build();
+        assertTrue(listener.await());
+        assertTrue(listener.findToken(session1.getToken()));
+
+        listener.reset();
+        session1.close();
+        assertTrue(listener.await());
+        assertFalse(listener.findToken(session1.getToken()));
+
+        listener.reset();
+        MediaSession2 session2 = new MediaSession2.Builder(mContext)
+                .setPlayer(new MockPlayer(0))
+                .setId(UUID.randomUUID().toString())
+                .build();
+        assertTrue(listener.await());
+        assertFalse(listener.findToken(session1.getToken()));
+        assertTrue(listener.findToken(session2.getToken()));
+
+        listener.reset();
+        MediaSession2 session3 = new MediaSession2.Builder(mContext)
+                .setPlayer(new MockPlayer(0))
+                .setId(UUID.randomUUID().toString())
+                .build();
+        assertTrue(listener.await());
+        assertFalse(listener.findToken(session1.getToken()));
+        assertTrue(listener.findToken(session2.getToken()));
+        assertTrue(listener.findToken(session3.getToken()));
+
+        listener.reset();
+        session2.close();
+        assertTrue(listener.await());
+        assertFalse(listener.findToken(session1.getToken()));
+        assertFalse(listener.findToken(session2.getToken()));
+        assertTrue(listener.findToken(session3.getToken()));
+
+        listener.reset();
+        session3.close();
+        assertTrue(listener.await());
+        assertFalse(listener.findToken(session1.getToken()));
+        assertFalse(listener.findToken(session2.getToken()));
+        assertFalse(listener.findToken(session3.getToken()));
+
+        mManager.removeOnSessionTokensChangedListener(listener);
+    }
+
+    @Test
+    public void testRemoveOnSessionTokensChangedListener() throws InterruptedException {
+        TokensChangedListener listener = new TokensChangedListener();
+        mManager.addOnSessionTokensChangedListener(sHandlerExecutor, listener);
+
+        listener.reset();
+        MediaSession2 session1 = new MediaSession2.Builder(mContext)
+                .setPlayer(new MockPlayer(0))
+                .setId(UUID.randomUUID().toString())
+                .build();
+        assertTrue(listener.await());
+
+        mManager.removeOnSessionTokensChangedListener(listener);
+
+        listener.reset();
+        session1.close();
+        assertFalse(listener.await());
+
+        listener.reset();
+        MediaSession2 session2 = new MediaSession2.Builder(mContext)
+                .setPlayer(new MockPlayer(0))
+                .setId(UUID.randomUUID().toString())
+                .build();
+        assertFalse(listener.await());
+
+        listener.reset();
+        MediaSession2 session3 = new MediaSession2.Builder(mContext)
+                .setPlayer(new MockPlayer(0))
+                .setId(UUID.randomUUID().toString())
+                .build();
+        assertFalse(listener.await());
+
+        listener.reset();
+        session2.close();
+        assertFalse(listener.await());
+
+        listener.reset();
+        session3.close();
+        assertFalse(listener.await());
+    }
+
     // Ensures if the session creation/release is notified to the server.
     private void ensureChangeInSession() throws InterruptedException {
         // TODO(jaewan): Wait by listener.
         Thread.sleep(WAIT_TIME_MS);
     }
+
+    private class TokensChangedListener implements OnSessionTokensChangedListener {
+        private CountDownLatch mLatch;
+        private List<SessionToken2> mTokens;
+
+        private void reset() {
+            mLatch = new CountDownLatch(1);
+            mTokens = null;
+        }
+
+        private boolean await() throws InterruptedException {
+            return mLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS);
+        }
+
+        private boolean findToken(SessionToken2 token) {
+            return mTokens.contains(token);
+        }
+
+        @Override
+        public void onSessionTokensChanged(List<SessionToken2> tokens) {
+            mTokens = tokens;
+            mLatch.countDown();
+        }
+    }
 }
diff --git a/packages/MediaComponents/test/src/android/media/MockMediaLibraryService2.java b/packages/MediaComponents/test/src/android/media/MockMediaLibraryService2.java
index 20ea3d2..df516c5 100644
--- a/packages/MediaComponents/test/src/android/media/MockMediaLibraryService2.java
+++ b/packages/MediaComponents/test/src/android/media/MockMediaLibraryService2.java
@@ -23,11 +23,12 @@
 import android.content.Context;
 import android.media.MediaSession2.CommandGroup;
 import android.media.MediaSession2.ControllerInfo;
-import android.media.MediaLibraryService2.MediaLibrarySession;
 import android.media.MediaLibraryService2.MediaLibrarySession.MediaLibrarySessionCallback;
 import android.media.TestServiceRegistry.SessionCallbackProxy;
 import android.media.TestUtils.SyncHandler;
 import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 import android.util.Log;
 
 import java.io.FileDescriptor;
@@ -97,8 +98,8 @@
 
     @Override
     public void onCreate() {
-        super.onCreate();
         TestServiceRegistry.getInstance().setServiceInstance(this);
+        super.onCreate();
     }
 
     @Override
@@ -145,17 +146,20 @@
         }
 
         @Override
-        public CommandGroup onConnect(ControllerInfo controller) {
-            return mCallbackProxy.onConnect(controller);
+        public CommandGroup onConnect(@NonNull MediaSession2 session,
+                @NonNull ControllerInfo controller) {
+            return mCallbackProxy.onConnect(session, controller);
         }
 
         @Override
-        public LibraryRoot onGetLibraryRoot(ControllerInfo controller, Bundle rootHints) {
+        public LibraryRoot onGetLibraryRoot(MediaLibrarySession session, ControllerInfo controller,
+                Bundle rootHints) {
             return new LibraryRoot(MockMediaLibraryService2.this, ROOT_ID, EXTRAS);
         }
 
         @Override
-        public MediaItem2 onGetItem(ControllerInfo controller, String mediaId) {
+        public MediaItem2 onGetItem(MediaLibrarySession session, ControllerInfo controller,
+                String mediaId) {
             if (MEDIA_ID_GET_ITEM.equals(mediaId)) {
                 return createMediaItem(mediaId);
             } else {
@@ -164,8 +168,8 @@
         }
 
         @Override
-        public List<MediaItem2> onGetChildren(ControllerInfo controller, String parentId, int page,
-                int pageSize, Bundle extras) {
+        public List<MediaItem2> onGetChildren(MediaLibrarySession session,
+                ControllerInfo controller, String parentId, int page, int pageSize, Bundle extras) {
             if (PARENT_ID.equals(parentId)) {
                 return getPaginatedResult(GET_CHILDREN_RESULT, page, pageSize);
             } else if (PARENT_ID_ERROR.equals(parentId)) {
@@ -176,7 +180,8 @@
         }
 
         @Override
-        public void onSearch(ControllerInfo controllerInfo, String query, Bundle extras) {
+        public void onSearch(MediaLibrarySession session, ControllerInfo controllerInfo,
+                String query, Bundle extras) {
             if (SEARCH_QUERY.equals(query)) {
                 mSession.notifySearchResultChanged(controllerInfo, query, SEARCH_RESULT_COUNT,
                         extras);
@@ -197,8 +202,9 @@
         }
 
         @Override
-        public List<MediaItem2> onGetSearchResult(ControllerInfo controllerInfo,
-                String query, int page, int pageSize, Bundle extras) {
+        public List<MediaItem2> onGetSearchResult(MediaLibrarySession session,
+                ControllerInfo controllerInfo, String query, int page, int pageSize,
+                Bundle extras) {
             if (SEARCH_QUERY.equals(query)) {
                 return getPaginatedResult(SEARCH_RESULT, page, pageSize);
             } else {
@@ -207,13 +213,16 @@
         }
 
         @Override
-        public void onSubscribe(ControllerInfo controller, String parentId, Bundle extras) {
-            mCallbackProxy.onSubscribe(controller, parentId, extras);
+        public void onSubscribe(@NonNull MediaLibrarySession session,
+                @NonNull ControllerInfo controller, @NonNull String parentId,
+                @Nullable Bundle extras) {
+            mCallbackProxy.onSubscribe(session, controller, parentId, extras);
         }
 
         @Override
-        public void onUnsubscribe(ControllerInfo controller, String parentId) {
-            mCallbackProxy.onUnsubscribe(controller, parentId);
+        public void onUnsubscribe(@NonNull MediaLibrarySession session,
+                @NonNull ControllerInfo controller, String parentId) {
+            mCallbackProxy.onUnsubscribe(session, controller, parentId);
         }
     }
 
@@ -241,13 +250,12 @@
 
     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 */);
+        return new MediaItem2.Builder(context, 0 /* Flags */)
+                .setMediaId(mediaId)
+                .setDataSourceDesc(DATA_SOURCE_DESC)
+                .setMetadata(new MediaMetadata2.Builder(context)
+                                .putString(MediaMetadata2.METADATA_KEY_MEDIA_ID, mediaId)
+                                .build())
+                .build();
     }
 }
diff --git a/packages/MediaComponents/test/src/android/media/MockMediaSessionService2.java b/packages/MediaComponents/test/src/android/media/MockMediaSessionService2.java
index 1c6534d..64b1acd 100644
--- a/packages/MediaComponents/test/src/android/media/MockMediaSessionService2.java
+++ b/packages/MediaComponents/test/src/android/media/MockMediaSessionService2.java
@@ -27,6 +27,7 @@
 import android.media.MediaSession2.SessionCallback;
 import android.media.TestServiceRegistry.SessionCallbackProxy;
 import android.media.TestUtils.SyncHandler;
+import android.support.annotation.NonNull;
 
 import java.util.concurrent.Executor;
 
@@ -46,8 +47,8 @@
 
     @Override
     public void onCreate() {
-        super.onCreate();
         TestServiceRegistry.getInstance().setServiceInstance(this);
+        super.onCreate();
         mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
     }
 
@@ -103,8 +104,9 @@
         }
 
         @Override
-        public CommandGroup onConnect(ControllerInfo controller) {
-            return mCallbackProxy.onConnect(controller);
+        public CommandGroup onConnect(@NonNull MediaSession2 session,
+                @NonNull ControllerInfo controller) {
+            return mCallbackProxy.onConnect(session, controller);
         }
     }
 }
diff --git a/packages/MediaComponents/test/src/android/media/MockPlayer.java b/packages/MediaComponents/test/src/android/media/MockPlayer.java
index 6d99a6e..05962cf 100644
--- a/packages/MediaComponents/test/src/android/media/MockPlayer.java
+++ b/packages/MediaComponents/test/src/android/media/MockPlayer.java
@@ -63,6 +63,11 @@
     }
 
     @Override
+    public void reset() {
+        // no-op
+    }
+
+    @Override
     public void play() {
         mPlayCalled = true;
         if (mCountDownLatch != null) {
@@ -299,7 +304,7 @@
     }
 
     @Override
-    public void setVolume(float volume) {
+    public void setPlayerVolume(float volume) {
         // TODO: implement this
     }
 
diff --git a/packages/MediaComponents/test/src/android/media/TestServiceRegistry.java b/packages/MediaComponents/test/src/android/media/TestServiceRegistry.java
index 08e0cf0..27b6f89 100644
--- a/packages/MediaComponents/test/src/android/media/TestServiceRegistry.java
+++ b/packages/MediaComponents/test/src/android/media/TestServiceRegistry.java
@@ -19,6 +19,7 @@
 import static org.junit.Assert.fail;
 
 import android.content.Context;
+import android.media.MediaLibraryService2.MediaLibrarySession;
 import android.media.MediaSession2.CommandGroup;
 import android.media.MediaSession2.ControllerInfo;
 import android.media.TestUtils.SyncHandler;
@@ -26,6 +27,8 @@
 import android.os.Handler;
 import android.os.Process;
 import android.support.annotation.GuardedBy;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 
 /**
  * Keeps the instance of currently running {@link MockMediaSessionService2}. And also provides
@@ -36,7 +39,7 @@
 public class TestServiceRegistry {
     /**
      * Proxy for both {@link MediaSession2.SessionCallback} and
-     * {@link MediaLibraryService2.MediaLibrarySessionCallback}.
+     * {@link MediaLibraryService2.MediaLibrarySession.MediaLibrarySessionCallback}.
      */
     public static abstract class SessionCallbackProxy {
         private final Context mContext;
@@ -56,7 +59,8 @@
          * @param controller
          * @return
          */
-        public CommandGroup onConnect(ControllerInfo controller) {
+        public CommandGroup onConnect(@NonNull MediaSession2 session,
+                @NonNull ControllerInfo controller) {
             if (Process.myUid() == controller.getUid()) {
                 CommandGroup commands = new CommandGroup(mContext);
                 commands.addAllPredefinedCommands();
@@ -75,8 +79,10 @@
          */
         public void onServiceDestroyed() { }
 
-        public void onSubscribe(ControllerInfo info, String parentId, Bundle extra) { }
-        public void onUnsubscribe(ControllerInfo info, String parentId) { }
+        public void onSubscribe(@NonNull MediaLibrarySession session, @NonNull ControllerInfo info,
+                @NonNull String parentId, @Nullable Bundle extra) { }
+        public void onUnsubscribe(@NonNull MediaLibrarySession session,
+                @NonNull ControllerInfo info, @NonNull String parentId) { }
     }
 
     @GuardedBy("TestServiceRegistry.class")
diff --git a/services/audioflinger/ServiceUtilities.cpp b/services/audioflinger/ServiceUtilities.cpp
index 84b43ce..aa267ea 100644
--- a/services/audioflinger/ServiceUtilities.cpp
+++ b/services/audioflinger/ServiceUtilities.cpp
@@ -50,69 +50,7 @@
     }
 }
 
-bool recordingAllowed(const String16& opPackageName, pid_t pid, uid_t uid) {
-    return checkRecordingInternal(opPackageName, pid, uid, false);
-}
-
-bool startRecording(const String16& opPackageName, pid_t pid, uid_t uid) {
-     return checkRecordingInternal(opPackageName, pid, uid, true);
-}
-
-bool checkRecordingInternal(const String16& opPackageName, pid_t pid, uid_t uid, bool start) {
-     // we're always OK.
-     if (getpid_cached == IPCThreadState::self()->getCallingPid()) return true;
-
-     // To permit command-line native tests
-     if (uid == AID_ROOT) return true;
-
-     // We specify a pid and uid here as mediaserver (aka MediaRecorder or StageFrightRecorder)
-     // may open a record track on behalf of a client.  Note that pid may be a tid.
-     // IMPORTANT: DON'T USE PermissionCache - RUNTIME PERMISSIONS CHANGE.
-     PermissionController permissionController;
-     const bool ok = permissionController.checkPermission(sAndroidPermissionRecordAudio, pid, uid);
-     if (!ok) {
-         ALOGE("Request requires %s", String8(sAndroidPermissionRecordAudio).c_str());
-         return false;
-     }
-
-     const String16 resolvedOpPackageName = resolveCallingPackage(
-             permissionController, opPackageName, uid);
-     if (opPackageName.size() <= 0) {
-         return false;
-     }
-
-     AppOpsManager appOps;
-     const int32_t op = appOps.permissionToOpCode(sAndroidPermissionRecordAudio);
-     if (start) {
-         if (appOps.startOpNoThrow(op, uid, resolvedOpPackageName, /*startIfModeDefault*/ false)
-                 != AppOpsManager::MODE_ALLOWED) {
-             ALOGE("Request denied by app op: %d", op);
-             return false;
-         }
-     } else {
-         if (appOps.noteOp(op, uid, resolvedOpPackageName) != AppOpsManager::MODE_ALLOWED) {
-             ALOGE("Request denied by app op: %d", op);
-             return false;
-         }
-     }
-
-     return true;
-}
-
-void finishRecording(const String16& opPackageName, uid_t uid) {
-    PermissionController permissionController;
-    const String16 resolvedOpPackageName = resolveCallingPackage(
-            permissionController, opPackageName, uid);
-    if (opPackageName.size() <= 0) {
-        return;
-    }
-
-    AppOpsManager appOps;
-    const int32_t op = appOps.permissionToOpCode(sAndroidPermissionRecordAudio);
-    appOps.finishOp(op, uid, resolvedOpPackageName);
-}
-
-const String16 resolveCallingPackage(PermissionController& permissionController,
+static String16 resolveCallingPackage(PermissionController& permissionController,
         const String16& opPackageName, uid_t uid) {
     if (opPackageName.size() > 0) {
         return opPackageName;
@@ -128,11 +66,81 @@
     permissionController.getPackagesForUid(uid, packages);
     if (packages.isEmpty()) {
         ALOGE("No packages for uid %d", uid);
-        return opPackageName;
+        return opPackageName; // empty string
     }
     return packages[0];
 }
 
+static inline bool isAudioServerOrRoot(uid_t uid) {
+    // AID_ROOT is OK for command-line tests.  Native unforked audioserver always OK.
+    return uid == AID_ROOT || uid == AID_AUDIOSERVER ;
+}
+
+static bool checkRecordingInternal(const String16& opPackageName, pid_t pid,
+        uid_t uid, bool start) {
+    // Okay to not track in app ops as audio server is us and if
+    // device is rooted security model is considered compromised.
+    if (isAudioServerOrRoot(uid)) return true;
+
+    // We specify a pid and uid here as mediaserver (aka MediaRecorder or StageFrightRecorder)
+    // may open a record track on behalf of a client.  Note that pid may be a tid.
+    // IMPORTANT: DON'T USE PermissionCache - RUNTIME PERMISSIONS CHANGE.
+    PermissionController permissionController;
+    const bool ok = permissionController.checkPermission(sAndroidPermissionRecordAudio, pid, uid);
+    if (!ok) {
+        ALOGE("Request requires %s", String8(sAndroidPermissionRecordAudio).c_str());
+        return false;
+    }
+
+    String16 resolvedOpPackageName = resolveCallingPackage(
+            permissionController, opPackageName, uid);
+    if (resolvedOpPackageName.size() == 0) {
+        return false;
+    }
+
+    AppOpsManager appOps;
+    const int32_t op = appOps.permissionToOpCode(sAndroidPermissionRecordAudio);
+    if (start) {
+        if (appOps.startOpNoThrow(op, uid, resolvedOpPackageName, /*startIfModeDefault*/ false)
+                != AppOpsManager::MODE_ALLOWED) {
+            ALOGE("Request denied by app op: %d", op);
+            return false;
+        }
+    } else {
+        if (appOps.noteOp(op, uid, resolvedOpPackageName) != AppOpsManager::MODE_ALLOWED) {
+            ALOGE("Request denied by app op: %d", op);
+            return false;
+        }
+    }
+
+    return true;
+}
+
+bool recordingAllowed(const String16& opPackageName, pid_t pid, uid_t uid) {
+    return checkRecordingInternal(opPackageName, pid, uid, /*start*/ false);
+}
+
+bool startRecording(const String16& opPackageName, pid_t pid, uid_t uid) {
+     return checkRecordingInternal(opPackageName, pid, uid, /*start*/ true);
+}
+
+void finishRecording(const String16& opPackageName, uid_t uid) {
+    // Okay to not track in app ops as audio server is us and if
+    // device is rooted security model is considered compromised.
+    if (isAudioServerOrRoot(uid)) return;
+
+    PermissionController permissionController;
+    String16 resolvedOpPackageName = resolveCallingPackage(
+            permissionController, opPackageName, uid);
+    if (resolvedOpPackageName.size() == 0) {
+        return;
+    }
+
+    AppOpsManager appOps;
+    const int32_t op = appOps.permissionToOpCode(sAndroidPermissionRecordAudio);
+    appOps.finishOp(op, uid, resolvedOpPackageName);
+}
+
 bool captureAudioOutputAllowed(pid_t pid, uid_t uid) {
     if (getpid_cached == IPCThreadState::self()->getCallingPid()) return true;
     static const String16 sCaptureAudioOutput("android.permission.CAPTURE_AUDIO_OUTPUT");
diff --git a/services/audioflinger/ServiceUtilities.h b/services/audioflinger/ServiceUtilities.h
index 8f96282..f45ada1 100644
--- a/services/audioflinger/ServiceUtilities.h
+++ b/services/audioflinger/ServiceUtilities.h
@@ -25,10 +25,6 @@
 bool recordingAllowed(const String16& opPackageName, pid_t pid, uid_t uid);
 bool startRecording(const String16& opPackageName, pid_t pid, uid_t uid);
 void finishRecording(const String16& opPackageName, uid_t uid);
-// DON'T USE THIS INTERNAL METHOD
-bool checkRecordingInternal(const String16& opPackageName, pid_t pid, uid_t uid, bool start);
-const String16 resolveCallingPackage(PermissionController& permissionController,
-        const String16& opPackageName, uid_t uid);
 bool captureAudioOutputAllowed(pid_t pid, uid_t uid);
 bool captureHotwordAllowed(pid_t pid, uid_t uid);
 bool settingsAllowed();
diff --git a/services/audioflinger/Threads.cpp b/services/audioflinger/Threads.cpp
index 1301998..3134323 100644
--- a/services/audioflinger/Threads.cpp
+++ b/services/audioflinger/Threads.cpp
@@ -1837,6 +1837,10 @@
 void AudioFlinger::PlaybackThread::preExit()
 {
     ALOGV("  preExit()");
+    // FIXME this is using hard-coded strings but in the future, this functionality will be
+    //       converted to use audio HAL extensions required to support tunneling
+    status_t result = mOutput->stream->setParameters(String8("exiting=1"));
+    ALOGE_IF(result != OK, "Error when setting parameters on exit: %d", result);
 }
 
 // PlaybackThread::createTrack_l() must be called with AudioFlinger::mLock held
diff --git a/services/audiopolicy/managerdefault/AudioPolicyManager.cpp b/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
index 57d9371..42b199a 100644
--- a/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
+++ b/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
@@ -810,7 +810,7 @@
           "flags %#x",
           device, config->sample_rate, config->format, config->channel_mask, *flags);
 
-    *output = getOutputForDevice(device, session, *stream, config, flags);
+    *output = getOutputForDevice(device, session, *stream, *output, config, flags);
     if (*output == AUDIO_IO_HANDLE_NONE) {
         mOutputRoutes.removeRoute(session);
         return INVALID_OPERATION;
@@ -829,10 +829,11 @@
         audio_devices_t device,
         audio_session_t session,
         audio_stream_type_t stream,
+        audio_io_handle_t originalOutput,
         const audio_config_t *config,
         audio_output_flags_t *flags)
 {
-    audio_io_handle_t output = AUDIO_IO_HANDLE_NONE;
+    audio_io_handle_t output = originalOutput;
     status_t status;
 
     // open a direct output if required by specified parameters
@@ -896,19 +897,22 @@
     }
 
     if (profile != 0) {
-        for (size_t i = 0; i < mOutputs.size(); i++) {
-            sp<SwAudioOutputDescriptor> desc = mOutputs.valueAt(i);
-            if (!desc->isDuplicated() && (profile == desc->mProfile)) {
-                // reuse direct output if currently open by the same client
-                // and configured with same parameters
-                if ((config->sample_rate == desc->mSamplingRate) &&
-                    audio_formats_match(config->format, desc->mFormat) &&
-                    (config->channel_mask == desc->mChannelMask) &&
-                    (session == desc->mDirectClientSession)) {
-                    desc->mDirectOpenCount++;
-                    ALOGV("getOutputForDevice() reusing direct output %d for session %d",
-                        mOutputs.keyAt(i), session);
-                    return mOutputs.keyAt(i);
+        // exclude MMAP streams
+        if ((*flags & AUDIO_OUTPUT_FLAG_MMAP_NOIRQ) == 0 || output != AUDIO_IO_HANDLE_NONE) {
+            for (size_t i = 0; i < mOutputs.size(); i++) {
+                sp<SwAudioOutputDescriptor> desc = mOutputs.valueAt(i);
+                if (!desc->isDuplicated() && (profile == desc->mProfile)) {
+                    // reuse direct output if currently open by the same client
+                    // and configured with same parameters
+                    if ((config->sample_rate == desc->mSamplingRate) &&
+                        audio_formats_match(config->format, desc->mFormat) &&
+                        (config->channel_mask == desc->mChannelMask) &&
+                        (session == desc->mDirectClientSession)) {
+                        desc->mDirectOpenCount++;
+                        ALOGI("getOutputForDevice() reusing direct output %d for session %d",
+                              mOutputs.keyAt(i), session);
+                        return mOutputs.keyAt(i);
+                    }
                 }
             }
         }
@@ -962,7 +966,7 @@
 
     // A request for HW A/V sync cannot fallback to a mixed output because time
     // stamps are embedded in audio data
-    if ((*flags & AUDIO_OUTPUT_FLAG_HW_AV_SYNC) != 0) {
+    if ((*flags & (AUDIO_OUTPUT_FLAG_HW_AV_SYNC | AUDIO_OUTPUT_FLAG_MMAP_NOIRQ)) != 0) {
         return AUDIO_IO_HANDLE_NONE;
     }
 
diff --git a/services/audiopolicy/managerdefault/AudioPolicyManager.h b/services/audiopolicy/managerdefault/AudioPolicyManager.h
index 2b68882..d05ba1f 100644
--- a/services/audiopolicy/managerdefault/AudioPolicyManager.h
+++ b/services/audiopolicy/managerdefault/AudioPolicyManager.h
@@ -628,6 +628,7 @@
                 audio_devices_t device,
                 audio_session_t session,
                 audio_stream_type_t stream,
+                audio_io_handle_t originalOutput,
                 const audio_config_t *config,
                 audio_output_flags_t *flags);
         // internal method to return the input handle for the given device and format
diff --git a/services/audiopolicy/service/AudioPolicyInterfaceImpl.cpp b/services/audiopolicy/service/AudioPolicyInterfaceImpl.cpp
index d04b21e..8f0c846 100644
--- a/services/audiopolicy/service/AudioPolicyInterfaceImpl.cpp
+++ b/services/audiopolicy/service/AudioPolicyInterfaceImpl.cpp
@@ -439,6 +439,8 @@
         if (concurrency & AudioPolicyInterface::API_INPUT_CONCURRENCY_CAPTURE) {
             //TODO: check concurrent capture permission
         }
+    } else {
+        finishRecording(client->opPackageName, client->uid);
     }
 
     return status;
diff --git a/services/camera/libcameraservice/CameraFlashlight.cpp b/services/camera/libcameraservice/CameraFlashlight.cpp
index 394701a..471c77d 100644
--- a/services/camera/libcameraservice/CameraFlashlight.cpp
+++ b/services/camera/libcameraservice/CameraFlashlight.cpp
@@ -120,20 +120,15 @@
     return res;
 }
 
-int CameraFlashlight::getNumberOfCameras() {
-    size_t len = mProviderManager->getAPI1CompatibleCameraDeviceIds().size();
-    return static_cast<int>(len);
-}
-
 status_t CameraFlashlight::findFlashUnits() {
     Mutex::Autolock l(mLock);
     status_t res;
 
     std::vector<String8> cameraIds;
-    int numberOfCameras = getNumberOfCameras();
+    std::vector<std::string> ids = mProviderManager->getAPI1CompatibleCameraDeviceIds();
+    int numberOfCameras = static_cast<int>(ids.size());
     cameraIds.resize(numberOfCameras);
     // No module, must be provider
-    std::vector<std::string> ids = mProviderManager->getAPI1CompatibleCameraDeviceIds();
     for (size_t i = 0; i < cameraIds.size(); i++) {
         cameraIds[i] = String8(ids[i].c_str());
     }
@@ -187,7 +182,8 @@
 
     ssize_t index = mHasFlashlightMap.indexOfKey(cameraId);
     if (index == NAME_NOT_FOUND) {
-        ALOGE("%s: camera %s not present when findFlashUnits() was called",
+        // Might be external camera
+        ALOGW("%s: camera %s not present when findFlashUnits() was called",
                 __FUNCTION__, cameraId.string());
         return false;
     }
@@ -221,11 +217,13 @@
 
         if (mOpenedCameraIds.size() == 0) {
             // notify torch unavailable for all cameras with a flash
-            int numCameras = getNumberOfCameras();
+            std::vector<std::string> ids = mProviderManager->getAPI1CompatibleCameraDeviceIds();
+            int numCameras = static_cast<int>(ids.size());
             for (int i = 0; i < numCameras; i++) {
-                if (hasFlashUnitLocked(String8::format("%d", i))) {
+                String8 id8(ids[i].c_str());
+                if (hasFlashUnitLocked(id8)) {
                     mCallbacks->onTorchStatusChanged(
-                            String8::format("%d", i), TorchModeStatus::NOT_AVAILABLE);
+                            id8, TorchModeStatus::NOT_AVAILABLE);
                 }
             }
         }
@@ -265,11 +263,13 @@
 
     if (isBackwardCompatibleMode(cameraId)) {
         // notify torch available for all cameras with a flash
-        int numCameras = getNumberOfCameras();
+        std::vector<std::string> ids = mProviderManager->getAPI1CompatibleCameraDeviceIds();
+        int numCameras = static_cast<int>(ids.size());
         for (int i = 0; i < numCameras; i++) {
-            if (hasFlashUnitLocked(String8::format("%d", i))) {
+            String8 id8(ids[i].c_str());
+            if (hasFlashUnitLocked(id8)) {
                 mCallbacks->onTorchStatusChanged(
-                        String8::format("%d", i), TorchModeStatus::AVAILABLE_OFF);
+                        id8, TorchModeStatus::AVAILABLE_OFF);
             }
         }
     }
diff --git a/services/camera/libcameraservice/CameraFlashlight.h b/services/camera/libcameraservice/CameraFlashlight.h
index 07ce829..1baaba2 100644
--- a/services/camera/libcameraservice/CameraFlashlight.h
+++ b/services/camera/libcameraservice/CameraFlashlight.h
@@ -92,8 +92,6 @@
         // opening cameras)
         bool isBackwardCompatibleMode(const String8& cameraId);
 
-        int getNumberOfCameras();
-
         sp<FlashControlBase> mFlashControl;
 
         sp<CameraProviderManager> mProviderManager;
diff --git a/services/camera/libcameraservice/api1/client2/FrameProcessor.cpp b/services/camera/libcameraservice/api1/client2/FrameProcessor.cpp
index 6e21126..187bea9 100644
--- a/services/camera/libcameraservice/api1/client2/FrameProcessor.cpp
+++ b/services/camera/libcameraservice/api1/client2/FrameProcessor.cpp
@@ -292,7 +292,8 @@
     }
 
     // Once all 3A states are received, notify the client about 3A changes.
-    if (pendingState.aeState != m3aState.aeState) {
+    if (pendingState.aeState != m3aState.aeState ||
+            pendingState.aeTriggerId > m3aState.aeTriggerId) {
         ALOGV("%s: Camera %d: AE state %d->%d",
                 __FUNCTION__, cameraId,
                 m3aState.aeState, pendingState.aeState);
diff --git a/services/camera/libcameraservice/api1/client2/Parameters.cpp b/services/camera/libcameraservice/api1/client2/Parameters.cpp
index b4c7e9d..8d8bcab 100644
--- a/services/camera/libcameraservice/api1/client2/Parameters.cpp
+++ b/services/camera/libcameraservice/api1/client2/Parameters.cpp
@@ -242,7 +242,9 @@
                 HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED, availableFpsRanges.data.i32[i+1])) {
                 continue;
             }
-            if (i != 0) supportedPreviewFpsRange += ",";
+            if (supportedPreviewFpsRange.length() > 0) {
+                supportedPreviewFpsRange += ",";
+            }
             supportedPreviewFpsRange += String8::format("(%d,%d)",
                     availableFpsRanges.data.i32[i] * kFpsToApiScale,
                     availableFpsRanges.data.i32[i+1] * kFpsToApiScale);
diff --git a/services/camera/libcameraservice/api2/CameraDeviceClient.cpp b/services/camera/libcameraservice/api2/CameraDeviceClient.cpp
index efe3ca1..9ab2d88 100644
--- a/services/camera/libcameraservice/api2/CameraDeviceClient.cpp
+++ b/services/camera/libcameraservice/api2/CameraDeviceClient.cpp
@@ -1083,9 +1083,10 @@
     }
 
     // FIXME: remove this override since the default format should be
-    //       IMPLEMENTATION_DEFINED. b/9487482
-    if (format >= HAL_PIXEL_FORMAT_RGBA_8888 &&
-        format <= HAL_PIXEL_FORMAT_BGRA_8888) {
+    //       IMPLEMENTATION_DEFINED. b/9487482 & b/35317944
+    if ((format >= HAL_PIXEL_FORMAT_RGBA_8888 && format <= HAL_PIXEL_FORMAT_BGRA_8888) &&
+            ((consumerUsage & GRALLOC_USAGE_HW_MASK) &&
+             ((consumerUsage & GRALLOC_USAGE_SW_READ_MASK) == 0))) {
         ALOGW("%s: Camera %s: Overriding format %#x to IMPLEMENTATION_DEFINED",
                 __FUNCTION__, mCameraIdStr.string(), format);
         format = HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED;
diff --git a/services/camera/libcameraservice/device3/Camera3StreamSplitter.cpp b/services/camera/libcameraservice/device3/Camera3StreamSplitter.cpp
index bc12f39..e3bb5dc 100644
--- a/services/camera/libcameraservice/device3/Camera3StreamSplitter.cpp
+++ b/services/camera/libcameraservice/device3/Camera3StreamSplitter.cpp
@@ -364,7 +364,7 @@
     // queue, no onBufferReleased is called by the buffer queue.
     // Proactively trigger the callback to avoid buffer loss.
     if (queueOutput.bufferReplaced) {
-        onBufferReleasedByOutputLocked(output, surfaceId);
+        onBufferReplacedLocked(output, surfaceId);
     }
 
     return res;
@@ -582,7 +582,16 @@
 void Camera3StreamSplitter::onBufferReleasedByOutput(
         const sp<IGraphicBufferProducer>& from) {
     ATRACE_CALL();
+    sp<Fence> fence;
+
+    int slot = BufferItem::INVALID_BUFFER_SLOT;
+    auto res = from->dequeueBuffer(&slot, &fence, mWidth, mHeight, mFormat, mProducerUsage,
+            nullptr, nullptr);
     Mutex::Autolock lock(mMutex);
+    handleOutputDequeueStatusLocked(res, slot);
+    if (res != OK) {
+        return;
+    }
 
     size_t surfaceId = 0;
     bool found = false;
@@ -598,61 +607,47 @@
         return;
     }
 
-    onBufferReleasedByOutputLocked(from, surfaceId);
+    returnOutputBufferLocked(fence, from, surfaceId, slot);
 }
 
-void Camera3StreamSplitter::onBufferReleasedByOutputLocked(
+void Camera3StreamSplitter::onBufferReplacedLocked(
         const sp<IGraphicBufferProducer>& from, size_t surfaceId) {
     ATRACE_CALL();
-    sp<GraphicBuffer> buffer;
     sp<Fence> fence;
-    if (mOutputSlots[from] == nullptr) {
-        //Output surface got likely removed by client.
-        return;
-    }
-    auto outputSlots = *mOutputSlots[from];
 
     int slot = BufferItem::INVALID_BUFFER_SLOT;
     auto res = from->dequeueBuffer(&slot, &fence, mWidth, mHeight, mFormat, mProducerUsage,
             nullptr, nullptr);
-    if (res == NO_INIT) {
-        // If we just discovered that this output has been abandoned, note that,
-        // but we can't do anything else, since buffer is invalid
-        onAbandonedLocked();
-        return;
-    } else if (res == IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION) {
-        SP_LOGE("%s: Producer needs to re-allocate buffer!", __FUNCTION__);
-        SP_LOGE("%s: This should not happen with buffer allocation disabled!", __FUNCTION__);
-        return;
-    } else if (res == IGraphicBufferProducer::RELEASE_ALL_BUFFERS) {
-        SP_LOGE("%s: All slot->buffer mapping should be released!", __FUNCTION__);
-        SP_LOGE("%s: This should not happen with buffer allocation disabled!", __FUNCTION__);
-        return;
-    } else if (res == NO_MEMORY) {
-        SP_LOGE("%s: No free buffers", __FUNCTION__);
-        return;
-    } else if (res == WOULD_BLOCK) {
-        SP_LOGE("%s: Dequeue call will block", __FUNCTION__);
-        return;
-    } else if (res != OK || (slot == BufferItem::INVALID_BUFFER_SLOT)) {
-        SP_LOGE("%s: dequeue buffer from output failed (%d)", __FUNCTION__, res);
+    handleOutputDequeueStatusLocked(res, slot);
+    if (res != OK) {
         return;
     }
-    buffer = outputSlots[slot];
 
+    returnOutputBufferLocked(fence, from, surfaceId, slot);
+}
+
+void Camera3StreamSplitter::returnOutputBufferLocked(const sp<Fence>& fence,
+        const sp<IGraphicBufferProducer>& from, size_t surfaceId, int slot) {
+    sp<GraphicBuffer> buffer;
+
+    if (mOutputSlots[from] == nullptr) {
+        //Output surface got likely removed by client.
+        return;
+    }
+
+    auto outputSlots = *mOutputSlots[from];
+    buffer = outputSlots[slot];
     BufferTracker& tracker = *(mBuffers[buffer->getId()]);
     // Merge the release fence of the incoming buffer so that the fence we send
     // back to the input includes all of the outputs' fences
     if (fence != nullptr && fence->isValid()) {
         tracker.mergeFence(fence);
     }
-    SP_LOGV("%s: dequeued buffer %" PRId64 " %p from output %p", __FUNCTION__,
-            buffer->getId(), buffer.get(), from.get());
 
     auto detachBuffer = mDetachedBuffers.find(buffer->getId());
     bool detach = (detachBuffer != mDetachedBuffers.end());
     if (detach) {
-        res = from->detachBuffer(slot);
+        auto res = from->detachBuffer(slot);
         if (res == NO_ERROR) {
             outputSlots[slot] = nullptr;
         } else {
@@ -664,6 +659,26 @@
     decrementBufRefCountLocked(buffer->getId(), surfaceId);
 }
 
+void Camera3StreamSplitter::handleOutputDequeueStatusLocked(status_t res, int slot) {
+    if (res == NO_INIT) {
+        // If we just discovered that this output has been abandoned, note that,
+        // but we can't do anything else, since buffer is invalid
+        onAbandonedLocked();
+    } else if (res == IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION) {
+        SP_LOGE("%s: Producer needs to re-allocate buffer!", __FUNCTION__);
+        SP_LOGE("%s: This should not happen with buffer allocation disabled!", __FUNCTION__);
+    } else if (res == IGraphicBufferProducer::RELEASE_ALL_BUFFERS) {
+        SP_LOGE("%s: All slot->buffer mapping should be released!", __FUNCTION__);
+        SP_LOGE("%s: This should not happen with buffer allocation disabled!", __FUNCTION__);
+    } else if (res == NO_MEMORY) {
+        SP_LOGE("%s: No free buffers", __FUNCTION__);
+    } else if (res == WOULD_BLOCK) {
+        SP_LOGE("%s: Dequeue call will block", __FUNCTION__);
+    } else if (res != OK || (slot == BufferItem::INVALID_BUFFER_SLOT)) {
+        SP_LOGE("%s: dequeue buffer from output failed (%d)", __FUNCTION__, res);
+    }
+}
+
 void Camera3StreamSplitter::onAbandonedLocked() {
     // If this is called from binderDied callback, it means the app process
     // holding the binder has died. CameraService will be notified of the binder
diff --git a/services/camera/libcameraservice/device3/Camera3StreamSplitter.h b/services/camera/libcameraservice/device3/Camera3StreamSplitter.h
index 76a5b7d..fea1bdb 100644
--- a/services/camera/libcameraservice/device3/Camera3StreamSplitter.h
+++ b/services/camera/libcameraservice/device3/Camera3StreamSplitter.h
@@ -122,10 +122,8 @@
     // onFrameAvailable call to proceed.
     void onBufferReleasedByOutput(const sp<IGraphicBufferProducer>& from);
 
-    // This is the implementation of onBufferReleasedByOutput without the mutex locked.
-    // It could either be called from onBufferReleasedByOutput or from
-    // onFrameAvailable when a buffer in the async buffer queue is overwritten.
-    void onBufferReleasedByOutputLocked(const sp<IGraphicBufferProducer>& from, size_t surfaceId);
+    // Called by outputBufferLocked when a buffer in the async buffer queue got replaced.
+    void onBufferReplacedLocked(const sp<IGraphicBufferProducer>& from, size_t surfaceId);
 
     // When this is called, the splitter disconnects from (i.e., abandons) its
     // input queue and signals any waiting onFrameAvailable calls to wake up.
@@ -138,6 +136,13 @@
     // 0, return the buffer back to the input BufferQueue.
     void decrementBufRefCountLocked(uint64_t id, size_t surfaceId);
 
+    // Check for and handle any output surface dequeue errors.
+    void handleOutputDequeueStatusLocked(status_t res, int slot);
+
+    // Handles released output surface buffers.
+    void returnOutputBufferLocked(const sp<Fence>& fence, const sp<IGraphicBufferProducer>& from,
+            size_t surfaceId, int slot);
+
     // This is a thin wrapper class that lets us determine which BufferQueue
     // the IProducerListener::onBufferReleased callback is associated with. We
     // create one of these per output BufferQueue, and then pass the producer
diff --git a/services/oboeservice/AAudioService.cpp b/services/oboeservice/AAudioService.cpp
index ac3202b..c708fee 100644
--- a/services/oboeservice/AAudioService.cpp
+++ b/services/oboeservice/AAudioService.cpp
@@ -32,7 +32,6 @@
 #include "AAudioService.h"
 #include "AAudioServiceStreamMMAP.h"
 #include "AAudioServiceStreamShared.h"
-#include "AAudioServiceStreamMMAP.h"
 #include "binding/IAAudioService.h"
 #include "ServiceUtilities.h"