Merge "Initialize mDataSource in MidiIoWrapper c-tors."
diff --git a/drm/mediadrm/plugins/clearkey/hidl/DrmPlugin.cpp b/drm/mediadrm/plugins/clearkey/hidl/DrmPlugin.cpp
index d921d9e..6b0201a 100644
--- a/drm/mediadrm/plugins/clearkey/hidl/DrmPlugin.cpp
+++ b/drm/mediadrm/plugins/clearkey/hidl/DrmPlugin.cpp
@@ -110,6 +110,38 @@
     return Status::ERROR_DRM_SESSION_NOT_OPENED;
 }
 
+Status DrmPlugin::getKeyRequestCommon(const hidl_vec<uint8_t>& scope,
+        const hidl_vec<uint8_t>& initData,
+        const hidl_string& mimeType,
+        KeyType keyType,
+        const hidl_vec<KeyValue>& optionalParameters,
+        std::vector<uint8_t> *request,
+        KeyRequestType *keyRequestType,
+        std::string *defaultUrl) {
+        UNUSED(optionalParameters);
+
+    *defaultUrl = "";
+    *keyRequestType = KeyRequestType::UNKNOWN;
+    *request = std::vector<uint8_t>();
+
+    if (scope.size() == 0) {
+        return Status::BAD_VALUE;
+    }
+
+    if (keyType != KeyType::STREAMING) {
+        return Status::ERROR_DRM_CANNOT_HANDLE;
+    }
+
+    sp<Session> session = mSessionLibrary->findSession(toVector(scope));
+    if (!session.get()) {
+        return Status::ERROR_DRM_SESSION_NOT_OPENED;
+    }
+
+    Status status = session->getKeyRequest(initData, mimeType, request);
+    *keyRequestType = KeyRequestType::INITIAL;
+    return status;
+}
+
 Return<void> DrmPlugin::getKeyRequest(
         const hidl_vec<uint8_t>& scope,
         const hidl_vec<uint8_t>& initData,
@@ -119,29 +151,16 @@
         getKeyRequest_cb _hidl_cb) {
     UNUSED(optionalParameters);
 
-    if (scope.size() == 0) {
-        // Returns empty keyRequest, unknown keyType and empty defaultUrl
-        _hidl_cb(Status::BAD_VALUE, hidl_vec<uint8_t>(),
-                KeyRequestType::UNKNOWN, "");
-        return Void();
-    }
-
-    if (keyType != KeyType::STREAMING) {
-        _hidl_cb(Status::ERROR_DRM_CANNOT_HANDLE, hidl_vec<uint8_t>(),
-                KeyRequestType::UNKNOWN, "");
-        return Void();
-    }
-
-    sp<Session> session = mSessionLibrary->findSession(toVector(scope));
-    if (!session.get()) {
-        _hidl_cb(Status::ERROR_DRM_SESSION_NOT_OPENED, hidl_vec<uint8_t>(),
-                KeyRequestType::UNKNOWN, "");
-        return Void();
-    }
-
+    KeyRequestType keyRequestType = KeyRequestType::UNKNOWN;
+    std::string defaultUrl("");
     std::vector<uint8_t> request;
-    Status status = session->getKeyRequest(initData, mimeType, &request);
-    _hidl_cb(status, toHidlVec(request), KeyRequestType::INITIAL, "");
+    Status status = getKeyRequestCommon(
+            scope, initData, mimeType, keyType, optionalParameters,
+            &request, &keyRequestType, &defaultUrl);
+
+    _hidl_cb(status, toHidlVec(request),
+            static_cast<drm::V1_0::KeyRequestType>(keyRequestType),
+            hidl_string(defaultUrl));
     return Void();
 }
 
@@ -152,23 +171,16 @@
         KeyType keyType,
         const hidl_vec<KeyValue>& optionalParameters,
         getKeyRequest_1_1_cb _hidl_cb) {
-    hidl_string defaultUrl;
-    hidl_vec<uint8_t> request;
-    ::android::hardware::drm::V1_1::KeyRequestType requestType =
-            static_cast<::android::hardware::drm::V1_1::KeyRequestType>(KeyRequestType::UNKNOWN);
-    Status status = Status::OK;
+    UNUSED(optionalParameters);
 
-    defaultUrl.clear();
-    getKeyRequest(scope, initData, mimeType, keyType, optionalParameters,
-            [&](Status statusCode, const hidl_vec<uint8_t>& hResult,
-            KeyRequestType hKeyRequestType,
-            const hidl_string& hDefaultUrl) {
-        defaultUrl = hDefaultUrl;
-        request = hResult;
-        requestType = static_cast<::android::hardware::drm::V1_1::KeyRequestType>(hKeyRequestType);
-        status = statusCode;
-    });
-    _hidl_cb(status, request, requestType, defaultUrl);
+    KeyRequestType keyRequestType = KeyRequestType::UNKNOWN;
+    std::string defaultUrl("");
+    std::vector<uint8_t> request;
+    Status status = getKeyRequestCommon(
+            scope, initData, mimeType, keyType, optionalParameters,
+            &request, &keyRequestType, &defaultUrl);
+
+    _hidl_cb(status, toHidlVec(request), keyRequestType, hidl_string(defaultUrl));
     return Void();
 }
 
diff --git a/drm/mediadrm/plugins/clearkey/hidl/include/DrmPlugin.h b/drm/mediadrm/plugins/clearkey/hidl/include/DrmPlugin.h
index 5d12598..19baf0b 100644
--- a/drm/mediadrm/plugins/clearkey/hidl/include/DrmPlugin.h
+++ b/drm/mediadrm/plugins/clearkey/hidl/include/DrmPlugin.h
@@ -31,7 +31,6 @@
 
 using ::android::hardware::drm::V1_0::EventType;
 using ::android::hardware::drm::V1_0::IDrmPluginListener;
-using ::android::hardware::drm::V1_0::KeyRequestType;
 using ::android::hardware::drm::V1_0::KeyStatus;
 using ::android::hardware::drm::V1_0::KeyType;
 using ::android::hardware::drm::V1_0::KeyValue;
@@ -39,6 +38,8 @@
 using ::android::hardware::drm::V1_0::SecureStopId;
 using ::android::hardware::drm::V1_0::Status;
 using ::android::hardware::drm::V1_1::DrmMetricGroup;
+using ::android::hardware::drm::V1_1::IDrmPlugin;
+using ::android::hardware::drm::V1_1::KeyRequestType;
 
 using ::android::hardware::hidl_string;
 using ::android::hardware::hidl_vec;
@@ -46,7 +47,6 @@
 using ::android::hardware::Void;
 using ::android::sp;
 
-
 struct DrmPlugin : public IDrmPlugin {
     explicit DrmPlugin(SessionLibrary* sessionLibrary);
 
@@ -335,6 +335,15 @@
     Return<Status> setSecurityLevel(const hidl_vec<uint8_t>& sessionId,
             SecurityLevel level);
 
+    Status getKeyRequestCommon(const hidl_vec<uint8_t>& scope,
+            const hidl_vec<uint8_t>& initData,
+            const hidl_string& mimeType,
+            KeyType keyType,
+            const hidl_vec<KeyValue>& optionalParameters,
+            std::vector<uint8_t> *request,
+            KeyRequestType *getKeyRequestType,
+            std::string *defaultUrl);
+
     std::vector<KeyValue> mPlayPolicy;
     std::map<std::string, std::string> mStringProperties;
     std::map<std::string, std::vector<uint8_t> > mByteArrayProperties;
diff --git a/media/libmediaplayer2/JAudioTrack.cpp b/media/libmediaplayer2/JAudioTrack.cpp
index 6d9605a..ac0cc57 100644
--- a/media/libmediaplayer2/JAudioTrack.cpp
+++ b/media/libmediaplayer2/JAudioTrack.cpp
@@ -32,6 +32,8 @@
         uint32_t sampleRate,                          // AudioFormat && bufferSizeInBytes
         audio_format_t format,                        // AudioFormat && bufferSizeInBytes
         audio_channel_mask_t channelMask,             // AudioFormat && bufferSizeInBytes
+        callback_t cbf,                               // Offload
+        void* user,                                   // Offload
         size_t frameCount,                            // bufferSizeInBytes
         audio_session_t sessionId,                    // AudioTrack
         const audio_attributes_t* pAttributes,        // AudioAttributes
@@ -90,8 +92,27 @@
         jBuilderObj = env->CallObjectMethod(jBuilderObj, jSetSessionId, sessionId);
     }
 
+    if (cbf != NULL) {
+        jmethodID jSetOffloadedPlayback = env->GetMethodID(jBuilderCls, "setOffloadedPlayback",
+                "(Z)Landroid/media/AudioTrack$Builder;");
+        jBuilderObj = env->CallObjectMethod(jBuilderObj, jSetOffloadedPlayback, true);
+        mFlags = AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD;
+    }
+
     jmethodID jBuild = env->GetMethodID(jBuilderCls, "build", "()Landroid/media/AudioTrack;");
     mAudioTrackObj = env->CallObjectMethod(jBuilderObj, jBuild);
+
+    if (cbf != NULL) {
+        // Set offload mode callback
+        jobject jStreamEventCallbackObj = createStreamEventCallback(cbf, user);
+        jobject jExecutorObj = createCallbackExecutor();
+        jmethodID jSetStreamEventCallback = env->GetMethodID(
+                jAudioTrackCls,
+                "setStreamEventCallback",
+                "(Ljava/util/concurrent/Executor;Landroid/media/AudioTrack$StreamEventCallback;)V");
+        env->CallVoidMethod(
+                mAudioTrackObj, jSetStreamEventCallback, jExecutorObj, jStreamEventCallbackObj);
+    }
 }
 
 JAudioTrack::~JAudioTrack() {
@@ -160,6 +181,11 @@
     return true;
 }
 
+status_t JAudioTrack::getTimestamp(ExtendedTimestamp *timestamp __unused) {
+    // TODO: Implement this after appropriate Java AudioTrack method is available.
+    return NO_ERROR;
+}
+
 status_t JAudioTrack::setPlaybackRate(const AudioPlaybackRate &playbackRate) {
     // TODO: existing native AudioTrack returns INVALID_OPERATION on offload/direct/fast tracks.
     // Should we do the same thing?
@@ -442,6 +468,80 @@
     return routedDeviceId;
 }
 
+audio_session_t JAudioTrack::getAudioSessionId() {
+    JNIEnv *env = AndroidRuntime::getJNIEnv();
+    jmethodID jGetAudioSessionId = env->GetMethodID(mAudioTrackCls, "getAudioSessionId", "()I");
+    jint sessionId = env->CallIntMethod(mAudioTrackObj, jGetAudioSessionId);
+    return (audio_session_t) sessionId;
+}
+
+status_t JAudioTrack::setOutputDevice(audio_port_handle_t deviceId) {
+    JNIEnv *env = AndroidRuntime::getJNIEnv();
+    jclass jMP2ImplCls = env->FindClass("android/media/MediaPlayer2Impl");
+    jmethodID jSetAudioOutputDeviceById = env->GetMethodID(
+            jMP2ImplCls, "setAudioOutputDeviceById", "(Landroid/media/AudioTrack;I)Z");
+    jboolean result = env->CallStaticBooleanMethod(
+            jMP2ImplCls, jSetAudioOutputDeviceById, mAudioTrackObj, deviceId);
+    return result == true ? NO_ERROR : BAD_VALUE;
+}
+
+status_t JAudioTrack::pendingDuration(int32_t *msec) {
+    if (msec == nullptr) {
+        return BAD_VALUE;
+    }
+
+    bool isPurePcmData = audio_is_linear_pcm(format()) && (getFlags() & AUDIO_FLAG_HW_AV_SYNC) == 0;
+    if (!isPurePcmData) {
+        return INVALID_OPERATION;
+    }
+
+    // TODO: Need to know the difference btw. client and server time.
+    // If getTimestamp(ExtendedTimestamp) is ready, and un-comment below and modify appropriately.
+    // (copied from AudioTrack.cpp)
+
+//    ExtendedTimestamp ets;
+//    ExtendedTimestamp::LOCATION location = ExtendedTimestamp::LOCATION_SERVER;
+//    if (getTimestamp_l(&ets) == OK && ets.mTimeNs[location] > 0) {
+//        int64_t diff = ets.mPosition[ExtendedTimestamp::LOCATION_CLIENT]
+//                - ets.mPosition[location];
+//        if (diff < 0) {
+//            *msec = 0;
+//        } else {
+//            // ms is the playback time by frames
+//            int64_t ms = (int64_t)((double)diff * 1000 /
+//                    ((double)mSampleRate * mPlaybackRate.mSpeed));
+//            // clockdiff is the timestamp age (negative)
+//            int64_t clockdiff = (mState != STATE_ACTIVE) ? 0 :
+//                    ets.mTimeNs[location]
+//                    + ets.mTimebaseOffset[ExtendedTimestamp::TIMEBASE_MONOTONIC]
+//                    - systemTime(SYSTEM_TIME_MONOTONIC);
+//
+//            //ALOGV("ms: %lld  clockdiff: %lld", (long long)ms, (long long)clockdiff);
+//            static const int NANOS_PER_MILLIS = 1000000;
+//            *msec = (int32_t)(ms + clockdiff / NANOS_PER_MILLIS);
+//        }
+//        return NO_ERROR;
+//    }
+
+    return NO_ERROR;
+}
+
+status_t JAudioTrack::addAudioDeviceCallback(
+        const sp<AudioSystem::AudioDeviceCallback>& callback __unused) {
+    // TODO: Implement this after appropriate Java AudioTrack method is available.
+    return NO_ERROR;
+}
+
+status_t JAudioTrack::removeAudioDeviceCallback(
+        const sp<AudioSystem::AudioDeviceCallback>& callback __unused) {
+    // TODO: Implement this after appropriate Java AudioTrack method is available.
+    return NO_ERROR;
+}
+
+/////////////////////////////////////////////////////////////
+///                Private method begins                  ///
+/////////////////////////////////////////////////////////////
+
 jobject JAudioTrack::createVolumeShaperConfigurationObj(
         const sp<media::VolumeShaper::Configuration>& config) {
 
@@ -546,6 +646,24 @@
     return env->CallObjectMethod(jBuilderObj, jBuild);
 }
 
+jobject JAudioTrack::createStreamEventCallback(callback_t cbf, void* user) {
+    JNIEnv *env = AndroidRuntime::getJNIEnv();
+    jclass jCallbackCls = env->FindClass("android/media/MediaPlayer2Impl$StreamEventCallback");
+    jmethodID jCallbackCtor = env->GetMethodID(jCallbackCls, "<init>", "(JJJ)V");
+    jobject jCallbackObj = env->NewObject(jCallbackCls, jCallbackCtor, this, cbf, user);
+    return jCallbackObj;
+}
+
+jobject JAudioTrack::createCallbackExecutor() {
+    JNIEnv *env = AndroidRuntime::getJNIEnv();
+    jclass jExecutorsCls = env->FindClass("java/util/concurrent/Executors");
+    jmethodID jNewSingleThreadExecutor = env->GetStaticMethodID(jExecutorsCls,
+            "newSingleThreadExecutor", "()Ljava/util/concurrent/ExecutorService;");
+    jobject jSingleThreadExecutorObj =
+            env->CallStaticObjectMethod(jExecutorsCls, jNewSingleThreadExecutor);
+    return jSingleThreadExecutorObj;
+}
+
 status_t JAudioTrack::javaToNativeStatus(int javaStatus) {
     switch (javaStatus) {
     case AUDIO_JAVA_SUCCESS:
diff --git a/media/libmediaplayer2/include/mediaplayer2/JAudioTrack.h b/media/libmediaplayer2/include/mediaplayer2/JAudioTrack.h
index 10fa5e8..301825b 100644
--- a/media/libmediaplayer2/include/mediaplayer2/JAudioTrack.h
+++ b/media/libmediaplayer2/include/mediaplayer2/JAudioTrack.h
@@ -19,6 +19,7 @@
 
 #include <jni.h>
 #include <media/AudioResamplerPublic.h>
+#include <media/AudioSystem.h>
 #include <media/VolumeShaper.h>
 #include <system/audio.h>
 #include <utils/Errors.h>
@@ -31,6 +32,42 @@
 class JAudioTrack {
 public:
 
+    /* Events used by AudioTrack callback function (callback_t).
+     * Keep in sync with frameworks/base/media/java/android/media/AudioTrack.java NATIVE_EVENT_*.
+     */
+    enum event_type {
+        EVENT_MORE_DATA = 0,        // Request to write more data to buffer.
+        EVENT_NEW_IAUDIOTRACK = 6,  // IAudioTrack was re-created, either due to re-routing and
+                                    // voluntary invalidation by mediaserver, or mediaserver crash.
+        EVENT_STREAM_END = 7,       // Sent after all the buffers queued in AF and HW are played
+                                    // back (after stop is called) for an offloaded track.
+    };
+
+    class Buffer
+    {
+    public:
+        size_t      mSize;        // input/output in bytes.
+        void*       mData;        // pointer to the audio data.
+    };
+
+    /* As a convenience, if a callback is supplied, a handler thread
+     * is automatically created with the appropriate priority. This thread
+     * invokes the callback when a new buffer becomes available or various conditions occur.
+     *
+     * Parameters:
+     *
+     * event:   type of event notified (see enum AudioTrack::event_type).
+     * user:    Pointer to context for use by the callback receiver.
+     * info:    Pointer to optional parameter according to event type:
+     *          - EVENT_MORE_DATA: pointer to JAudioTrack::Buffer struct. The callback must not
+     *            write more bytes than indicated by 'size' field and update 'size' if fewer bytes
+     *            are written.
+     *          - EVENT_NEW_IAUDIOTRACK: unused.
+     *          - EVENT_STREAM_END: unused.
+     */
+
+    typedef void (*callback_t)(int event, void* user, void *info);
+
     /* Creates an JAudioTrack object for non-offload mode.
      * Once created, the track needs to be started before it can be used.
      * Unspecified values are set to appropriate default values.
@@ -49,6 +86,9 @@
      *                     output sink.
      *                     (TODO: How can we check whether a format is supported?)
      * channelMask:        Channel mask, such that audio_is_output_channel(channelMask) is true.
+     * cbf:                Callback function. If not null, this function is called periodically
+     *                     to provide new data and inform of marker, position updates, etc.
+     * user:               Context for use by the callback receiver.
      * frameCount:         Minimum size of track PCM buffer in frames. This defines the
      *                     application's contribution to the latency of the track.
      *                     The actual size selected by the JAudioTrack could be larger if the
@@ -68,35 +108,20 @@
                 uint32_t sampleRate,
                 audio_format_t format,
                 audio_channel_mask_t channelMask,
+                callback_t cbf,
+                void* user,
                 size_t frameCount = 0,
                 audio_session_t sessionId  = AUDIO_SESSION_ALLOCATE,
                 const audio_attributes_t* pAttributes = NULL,
                 float maxRequiredSpeed = 1.0f);
 
     /*
-       Temporarily removed constructor arguments:
-
-       // Q. Values are in audio-base.h, but where can we find explanation for them?
-       audio_output_flags_t flags,
-
        // Q. May be used in AudioTrack.setPreferredDevice(AudioDeviceInfo)?
        audio_port_handle_t selectedDeviceId,
 
-       // Should be deleted, since we don't use Binder anymore.
-       bool doNotReconnect,
-
-       // Do we need UID and PID?
-       uid_t uid,
-       pid_t pid,
-
-       // TODO: Uses these values when Java AudioTrack supports the offload mode.
-       callback_t cbf,
-       void* user,
+       // TODO: No place to use these values.
        int32_t notificationFrames,
        const audio_offload_info_t *offloadInfo,
-
-       // Fixed to false, but what is this?
-       threadCanCallJava
     */
 
     virtual ~JAudioTrack();
@@ -138,6 +163,46 @@
      */
     bool getTimestamp(AudioTimestamp& timestamp);
 
+    // TODO: This doc is just copied from AudioTrack.h. Revise it after implemenation.
+    /* Return the extended timestamp, with additional timebase info and improved drain behavior.
+     *
+     * This is similar to the AudioTrack.java API:
+     * getTimestamp(@NonNull AudioTimestamp timestamp, @AudioTimestamp.Timebase int timebase)
+     *
+     * Some differences between this method and the getTimestamp(AudioTimestamp& timestamp) method
+     *
+     *   1. stop() by itself does not reset the frame position.
+     *      A following start() resets the frame position to 0.
+     *   2. flush() by itself does not reset the frame position.
+     *      The frame position advances by the number of frames flushed,
+     *      when the first frame after flush reaches the audio sink.
+     *   3. BOOTTIME clock offsets are provided to help synchronize with
+     *      non-audio streams, e.g. sensor data.
+     *   4. Position is returned with 64 bits of resolution.
+     *
+     * Parameters:
+     *  timestamp: A pointer to the caller allocated ExtendedTimestamp.
+     *
+     * Returns NO_ERROR    on success; timestamp is filled with valid data.
+     *         BAD_VALUE   if timestamp is NULL.
+     *         WOULD_BLOCK if called immediately after start() when the number
+     *                     of frames consumed is less than the
+     *                     overall hardware latency to physical output. In WOULD_BLOCK cases,
+     *                     one might poll again, or use getPosition(), or use 0 position and
+     *                     current time for the timestamp.
+     *                     If WOULD_BLOCK is returned, the timestamp is still
+     *                     modified with the LOCATION_CLIENT portion filled.
+     *         DEAD_OBJECT if AudioFlinger dies or the output device changes and
+     *                     the track cannot be automatically restored.
+     *                     The application needs to recreate the AudioTrack
+     *                     because the audio device changed or AudioFlinger died.
+     *                     This typically occurs for direct or offloaded tracks
+     *                     or if mDoNotReconnect is true.
+     *         INVALID_OPERATION  if called on a offloaded or direct track.
+     *                     Use getTimestamp(AudioTimestamp& timestamp) instead.
+     */
+    status_t getTimestamp(ExtendedTimestamp *timestamp);
+
     /* Set source playback rate for timestretch
      * 1.0 is normal speed: < 1.0 is slower, > 1.0 is faster
      * 1.0 is normal pitch: < 1.0 is lower pitch, > 1.0 is higher pitch
@@ -270,7 +335,65 @@
      */
     audio_port_handle_t getRoutedDeviceId();
 
+    /* Returns the ID of the audio session this AudioTrack belongs to. */
+    audio_session_t getAudioSessionId();
+
+    /* Selects the audio device to use for output of this AudioTrack. A value of
+     * AUDIO_PORT_HANDLE_NONE indicates default routing.
+     *
+     * Parameters:
+     *  The device ID of the selected device (as returned by the AudioDevicesManager API).
+     *
+     * Returned value:
+     *  - NO_ERROR: successful operation
+     *  - BAD_VALUE: failed to find the valid output device with given device Id.
+     */
+    status_t setOutputDevice(audio_port_handle_t deviceId);
+
+    // TODO: Add AUDIO_OUTPUT_FLAG_DIRECT when it is possible to check.
+    // TODO: Add AUDIO_FLAG_HW_AV_SYNC when it is possible to check.
+    /* Returns the flags */
+    audio_output_flags_t getFlags() const { return mFlags; }
+
+    /* Obtain the pending duration in milliseconds for playback of pure PCM data remaining in
+     * AudioTrack.
+     *
+     * Returns NO_ERROR if successful.
+     *         INVALID_OPERATION if the AudioTrack does not contain pure PCM data.
+     *         BAD_VALUE if msec is nullptr.
+     */
+    status_t pendingDuration(int32_t *msec);
+
+    /* Adds an AudioDeviceCallback. The caller will be notified when the audio device to which this
+     * AudioTrack is routed is updated.
+     * Replaces any previously installed callback.
+     *
+     * Parameters:
+     *
+     * callback: The callback interface
+     *
+     * Returns NO_ERROR if successful.
+     *         INVALID_OPERATION if the same callback is already installed.
+     *         NO_INIT or PREMISSION_DENIED if AudioFlinger service is not reachable
+     *         BAD_VALUE if the callback is NULL
+     */
+    status_t addAudioDeviceCallback(const sp<AudioSystem::AudioDeviceCallback>& callback);
+
+    /* Removes an AudioDeviceCallback.
+     *
+     * Parameters:
+     *
+     * callback: The callback interface
+     *
+     * Returns NO_ERROR if successful.
+     *         INVALID_OPERATION if the callback is not installed
+     *         BAD_VALUE if the callback is NULL
+     */
+    status_t removeAudioDeviceCallback(const sp<AudioSystem::AudioDeviceCallback>& callback);
+
 private:
+    audio_output_flags_t mFlags;
+
     jclass mAudioTrackCls;
     jobject mAudioTrackObj;
 
@@ -282,6 +405,12 @@
     jobject createVolumeShaperOperationObj(
             const sp<media::VolumeShaper::Operation>& operation);
 
+    /* Creates a Java StreamEventCallback object */
+    jobject createStreamEventCallback(callback_t cbf, void* user);
+
+    /* Creates a Java Executor object for running a callback */
+    jobject createCallbackExecutor();
+
     status_t javaToNativeStatus(int javaStatus);
 };
 
diff --git a/media/libstagefright/codec2/Android.bp b/media/libstagefright/codec2/Android.bp
index 1182cd8..e1ac44e 100644
--- a/media/libstagefright/codec2/Android.bp
+++ b/media/libstagefright/codec2/Android.bp
@@ -22,6 +22,16 @@
         "include",
     ],
 
+    header_libs: [
+        "libhardware_headers",
+        "libutils_headers",
+    ],
+
+    export_header_lib_headers: [
+        "libhardware_headers",
+        "libutils_headers",
+    ],
+
     sanitize: {
         misc_undefined: [
             "unsigned-integer-overflow",
diff --git a/packages/MediaComponents/src/com/android/media/IMediaSession2.aidl b/packages/MediaComponents/src/com/android/media/IMediaSession2.aidl
index 81e5547..9538c3d 100644
--- a/packages/MediaComponents/src/com/android/media/IMediaSession2.aidl
+++ b/packages/MediaComponents/src/com/android/media/IMediaSession2.aidl
@@ -65,5 +65,8 @@
     void getBrowserRoot(IMediaSession2Callback callback, in Bundle rootHints);
     void getItem(IMediaSession2Callback callback, String mediaId);
     void getChildren(IMediaSession2Callback callback, String parentId, int page, int pageSize,
-            in Bundle options);
+            in Bundle extras);
+    void search(IMediaSession2Callback callback, String query, in Bundle extras);
+    void getSearchResult(IMediaSession2Callback callback, String query, int page, int pageSize,
+            in Bundle extras);
 }
diff --git a/packages/MediaComponents/src/com/android/media/IMediaSession2Callback.aidl b/packages/MediaComponents/src/com/android/media/IMediaSession2Callback.aidl
index 7e76d1d..b3aa59c 100644
--- a/packages/MediaComponents/src/com/android/media/IMediaSession2Callback.aidl
+++ b/packages/MediaComponents/src/com/android/media/IMediaSession2Callback.aidl
@@ -49,6 +49,8 @@
     //////////////////////////////////////////////////////////////////////////////////////////////
     void onGetRootResult(in Bundle rootHints, String rootMediaId, in Bundle rootExtra);
     void onItemLoaded(String mediaId, in Bundle result);
-    void onChildrenLoaded(String parentId, int page, int pageSize, in Bundle options,
+    void onChildrenLoaded(String parentId, int page, int pageSize, in Bundle extras,
+            in List<Bundle> result);
+    void onSearchResultLoaded(String query, int page, int pageSize, in Bundle extras,
             in List<Bundle> result);
 }
diff --git a/packages/MediaComponents/src/com/android/media/MediaBrowser2Impl.java b/packages/MediaComponents/src/com/android/media/MediaBrowser2Impl.java
index 5e88262..76da42b 100644
--- a/packages/MediaComponents/src/com/android/media/MediaBrowser2Impl.java
+++ b/packages/MediaComponents/src/com/android/media/MediaBrowser2Impl.java
@@ -63,12 +63,12 @@
     }
 
     @Override
-    public void subscribe_impl(String parentId, Bundle options) {
+    public void subscribe_impl(String parentId, Bundle extras) {
         // TODO(jaewan): Implement
     }
 
     @Override
-    public void unsubscribe_impl(String parentId, Bundle options) {
+    public void unsubscribe_impl(String parentId, Bundle extras) {
         // TODO(jaewan): Implement
     }
 
@@ -94,7 +94,7 @@
     }
 
     @Override
-    public void getChildren_impl(String parentId, int page, int pageSize, Bundle options) {
+    public void getChildren_impl(String parentId, int page, int pageSize, Bundle extras) {
         if (parentId == null) {
             throw new IllegalArgumentException("parentId shouldn't be null");
         }
@@ -105,7 +105,7 @@
         final IMediaSession2 binder = getSessionBinder();
         if (binder != null) {
             try {
-                binder.getChildren(getControllerStub(), parentId, page, pageSize, options);
+                binder.getChildren(getControllerStub(), parentId, page, pageSize, extras);
             } catch (RemoteException e) {
                 // TODO(jaewan): Handle disconnect.
                 if (DEBUG) {
@@ -118,8 +118,43 @@
     }
 
     @Override
-    public void search_impl(String query, int page, int pageSize, Bundle extras) {
-        // TODO(jaewan): Implement
+    public void search_impl(String query, Bundle extras) {
+        if (TextUtils.isEmpty(query)) {
+            throw new IllegalArgumentException("query shouldn't be empty");
+        }
+        final IMediaSession2 binder = getSessionBinder();
+        if (binder != null) {
+            try {
+                binder.search(getControllerStub(), query, extras);
+            } catch (RemoteException e) {
+                // TODO(jaewan): Handle disconnect.
+                if (DEBUG) {
+                    Log.w(TAG, "Cannot connect to the service or the session is gone", e);
+                }
+            }
+        } else {
+            Log.w(TAG, "Session isn't active", new IllegalStateException());
+        }
+    }
+
+    @Override
+    public void getSearchResult_impl(String query, int page, int pageSize, Bundle extras) {
+        if (TextUtils.isEmpty(query)) {
+            throw new IllegalArgumentException("query shouldn't be empty");
+        }
+        final IMediaSession2 binder = getSessionBinder();
+        if (binder != null) {
+            try {
+                binder.getSearchResult(getControllerStub(), query, page, pageSize, extras);
+            } catch (RemoteException e) {
+                // TODO(jaewan): Handle disconnect.
+                if (DEBUG) {
+                    Log.w(TAG, "Cannot connect to the service or the session is gone", e);
+                }
+            }
+        } else {
+            Log.w(TAG, "Session isn't active", new IllegalStateException());
+        }
     }
 
     public void onGetRootResult(
@@ -135,10 +170,17 @@
         });
     }
 
-    public void onChildrenLoaded(String parentId, int page, int pageSize, Bundle options,
+    public void onChildrenLoaded(String parentId, int page, int pageSize, Bundle extras,
             List<MediaItem2> result) {
         getCallbackExecutor().execute(() -> {
-            mCallback.onChildrenLoaded(parentId, page, pageSize, options, result);
+            mCallback.onChildrenLoaded(parentId, page, pageSize, extras, result);
+        });
+    }
+
+    public void onSearchResultLoaded(String query, int page, int pageSize, Bundle extras,
+            List<MediaItem2> result) {
+        getCallbackExecutor().execute(() -> {
+            mCallback.onSearchResultLoaded(query, page, pageSize, extras, result);
         });
     }
 }
diff --git a/packages/MediaComponents/src/com/android/media/MediaItem2Impl.java b/packages/MediaComponents/src/com/android/media/MediaItem2Impl.java
index f51e246..4c4ef24 100644
--- a/packages/MediaComponents/src/com/android/media/MediaItem2Impl.java
+++ b/packages/MediaComponents/src/com/android/media/MediaItem2Impl.java
@@ -52,7 +52,7 @@
             throw new IllegalArgumentException("dsd shouldn't be null");
         }
         if (metadata != null && !TextUtils.equals(mediaId, metadata.getMediaId())) {
-            throw new IllegalArgumentException("metadata's id should be match with the mediaid");
+            throw new IllegalArgumentException("metadata's id should be matched with the mediaid");
         }
 
         mContext = context;
@@ -71,9 +71,9 @@
             throw new IllegalArgumentException("mediaId shouldn't be null");
         }
         if (metadata != null && !TextUtils.equals(mediaId, metadata.getMediaId())) {
-            throw new IllegalArgumentException("metadata's id should be match with the mediaid");
+            throw new IllegalArgumentException("metadata's id should be matched with the mediaid");
         }
-        mContext =context;
+        mContext = context;
         mId = mediaId;
         mMetadata = metadata;
         mFlags = flags;
@@ -136,24 +136,21 @@
     }
 
     @Override
-    public void setMetadata_impl(@NonNull MediaMetadata2 metadata) {
-        if (metadata == null) {
-            throw new IllegalArgumentException("metadata shouldn't be null");
-        }
-        if (TextUtils.isEmpty(metadata.getMediaId())) {
-            throw new IllegalArgumentException("metadata must have a non-empty media id");
+    public void setMetadata_impl(@Nullable MediaMetadata2 metadata) {
+        if (metadata != null && !TextUtils.equals(mId, metadata.getMediaId())) {
+            throw new IllegalArgumentException("metadata's id should be matched with the mediaId");
         }
         mMetadata = metadata;
     }
 
     @Override
-    public MediaMetadata2 getMetadata_impl() {
+    public @Nullable MediaMetadata2 getMetadata_impl() {
         return mMetadata;
     }
 
     @Override
-    public @Nullable String getMediaId_impl() {
-        return mMetadata.getMediaId();
+    public @NonNull String getMediaId_impl() {
+        return mId;
     }
 
     @Override
diff --git a/packages/MediaComponents/src/com/android/media/MediaSession2CallbackStub.java b/packages/MediaComponents/src/com/android/media/MediaSession2CallbackStub.java
index 1f4d12b..852029a 100644
--- a/packages/MediaComponents/src/com/android/media/MediaSession2CallbackStub.java
+++ b/packages/MediaComponents/src/com/android/media/MediaSession2CallbackStub.java
@@ -244,7 +244,7 @@
     }
 
     @Override
-    public void onChildrenLoaded(String parentId, int page, int pageSize, Bundle options,
+    public void onChildrenLoaded(String parentId, int page, int pageSize, Bundle extras,
             List<Bundle> itemBundleList) throws RuntimeException {
         final MediaBrowser2Impl browser;
         try {
@@ -265,6 +265,31 @@
                 result.add(MediaItem2.fromBundle(browser.getContext(), bundle));
             }
         }
-        browser.onChildrenLoaded(parentId, page, pageSize, options, result);
+        browser.onChildrenLoaded(parentId, page, pageSize, extras, result);
+    }
+
+    @Override
+    public void onSearchResultLoaded(String query, int page, int pageSize, Bundle extras,
+            List<Bundle> itemBundleList) throws RuntimeException {
+        final MediaBrowser2Impl browser;
+        try {
+            browser = getBrowser();
+        } catch (IllegalStateException e) {
+            Log.w(TAG, "Don't fail silently here. Highly likely a bug");
+            return;
+        }
+        if (browser == null) {
+            // TODO(jaewan): Revisit here. Could be a bug
+            return;
+        }
+
+        List<MediaItem2> result = null;
+        if (itemBundleList != null) {
+            result = new ArrayList<>();
+            for (Bundle bundle : itemBundleList) {
+                result.add(MediaItem2.fromBundle(browser.getContext(), bundle));
+            }
+        }
+        browser.onSearchResultLoaded(query, page, pageSize, extras, result);
     }
 }
diff --git a/packages/MediaComponents/src/com/android/media/MediaSession2Stub.java b/packages/MediaComponents/src/com/android/media/MediaSession2Stub.java
index f160ef8..759d580 100644
--- a/packages/MediaComponents/src/com/android/media/MediaSession2Stub.java
+++ b/packages/MediaComponents/src/com/android/media/MediaSession2Stub.java
@@ -36,6 +36,7 @@
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.support.annotation.GuardedBy;
+import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.Log;
 
@@ -574,7 +575,7 @@
 
     @Override
     public void getChildren(IMediaSession2Callback caller, String parentId, int page,
-            int pageSize, Bundle options) throws RuntimeException {
+            int pageSize, Bundle extras) throws RuntimeException {
         final MediaLibrarySessionImpl sessionImpl = getLibrarySession();
         final ControllerInfo controller = getController(caller);
         if (controller == null) {
@@ -584,11 +585,15 @@
             return;
         }
         if (parentId == null) {
-            Log.d(TAG, "parentId shouldn't be null");
+            if (DEBUG) {
+                Log.d(TAG, "parentId shouldn't be null");
+            }
             return;
         }
         if (page < 1 || pageSize < 1) {
-            Log.d(TAG, "Neither page nor pageSize should be less than 1");
+            if (DEBUG) {
+                Log.d(TAG, "Neither page nor pageSize should be less than 1");
+            }
             return;
         }
 
@@ -599,7 +604,7 @@
             }
             final ControllerInfoImpl controllerImpl = ControllerInfoImpl.from(controller);
             List<MediaItem2> result = session.getCallback().onLoadChildren(
-                    controller, parentId, page, pageSize, options);
+                    controller, parentId, page, pageSize, extras);
             if (result != null && result.size() > pageSize) {
                 throw new IllegalArgumentException("onLoadChildren() shouldn't return media items "
                         + "more than pageSize. result.size()=" + result.size() + " pageSize="
@@ -616,7 +621,90 @@
 
             try {
                 controllerImpl.getControllerBinder().onChildrenLoaded(
-                        parentId, page, pageSize, options, bundleList);
+                        parentId, page, pageSize, extras, bundleList);
+            } catch (RemoteException e) {
+                // Controller may be died prematurely.
+                // TODO(jaewan): Handle this.
+            }
+        });
+    }
+
+    @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");
+            }
+            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) {
+                return;
+            }
+            final ControllerInfoImpl controllerImpl = ControllerInfoImpl.from(controller);
+            session.getCallback().onSearch(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;
+        }
+        if (TextUtils.isEmpty(query)) {
+            if (DEBUG) {
+                Log.d(TAG, "query shouldn't be empty");
+            }
+            return;
+        }
+        if (page < 1 || pageSize < 1) {
+            if (DEBUG) {
+                Log.d(TAG, "Neither page nor pageSize should be less than 1");
+            }
+            return;
+        }
+
+        sessionImpl.getCallbackExecutor().execute(() -> {
+            final MediaLibrarySessionImpl session = getLibrarySession();
+            if (session == null) {
+                return;
+            }
+            final ControllerInfoImpl controllerImpl = ControllerInfoImpl.from(controller);
+            List<MediaItem2> result = session.getCallback().onLoadSearchResult(
+                    controller, query, page, pageSize, extras);
+            if (result != null && result.size() > pageSize) {
+                throw new IllegalArgumentException("onLoadSearchResult() shouldn't return media "
+                        + "items more than pageSize. result.size()=" + result.size() + " pageSize="
+                        + pageSize);
+            }
+
+            List<Bundle> bundleList = null;
+            if (result != null) {
+                bundleList = new ArrayList<>();
+                for (MediaItem2 item : result) {
+                    bundleList.add(item == null ? null : item.toBundle());
+                }
+            }
+
+            try {
+                controllerImpl.getControllerBinder().onSearchResultLoaded(
+                        query, page, pageSize, extras, bundleList);
             } catch (RemoteException e) {
                 // Controller may be died prematurely.
                 // TODO(jaewan): Handle this.
diff --git a/packages/MediaComponents/src/com/android/media/SessionToken2Impl.java b/packages/MediaComponents/src/com/android/media/SessionToken2Impl.java
index b2b7959..f36aa43 100644
--- a/packages/MediaComponents/src/com/android/media/SessionToken2Impl.java
+++ b/packages/MediaComponents/src/com/android/media/SessionToken2Impl.java
@@ -34,6 +34,8 @@
 import android.os.IBinder;
 import android.text.TextUtils;
 
+import java.util.List;
+
 public class SessionToken2Impl implements SessionToken2Provider {
     private static final String KEY_UID = "android.media.token.uid";
     private static final String KEY_TYPE = "android.media.token.type";
@@ -73,25 +75,24 @@
             }
         }
         mUid = uid;
-        // calculate id and type
-        Intent serviceIntent = new Intent(MediaLibraryService2.SERVICE_INTERFACE);
-        serviceIntent.setClassName(packageName, serviceName);
-        String id = getSessionId(manager.resolveService(serviceIntent,
-                PackageManager.GET_META_DATA));
-        int type = TYPE_LIBRARY_SERVICE;
-        if (id == null) {
+
+        // Infer id and type from package name and service name
+        // TODO(jaewan): Handle multi-user.
+        String id = getSessionIdFromService(manager, MediaLibraryService2.SERVICE_INTERFACE,
+                packageName, serviceName);
+        if (id != null) {
+            mId = id;
+            mType = TYPE_LIBRARY_SERVICE;
+        } else {
             // retry with session service
-            serviceIntent.setClassName(packageName, serviceName);
-            id = getSessionId(manager.resolveService(serviceIntent,
-                    PackageManager.GET_META_DATA));
-            type = TYPE_SESSION_SERVICE;
+            mId = getSessionIdFromService(manager, MediaSessionService2.SERVICE_INTERFACE,
+                    packageName, serviceName);
+            mType = TYPE_SESSION_SERVICE;
         }
-        if (id == null) {
+        if (mId == null) {
             throw new IllegalArgumentException("service " + serviceName + " doesn't implement"
-                    + " session service nor library service");
+                    + " session service nor library service. Use service's full name.");
         }
-        mId = id;
-        mType = type;
         mPackageName = packageName;
         mServiceName = serviceName;
         mSessionBinder = null;
@@ -109,6 +110,29 @@
         mInstance = new SessionToken2(this);
     }
 
+    private static String getSessionIdFromService(PackageManager manager, String serviceInterface,
+            String packageName, String serviceName) {
+        Intent serviceIntent = new Intent(serviceInterface);
+        serviceIntent.setPackage(packageName);
+        // Use queryIntentServices to find services with MediaLibraryService2.SERVICE_INTERFACE.
+        // We cannot use resolveService with intent specified class name, because resolveService
+        // ignores actions if Intent.setClassName() is specified.
+        List<ResolveInfo> list = manager.queryIntentServices(
+                serviceIntent, PackageManager.GET_META_DATA);
+        if (list != null) {
+            for (int i = 0; i < list.size(); i++) {
+                ResolveInfo resolveInfo = list.get(i);
+                if (resolveInfo == null || resolveInfo.serviceInfo == null) {
+                    continue;
+                }
+                if (TextUtils.equals(resolveInfo.serviceInfo.name, serviceName)) {
+                    return getSessionId(resolveInfo);
+                }
+            }
+        }
+        return null;
+    }
+
     public static String getSessionId(ResolveInfo resolveInfo) {
         if (resolveInfo == null || resolveInfo.serviceInfo == null) {
             return null;
diff --git a/packages/MediaComponents/test/AndroidManifest.xml b/packages/MediaComponents/test/AndroidManifest.xml
index 48e4292..5ebe31a 100644
--- a/packages/MediaComponents/test/AndroidManifest.xml
+++ b/packages/MediaComponents/test/AndroidManifest.xml
@@ -34,7 +34,7 @@
             <intent-filter>
                 <action android:name="android.media.MediaLibraryService2" />
             </intent-filter>
-            <meta-data android:name="android.media.session" android:value="TestBrowser" />
+            <meta-data android:name="android.media.session" android:value="TestLibrary" />
         </service>
     </application>
 
diff --git a/packages/MediaComponents/test/src/android/media/SessionToken2Test.java b/packages/MediaComponents/test/src/android/media/SessionToken2Test.java
new file mode 100644
index 0000000..efde78a
--- /dev/null
+++ b/packages/MediaComponents/test/src/android/media/SessionToken2Test.java
@@ -0,0 +1,63 @@
+/*
+ * 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 junit.framework.Assert.assertEquals;
+
+import android.content.Context;
+import android.os.Process;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.test.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests {@link SessionToken2}.
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class SessionToken2Test {
+    private Context mContext;
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getTargetContext();
+    }
+
+    @Test
+    public void testConstructor_sessionService() {
+        SessionToken2 token = new SessionToken2(mContext, mContext.getPackageName(),
+                MockMediaSessionService2.class.getCanonicalName());
+        assertEquals(MockMediaSessionService2.ID, token.getId());
+        assertEquals(mContext.getPackageName(), token.getPackageName());
+        assertEquals(Process.myUid(), token.getUid());
+        assertEquals(SessionToken2.TYPE_SESSION_SERVICE, token.getType());
+    }
+
+    @Test
+    public void testConstructor_libraryService() {
+        SessionToken2 token = new SessionToken2(mContext, mContext.getPackageName(),
+                MockMediaLibraryService2.class.getCanonicalName());
+        assertEquals(MockMediaLibraryService2.ID, token.getId());
+        assertEquals(mContext.getPackageName(), token.getPackageName());
+        assertEquals(Process.myUid(), token.getUid());
+        assertEquals(SessionToken2.TYPE_LIBRARY_SERVICE, token.getType());
+    }
+}
\ No newline at end of file