Merge "Create Codec2.0 service"
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/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/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/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/Component.cpp b/media/libstagefright/codec2/hidl/impl/1.0/Component.cpp
index bbcd630..2b34fde 100644
--- a/media/libstagefright/codec2/hidl/impl/1.0/Component.cpp
+++ b/media/libstagefright/codec2/hidl/impl/1.0/Component.cpp
@@ -48,7 +48,7 @@
     virtual c2_status_t query(
             const std::vector<C2Param::Index>& indices,
             c2_blocking_t mayBlock,
-            std::vector<std::unique_ptr<C2Param>>* const params) override {
+            std::vector<std::unique_ptr<C2Param>>* const params) const override {
         return mIntf->query_vb({}, indices, mayBlock, params);
     }
 
@@ -108,7 +108,9 @@
             for (const std::shared_ptr<C2SettingResult> &c2result :
                     c2settingResult) {
                 if (c2result) {
-                    objcpy(&settingResults[ix++], *c2result);
+                    if (objcpy(&settingResults[ix++], *c2result) != Status::OK) {
+                        break;
+                    }
                 }
             }
             settingResults.resize(ix);
@@ -166,7 +168,6 @@
     if (objcpy(&c2works, workBundle) != C2_OK) {
         return Status::CORRUPTED;
     }
-    (void)workBundle;
     return static_cast<Status>(mComponent->queue_nb(&c2works));
 }
 
diff --git a/media/libstagefright/codec2/hidl/impl/1.0/ComponentStore.cpp b/media/libstagefright/codec2/hidl/impl/1.0/ComponentStore.cpp
index d42d67a..4d51fba 100644
--- a/media/libstagefright/codec2/hidl/impl/1.0/ComponentStore.cpp
+++ b/media/libstagefright/codec2/hidl/impl/1.0/ComponentStore.cpp
@@ -54,7 +54,7 @@
     c2_status_t query(
             const std::vector<C2Param::Index> &indices,
             c2_blocking_t mayBlock,
-            std::vector<std::unique_ptr<C2Param>> *const params) override {
+            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) {
@@ -201,7 +201,7 @@
     // TODO implement
     (void)src;
     (void)dst;
-    return Status {};
+    return Status::OMITTED;
 }
 
 }  // namespace implementation
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
index 94d2e6d..da90996 100644
--- 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
@@ -49,7 +49,7 @@
     virtual c2_status_t query(
             const std::vector<C2Param::Index> &indices,
             c2_blocking_t mayBlock,
-            std::vector<std::unique_ptr<C2Param>>* const params) = 0;
+            std::vector<std::unique_ptr<C2Param>>* const params) const = 0;
     /** C2ComponentInterface::config_vb */
     virtual c2_status_t config(
             const std::vector<C2Param*> &params,
@@ -60,7 +60,7 @@
             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;
+            std::vector<C2FieldSupportedValuesQuery>& fields, c2_blocking_t mayBlock) const = 0;
 
     virtual ~ConfigurableC2Intf() = default;
 
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
index 41ee275..1eace56 100644
--- 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
@@ -53,77 +53,79 @@
 typedef C2GlobalParam<C2Info, C2Hidl_Rect, 1> C2Hidl_RectInfo;
 
 // C2SettingResult -> SettingResult
-Status C2_HIDE objcpy(
+Status objcpy(
         SettingResult* d,
         const C2SettingResult& s);
 
-// SettingResult -> C2SettingResult
-c2_status_t C2_HIDE objcpy(
-        C2SettingResult* d,
+// SettingResult -> std::unique_ptr<C2SettingResult>
+c2_status_t objcpy(
+        std::unique_ptr<C2SettingResult>* d,
         const SettingResult& s);
 
 // C2ParamDescriptor -> ParamDescriptor
-Status C2_HIDE objcpy(
+Status objcpy(
         ParamDescriptor* d,
         const C2ParamDescriptor& s);
 
-// ParamDescriptor -> std::unique_ptr<C2ParamDescriptor>
-c2_status_t C2_HIDE objcpy(
-        std::unique_ptr<C2ParamDescriptor>* d,
+// ParamDescriptor -> std::shared_ptr<C2ParamDescriptor>
+c2_status_t objcpy(
+        std::shared_ptr<C2ParamDescriptor>* d,
         const ParamDescriptor& s);
 
 // C2FieldSupportedValuesQuery -> FieldSupportedValuesQuery
-Status C2_HIDE objcpy(
+Status objcpy(
         FieldSupportedValuesQuery* d,
         const C2FieldSupportedValuesQuery& s);
 
 // FieldSupportedValuesQuery -> C2FieldSupportedValuesQuery
-c2_status_t C2_HIDE objcpy(
+c2_status_t objcpy(
         C2FieldSupportedValuesQuery* d,
         const FieldSupportedValuesQuery& s);
 
 // C2FieldSupportedValuesQuery -> FieldSupportedValuesQueryResult
-Status C2_HIDE objcpy(
+Status objcpy(
         FieldSupportedValuesQueryResult* d,
         const C2FieldSupportedValuesQuery& s);
 
 // FieldSupportedValuesQuery, FieldSupportedValuesQueryResult -> C2FieldSupportedValuesQuery
-c2_status_t C2_HIDE objcpy(
+c2_status_t objcpy(
         C2FieldSupportedValuesQuery* d,
         const FieldSupportedValuesQuery& sq,
         const FieldSupportedValuesQueryResult& sr);
 
 // C2Component::Traits -> ComponentTraits
-Status C2_HIDE objcpy(
+Status objcpy(
         IComponentStore::ComponentTraits* d,
         const C2Component::Traits& s);
 
-// ComponentTraits -> C2Component::Traits
-c2_status_t C2_HIDE objcpy(
+// 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 C2_HIDE objcpy(
+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 C2_HIDE objcpy(
+c2_status_t objcpy(
         C2StructDescriptor* d,
         const StructDescriptor& s);
 
 // std::list<std::unique_ptr<C2Work>> -> WorkBundle
 // TODO: Connect with Bufferpool
-Status C2_HIDE objcpy(
+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 C2_HIDE objcpy(
+c2_status_t objcpy(
         std::list<std::unique_ptr<C2Work>>* d,
         const WorkBundle& s);
 
@@ -159,6 +161,29 @@
         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
diff --git a/media/libstagefright/codec2/hidl/impl/1.0/types.cpp b/media/libstagefright/codec2/hidl/impl/1.0/types.cpp
index 14ced2e..f14c21a 100644
--- a/media/libstagefright/codec2/hidl/impl/1.0/types.cpp
+++ b/media/libstagefright/codec2/hidl/impl/1.0/types.cpp
@@ -31,6 +31,7 @@
 #include <C2Component.h>
 #include <util/C2ParamUtils.h>
 
+#include <unordered_map>
 #include <algorithm>
 
 namespace vendor {
@@ -264,25 +265,26 @@
     return Status::OK;
 }
 
-// ComponentTraits -> C2Component::Traits
+// 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;
-
-    // TODO: Currently, we do not have any domain values defined in Codec2.0.
+    d->name = s.name.c_str();
     d->domain = static_cast<C2Component::domain_t>(s.domainOther);
-
-    // TODO: Currently, we do not have any kind values defined in Codec2.0.
     d->kind = static_cast<C2Component::kind_t>(s.kindOther);
-
     d->rank = static_cast<C2Component::rank_t>(s.rank);
-
     d->mediaType = s.mediaType.c_str();
 
-    // TODO: Currently, aliases are pointers to static strings. This is not
-    // supported by HIDL.
-    d->aliases.clear();
+    // 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;
 }
 
@@ -366,48 +368,60 @@
     return Status::OK;
 }
 
-// SettingResult -> C2SettingResult
-c2_status_t objcpy(C2SettingResult *d, const SettingResult &s) {
+// 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;
+        (*d)->failure = C2SettingResult::READ_ONLY;
         break;
     case SettingResult::Failure::MISMATCH:
-        d->failure = C2SettingResult::MISMATCH;
+        (*d)->failure = C2SettingResult::MISMATCH;
         break;
     case SettingResult::Failure::BAD_VALUE:
-        d->failure = C2SettingResult::BAD_VALUE;
+        (*d)->failure = C2SettingResult::BAD_VALUE;
         break;
     case SettingResult::Failure::BAD_TYPE:
-        d->failure = C2SettingResult::BAD_TYPE;
+        (*d)->failure = C2SettingResult::BAD_TYPE;
         break;
     case SettingResult::Failure::BAD_PORT:
-        d->failure = C2SettingResult::BAD_PORT;
+        (*d)->failure = C2SettingResult::BAD_PORT;
         break;
     case SettingResult::Failure::BAD_INDEX:
-        d->failure = C2SettingResult::BAD_INDEX;
+        (*d)->failure = C2SettingResult::BAD_INDEX;
         break;
     case SettingResult::Failure::CONFLICT:
-        d->failure = C2SettingResult::CONFLICT;
+        (*d)->failure = C2SettingResult::CONFLICT;
         break;
     case SettingResult::Failure::UNSUPPORTED:
-        d->failure = C2SettingResult::UNSUPPORTED;
+        (*d)->failure = C2SettingResult::UNSUPPORTED;
         break;
     case SettingResult::Failure::INFO_CONFLICT:
-        d->failure = C2SettingResult::INFO_CONFLICT;
+        (*d)->failure = C2SettingResult::INFO_CONFLICT;
         break;
     default:
-        d->failure = static_cast<C2SettingResult::Failure>(s.failureOther);
+        (*d)->failure = static_cast<C2SettingResult::Failure>(s.failureOther);
     }
-    c2_status_t status = objcpy(&d->field, s.field);
+
+    // field
+    c2_status_t status = objcpy(&(*d)->field, s.field);
     if (status != C2_OK) {
         return status;
     }
-    d->conflicts.clear();
+
+    // conflicts
+    (*d)->conflicts.clear();
+    (*d)->conflicts.reserve(s.conflicts.size());
     for (const ParamFieldValues& sConflict : s.conflicts) {
-        d->conflicts.emplace_back(
+        (*d)->conflicts.emplace_back(
                 C2ParamFieldValues{ C2ParamFieldBuilder(), nullptr });
-        status = objcpy(&d->conflicts.back(), sConflict);
+        status = objcpy(&(*d)->conflicts.back(), sConflict);
         if (status != C2_OK) {
             return status;
         }
@@ -426,13 +440,13 @@
 }
 
 // ParamDescriptor -> C2ParamDescriptor
-c2_status_t objcpy(std::unique_ptr<C2ParamDescriptor> *d, const ParamDescriptor &s) {
+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_unique<C2ParamDescriptor>(
+    *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()),
@@ -1058,10 +1072,8 @@
             dWorklet->failures.clear();
             dWorklet->failures.reserve(sWorklet.failures.size());
             for (const SettingResult& sFailure : sWorklet.failures) {
-                std::unique_ptr<C2SettingResult> dFailure(
-                        new C2SettingResult { .field = C2ParamFieldValues {
-                        C2ParamFieldBuilder(), nullptr } });
-                status = objcpy(dFailure.get(), sFailure);
+                std::unique_ptr<C2SettingResult> dFailure;
+                status = objcpy(&dFailure, sFailure);
                 if (status != C2_OK) {
                     ALOGE("Failed to create C2SettingResult in C2Worklet.");
                     return C2_BAD_VALUE;
@@ -1176,6 +1188,74 @@
     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
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/packages/MediaComponents/res/layout/media_controller.xml b/packages/MediaComponents/res/layout/media_controller.xml
index 38f139d..aafe8b0 100644
--- a/packages/MediaComponents/res/layout/media_controller.xml
+++ b/packages/MediaComponents/res/layout/media_controller.xml
@@ -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/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..9ee4928 100644
--- a/packages/MediaComponents/src/com/android/media/IMediaSession2Callback.aidl
+++ b/packages/MediaComponents/src/com/android/media/IMediaSession2Callback.aidl
@@ -42,7 +42,7 @@
 
     void onCustomLayoutChanged(in List<Bundle> commandButtonlist);
 
-    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..4a4f250 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;
@@ -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_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_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,6 +552,10 @@
 
     @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();
@@ -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());
@@ -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,13 @@
         }
         mCallbackExecutor.execute(() -> {
             // TODO(jaewan): Double check if the controller exists.
-            mCallback.onCustomCommand(command, args, receiver);
+            mCallback.onCustomCommand(mInstance, command, args, receiver);
         });
     }
 
     void onCustomLayoutChanged(final List<CommandButton> layout) {
         mCallbackExecutor.execute(() -> {
-            mCallback.onCustomLayoutChanged(layout);
+            mCallback.onCustomLayoutChanged(mInstance, layout);
         });
     }
 
diff --git a/packages/MediaComponents/src/com/android/media/MediaSession2CallbackStub.java b/packages/MediaComponents/src/com/android/media/MediaSession2CallbackStub.java
index 2178ccd..b8e651e 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,7 @@
     }
 
     @Override
-    public void sendCustomCommand(Bundle commandBundle, Bundle args, ResultReceiver receiver) {
+    public void onCustomCommand(Bundle commandBundle, Bundle args, ResultReceiver receiver) {
         final MediaController2Impl controller;
         try {
             controller = getController();
@@ -200,6 +225,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 +254,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 +276,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 +305,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 +326,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 +354,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..c407e5a 100644
--- a/packages/MediaComponents/src/com/android/media/MediaSession2Impl.java
+++ b/packages/MediaComponents/src/com/android/media/MediaSession2Impl.java
@@ -149,10 +149,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 +203,13 @@
     }
 
     @Override
-    public void setPlayer_impl(MediaPlayerBase player, MediaPlaylistController mpcl,
+    public void updatePlayer_impl(MediaPlayerBase player, MediaPlaylistController mpcl,
             VolumeProvider2 volumeProvider) throws IllegalArgumentException {
         ensureCallingThread();
         if (player == null) {
             throw new IllegalArgumentException("player shouldn't be null");
         }
-
+        // TODO(jaewan): Handle mpcl
         setPlayer(player, volumeProvider);
     }
 
@@ -294,7 +294,11 @@
         return getPlayer();
     }
 
-    // TODO(jaewan): Change this to @NonNull
+    @Override
+    public VolumeProvider2 getVolumeProvider_impl() {
+        return mVolumeProvider;
+    }
+
     @Override
     public SessionToken2 getToken_impl() {
         return mSessionToken;
@@ -807,7 +811,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 +863,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;
 
@@ -900,13 +904,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 +924,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);
@@ -1010,8 +1019,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
@@ -1276,7 +1294,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}.
          */
diff --git a/packages/MediaComponents/src/com/android/media/MediaSession2Stub.java b/packages/MediaComponents/src/com/android/media/MediaSession2Stub.java
index b1e71e2..785248c 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)
+                                ? 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,25 +328,23 @@
     }
 
     @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_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_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);
+            boolean accepted = session.getCallback().onCommandRequest(session.getInstance(),
+                    controller, command);
             if (!accepted) {
                 // Don't run rejected command.
                 if (DEBUG) {
@@ -259,23 +366,21 @@
     @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_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_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);
+            boolean accepted = session.getCallback().onCommandRequest(session.getInstance(),
+                    controller, command);
             if (!accepted) {
                 // Don't run rejected command.
                 if (DEBUG) {
@@ -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) {
@@ -377,141 +473,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 +607,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 +620,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 +630,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 +656,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 +686,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 +700,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 +724,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 +735,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 +765,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 +791,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 +802,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 +827,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 +863,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 +878,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 +898,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 +909,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);
+            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 +925,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 +941,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());
@@ -906,11 +964,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 +979,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 +998,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 +1012,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 +1032,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..4537d5f 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();
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/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/VideoView2Impl.java b/packages/MediaComponents/src/com/android/widget/VideoView2Impl.java
index cdb1470..c3ca057 100644
--- a/packages/MediaComponents/src/com/android/widget/VideoView2Impl.java
+++ b/packages/MediaComponents/src/com/android/widget/VideoView2Impl.java
@@ -126,10 +126,17 @@
     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().
@@ -150,7 +157,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;
 
@@ -428,8 +435,11 @@
         if (mMediaRouter != null) {
             mMediaRouter.setMediaSession(mMediaSession);
         }
-
         attachMediaControlView();
+        // TODO: remove this after moving MediaSession creating code inside initializing VideoView2
+        if (mCurrentState == STATE_PREPARED) {
+            extractTracks();
+        }
     }
 
     @Override
@@ -792,48 +802,44 @@
         }
         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() {
+    private void extractTracks() {
         MediaPlayer.TrackInfo[] trackInfos = mMediaPlayer.getTrackInfo();
-        boolean previouslyNoTracks = mSubtitleTrackIndices == null
-                || mSubtitleTrackIndices.size() == 0;
+        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
+            if (trackType == MediaPlayer.TrackInfo.MEDIA_TRACK_TYPE_VIDEO) {
+                mVideoTrackIndices.add(i);
+            } else if (trackType == MediaPlayer.TrackInfo.MEDIA_TRACK_TYPE_AUDIO) {
+                mAudioTrackIndices.add(i);
+            } else if (trackType == MediaPlayer.TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE
                     || trackType == MediaPlayer.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 =
@@ -864,7 +870,12 @@
             mCurrentState = STATE_PREPARED;
             // Create and set playback state for MediaControlView2
             updatePlaybackState();
-            extractSubtitleTracks();
+
+            // TODO: change this to send TrackInfos to MediaControlView2
+            // TODO: create MediaSession when initializing VideoView2
+            if (mMediaSession != null) {
+                extractTracks();
+            }
 
             if (mMediaControlView != null) {
                 mMediaControlView.setEnabled(true);
@@ -943,7 +954,7 @@
             new MediaPlayer.OnInfoListener() {
                 public boolean onInfo(MediaPlayer mp, int what, int extra) {
                     if (what == MediaPlayer.MEDIA_INFO_METADATA_UPDATE) {
-                        extractSubtitleTracks();
+                        extractTracks();
                     }
                     return true;
                 }
@@ -990,13 +1001,13 @@
                 mRouteSessionCallback.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,
diff --git a/packages/MediaComponents/test/src/android/media/MediaBrowser2Test.java b/packages/MediaComponents/test/src/android/media/MediaBrowser2Test.java
index 27822e6..d1c7717 100644
--- a/packages/MediaComponents/test/src/android/media/MediaBrowser2Test.java
+++ b/packages/MediaComponents/test/src/android/media/MediaBrowser2Test.java
@@ -462,45 +462,48 @@
 
         @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 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 +511,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 +529,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 +539,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 +549,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..e6ad098 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;
@@ -322,9 +321,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 +351,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 +390,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 +417,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 +443,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 +470,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 +496,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 +522,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 +550,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);
@@ -744,13 +750,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..8c1a749 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,7 +37,6 @@
 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;
@@ -391,11 +389,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 +403,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) {
@@ -470,7 +473,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 +494,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..b32400f 100644
--- a/packages/MediaComponents/test/src/android/media/MediaSession2TestBase.java
+++ b/packages/MediaComponents/test/src/android/media/MediaSession2TestBase.java
@@ -187,23 +187,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,22 +227,24 @@
         }
 
         @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);
         }
     }
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..d89cecd
--- /dev/null
+++ b/packages/MediaComponents/test/src/android/media/MediaSession2_PermissionTest.java
@@ -0,0 +1,365 @@
+/*
+ * 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.*;
+
+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.mock;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.media.MediaSession2;
+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 org.mockito.ArgumentMatcher;
+import org.mockito.Mockito;
+
+/**
+ * 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()).skipToNext();
+        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()).skipToNext();
+        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()).skipToPrevious();
+        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()).skipToPrevious();
+        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_SET_VOLUME));
+        createController(mSession.getToken()).setVolumeTo(0, 0);
+        verify(mCallback, timeout(TIMEOUT_MS).atLeastOnce()).onCommandRequest(
+                matchesSession(), matchesCaller(), matches(COMMAND_CODE_SET_VOLUME));
+
+        createSessionWithAllowedActions(createCommandGroupWithout(COMMAND_CODE_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());
+    }
+}
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 c18d025..fb02f7a 100644
--- a/packages/MediaComponents/test/src/android/media/MockMediaLibraryService2.java
+++ b/packages/MediaComponents/test/src/android/media/MockMediaLibraryService2.java
@@ -23,7 +23,6 @@
 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;
@@ -145,17 +144,20 @@
         }
 
         @Override
-        public CommandGroup onConnect(ControllerInfo controller) {
+        public CommandGroup onConnect(MediaSession2 session,
+                ControllerInfo controller) {
             return mCallbackProxy.onConnect(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 +166,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 +178,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 +200,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,12 +211,14 @@
         }
 
         @Override
-        public void onSubscribe(ControllerInfo controller, String parentId, Bundle extras) {
+        public void onSubscribe(MediaLibrarySession session, ControllerInfo controller,
+                String parentId, Bundle extras) {
             mCallbackProxy.onSubscribe(controller, parentId, extras);
         }
 
         @Override
-        public void onUnsubscribe(ControllerInfo controller, String parentId) {
+        public void onUnsubscribe(MediaLibrarySession session, ControllerInfo controller,
+                String parentId) {
             mCallbackProxy.onUnsubscribe(controller, parentId);
         }
     }
diff --git a/packages/MediaComponents/test/src/android/media/MockMediaSessionService2.java b/packages/MediaComponents/test/src/android/media/MockMediaSessionService2.java
index 1c6534d..ce7ce8b 100644
--- a/packages/MediaComponents/test/src/android/media/MockMediaSessionService2.java
+++ b/packages/MediaComponents/test/src/android/media/MockMediaSessionService2.java
@@ -103,7 +103,8 @@
         }
 
         @Override
-        public CommandGroup onConnect(ControllerInfo controller) {
+        public CommandGroup onConnect(MediaSession2 session,
+                ControllerInfo controller) {
             return mCallbackProxy.onConnect(controller);
         }
     }
diff --git a/packages/MediaComponents/test/src/android/media/MockPlayer.java b/packages/MediaComponents/test/src/android/media/MockPlayer.java
index da4acb5..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) {
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/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/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"