Merge "Load system extractor plugins from /system|vendor/lib[64]/extractors."
diff --git a/camera/aidl/android/hardware/camera2/ICameraDeviceCallbacks.aidl b/camera/aidl/android/hardware/camera2/ICameraDeviceCallbacks.aidl
index 28252c0..4db7f85 100644
--- a/camera/aidl/android/hardware/camera2/ICameraDeviceCallbacks.aidl
+++ b/camera/aidl/android/hardware/camera2/ICameraDeviceCallbacks.aidl
@@ -30,6 +30,7 @@
     const int ERROR_CAMERA_REQUEST = 3;
     const int ERROR_CAMERA_RESULT = 4;
     const int ERROR_CAMERA_BUFFER = 5;
+    const int ERROR_CAMERA_DISABLED = 6;
 
     oneway void onDeviceError(int errorCode, in CaptureResultExtras resultExtras);
     oneway void onDeviceIdle();
diff --git a/drm/libmediadrm/DrmHal.cpp b/drm/libmediadrm/DrmHal.cpp
index bc37557..31344c3 100644
--- a/drm/libmediadrm/DrmHal.cpp
+++ b/drm/libmediadrm/DrmHal.cpp
@@ -838,6 +838,11 @@
     return toStatusT(status);
 }
 
+status_t DrmHal::getMetrics(MediaAnalyticsItem* metrics) {
+    // TODO: Replace this with real metrics.
+    metrics->setCString("/drm/mediadrm/dummymetric", "dummy");
+    return OK;
+}
 
 status_t DrmHal::setCipherAlgorithm(Vector<uint8_t> const &sessionId,
                                  String8 const &algorithm) {
@@ -1030,6 +1035,7 @@
     }
 }
 
+
 void DrmHal::reportMetrics() const
 {
     Vector<uint8_t> metrics;
diff --git a/drm/libmediadrm/IDrm.cpp b/drm/libmediadrm/IDrm.cpp
index 8ff6e6a..d157be7 100644
--- a/drm/libmediadrm/IDrm.cpp
+++ b/drm/libmediadrm/IDrm.cpp
@@ -46,6 +46,7 @@
     GET_PROPERTY_BYTE_ARRAY,
     SET_PROPERTY_STRING,
     SET_PROPERTY_BYTE_ARRAY,
+    GET_METRICS,
     SET_CIPHER_ALGORITHM,
     SET_MAC_ALGORITHM,
     ENCRYPT,
@@ -393,6 +394,18 @@
         return reply.readInt32();
     }
 
+    virtual status_t getMetrics(MediaAnalyticsItem *item) {
+        Parcel data, reply;
+        data.writeInterfaceToken(IDrm::getInterfaceDescriptor());
+
+        status_t status = remote()->transact(GET_METRICS, data, &reply);
+        if (status != OK) {
+            return status;
+        }
+
+        item->readFromParcel(reply);
+        return reply.readInt32();
+    }
 
     virtual status_t setCipherAlgorithm(Vector<uint8_t> const &sessionId,
                                         String8 const &algorithm) {
@@ -829,6 +842,17 @@
             return OK;
         }
 
+        case GET_METRICS:
+        {
+            CHECK_INTERFACE(IDrm, data, reply);
+
+            MediaAnalyticsItem item;
+            status_t result = getMetrics(&item);
+            item.writeToParcel(reply);
+            reply->writeInt32(result);
+            return OK;
+        }
+
         case SET_CIPHER_ALGORITHM:
         {
             CHECK_INTERFACE(IDrm, data, reply);
diff --git a/media/libaaudio/src/legacy/AudioStreamLegacy.cpp b/media/libaaudio/src/legacy/AudioStreamLegacy.cpp
index c5dfb7c..3352b33 100644
--- a/media/libaaudio/src/legacy/AudioStreamLegacy.cpp
+++ b/media/libaaudio/src/legacy/AudioStreamLegacy.cpp
@@ -86,10 +86,15 @@
             // AudioRecord::Buffer
             // TODO define our own AudioBuffer and pass it from the subclasses.
             AudioTrack::Buffer *audioBuffer = static_cast<AudioTrack::Buffer *>(info);
-            if (getState() == AAUDIO_STREAM_STATE_DISCONNECTED || !mCallbackEnabled.load()) {
+            if (getState() == AAUDIO_STREAM_STATE_DISCONNECTED) {
+                ALOGW("processCallbackCommon() data, stream disconnected");
+                audioBuffer->size = SIZE_STOP_CALLBACKS;
+            } else if (!mCallbackEnabled.load()) {
+                ALOGW("processCallbackCommon() stopping because callback disabled");
                 audioBuffer->size = SIZE_STOP_CALLBACKS;
             } else {
                 if (audioBuffer->frameCount == 0) {
+                    ALOGW("processCallbackCommon() data, frameCount is zero");
                     return;
                 }
 
@@ -106,7 +111,7 @@
                 if (callbackResult == AAUDIO_CALLBACK_RESULT_CONTINUE) {
                     audioBuffer->size = audioBuffer->frameCount * getBytesPerFrame();
                 } else { // STOP or invalid result
-                    ALOGW("%s() stop stream by faking an error", __func__);
+                    ALOGW("%s() callback requested stop, fake an error", __func__);
                     audioBuffer->size = SIZE_STOP_CALLBACKS;
                     // Disable the callback just in case AudioFlinger keeps trying to call us.
                     mCallbackEnabled.store(false);
diff --git a/media/libaaudio/src/legacy/AudioStreamLegacy.h b/media/libaaudio/src/legacy/AudioStreamLegacy.h
index 6a506b3..494edbc 100644
--- a/media/libaaudio/src/legacy/AudioStreamLegacy.h
+++ b/media/libaaudio/src/legacy/AudioStreamLegacy.h
@@ -121,9 +121,6 @@
 
     void forceDisconnect(bool errorCallbackEnabled = true);
 
-    void onStart() { mCallbackEnabled.store(true); }
-    void onStop() { mCallbackEnabled.store(false); }
-
     int64_t incrementFramesWritten(int32_t frames) {
         return mFramesWritten.increment(frames);
     }
diff --git a/media/libaaudio/src/legacy/AudioStreamRecord.cpp b/media/libaaudio/src/legacy/AudioStreamRecord.cpp
index 61a0f8a..f7ae7d7 100644
--- a/media/libaaudio/src/legacy/AudioStreamRecord.cpp
+++ b/media/libaaudio/src/legacy/AudioStreamRecord.cpp
@@ -244,11 +244,13 @@
         return AAudioConvert_androidToAAudioResult(err);
     }
 
+    // Enable callback before starting AudioTrack to avoid shutting
+    // down because of a race condition.
+    mCallbackEnabled.store(true);
     err = mAudioRecord->start();
     if (err != OK) {
         return AAudioConvert_androidToAAudioResult(err);
     } else {
-        onStart();
         setState(AAUDIO_STREAM_STATE_STARTING);
     }
     return AAUDIO_OK;
@@ -258,11 +260,11 @@
     if (mAudioRecord.get() == nullptr) {
         return AAUDIO_ERROR_INVALID_STATE;
     }
-    onStop();
     setState(AAUDIO_STREAM_STATE_STOPPING);
     incrementFramesWritten(getFramesRead() - getFramesWritten()); // TODO review
     mTimestampPosition.set(getFramesRead());
     mAudioRecord->stop();
+    mCallbackEnabled.store(false);
     mFramesRead.reset32();
     mTimestampPosition.reset32();
     // Pass false to prevent errorCallback from being called after disconnect
diff --git a/media/libaaudio/src/legacy/AudioStreamTrack.cpp b/media/libaaudio/src/legacy/AudioStreamTrack.cpp
index 52c7822..4d1b187 100644
--- a/media/libaaudio/src/legacy/AudioStreamTrack.cpp
+++ b/media/libaaudio/src/legacy/AudioStreamTrack.cpp
@@ -271,11 +271,13 @@
         return AAudioConvert_androidToAAudioResult(err);
     }
 
+    // Enable callback before starting AudioTrack to avoid shutting
+    // down because of a race condition.
+    mCallbackEnabled.store(true);
     err = mAudioTrack->start();
     if (err != OK) {
         return AAudioConvert_androidToAAudioResult(err);
     } else {
-        onStart();
         setState(AAUDIO_STREAM_STATE_STARTING);
     }
     return AAUDIO_OK;
@@ -292,9 +294,9 @@
               AAudio_convertStreamStateToText(getState()));
         return AAUDIO_ERROR_INVALID_STATE;
     }
-    onStop();
     setState(AAUDIO_STREAM_STATE_PAUSING);
     mAudioTrack->pause();
+    mCallbackEnabled.store(false);
     status_t err = mAudioTrack->getPosition(&mPositionWhenPausing);
     if (err != OK) {
         return AAudioConvert_androidToAAudioResult(err);
@@ -323,13 +325,13 @@
         ALOGE("requestStop() no AudioTrack");
         return AAUDIO_ERROR_INVALID_STATE;
     }
-    onStop();
     setState(AAUDIO_STREAM_STATE_STOPPING);
     incrementFramesRead(getFramesWritten() - getFramesRead()); // TODO review
     mTimestampPosition.set(getFramesWritten());
     mFramesWritten.reset32();
     mTimestampPosition.reset32();
     mAudioTrack->stop();
+    mCallbackEnabled.store(false);
     return checkForDisconnectRequest(false);;
 }
 
diff --git a/media/libaudioclient/AudioSystem.cpp b/media/libaudioclient/AudioSystem.cpp
index 50fe385..16150f6 100644
--- a/media/libaudioclient/AudioSystem.cpp
+++ b/media/libaudioclient/AudioSystem.cpp
@@ -39,8 +39,7 @@
 sp<AudioSystem::AudioFlingerClient> AudioSystem::gAudioFlingerClient;
 audio_error_callback AudioSystem::gAudioErrorCallback = NULL;
 dynamic_policy_callback AudioSystem::gDynPolicyCallback = NULL;
-record_config_callback  AudioSystem::gRecordConfigCallback = NULL;
-
+record_config_callback AudioSystem::gRecordConfigCallback = NULL;
 
 // establish binder interface to AudioFlinger service
 const sp<IAudioFlinger> AudioSystem::get_audio_flinger()
@@ -917,11 +916,14 @@
 }
 
 status_t AudioSystem::startInput(audio_io_handle_t input,
-                                 audio_session_t session)
+                                 audio_session_t session,
+                                 audio_devices_t device,
+                                 uid_t uid,
+                                 bool *silenced)
 {
     const sp<IAudioPolicyService>& aps = AudioSystem::get_audio_policy_service();
     if (aps == 0) return PERMISSION_DENIED;
-    return aps->startInput(input, session);
+    return aps->startInput(input, session, device, uid, silenced);
 }
 
 status_t AudioSystem::stopInput(audio_io_handle_t input,
diff --git a/media/libaudioclient/IAudioFlinger.cpp b/media/libaudioclient/IAudioFlinger.cpp
index 56ddd4f..6507a5c 100644
--- a/media/libaudioclient/IAudioFlinger.cpp
+++ b/media/libaudioclient/IAudioFlinger.cpp
@@ -48,6 +48,7 @@
     SET_MODE,
     SET_MIC_MUTE,
     GET_MIC_MUTE,
+    SET_RECORD_SILENCED,
     SET_PARAMETERS,
     GET_PARAMETERS,
     REGISTER_CLIENT,
@@ -306,6 +307,15 @@
         return reply.readInt32();
     }
 
+    virtual void setRecordSilenced(uid_t uid, bool silenced)
+    {
+        Parcel data, reply;
+        data.writeInterfaceToken(IAudioFlinger::getInterfaceDescriptor());
+        data.writeInt32(uid);
+        data.writeInt32(silenced ? 1 : 0);
+        remote()->transact(SET_RECORD_SILENCED, data, &reply);
+    }
+
     virtual status_t setParameters(audio_io_handle_t ioHandle, const String8& keyValuePairs)
     {
         Parcel data, reply;
@@ -859,6 +869,7 @@
         case RELEASE_AUDIO_PATCH:
         case LIST_AUDIO_PATCHES:
         case SET_AUDIO_PORT_CONFIG:
+        case SET_RECORD_SILENCED:
             ALOGW("%s: transaction %d received from PID %d",
                   __func__, code, IPCThreadState::self()->getCallingPid());
             return INVALID_OPERATION;
@@ -1024,6 +1035,15 @@
             reply->writeInt32( getMicMute() );
             return NO_ERROR;
         } break;
+        case SET_RECORD_SILENCED: {
+            CHECK_INTERFACE(IAudioFlinger, data, reply);
+            uid_t uid = data.readInt32();
+            audio_source_t source;
+            data.read(&source, sizeof(audio_source_t));
+            bool silenced = data.readInt32() == 1;
+            setRecordSilenced(uid, silenced);
+            return NO_ERROR;
+        } break;
         case SET_PARAMETERS: {
             CHECK_INTERFACE(IAudioFlinger, data, reply);
             audio_io_handle_t ioHandle = (audio_io_handle_t) data.readInt32();
diff --git a/media/libaudioclient/IAudioPolicyService.cpp b/media/libaudioclient/IAudioPolicyService.cpp
index 53bc1b7..c0e53b3 100644
--- a/media/libaudioclient/IAudioPolicyService.cpp
+++ b/media/libaudioclient/IAudioPolicyService.cpp
@@ -330,14 +330,22 @@
     }
 
     virtual status_t startInput(audio_io_handle_t input,
-                                audio_session_t session)
+                                audio_session_t session,
+                                audio_devices_t device,
+                                uid_t uid,
+                                bool *silenced)
     {
         Parcel data, reply;
         data.writeInterfaceToken(IAudioPolicyService::getInterfaceDescriptor());
         data.writeInt32(input);
         data.writeInt32(session);
+        data.writeInt32(device);
+        data.writeInt32(uid);
+        data.writeInt32(*silenced ? 1 : 0);
         remote()->transact(START_INPUT, data, &reply);
-        return static_cast <status_t> (reply.readInt32());
+        status_t status = static_cast <status_t> (reply.readInt32());
+        *silenced = reply.readInt32() == 1;
+        return status;
     }
 
     virtual status_t stopInput(audio_io_handle_t input,
@@ -1045,7 +1053,12 @@
             CHECK_INTERFACE(IAudioPolicyService, data, reply);
             audio_io_handle_t input = static_cast <audio_io_handle_t>(data.readInt32());
             audio_session_t session = static_cast <audio_session_t>(data.readInt32());
-            reply->writeInt32(static_cast <uint32_t>(startInput(input, session)));
+            audio_devices_t device = static_cast <audio_devices_t>(data.readInt32());
+            uid_t uid = static_cast <uid_t>(data.readInt32());
+            bool silenced = data.readInt32() == 1;
+            status_t status = startInput(input, session, device, uid, &silenced);
+            reply->writeInt32(static_cast <uint32_t>(status));
+            reply->writeInt32(silenced ? 1 : 0);
             return NO_ERROR;
         } break;
 
diff --git a/media/libaudioclient/include/media/AudioSystem.h b/media/libaudioclient/include/media/AudioSystem.h
index 24a6e22..d6cc37a 100644
--- a/media/libaudioclient/include/media/AudioSystem.h
+++ b/media/libaudioclient/include/media/AudioSystem.h
@@ -244,7 +244,10 @@
                                     audio_port_handle_t *portId);
 
     static status_t startInput(audio_io_handle_t input,
-                               audio_session_t session);
+                               audio_session_t session,
+                               audio_devices_t device,
+                               uid_t uid,
+                               bool *silenced);
     static status_t stopInput(audio_io_handle_t input,
                               audio_session_t session);
     static void releaseInput(audio_io_handle_t input,
diff --git a/media/libaudioclient/include/media/IAudioFlinger.h b/media/libaudioclient/include/media/IAudioFlinger.h
index 57d9778..472a3da 100644
--- a/media/libaudioclient/include/media/IAudioFlinger.h
+++ b/media/libaudioclient/include/media/IAudioFlinger.h
@@ -368,6 +368,7 @@
     // mic mute/state
     virtual     status_t    setMicMute(bool state) = 0;
     virtual     bool        getMicMute() const = 0;
+    virtual     void        setRecordSilenced(uid_t uid, bool silenced) = 0;
 
     virtual     status_t    setParameters(audio_io_handle_t ioHandle,
                                     const String8& keyValuePairs) = 0;
diff --git a/media/libaudioclient/include/media/IAudioPolicyService.h b/media/libaudioclient/include/media/IAudioPolicyService.h
index 5558b77..7e9413d 100644
--- a/media/libaudioclient/include/media/IAudioPolicyService.h
+++ b/media/libaudioclient/include/media/IAudioPolicyService.h
@@ -84,7 +84,10 @@
                               audio_port_handle_t *selectedDeviceId,
                               audio_port_handle_t *portId) = 0;
     virtual status_t startInput(audio_io_handle_t input,
-                                audio_session_t session) = 0;
+                                audio_session_t session,
+                                audio_devices_t device,
+                                uid_t uid,
+                                bool *silenced) = 0;
     virtual status_t stopInput(audio_io_handle_t input,
                                audio_session_t session) = 0;
     virtual void releaseInput(audio_io_handle_t input,
diff --git a/media/libaudioprocessing/AudioMixer.cpp b/media/libaudioprocessing/AudioMixer.cpp
index f8e05e7..5fafb8a 100644
--- a/media/libaudioprocessing/AudioMixer.cpp
+++ b/media/libaudioprocessing/AudioMixer.cpp
@@ -1469,13 +1469,14 @@
         int32_t *out = t1.mainBuffer;
         size_t numFrames = 0;
         do {
+            const size_t frameCount = min((size_t)BLOCKSIZE, state->frameCount - numFrames);
             memset(outTemp, 0, sizeof(outTemp));
             e2 = e1;
             while (e2) {
                 const int i = 31 - __builtin_clz(e2);
                 e2 &= ~(1<<i);
                 track_t& t = state->tracks[i];
-                size_t outFrames = BLOCKSIZE;
+                size_t outFrames = frameCount;
                 int32_t *aux = NULL;
                 if (CC_UNLIKELY(t.needs & NEEDS_AUX)) {
                     aux = t.auxBuffer + numFrames;
@@ -1490,7 +1491,7 @@
                     }
                     size_t inFrames = (t.frameCount > outFrames)?outFrames:t.frameCount;
                     if (inFrames > 0) {
-                        t.hook(&t, outTemp + (BLOCKSIZE - outFrames) * t.mMixerChannelCount,
+                        t.hook(&t, outTemp + (frameCount - outFrames) * t.mMixerChannelCount,
                                 inFrames, state->resampleTemp, aux);
                         t.frameCount -= inFrames;
                         outFrames -= inFrames;
@@ -1501,7 +1502,7 @@
                     if (t.frameCount == 0 && outFrames) {
                         t.bufferProvider->releaseBuffer(&t.buffer);
                         t.buffer.frameCount = (state->frameCount - numFrames) -
-                                (BLOCKSIZE - outFrames);
+                                (frameCount - outFrames);
                         t.bufferProvider->getNextBuffer(&t.buffer);
                         t.in = t.buffer.raw;
                         if (t.in == NULL) {
@@ -1515,12 +1516,12 @@
             }
 
             convertMixerFormat(out, t1.mMixerFormat, outTemp, t1.mMixerInFormat,
-                    BLOCKSIZE * t1.mMixerChannelCount);
+                    frameCount * t1.mMixerChannelCount);
             // TODO: fix ugly casting due to choice of out pointer type
             out = reinterpret_cast<int32_t*>((uint8_t*)out
-                    + BLOCKSIZE * t1.mMixerChannelCount
+                    + frameCount * t1.mMixerChannelCount
                         * audio_bytes_per_sample(t1.mMixerFormat));
-            numFrames += BLOCKSIZE;
+            numFrames += frameCount;
         } while (numFrames < state->frameCount);
     }
 
diff --git a/media/libmedia/JAudioTrack.cpp b/media/libmedia/JAudioTrack.cpp
index b228d8b..99da0f7 100644
--- a/media/libmedia/JAudioTrack.cpp
+++ b/media/libmedia/JAudioTrack.cpp
@@ -112,6 +112,11 @@
     return env->CallIntMethod(mAudioTrackObj, jGetChannelCount);
 }
 
+uint32_t JAudioTrack::latency() {
+    // TODO: Currently hard-coded as returning zero.
+    return 0;
+}
+
 status_t JAudioTrack::getPosition(uint32_t *position) {
     if (position == NULL) {
         return BAD_VALUE;
@@ -125,7 +130,7 @@
     return NO_ERROR;
 }
 
-bool JAudioTrack::getTimeStamp(AudioTimestamp& timestamp) {
+bool JAudioTrack::getTimestamp(AudioTimestamp& timestamp) {
     JNIEnv *env = AndroidRuntime::getJNIEnv();
 
     jclass jAudioTimeStampCls = env->FindClass("android/media/AudioTimestamp");
@@ -392,6 +397,51 @@
     return audioFormatToNative(javaFormat);
 }
 
+status_t JAudioTrack::dump(int fd, const Vector<String16>& args __unused) const
+{
+    String8 result;
+
+    result.append(" JAudioTrack::dump\n");
+
+    // TODO: Remove logs that includes unavailable information from below.
+//    result.appendFormat("  status(%d), state(%d), session Id(%d), flags(%#x)\n",
+//                        mStatus, mState, mSessionId, mFlags);
+//    result.appendFormat("  stream type(%d), left - right volume(%f, %f)\n",
+//                        (mStreamType == AUDIO_STREAM_DEFAULT) ?
+//                                audio_attributes_to_stream_type(&mAttributes) : mStreamType,
+//                        mVolume[AUDIO_INTERLEAVE_LEFT], mVolume[AUDIO_INTERLEAVE_RIGHT]);
+//    result.appendFormat("  format(%#x), channel mask(%#x), channel count(%u)\n",
+//                  format(), mChannelMask, channelCount());
+//    result.appendFormat("  sample rate(%u), original sample rate(%u), speed(%f)\n",
+//            getSampleRate(), mOriginalSampleRate, mPlaybackRate.mSpeed);
+//    result.appendFormat("  frame count(%zu), req. frame count(%zu)\n",
+//                  frameCount(), mReqFrameCount);
+//    result.appendFormat("  notif. frame count(%u), req. notif. frame count(%u),"
+//            " req. notif. per buff(%u)\n",
+//             mNotificationFramesAct, mNotificationFramesReq, mNotificationsPerBufferReq);
+//    result.appendFormat("  latency (%d), selected device Id(%d), routed device Id(%d)\n",
+//                        latency(), mSelectedDeviceId, getRoutedDeviceId());
+//    result.appendFormat("  output(%d) AF latency (%u) AF frame count(%zu) AF SampleRate(%u)\n",
+//                        mOutput, mAfLatency, mAfFrameCount, mAfSampleRate);
+    ::write(fd, result.string(), result.size());
+    return NO_ERROR;
+}
+
+audio_port_handle_t JAudioTrack::getRoutedDeviceId() {
+    JNIEnv *env = AndroidRuntime::getJNIEnv();
+    jmethodID jGetRoutedDevice = env->GetMethodID(mAudioTrackCls, "getRoutedDevice",
+            "()Landroid/media/AudioDeviceInfo;");
+    jobject jAudioDeviceInfoObj = env->CallObjectMethod(mAudioTrackObj, jGetRoutedDevice);
+    if (env->IsSameObject(jAudioDeviceInfoObj, NULL)) {
+        return AUDIO_PORT_HANDLE_NONE;
+    }
+
+    jclass jAudioDeviceInfoCls = env->FindClass("Landroid/media/AudioDeviceInfo");
+    jmethodID jGetId = env->GetMethodID(jAudioDeviceInfoCls, "getId", "()I");
+    jint routedDeviceId = env->CallIntMethod(jAudioDeviceInfoObj, jGetId);
+    return routedDeviceId;
+}
+
 jobject JAudioTrack::createVolumeShaperConfigurationObj(
         const sp<media::VolumeShaper::Configuration>& config) {
 
diff --git a/media/libmedia/include/media/DrmHal.h b/media/libmedia/include/media/DrmHal.h
index 5d25e4d..55fbce9 100644
--- a/media/libmedia/include/media/DrmHal.h
+++ b/media/libmedia/include/media/DrmHal.h
@@ -24,6 +24,7 @@
 
 #include <media/IDrm.h>
 #include <media/IDrmClient.h>
+#include <media/MediaAnalyticsItem.h>
 #include <utils/threads.h>
 
 using ::android::hardware::drm::V1_0::EventType;
@@ -104,6 +105,7 @@
     virtual status_t setPropertyString(String8 const &name, String8 const &value ) const;
     virtual status_t setPropertyByteArray(String8 const &name,
                                           Vector<uint8_t> const &value ) const;
+    virtual status_t getMetrics(MediaAnalyticsItem *item);
 
     virtual status_t setCipherAlgorithm(Vector<uint8_t> const &sessionId,
                                         String8 const &algorithm);
diff --git a/media/libmedia/include/media/IDrm.h b/media/libmedia/include/media/IDrm.h
index a57e372..ce0360b 100644
--- a/media/libmedia/include/media/IDrm.h
+++ b/media/libmedia/include/media/IDrm.h
@@ -18,6 +18,7 @@
 #include <media/stagefright/foundation/ABase.h>
 #include <media/drm/DrmAPI.h>
 #include <media/IDrmClient.h>
+#include <media/MediaAnalyticsItem.h>
 
 #ifndef ANDROID_IDRM_H_
 
@@ -86,6 +87,8 @@
     virtual status_t setPropertyByteArray(String8 const &name,
                                           Vector<uint8_t> const &value) const = 0;
 
+    virtual status_t getMetrics(MediaAnalyticsItem *item) = 0;
+
     virtual status_t setCipherAlgorithm(Vector<uint8_t> const &sessionId,
                                         String8 const &algorithm) = 0;
 
diff --git a/media/libmedia/include/media/JAudioTrack.h b/media/libmedia/include/media/JAudioTrack.h
index 8af30b7..10fa5e8 100644
--- a/media/libmedia/include/media/JAudioTrack.h
+++ b/media/libmedia/include/media/JAudioTrack.h
@@ -104,6 +104,12 @@
     size_t frameCount();
     size_t channelCount();
 
+    /* Returns this track's estimated latency in milliseconds.
+     * This includes the latency due to AudioTrack buffer size, AudioMixer (if any)
+     * and audio hardware driver.
+     */
+    uint32_t latency();
+
     /* Return the total number of frames played since playback start.
      * The counter will wrap (overflow) periodically, e.g. every ~27 hours at 44.1 kHz.
      * It is reset to zero by flush(), reload(), and stop().
@@ -130,7 +136,7 @@
      * Returns true if timestamp is valid.
      * The timestamp parameter is undefined on return, if false is returned.
      */
-    bool getTimeStamp(AudioTimestamp& timestamp);
+    bool getTimestamp(AudioTimestamp& timestamp);
 
     /* Set source playback rate for timestretch
      * 1.0 is normal speed: < 1.0 is slower, > 1.0 is faster
@@ -253,6 +259,17 @@
 
     audio_format_t format();
 
+    /*
+     * Dumps the state of an audio track.
+     * Not a general-purpose API; intended only for use by media player service to dump its tracks.
+     */
+    status_t dump(int fd, const Vector<String16>& args) const;
+
+    /* Returns the ID of the audio device actually used by the output to which this AudioTrack is
+     * attached. When the AudioTrack is inactive, it will return AUDIO_PORT_HANDLE_NONE.
+     */
+    audio_port_handle_t getRoutedDeviceId();
+
 private:
     jclass mAudioTrackCls;
     jobject mAudioTrackObj;
diff --git a/media/libstagefright/codec2/SimpleC2Interface.cpp b/media/libstagefright/codec2/SimpleC2Interface.cpp
index f9cab26..f082243 100644
--- a/media/libstagefright/codec2/SimpleC2Interface.cpp
+++ b/media/libstagefright/codec2/SimpleC2Interface.cpp
@@ -23,7 +23,7 @@
 namespace android {
 
 c2_status_t SimpleC2Interface::query_vb(
-        const std::vector<C2Param* const> &stackParams,
+        const std::vector<C2Param*> &stackParams,
         const std::vector<C2Param::Index> &heapParamIndices,
         c2_blocking_t mayBlock,
         std::vector<std::unique_ptr<C2Param>>* const heapParams) const {
diff --git a/media/libstagefright/codec2/include/C2Buffer.h b/media/libstagefright/codec2/include/C2Buffer.h
index df9362c..cd90978 100644
--- a/media/libstagefright/codec2/include/C2Buffer.h
+++ b/media/libstagefright/codec2/include/C2Buffer.h
@@ -284,6 +284,7 @@
 /// @{
 public:
     inline uint32_t offset() const { return mOffset; }
+    inline uint32_t endOffset() const { return mOffset + mSize; }
     inline uint32_t size() const { return mSize; }
 
 protected:
@@ -311,6 +312,32 @@
 /// @}
 };
 
+class C2_HIDE _C2LinearCapacityBase : public _C2LinearCapacityAspect {
+public:
+    inline explicit _C2LinearCapacityBase(size_t capacity)
+        : _C2LinearCapacityAspect(c2_min(capacity, std::numeric_limits<uint32_t>::max())) {}
+};
+
+/**
+ * Utility class for safe range calculations.
+ */
+class C2LinearRange : public _C2LinearRangeAspect {
+public:
+    inline C2LinearRange(const _C2LinearCapacityBase &parent, size_t offset, size_t size)
+        : _C2LinearRangeAspect(&parent, offset, size) {}
+};
+
+/**
+ * Utility class for simple capacity and range construction.
+ */
+class C2LinearCapacity : public _C2LinearCapacityBase {
+public:
+    using _C2LinearCapacityBase::_C2LinearCapacityBase;
+    inline C2LinearRange range(size_t offset, size_t size) {
+        return C2LinearRange(*this, offset, size);
+    }
+};
+
 /**
  * Aspect for objects that have an editable linear range.
  *
diff --git a/media/libstagefright/codec2/include/C2Component.h b/media/libstagefright/codec2/include/C2Component.h
index 38d545e..a2168a0 100644
--- a/media/libstagefright/codec2/include/C2Component.h
+++ b/media/libstagefright/codec2/include/C2Component.h
@@ -150,7 +150,7 @@
      *                      (this error code is only allowed for interfaces connected to components)
      */
     virtual c2_status_t query_vb(
-        const std::vector<C2Param* const> &stackParams,
+        const std::vector<C2Param*> &stackParams,
         const std::vector<C2Param::Index> &heapParamIndices,
         c2_blocking_t mayBlock,
         std::vector<std::unique_ptr<C2Param>>* const heapParams) const = 0;
@@ -211,7 +211,7 @@
      *                      (this error code is only allowed for interfaces connected to components)
      */
     virtual c2_status_t config_vb(
-            const std::vector<C2Param* const> &params,
+            const std::vector<C2Param*> &params,
             c2_blocking_t mayBlock,
             std::vector<std::unique_ptr<C2SettingResult>>* const failures) = 0;
 
@@ -850,7 +850,7 @@
      *                      (unexpected)
      */
     virtual c2_status_t query_sm(
-        const std::vector<C2Param* const> &stackParams,
+        const std::vector<C2Param*> &stackParams,
         const std::vector<C2Param::Index> &heapParamIndices,
         std::vector<std::unique_ptr<C2Param>>* const heapParams) const = 0;
 
@@ -889,7 +889,7 @@
      *                      (unexpected)
      */
     virtual c2_status_t config_sm(
-            const std::vector<C2Param* const> &params,
+            const std::vector<C2Param*> &params,
             std::vector<std::unique_ptr<C2SettingResult>>* const failures) = 0;
 
     // REFLECTION MECHANISM (USED FOR EXTENSION)
diff --git a/media/libstagefright/codec2/include/C2Param.h b/media/libstagefright/codec2/include/C2Param.h
index 2a8c1b2..9785c87 100644
--- a/media/libstagefright/codec2/include/C2Param.h
+++ b/media/libstagefright/codec2/include/C2Param.h
@@ -397,8 +397,8 @@
 
     /// safe(r) type cast from pointer and size
     inline static C2Param* From(void *addr, size_t len) {
-        // _mSize must fit into size
-        if (len < sizeof(_mSize) + offsetof(C2Param, _mSize)) {
+        // _mSize must fit into size, but really C2Param must also to be a valid param
+        if (len < sizeof(C2Param)) {
             return nullptr;
         }
         // _mSize must match length
@@ -446,7 +446,7 @@
     // if other is the same kind of (valid) param as this, copy it into this and return true.
     // otherwise, do not copy anything, and return false.
     inline bool updateFrom(const C2Param &other) {
-        if (other._mSize == _mSize && other._mIndex == _mIndex && _mSize > 0) {
+        if (other._mSize <= _mSize && other._mIndex == _mIndex && _mSize > 0) {
             memcpy(this, &other, _mSize);
             return true;
         }
@@ -620,6 +620,8 @@
 #endif
 
 private:
+    friend struct _C2ParamInspector;
+
     uint32_t _mOffset; // offset of field
     uint32_t _mSize;   // size of field
 };
@@ -720,6 +722,8 @@
     DEFINE_OTHER_COMPARISON_OPERATORS(C2ParamField)
 
 private:
+    friend struct _C2ParamInspector;
+
     C2Param::Index _mIndex; ///< parameter index
     _C2FieldId _mFieldId;   ///< field identifier
 };
diff --git a/media/libstagefright/codec2/include/SimpleC2Interface.h b/media/libstagefright/codec2/include/SimpleC2Interface.h
index 3796b0b..b934f12 100644
--- a/media/libstagefright/codec2/include/SimpleC2Interface.h
+++ b/media/libstagefright/codec2/include/SimpleC2Interface.h
@@ -59,12 +59,12 @@
     inline C2String getName() const override { return mName; }
     inline c2_node_id_t getId() const override { return mId; }
     c2_status_t query_vb(
-            const std::vector<C2Param* const> &stackParams,
+            const std::vector<C2Param*> &stackParams,
             const std::vector<C2Param::Index> &heapParamIndices,
             c2_blocking_t mayBlock,
             std::vector<std::unique_ptr<C2Param>>* const heapParams) const override;
     inline c2_status_t config_vb(
-            const std::vector<C2Param* const> &,
+            const std::vector<C2Param*> &,
             c2_blocking_t,
             std::vector<std::unique_ptr<C2SettingResult>>* const) override {
         return C2_OMITTED;
diff --git a/media/libstagefright/codec2/tests/C2ComponentInterface_test.cpp b/media/libstagefright/codec2/tests/C2ComponentInterface_test.cpp
index 339f927..f50af81 100644
--- a/media/libstagefright/codec2/tests/C2ComponentInterface_test.cpp
+++ b/media/libstagefright/codec2/tests/C2ComponentInterface_test.cpp
@@ -137,7 +137,7 @@
     template <typename T> void queryUnsupportedParam();
 
     // Execute an interface's config_vb(). |T| is a single parameter type, not std::vector.
-    // config() creates std::vector<C2Param *const> {p} and passes it to config_vb().
+    // config() creates std::vector<C2Param *> {p} and passes it to config_vb().
     template <typename T>
     c2_status_t
     config(T *const p,
@@ -195,7 +195,7 @@
     } while (false)
 
 template <typename T> c2_status_t C2CompIntfTest::queryOnStack(T *const p) {
-    std::vector<C2Param *const> stackParams{p};
+    std::vector<C2Param*> stackParams{p};
     return mIntf->query_vb(stackParams, {}, C2_DONT_BLOCK, nullptr);
 }
 
@@ -260,7 +260,7 @@
 template <typename T>
 c2_status_t C2CompIntfTest::config(
         T *const p, std::vector<std::unique_ptr<C2SettingResult>> *const failures) {
-    std::vector<C2Param *const> params{p};
+    std::vector<C2Param*> params{p};
     return mIntf->config_vb(params, C2_DONT_BLOCK, failures);
 }
 
@@ -276,7 +276,7 @@
 void C2CompIntfTest::configReadOnlyParam(const T &newParam) {
     std::unique_ptr<T> p = makeParamFrom(newParam);
 
-    std::vector<C2Param *const> params{p.get()};
+    std::vector<C2Param*> params{p.get()};
     std::vector<std::unique_ptr<C2SettingResult>> failures;
 
     // config_vb should be failed because a parameter is read-only.
@@ -289,7 +289,7 @@
 void C2CompIntfTest::configWritableParamValidValue(const T &newParam, c2_status_t *configResult) {
     std::unique_ptr<T> p = makeParamFrom(newParam);
 
-    std::vector<C2Param *const> params{p.get()};
+    std::vector<C2Param*> params{p.get()};
     std::vector<std::unique_ptr<C2SettingResult>> failures;
     // In most cases, config_vb return C2_OK and the parameter's value should be changed
     // to |newParam|, which is confirmed in a caller of configWritableParamValueValue().
@@ -312,7 +312,7 @@
 void C2CompIntfTest::configWritableParamInvalidValue(const T &newParam) {
     std::unique_ptr<T> p = makeParamFrom(newParam);
 
-    std::vector<C2Param *const> params{p.get()};
+    std::vector<C2Param*> params{p.get()};
     std::vector<std::unique_ptr<C2SettingResult>> failures;
     // Although a parameter is writable, config_vb should be failed,
     // because a new value is invalid.
diff --git a/media/libstagefright/codec2/tests/C2Param_test.cpp b/media/libstagefright/codec2/tests/C2Param_test.cpp
index 8ebc584..d186292 100644
--- a/media/libstagefright/codec2/tests/C2Param_test.cpp
+++ b/media/libstagefright/codec2/tests/C2Param_test.cpp
@@ -2450,7 +2450,7 @@
     }
 
     virtual c2_status_t config_vb(
-            const std::vector<C2Param* const> &params,
+            const std::vector<C2Param*> &params,
             c2_blocking_t mayBlock,
             std::vector<std::unique_ptr<C2SettingResult>>* const failures) override {
         (void)params;
@@ -2465,7 +2465,7 @@
     }
 
     virtual c2_status_t query_vb(
-            const std::vector<C2Param* const> &stackParams,
+            const std::vector<C2Param*> &stackParams,
             const std::vector<C2Param::Index> &heapParamIndices,
             c2_blocking_t mayBlock,
             std::vector<std::unique_ptr<C2Param>>* const heapParams) const override {
diff --git a/media/libstagefright/codec2/vndk/C2ParamInternal.h b/media/libstagefright/codec2/vndk/C2ParamInternal.h
new file mode 100644
index 0000000..0f3812a
--- /dev/null
+++ b/media/libstagefright/codec2/vndk/C2ParamInternal.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_STAGEFRIGHT_C2PARAM_INTERNAL_H_
+#define ANDROID_STAGEFRIGHT_C2PARAM_INTERNAL_H_
+
+#include <C2Param.h>
+
+namespace android {
+
+struct C2_HIDE _C2ParamInspector {
+   inline static uint32_t getIndex(const C2ParamField &pf) {
+        return pf._mIndex;
+    }
+
+    inline static uint32_t getOffset(const C2ParamField &pf) {
+        return pf._mFieldId._mOffset;
+    }
+
+    inline static uint32_t getSize(const C2ParamField &pf) {
+        return pf._mFieldId._mSize;
+    }
+
+    inline static
+    C2ParamField CreateParamField(C2Param::Index index, uint32_t offset, uint32_t size) {
+        return C2ParamField(index, offset, size);
+    }
+};
+
+}
+
+#endif // ANDROID_STAGEFRIGHT_C2PARAM_INTERNAL_H_
+
diff --git a/media/libstagefright/codec2/vndk/C2Store.cpp b/media/libstagefright/codec2/vndk/C2Store.cpp
index eb72d17..daa9d3f 100644
--- a/media/libstagefright/codec2/vndk/C2Store.cpp
+++ b/media/libstagefright/codec2/vndk/C2Store.cpp
@@ -167,7 +167,7 @@
     virtual c2_status_t querySupportedParams_nb(
             std::vector<std::shared_ptr<C2ParamDescriptor>> *const params) const override;
     virtual c2_status_t query_sm(
-            const std::vector<C2Param *const> &stackParams,
+            const std::vector<C2Param*> &stackParams,
             const std::vector<C2Param::Index> &heapParamIndices,
             std::vector<std::unique_ptr<C2Param>> *const heapParams) const override;
     virtual c2_status_t createInterface(
@@ -177,7 +177,7 @@
     virtual c2_status_t copyBuffer(
             std::shared_ptr<C2GraphicBuffer> src, std::shared_ptr<C2GraphicBuffer> dst) override;
     virtual c2_status_t config_sm(
-            const std::vector<C2Param *const> &params,
+            const std::vector<C2Param*> &params,
             std::vector<std::unique_ptr<C2SettingResult>> *const failures) override;
     C2PlatformComponentStore();
 
@@ -415,7 +415,7 @@
 }
 
 c2_status_t C2PlatformComponentStore::query_sm(
-        const std::vector<C2Param *const> &stackParams,
+        const std::vector<C2Param*> &stackParams,
         const std::vector<C2Param::Index> &heapParamIndices,
         std::vector<std::unique_ptr<C2Param>> *const heapParams) const {
     // there are no supported configs
@@ -424,7 +424,7 @@
 }
 
 c2_status_t C2PlatformComponentStore::config_sm(
-        const std::vector<C2Param *const> &params,
+        const std::vector<C2Param*> &params,
         std::vector<std::unique_ptr<C2SettingResult>> *const failures) {
     // there are no supported configs
     (void)failures;
diff --git a/media/libstagefright/codec2/vndk/include/util/C2ParamUtils.h b/media/libstagefright/codec2/vndk/include/util/C2ParamUtils.h
index 81c5495..3168248 100644
--- a/media/libstagefright/codec2/vndk/include/util/C2ParamUtils.h
+++ b/media/libstagefright/codec2/vndk/include/util/C2ParamUtils.h
@@ -59,6 +59,7 @@
 
 /// \endcond
 
+#undef DEFINE_C2_ENUM_VALUE_AUTO_HELPER
 #define DEFINE_C2_ENUM_VALUE_AUTO_HELPER(name, type, prefix, ...) \
 template<> C2FieldDescriptor::named_values_type C2FieldDescriptor::namedValuesFor(const name &r __unused) { \
     return C2ParamUtils::sanitizeEnumValues( \
@@ -67,6 +68,7 @@
             prefix); \
 }
 
+#undef DEFINE_C2_ENUM_VALUE_CUSTOM_HELPER
 #define DEFINE_C2_ENUM_VALUE_CUSTOM_HELPER(name, type, names, ...) \
 template<> C2FieldDescriptor::named_values_type C2FieldDescriptor::namedValuesFor(const name &r __unused) { \
     return C2ParamUtils::customEnumValues( \
@@ -260,6 +262,21 @@
         }
         return namedValues;
     }
+
+    /// safe(r) parsing from parameter blob
+    static
+    C2Param *ParseFirst(const uint8_t *blob, size_t size) {
+        // _mSize must fit into size, but really C2Param must also to be a valid param
+        if (size < sizeof(C2Param)) {
+            return nullptr;
+        }
+        // _mSize must match length
+        C2Param *param = (C2Param*)blob;
+        if (param->size() > size) {
+            return nullptr;
+        }
+        return param;
+    }
 };
 
 /* ---------------------------- UTILITIES FOR PARAMETER REFLECTION ---------------------------- */
diff --git a/media/libstagefright/codecs/avcdec/C2SoftAvcDec.cpp b/media/libstagefright/codecs/avcdec/C2SoftAvcDec.cpp
index ffe6332..0da9cc7 100644
--- a/media/libstagefright/codecs/avcdec/C2SoftAvcDec.cpp
+++ b/media/libstagefright/codecs/avcdec/C2SoftAvcDec.cpp
@@ -448,7 +448,7 @@
 }
 
 c2_status_t C2SoftAvcDecIntf::query_vb(
-        const std::vector<C2Param* const> & stackParams,
+        const std::vector<C2Param*> & stackParams,
         const std::vector<C2Param::Index> & heapParamIndices,
         c2_blocking_t mayBlock,
         std::vector<std::unique_ptr<C2Param>>* const heapParams) const {
@@ -485,7 +485,7 @@
 }
 
 c2_status_t C2SoftAvcDecIntf::config_vb(
-        const std::vector<C2Param* const> &params,
+        const std::vector<C2Param*> &params,
         c2_blocking_t mayBlock,
         std::vector<std::unique_ptr<C2SettingResult>>* const failures) {
     (void)mayBlock;
diff --git a/media/libstagefright/codecs/avcdec/C2SoftAvcDec.h b/media/libstagefright/codecs/avcdec/C2SoftAvcDec.h
index 0e8cf77..6632bf3 100644
--- a/media/libstagefright/codecs/avcdec/C2SoftAvcDec.h
+++ b/media/libstagefright/codecs/avcdec/C2SoftAvcDec.h
@@ -84,12 +84,12 @@
     virtual C2String getName() const override;
     virtual c2_node_id_t getId() const override;
     virtual c2_status_t query_vb(
-            const std::vector<C2Param* const> &stackParams,
+            const std::vector<C2Param*> &stackParams,
             const std::vector<C2Param::Index> &heapParamIndices,
             c2_blocking_t mayBlock,
             std::vector<std::unique_ptr<C2Param>>* const heapParams) const override;
     virtual c2_status_t config_vb(
-            const std::vector<C2Param* const> &params,
+            const std::vector<C2Param*> &params,
             c2_blocking_t mayBlock,
             std::vector<std::unique_ptr<C2SettingResult>>* const failures) override;
     virtual c2_status_t createTunnel_sm(c2_node_id_t targetComponent) override;
diff --git a/packages/MediaComponents/res/drawable/ic_replay.xml b/packages/MediaComponents/res/drawable/ic_replay.xml
new file mode 100644
index 0000000..2bde120
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/ic_replay.xml
@@ -0,0 +1,4 @@
+<vector android:height="40dp" android:viewportHeight="48.0"
+    android:viewportWidth="48.0" android:width="40dp" xmlns:android="http://schemas.android.com/apk/res/android">
+    <path android:fillColor="#FFFFFF" android:pathData="M24,10V2L14,12l10,10v-8c6.63,0 12,5.37 12,12s-5.37,12 -12,12 -12,-5.37 -12,-12H8c0,8.84 7.16,16 16,16s16,-7.16 16,-16 -7.16,-16 -16,-16z"/>
+</vector>
diff --git a/packages/MediaComponents/res/layout/mr_controller_material_dialog_b.xml b/packages/MediaComponents/res/layout/mr_controller_material_dialog_b.xml
index b409e6b..3751002 100644
--- a/packages/MediaComponents/res/layout/mr_controller_material_dialog_b.xml
+++ b/packages/MediaComponents/res/layout/mr_controller_material_dialog_b.xml
@@ -15,87 +15,192 @@
 -->
 
 <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
-          android:id="@+id/mr_expandable_area"
-          android:layout_width="fill_parent"
-          android:layout_height="fill_parent">
+    android:id="@+id/mr_expandable_area"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent">
     <LinearLayout android:id="@+id/mr_dialog_area"
-                  android:layout_width="fill_parent"
-                  android:layout_height="wrap_content"
-                  android:layout_gravity="center"
-                  android:orientation="vertical"
-                  android:background="?attr/colorBackgroundFloating">
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:orientation="vertical"
+        android:background="?attr/colorBackgroundFloating">
         <LinearLayout android:id="@+id/mr_title_bar"
-                      android:layout_width="fill_parent"
-                      android:layout_height="wrap_content"
-                      android:paddingLeft="24dp"
-                      android:paddingRight="12dp"
-                      android:orientation="horizontal" >
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:paddingLeft="24dp"
+            android:paddingRight="12dp"
+            android:orientation="horizontal" >
             <TextView android:id="@+id/mr_name"
-                      android:layout_width="0dp"
-                      android:layout_height="72dp"
-                      android:layout_weight="1"
-                      android:gravity="center_vertical"
-                      android:singleLine="true"
-                      android:ellipsize="end"
-                      android:textAppearance="@style/TextAppearance.MediaRouter.Title" />
+                android:layout_width="0dp"
+                android:layout_height="72dp"
+                android:layout_weight="1"
+                android:gravity="center_vertical"
+                android:singleLine="true"
+                android:ellipsize="end"
+                android:textAppearance="@style/TextAppearance.MediaRouter.Title" />
             <ImageButton android:id="@+id/mr_close"
-                         android:layout_width="48dp"
-                         android:layout_height="48dp"
-                         android:layout_gravity="center_vertical"
-                         android:contentDescription="@string/mr_controller_close_description"
-                         android:src="?attr/mediaRouteCloseDrawable"
-                         android:background="?attr/selectableItemBackgroundBorderless" />
+                android:layout_width="48dp"
+                android:layout_height="48dp"
+                android:layout_gravity="center_vertical"
+                android:contentDescription="@string/mr_controller_close_description"
+                android:src="?attr/mediaRouteCloseDrawable"
+                android:background="?attr/selectableItemBackgroundBorderless" />
         </LinearLayout>
         <FrameLayout android:id="@+id/mr_custom_control"
-                     android:layout_width="fill_parent"
-                     android:layout_height="wrap_content"
-                     android:visibility="gone" />
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:visibility="gone" />
         <FrameLayout android:id="@+id/mr_default_control"
-                     android:layout_width="fill_parent"
-                     android:layout_height="wrap_content">
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content">
             <ImageView android:id="@+id/mr_art"
-                       android:layout_width="fill_parent"
-                       android:layout_height="wrap_content"
-                       android:adjustViewBounds="true"
-                       android:scaleType="fitXY"
-                       android:background="?attr/colorPrimary"
-                       android:layout_gravity="top"
-                       android:contentDescription="@string/mr_controller_album_art"
-                       android:visibility="gone" />
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:adjustViewBounds="true"
+                android:scaleType="fitXY"
+                android:background="?attr/colorPrimary"
+                android:layout_gravity="top"
+                android:contentDescription="@string/mr_controller_album_art"
+                android:visibility="gone" />
             <LinearLayout android:layout_width="fill_parent"
-                          android:layout_height="wrap_content"
-                          android:orientation="vertical"
-                          android:layout_gravity="bottom"
-                          android:splitMotionEvents="false">
+                android:layout_height="wrap_content"
+                android:orientation="vertical"
+                android:layout_gravity="bottom"
+                android:splitMotionEvents="false">
                 <LinearLayout android:id="@+id/mr_media_main_control"
-                              android:layout_width="fill_parent"
-                              android:layout_height="wrap_content"
-                              android:orientation="vertical"
-                              android:paddingTop="16dp"
-                              android:paddingBottom="16dp"
-                              android:layout_gravity="bottom"
-                              android:theme="?attr/mediaRouteControlPanelThemeOverlay">
-                    <include android:id="@+id/mr_playback_control"
-                             layout="@layout/mr_playback_control" />
-                    <View android:id="@+id/mr_control_divider"
-                          android:layout_width="fill_parent"
-                          android:layout_height="8dp"
-                          android:visibility="gone" />
-                    <include android:id="@+id/mr_volume_control"
-                             layout="@layout/mr_volume_control" />
-                </LinearLayout>
-                <android.support.v7.app.OverlayListView
-                        android:id="@+id/mr_volume_group_list"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content"
+                    android:orientation="vertical"
+                    android:paddingTop="16dp"
+                    android:paddingBottom="16dp"
+                    android:layout_gravity="bottom"
+                    android:theme="?attr/mediaRouteControlPanelThemeOverlay">
+                    <RelativeLayout
+                        android:id="@+id/mr_playback_control"
                         android:layout_width="fill_parent"
                         android:layout_height="wrap_content"
-                        android:paddingTop="@dimen/mr_controller_volume_group_list_padding_top"
-                        android:scrollbarStyle="outsideOverlay"
-                        android:clipToPadding="false"
-                        android:visibility="gone"
-                        android:splitMotionEvents="false"
-                        android:theme="?attr/mediaRouteControlPanelThemeOverlay" />
+                        android:orientation="horizontal"
+                        android:paddingLeft="24dp"
+                        android:paddingRight="12dp" >
+                        <ImageButton android:id="@+id/mr_control_playback_ctrl"
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content"
+                            android:layout_marginLeft="12dp"
+                            android:layout_alignParentRight="true"
+                            android:layout_centerVertical="true"
+                            android:contentDescription="@string/mr_controller_play"
+                            android:background="?attr/selectableItemBackgroundBorderless"
+                            android:visibility="gone" />
+                        <LinearLayout android:id="@+id/mr_control_title_container"
+                            android:orientation="vertical"
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content"
+                            android:layout_toLeftOf="@id/mr_control_playback_ctrl"
+                            android:layout_alignParentLeft="true"
+                            android:layout_centerVertical="true">
+                            <TextView android:id="@+id/mr_control_title"
+                                android:layout_width="wrap_content"
+                                android:layout_height="wrap_content"
+                                android:textAppearance="@style/TextAppearance.MediaRouter.PrimaryText"
+                                android:singleLine="true" />
+                            <TextView android:id="@+id/mr_control_subtitle"
+                                android:layout_width="wrap_content"
+                                android:layout_height="wrap_content"
+                                android:textAppearance="@style/TextAppearance.MediaRouter.SecondaryText"
+                                android:singleLine="true" />
+                        </LinearLayout>
+                    </RelativeLayout>
+                    <View android:id="@+id/mr_control_divider"
+                        android:layout_width="fill_parent"
+                        android:layout_height="8dp"
+                        android:visibility="gone" />
+                    <LinearLayout
+                        android:id="@+id/mr_volume_control"
+                        android:layout_width="fill_parent"
+                        android:layout_height="wrap_content"
+                        android:minHeight="48dp"
+                        android:gravity="center_vertical"
+                        android:paddingLeft="24dp"
+                        android:paddingRight="12dp"
+                        android:splitMotionEvents="false">
+                        <ImageView
+                            android:layout_width="24dp"
+                            android:layout_height="24dp"
+                            android:src="?attr/mediaRouteAudioTrackDrawable"
+                            android:gravity="center"
+                            android:scaleType="center"/>
+                        <!-- Since dialog's top layout mr_expandable_area is clickable, it propagates pressed state
+                             to its non-clickable children. Specify android:clickable="true" to prevent volume slider
+                             from having false pressed state. -->
+                        <com.android.support.mediarouter.app.MediaRouteVolumeSlider
+                            android:id="@+id/mr_volume_slider"
+                            android:layout_width="0dp"
+                            android:layout_height="wrap_content"
+                            android:minHeight="48dp"
+                            android:maxHeight="48dp"
+                            android:layout_weight="1"
+                            android:clickable="true"
+                            android:contentDescription="@string/mr_controller_volume_slider" />
+                        <com.android.support.mediarouter.app.MediaRouteExpandCollapseButton
+                            android:id="@+id/mr_group_expand_collapse"
+                            android:layout_width="48dp"
+                            android:layout_height="48dp"
+                            android:padding="12dp"
+                            android:background="?attr/selectableItemBackgroundBorderless"
+                            android:visibility="gone"/>
+                    </LinearLayout>
+                </LinearLayout>
+                <com.android.support.mediarouter.app.OverlayListView
+                    android:id="@+id/mr_volume_group_list"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content"
+                    android:paddingTop="@dimen/mr_controller_volume_group_list_padding_top"
+                    android:scrollbarStyle="outsideOverlay"
+                    android:clipToPadding="false"
+                    android:visibility="gone"
+                    android:splitMotionEvents="false"
+                    android:theme="?attr/mediaRouteControlPanelThemeOverlay" />
             </LinearLayout>
         </FrameLayout>
-        <include layout="@layout/abc_alert_dialog_button_bar_material" />
+        <ScrollView
+            android:id="@+id/buttonPanel"
+            style="?attr/buttonBarStyle"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:fillViewport="true"
+            android:scrollIndicators="top|bottom">
+            <android.support.v7.widget.ButtonBarLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:gravity="bottom"
+                android:layoutDirection="locale"
+                android:orientation="horizontal"
+                android:paddingBottom="4dp"
+                android:paddingLeft="12dp"
+                android:paddingRight="12dp"
+                android:paddingTop="4dp">
+                <Button
+                    android:id="@android:id/button3"
+                    style="?attr/buttonBarNeutralButtonStyle"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"/>
+                <android.support.v4.widget.Space
+                    android:id="@+id/spacer"
+                    android:layout_width="0dp"
+                    android:layout_height="0dp"
+                    android:layout_weight="1"
+                    android:visibility="invisible"/>
+                <Button
+                    android:id="@android:id/button2"
+                    style="?android:attr/buttonBarNegativeButtonStyle"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"/>
+                <Button
+                    android:id="@android:id/button1"
+                    style="?attr/buttonBarPositiveButtonStyle"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"/>
+            </android.support.v7.widget.ButtonBarLayout>
+        </ScrollView>
     </LinearLayout>
 </FrameLayout>
diff --git a/packages/MediaComponents/res/layout/mr_playback_control.xml b/packages/MediaComponents/res/layout/mr_playback_control.xml
deleted file mode 100644
index 870dd50..0000000
--- a/packages/MediaComponents/res/layout/mr_playback_control.xml
+++ /dev/null
@@ -1,50 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
-                android:layout_width="fill_parent"
-                android:layout_height="wrap_content"
-                android:orientation="horizontal"
-                android:paddingLeft="24dp"
-                android:paddingRight="12dp" >
-    <ImageButton android:id="@+id/mr_control_playback_ctrl"
-                 android:layout_width="wrap_content"
-                 android:layout_height="wrap_content"
-                 android:layout_marginLeft="12dp"
-                 android:layout_alignParentRight="true"
-                 android:layout_centerVertical="true"
-                 android:contentDescription="@string/mr_controller_play"
-                 android:background="?attr/selectableItemBackgroundBorderless"
-                 android:visibility="gone" />
-    <LinearLayout android:id="@+id/mr_control_title_container"
-                  android:orientation="vertical"
-                  android:layout_width="wrap_content"
-                  android:layout_height="wrap_content"
-                  android:layout_toLeftOf="@id/mr_control_playback_ctrl"
-                  android:layout_alignParentLeft="true"
-                  android:layout_centerVertical="true">
-        <TextView android:id="@+id/mr_control_title"
-                  android:layout_width="wrap_content"
-                  android:layout_height="wrap_content"
-                  android:textAppearance="@style/TextAppearance.MediaRouter.PrimaryText"
-                  android:singleLine="true" />
-        <TextView android:id="@+id/mr_control_subtitle"
-                  android:layout_width="wrap_content"
-                  android:layout_height="wrap_content"
-                  android:textAppearance="@style/TextAppearance.MediaRouter.SecondaryText"
-                  android:singleLine="true" />
-    </LinearLayout>
-</RelativeLayout>
diff --git a/packages/MediaComponents/res/layout/mr_volume_control.xml b/packages/MediaComponents/res/layout/mr_volume_control.xml
deleted file mode 100644
index 5212532..0000000
--- a/packages/MediaComponents/res/layout/mr_volume_control.xml
+++ /dev/null
@@ -1,50 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-        android:layout_width="fill_parent"
-        android:layout_height="wrap_content"
-        android:minHeight="48dp"
-        android:gravity="center_vertical"
-        android:paddingLeft="24dp"
-        android:paddingRight="12dp"
-        android:splitMotionEvents="false">
-    <ImageView
-            android:layout_width="24dp"
-            android:layout_height="24dp"
-            android:src="?attr/mediaRouteAudioTrackDrawable"
-            android:gravity="center"
-            android:scaleType="center"/>
-    <!-- Since dialog's top layout mr_expandable_area is clickable, it propagates pressed state
-         to its non-clickable children. Specify android:clickable="true" to prevent volume slider
-         from having false pressed state. -->
-    <android.support.v7.app.MediaRouteVolumeSlider
-            android:id="@+id/mr_volume_slider"
-            android:layout_width="0dp"
-            android:layout_height="wrap_content"
-            android:minHeight="48dp"
-            android:maxHeight="48dp"
-            android:layout_weight="1"
-            android:clickable="true"
-            android:contentDescription="@string/mr_controller_volume_slider" />
-    <android.support.v7.app.MediaRouteExpandCollapseButton
-            android:id="@+id/mr_group_expand_collapse"
-            android:layout_width="48dp"
-            android:layout_height="48dp"
-            android:padding="12dp"
-            android:background="?attr/selectableItemBackgroundBorderless"
-            android:visibility="gone"/>
-</LinearLayout>
diff --git a/packages/MediaComponents/res/values/strings.xml b/packages/MediaComponents/res/values/strings.xml
index e93269c..35db0e5 100644
--- a/packages/MediaComponents/res/values/strings.xml
+++ b/packages/MediaComponents/res/values/strings.xml
@@ -84,6 +84,7 @@
 
     <string name="lockscreen_pause_button_content_description">Pause</string>
     <string name="lockscreen_play_button_content_description">Play</string>
+    <string name="lockscreen_replay_button_content_description">Replay</string>
 
     <!-- Text for error alert when a video container is not valid for progressive download/playback. -->
     <string name="VideoView2_error_text_invalid_progressive_playback">This video isn\'t valid for streaming to this device.</string>
diff --git a/packages/MediaComponents/src/com/android/widget/MediaControlView2Impl.java b/packages/MediaComponents/src/com/android/widget/MediaControlView2Impl.java
index 8053245e..317a45f 100644
--- a/packages/MediaComponents/src/com/android/widget/MediaControlView2Impl.java
+++ b/packages/MediaComponents/src/com/android/widget/MediaControlView2Impl.java
@@ -16,144 +16,664 @@
 
 package com.android.widget;
 
+import android.content.res.Resources;
+import android.media.MediaMetadata;
 import android.media.session.MediaController;
+import android.media.session.PlaybackState;
 import android.media.update.MediaControlView2Provider;
 import android.media.update.ViewProvider;
+import android.util.Log;
 import android.view.KeyEvent;
+import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
+import android.view.accessibility.AccessibilityManager;
+import android.widget.ImageButton;
 import android.widget.MediaControlView2;
+import android.widget.ProgressBar;
+import android.widget.SeekBar;
+import android.widget.SeekBar.OnSeekBarChangeListener;
+import android.widget.TextView;
+
+import com.android.media.update.ApiHelper;
+import com.android.media.update.R;
+
+import java.util.Formatter;
+import java.util.Locale;
 
 public class MediaControlView2Impl implements MediaControlView2Provider {
+    private static final String TAG = "MediaControlView2";
+
     private final MediaControlView2 mInstance;
     private final ViewProvider mSuperProvider;
 
     static final String ACTION_SHOW_SUBTITLE = "showSubtitle";
     static final String ACTION_HIDE_SUBTITLE = "hideSubtitle";
 
+    private static final int MAX_PROGRESS = 1000;
+    private static final int DEFAULT_PROGRESS_UPDATE_TIME_MS = 1000;
+    private static final int DEFAULT_TIMEOUT_MS = 2000;
+
+    private static final int REWIND_TIME_MS = 10000;
+    private static final int FORWARD_TIME_MS = 30000;
+
+    private final boolean mUseClosedCaption;
+    private final AccessibilityManager mAccessibilityManager;
+
+    private MediaController mController;
+    private MediaController.TransportControls mControls;
+    private PlaybackState mPlaybackState;
+    private MediaMetadata mMetadata;
+    private ProgressBar mProgress;
+    private TextView mEndTime, mCurrentTime;
+    private int mDuration;
+    private int mPrevState;
+    private boolean mShowing;
+    private boolean mDragging;
+    private boolean mListenersSet;
+    private boolean mCCIsEnabled;
+    private boolean mIsStopped;
+    private View.OnClickListener mNextListener, mPrevListener;
+    private ImageButton mPlayPauseButton;
+    private ImageButton mFfwdButton;
+    private ImageButton mRewButton;
+    private ImageButton mNextButton;
+    private ImageButton mPrevButton;
+    private ImageButton mCCButton;
+    private CharSequence mPlayDescription;
+    private CharSequence mPauseDescription;
+    private CharSequence mReplayDescription;
+
+    private StringBuilder mFormatBuilder;
+    private Formatter mFormatter;
+
     public MediaControlView2Impl(MediaControlView2 instance, ViewProvider superProvider) {
         mInstance = instance;
         mSuperProvider = superProvider;
+        mUseClosedCaption = true;
+        mAccessibilityManager = AccessibilityManager.getInstance(mInstance.getContext());
 
-        // TODO: Implement
+        // Inflate MediaControlView2 from XML
+        View root = makeControllerView();
+        mInstance.addView(root);
     }
 
     @Override
     public void setController_impl(MediaController controller) {
-        // TODO: Implement
+        mController = controller;
+        if (controller != null) {
+            mControls = controller.getTransportControls();
+            // Set mMetadata and mPlaybackState to existing MediaSession variables since they may
+            // be called before the callback is called
+            mPlaybackState = mController.getPlaybackState();
+            mMetadata = mController.getMetadata();
+            updateDuration();
+
+            mController.registerCallback(new MediaControllerCallback());
+        }
     }
 
     @Override
     public void show_impl() {
-        // TODO: Implement
+        mInstance.show(DEFAULT_TIMEOUT_MS);
     }
 
     @Override
     public void show_impl(int timeout) {
-        // TODO: Implement
+        if (!mShowing) {
+            setProgress();
+            if (mPlayPauseButton != null) {
+                mPlayPauseButton.requestFocus();
+            }
+            disableUnsupportedButtons();
+            mInstance.setVisibility(View.VISIBLE);
+            mShowing = true;
+        }
+        // cause the progress bar to be updated even if mShowing
+        // was already true.  This happens, for example, if we're
+        // paused with the progress bar showing the user hits play.
+        mInstance.post(mShowProgress);
+
+        if (timeout != 0 && !mAccessibilityManager.isTouchExplorationEnabled()) {
+            mInstance.removeCallbacks(mFadeOut);
+            mInstance.postDelayed(mFadeOut, timeout);
+        }
     }
 
     @Override
     public boolean isShowing_impl() {
-        // TODO: Implement
-        return false;
+        return mShowing;
     }
 
     @Override
     public void hide_impl() {
-        // TODO: Implement
+        if (mShowing) {
+            try {
+                mInstance.removeCallbacks(mShowProgress);
+                // Remove existing call to mFadeOut to avoid from being called later.
+                mInstance.removeCallbacks(mFadeOut);
+                mInstance.setVisibility(View.GONE);
+            } catch (IllegalArgumentException ex) {
+                Log.w(TAG, "already removed");
+            }
+            mShowing = false;
+        }
     }
 
     @Override
     public void showCCButton_impl() {
-        // TODO: Implement
+        if (mCCButton != null) {
+            mCCButton.setVisibility(View.VISIBLE);
+        }
     }
 
     @Override
     public boolean isPlaying_impl() {
-        // TODO: Implement
+        if (mPlaybackState != null) {
+            return mPlaybackState.getState() == PlaybackState.STATE_PLAYING;
+        }
         return false;
     }
 
     @Override
     public int getCurrentPosition_impl() {
-        // TODO: Implement
+        mPlaybackState = mController.getPlaybackState();
+        if (mPlaybackState != null) {
+            return (int) mPlaybackState.getPosition();
+        }
         return 0;
     }
 
     @Override
     public int getBufferPercentage_impl() {
-        // TODO: Implement
+        if (mDuration == 0) {
+            return 0;
+        }
+        mPlaybackState = mController.getPlaybackState();
+        if (mPlaybackState != null) {
+            return (int) (mPlaybackState.getBufferedPosition() * 100) / mDuration;
+        }
         return 0;
     }
 
     @Override
     public boolean canPause_impl() {
-        // TODO: Implement
-        return false;
+        if (mPlaybackState != null) {
+            return (mPlaybackState.getActions() & PlaybackState.ACTION_PAUSE) != 0;
+        }
+        return true;
     }
 
     @Override
     public boolean canSeekBackward_impl() {
-        // TODO: Implement
-        return false;
+        if (mPlaybackState != null) {
+            return (mPlaybackState.getActions() & PlaybackState.ACTION_REWIND) != 0;
+        }
+        return true;
     }
 
     @Override
     public boolean canSeekForward_impl() {
-        // TODO: Implement
-        return false;
+        if (mPlaybackState != null) {
+            return (mPlaybackState.getActions() & PlaybackState.ACTION_FAST_FORWARD) != 0;
+        }
+        return true;
     }
 
     @Override
     public void showSubtitle_impl() {
-        // TODO: Implement
+        mController.sendCommand(ACTION_SHOW_SUBTITLE, null, null);
     }
 
     @Override
     public void hideSubtitle_impl() {
-        // TODO: Implement
+        mController.sendCommand(ACTION_HIDE_SUBTITLE, null, null);
     }
 
     @Override
     public CharSequence getAccessibilityClassName_impl() {
-        // TODO: Implement
         return MediaControlView2.class.getName();
     }
 
     @Override
     public boolean onTouchEvent_impl(MotionEvent ev) {
-        // TODO: Implement
-        return mSuperProvider.onTouchEvent_impl(ev);
+        return false;
     }
 
+    // TODO: Should this function be removed?
     @Override
     public boolean onTrackballEvent_impl(MotionEvent ev) {
-        // TODO: Implement
-        return mSuperProvider.onTrackballEvent_impl(ev);
+        mInstance.show(DEFAULT_TIMEOUT_MS);
+        return false;
     }
 
     @Override
     public boolean onKeyDown_impl(int keyCode, KeyEvent event) {
-        // TODO: Implement
         return mSuperProvider.onKeyDown_impl(keyCode, event);
     }
 
     @Override
     public void onFinishInflate_impl() {
         mSuperProvider.onFinishInflate_impl();
-        // TODO: Implement
     }
 
     @Override
     public boolean dispatchKeyEvent_impl(KeyEvent event) {
-        // TODO: Implement
+        int keyCode = event.getKeyCode();
+        final boolean uniqueDown = event.getRepeatCount() == 0
+                && event.getAction() == KeyEvent.ACTION_DOWN;
+        if (keyCode == KeyEvent.KEYCODE_HEADSETHOOK
+                || keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE
+                || keyCode == KeyEvent.KEYCODE_SPACE) {
+            if (uniqueDown) {
+                togglePausePlayState();
+                mInstance.show(DEFAULT_TIMEOUT_MS);
+                if (mPlayPauseButton != null) {
+                    mPlayPauseButton.requestFocus();
+                }
+            }
+            return true;
+        } else if (keyCode == KeyEvent.KEYCODE_MEDIA_PLAY) {
+            if (uniqueDown && !mInstance.isPlaying()) {
+                togglePausePlayState();
+                mInstance.show(DEFAULT_TIMEOUT_MS);
+            }
+            return true;
+        } else if (keyCode == KeyEvent.KEYCODE_MEDIA_STOP
+                || keyCode == KeyEvent.KEYCODE_MEDIA_PAUSE) {
+            if (uniqueDown && mInstance.isPlaying()) {
+                togglePausePlayState();
+                mInstance.show(DEFAULT_TIMEOUT_MS);
+            }
+            return true;
+        } else if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN
+                || keyCode == KeyEvent.KEYCODE_VOLUME_UP
+                || keyCode == KeyEvent.KEYCODE_VOLUME_MUTE
+                || keyCode == KeyEvent.KEYCODE_CAMERA) {
+            // don't show the controls for volume adjustment
+            return mSuperProvider.dispatchKeyEvent_impl(event);
+        } else if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_MENU) {
+            if (uniqueDown) {
+                mInstance.hide();
+            }
+            return true;
+        }
+
+        mInstance.show(DEFAULT_TIMEOUT_MS);
         return mSuperProvider.dispatchKeyEvent_impl(event);
     }
 
     @Override
     public void setEnabled_impl(boolean enabled) {
+        if (mPlayPauseButton != null) {
+            mPlayPauseButton.setEnabled(enabled);
+        }
+        if (mFfwdButton != null) {
+            mFfwdButton.setEnabled(enabled);
+        }
+        if (mRewButton != null) {
+            mRewButton.setEnabled(enabled);
+        }
+        if (mNextButton != null) {
+            mNextButton.setEnabled(enabled && mNextListener != null);
+        }
+        if (mPrevButton != null) {
+            mPrevButton.setEnabled(enabled && mPrevListener != null);
+        }
+        if (mProgress != null) {
+            mProgress.setEnabled(enabled);
+        }
+        disableUnsupportedButtons();
         mSuperProvider.setEnabled_impl(enabled);
-        // TODO: Implement
     }
-}
+
+    ///////////////////////////////////////////////////
+    // Protected or private methods
+    ///////////////////////////////////////////////////
+
+    /**
+     * Create the view that holds the widgets that control playback.
+     * Derived classes can override this to create their own.
+     *
+     * @return The controller view.
+     * @hide This doesn't work as advertised
+     */
+    protected View makeControllerView() {
+        View root = LayoutInflater.from(mInstance.getContext()).inflate(
+                R.layout.media_controller, null);
+
+        initControllerView(root);
+
+        return root;
+    }
+
+    private void initControllerView(View v) {
+        Resources res = ApiHelper.getLibResources();
+        mPlayDescription = res.getText(R.string.lockscreen_play_button_content_description);
+        mPauseDescription = res.getText(R.string.lockscreen_pause_button_content_description);
+        mReplayDescription = res.getText(R.string.lockscreen_replay_button_content_description);
+        mPlayPauseButton = v.findViewById(R.id.pause);
+        if (mPlayPauseButton != null) {
+            mPlayPauseButton.requestFocus();
+            mPlayPauseButton.setOnClickListener(mPlayPauseListener);
+        }
+
+        // TODO: make the following buttons visible based upon whether they are supported for
+        // individual media files
+        mFfwdButton = v.findViewById(R.id.ffwd);
+        if (mFfwdButton != null) {
+            mFfwdButton.setOnClickListener(mFfwdListener);
+            mFfwdButton.setVisibility(View.GONE);
+        }
+        mRewButton = v.findViewById(R.id.rew);
+        if (mRewButton != null) {
+            mRewButton.setOnClickListener(mRewListener);
+            mRewButton.setVisibility(View.GONE);
+        }
+        mNextButton = v.findViewById(R.id.next);
+        if (mNextButton != null && !mListenersSet) {
+            mNextButton.setVisibility(View.GONE);
+        }
+        mPrevButton = v.findViewById(R.id.prev);
+        if (mPrevButton != null && !mListenersSet) {
+            mPrevButton.setVisibility(View.GONE);
+        }
+
+        // TODO: make CC button visible if the media file has a subtitle track
+        mCCButton = v.findViewById(R.id.cc);
+        if (mCCButton != null) {
+            mCCButton.setOnClickListener(mCCListener);
+            mCCButton.setVisibility(mUseClosedCaption ? View.VISIBLE : View.GONE);
+        }
+
+        mProgress =
+                v.findViewById(R.id.mediacontroller_progress);
+        if (mProgress != null) {
+            if (mProgress instanceof SeekBar) {
+                SeekBar seeker = (SeekBar) mProgress;
+                seeker.setOnSeekBarChangeListener(mSeekListener);
+            }
+            mProgress.setMax(MAX_PROGRESS);
+        }
+
+        mEndTime = v.findViewById(R.id.time);
+        mCurrentTime = v.findViewById(R.id.time_current);
+        mFormatBuilder = new StringBuilder();
+        mFormatter = new Formatter(mFormatBuilder, Locale.getDefault());
+
+        installPrevNextListeners();
+    }
+
+    /**
+     * Disable pause or seek buttons if the stream cannot be paused or seeked.
+     * This requires the control interface to be a MediaPlayerControlExt
+     */
+    private void disableUnsupportedButtons() {
+        try {
+            if (mPlayPauseButton != null && !mInstance.canPause()) {
+                mPlayPauseButton.setEnabled(false);
+            }
+            if (mRewButton != null && !mInstance.canSeekBackward()) {
+                mRewButton.setEnabled(false);
+            }
+            if (mFfwdButton != null && !mInstance.canSeekForward()) {
+                mFfwdButton.setEnabled(false);
+            }
+            // TODO What we really should do is add a canSeek to the MediaPlayerControl interface;
+            // this scheme can break the case when applications want to allow seek through the
+            // progress bar but disable forward/backward buttons.
+            //
+            // However, currently the flags SEEK_BACKWARD_AVAILABLE, SEEK_FORWARD_AVAILABLE,
+            // and SEEK_AVAILABLE are all (un)set together; as such the aforementioned issue
+            // shouldn't arise in existing applications.
+            if (mProgress != null && !mInstance.canSeekBackward() && !mInstance.canSeekForward()) {
+                mProgress.setEnabled(false);
+            }
+        } catch (IncompatibleClassChangeError ex) {
+            // We were given an old version of the interface, that doesn't have
+            // the canPause/canSeekXYZ methods. This is OK, it just means we
+            // assume the media can be paused and seeked, and so we don't disable
+            // the buttons.
+        }
+    }
+
+    private final Runnable mFadeOut = new Runnable() {
+        @Override
+        public void run() {
+            mInstance.hide();
+        }
+    };
+
+    private final Runnable mShowProgress = new Runnable() {
+        @Override
+        public void run() {
+            int pos = setProgress();
+            if (!mDragging && mShowing && mInstance.isPlaying()) {
+                mInstance.postDelayed(mShowProgress,
+                        DEFAULT_PROGRESS_UPDATE_TIME_MS - (pos % DEFAULT_PROGRESS_UPDATE_TIME_MS));
+            }
+        }
+    };
+
+    private String stringForTime(int timeMs) {
+        int totalSeconds = timeMs / 1000;
+
+        int seconds = totalSeconds % 60;
+        int minutes = (totalSeconds / 60) % 60;
+        int hours = totalSeconds / 3600;
+
+        mFormatBuilder.setLength(0);
+        if (hours > 0) {
+            return mFormatter.format("%d:%02d:%02d", hours, minutes, seconds).toString();
+        } else {
+            return mFormatter.format("%02d:%02d", minutes, seconds).toString();
+        }
+    }
+
+    private int setProgress() {
+        if (mController == null || mDragging) {
+            return 0;
+        }
+        int positionOnProgressBar = 0;
+        int currentPosition = mInstance.getCurrentPosition();
+        if (mDuration > 0) {
+            positionOnProgressBar = (int) (MAX_PROGRESS * (long) currentPosition / mDuration);
+        }
+        if (mProgress != null && currentPosition != mDuration) {
+            mProgress.setProgress(positionOnProgressBar);
+            mProgress.setSecondaryProgress(mInstance.getBufferPercentage() * 10);
+        }
+
+        if (mEndTime != null) {
+            mEndTime.setText(stringForTime(mDuration));
+
+        }
+        if (mCurrentTime != null) {
+            mCurrentTime.setText(stringForTime(currentPosition));
+        }
+
+        return currentPosition;
+    }
+
+    private void togglePausePlayState() {
+        if (mInstance.isPlaying()) {
+            mControls.pause();
+            mPlayPauseButton.setImageResource(R.drawable.ic_play_circle_filled);
+            mPlayPauseButton.setContentDescription(mPlayDescription);
+        } else {
+            mControls.play();
+            mPlayPauseButton.setImageResource(R.drawable.ic_pause_circle_filled);
+            mPlayPauseButton.setContentDescription(mPauseDescription);
+        }
+    }
+
+    // There are two scenarios that can trigger the seekbar listener to trigger:
+    //
+    // The first is the user using the touchpad to adjust the posititon of the
+    // seekbar's thumb. In this case onStartTrackingTouch is called followed by
+    // a number of onProgressChanged notifications, concluded by onStopTrackingTouch.
+    // We're setting the field "mDragging" to true for the duration of the dragging
+    // session to avoid jumps in the position in case of ongoing playback.
+    //
+    // The second scenario involves the user operating the scroll ball, in this
+    // case there WON'T BE onStartTrackingTouch/onStopTrackingTouch notifications,
+    // we will simply apply the updated position without suspending regular updates.
+    private final OnSeekBarChangeListener mSeekListener = new OnSeekBarChangeListener() {
+        @Override
+        public void onStartTrackingTouch(SeekBar bar) {
+            mInstance.show(3600000);
+
+            mDragging = true;
+
+            // By removing these pending progress messages we make sure
+            // that a) we won't update the progress while the user adjusts
+            // the seekbar and b) once the user is done dragging the thumb
+            // we will post one of these messages to the queue again and
+            // this ensures that there will be exactly one message queued up.
+            mInstance.removeCallbacks(mShowProgress);
+
+            // Check if playback is currently stopped. In this case, update the pause button to show
+            // the play image instead of the replay image.
+            if (mIsStopped) {
+                mPlayPauseButton.setImageResource(R.drawable.ic_play_circle_filled);
+                mPlayPauseButton.setContentDescription(mPlayDescription);
+                mIsStopped = false;
+            }
+        }
+
+        @Override
+        public void onProgressChanged(SeekBar bar, int progress, boolean fromUser) {
+            if (!fromUser) {
+                // We're not interested in programmatically generated changes to
+                // the progress bar's position.
+                return;
+            }
+            if (mDuration > 0) {
+                int newPosition = (int) (((long) mDuration * progress) / MAX_PROGRESS);
+                mControls.seekTo(newPosition);
+
+                if (mCurrentTime != null) {
+                    mCurrentTime.setText(stringForTime(newPosition));
+                }
+            }
+        }
+
+        @Override
+        public void onStopTrackingTouch(SeekBar bar) {
+            mDragging = false;
+
+            setProgress();
+            mInstance.show(DEFAULT_TIMEOUT_MS);
+
+            // Ensure that progress is properly updated in the future,
+            // the call to show() does not guarantee this because it is a
+            // no-op if we are already showing.
+            mInstance.post(mShowProgress);
+        }
+    };
+
+    private final View.OnClickListener mPlayPauseListener = new View.OnClickListener() {
+        @Override
+        public void onClick(View v) {
+            togglePausePlayState();
+            mInstance.show(DEFAULT_TIMEOUT_MS);
+        }
+    };
+
+    private final View.OnClickListener mRewListener = new View.OnClickListener() {
+        @Override
+        public void onClick(View v) {
+            int pos = mInstance.getCurrentPosition() - REWIND_TIME_MS;
+            mControls.seekTo(pos);
+            setProgress();
+
+            mInstance.show(DEFAULT_TIMEOUT_MS);
+        }
+    };
+
+    private final View.OnClickListener mFfwdListener = new View.OnClickListener() {
+        @Override
+        public void onClick(View v) {
+            int pos = mInstance.getCurrentPosition() + FORWARD_TIME_MS;
+            mControls.seekTo(pos);
+            setProgress();
+
+            mInstance.show(DEFAULT_TIMEOUT_MS);
+        }
+    };
+
+    private final View.OnClickListener mCCListener = new View.OnClickListener() {
+        @Override
+        public void onClick(View v) {
+            if (!mCCIsEnabled) {
+                mCCButton.setImageResource(R.drawable.ic_media_cc_enabled);
+                mCCIsEnabled = true;
+                mInstance.showSubtitle();
+            } else {
+                mCCButton.setImageResource(R.drawable.ic_media_cc_disabled);
+                mCCIsEnabled = false;
+                mInstance.hideSubtitle();
+            }
+        }
+    };
+
+    private void installPrevNextListeners() {
+        if (mNextButton != null) {
+            mNextButton.setOnClickListener(mNextListener);
+            mNextButton.setEnabled(mNextListener != null);
+        }
+
+        if (mPrevButton != null) {
+            mPrevButton.setOnClickListener(mPrevListener);
+            mPrevButton.setEnabled(mPrevListener != null);
+        }
+    }
+
+    private void updateDuration() {
+        if (mMetadata != null) {
+            if (mMetadata.containsKey(MediaMetadata.METADATA_KEY_DURATION)) {
+                mDuration = (int) mMetadata.getLong(MediaMetadata.METADATA_KEY_DURATION);
+                // update progress bar
+                setProgress();
+            }
+        }
+    }
+
+    private class MediaControllerCallback extends MediaController.Callback {
+        @Override
+        public void onPlaybackStateChanged(PlaybackState state) {
+            mPlaybackState = state;
+
+            // Update pause button depending on playback state for the following two reasons:
+            //   1) Need to handle case where app customizes playback state behavior when app
+            //      activity is resumed.
+            //   2) Need to handle case where the media file reaches end of duration.
+            if (mPlaybackState.getState() != mPrevState) {
+                switch (mPlaybackState.getState()) {
+                    case PlaybackState.STATE_PLAYING:
+                        mPlayPauseButton.setImageResource(R.drawable.ic_pause_circle_filled);
+                        mPlayPauseButton.setContentDescription(mPauseDescription);
+                        break;
+                    case PlaybackState.STATE_PAUSED:
+                        mPlayPauseButton.setImageResource(R.drawable.ic_play_circle_filled);
+                        mPlayPauseButton.setContentDescription(mPlayDescription);
+                        break;
+                    case PlaybackState.STATE_STOPPED:
+                        mPlayPauseButton.setImageResource(R.drawable.ic_replay);
+                        mPlayPauseButton.setContentDescription(mReplayDescription);
+                        mIsStopped = true;
+                        break;
+                    default:
+                        break;
+                }
+                mPrevState = mPlaybackState.getState();
+            }
+        }
+
+        @Override
+        public void onMetadataChanged(MediaMetadata metadata) {
+            mMetadata = metadata;
+            updateDuration();
+        }
+    }
+}
\ No newline at end of file
diff --git a/services/audioflinger/AudioFlinger.cpp b/services/audioflinger/AudioFlinger.cpp
index 2a2f6fc..a9d0054 100644
--- a/services/audioflinger/AudioFlinger.cpp
+++ b/services/audioflinger/AudioFlinger.cpp
@@ -993,6 +993,21 @@
     return mute;
 }
 
+void AudioFlinger::setRecordSilenced(uid_t uid, bool silenced)
+{
+    ALOGV("AudioFlinger::setRecordSilenced(uid:%d, silenced:%d)", uid, silenced);
+
+    // TODO: Notify MmapThreads
+
+    AutoMutex lock(mLock);
+    for (size_t i = 0; i < mRecordThreads.size(); i++) {
+        sp<RecordThread> thread = mRecordThreads.valueAt(i);
+        if (thread != 0) {
+            thread->setRecordSilenced(uid, silenced);
+        }
+    }
+}
+
 status_t AudioFlinger::setMasterMute(bool muted)
 {
     status_t ret = initCheck();
diff --git a/services/audioflinger/AudioFlinger.h b/services/audioflinger/AudioFlinger.h
index 5a64f0b..a1c3f36 100644
--- a/services/audioflinger/AudioFlinger.h
+++ b/services/audioflinger/AudioFlinger.h
@@ -147,6 +147,8 @@
     virtual     status_t    setMicMute(bool state);
     virtual     bool        getMicMute() const;
 
+    virtual     void        setRecordSilenced(uid_t uid, bool silenced);
+
     virtual     status_t    setParameters(audio_io_handle_t ioHandle, const String8& keyValuePairs);
     virtual     String8     getParameters(audio_io_handle_t ioHandle, const String8& keys) const;
 
diff --git a/services/audioflinger/RecordTracks.h b/services/audioflinger/RecordTracks.h
index f8da780..63a3d98 100644
--- a/services/audioflinger/RecordTracks.h
+++ b/services/audioflinger/RecordTracks.h
@@ -63,6 +63,9 @@
 
     virtual bool        isFastTrack() const { return (mFlags & AUDIO_INPUT_FLAG_FAST) != 0; }
 
+            void        setSilenced(bool silenced) { mSilenced = silenced; }
+            bool        isSilenced() const { return mSilenced; }
+
 private:
     friend class AudioFlinger;  // for mState
 
@@ -91,6 +94,8 @@
             // used by the record thread to convert frames to proper destination format
             RecordBufferConverter              *mRecordBufferConverter;
             audio_input_flags_t                mFlags;
+
+            bool                               mSilenced;
 };
 
 // playback track, used by PatchPanel
diff --git a/services/audioflinger/Threads.cpp b/services/audioflinger/Threads.cpp
index 3a41ac8..8bf50b1 100644
--- a/services/audioflinger/Threads.cpp
+++ b/services/audioflinger/Threads.cpp
@@ -6532,6 +6532,7 @@
         rear = mRsmpInRear += framesRead;
 
         size = activeTracks.size();
+
         // loop over each active track
         for (size_t i = 0; i < size; i++) {
             activeTrack = activeTracks[i];
@@ -6588,6 +6589,11 @@
                 if (activeTrack->mFramesToDrop == 0) {
                     if (framesOut > 0) {
                         activeTrack->mSink.frameCount = framesOut;
+                        // Sanitize before releasing if the track has no access to the source data
+                        // An idle UID receives silence from non virtual devices until active
+                        if (activeTrack->isSilenced()) {
+                            memset(activeTrack->mSink.raw, 0, framesOut * mFrameSize);
+                        }
                         activeTrack->releaseBuffer(&activeTrack->mSink);
                     }
                 } else {
@@ -6927,7 +6933,9 @@
         status_t status = NO_ERROR;
         if (recordTrack->isExternalTrack()) {
             mLock.unlock();
-            status = AudioSystem::startInput(mId, recordTrack->sessionId());
+            bool silenced;
+            status = AudioSystem::startInput(mId, recordTrack->sessionId(),
+                    mInDevice, recordTrack->uid(), &silenced);
             mLock.lock();
             // FIXME should verify that recordTrack is still in mActiveTracks
             if (status != NO_ERROR) {
@@ -6936,6 +6944,7 @@
                 ALOGV("RecordThread::start error %d", status);
                 return status;
             }
+            recordTrack->setSilenced(silenced);
         }
         // Catch up with current buffer indices if thread is already running.
         // This is what makes a new client discard all buffered data.  If the track's mRsmpInFront
@@ -7139,6 +7148,16 @@
     write(fd, result.string(), result.size());
 }
 
+void AudioFlinger::RecordThread::setRecordSilenced(uid_t uid, bool silenced)
+{
+    Mutex::Autolock _l(mLock);
+    for (size_t i = 0; i < mTracks.size() ; i++) {
+        sp<RecordTrack> track = mTracks[i];
+        if (track != 0 && track->uid() == uid) {
+            track->setSilenced(silenced);
+        }
+    }
+}
 
 void AudioFlinger::RecordThread::ResamplerBufferProvider::reset()
 {
@@ -7831,7 +7850,9 @@
     if (isOutput()) {
         ret = AudioSystem::startOutput(mId, streamType(), mSessionId);
     } else {
-        ret = AudioSystem::startInput(mId, mSessionId);
+        // TODO: Block recording for idle UIDs (b/72134552)
+        bool silenced;
+        ret = AudioSystem::startInput(mId, mSessionId, mInDevice, client.clientUid, &silenced);
     }
 
     // abort if start is rejected by audio policy manager
diff --git a/services/audioflinger/Threads.h b/services/audioflinger/Threads.h
index 17f26c5..41d87a4 100644
--- a/services/audioflinger/Threads.h
+++ b/services/audioflinger/Threads.h
@@ -1396,6 +1396,9 @@
 
             void        checkBtNrec();
 
+            // Sets the UID records silence
+            void        setRecordSilenced(uid_t uid, bool silenced);
+
 private:
             // Enter standby if not already in standby, and set mStandby flag
             void    standbyIfNotAlreadyInStandby();
diff --git a/services/audiopolicy/AudioPolicyInterface.h b/services/audiopolicy/AudioPolicyInterface.h
index f2cb25f..2a8f54d 100644
--- a/services/audiopolicy/AudioPolicyInterface.h
+++ b/services/audiopolicy/AudioPolicyInterface.h
@@ -65,15 +65,15 @@
         API_INPUT_TELEPHONY_RX, // used for capture from telephony RX path
     } input_type_t;
 
-   enum {
+    enum {
         API_INPUT_CONCURRENCY_NONE = 0,
         API_INPUT_CONCURRENCY_CALL = (1 << 0),      // Concurrency with a call
         API_INPUT_CONCURRENCY_CAPTURE = (1 << 1),   // Concurrency with another capture
 
         API_INPUT_CONCURRENCY_ALL = (API_INPUT_CONCURRENCY_CALL | API_INPUT_CONCURRENCY_CAPTURE),
-   };
+    };
 
-   typedef uint32_t concurrency_type__mask_t;
+    typedef uint32_t concurrency_type__mask_t;
 
 public:
     virtual ~AudioPolicyInterface() {}
@@ -145,6 +145,7 @@
     // indicates to the audio policy manager that the input starts being used.
     virtual status_t startInput(audio_io_handle_t input,
                                 audio_session_t session,
+                                bool silenced,
                                 concurrency_type__mask_t *concurrency) = 0;
     // indicates to the audio policy manager that the input stops being used.
     virtual status_t stopInput(audio_io_handle_t input,
@@ -239,6 +240,8 @@
 
     virtual float    getStreamVolumeDB(
                 audio_stream_type_t stream, int index, audio_devices_t device) = 0;
+
+    virtual void     setRecordSilenced(uid_t uid, bool silenced);
 };
 
 
diff --git a/services/audiopolicy/common/managerdefinitions/include/AudioSession.h b/services/audiopolicy/common/managerdefinitions/include/AudioSession.h
index 0d19373..dd5247d 100644
--- a/services/audiopolicy/common/managerdefinitions/include/AudioSession.h
+++ b/services/audiopolicy/common/managerdefinitions/include/AudioSession.h
@@ -55,6 +55,8 @@
     void setUid(uid_t uid) { mRecordClientInfo.uid = uid; }
     bool matches(const sp<AudioSession> &other) const;
     bool isSoundTrigger() const { return mIsSoundTrigger; }
+    void setSilenced(bool silenced) { mSilenced = silenced; }
+    bool isSilenced() const { return mSilenced; }
     uint32_t openCount() const { return mOpenCount; } ;
     uint32_t activeCount() const { return mActiveCount; } ;
 
@@ -70,6 +72,7 @@
     const struct audio_config_base mConfig;
     const audio_input_flags_t mFlags;
     bool  mIsSoundTrigger;
+    bool mSilenced;
     uint32_t  mOpenCount;
     uint32_t  mActiveCount;
     AudioMix* mPolicyMix; // non NULL when used by a dynamic policy
diff --git a/services/audiopolicy/managerdefault/AudioPolicyManager.cpp b/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
index 33f31e0..78a02b2 100644
--- a/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
+++ b/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
@@ -1801,10 +1801,15 @@
 
 status_t AudioPolicyManager::startInput(audio_io_handle_t input,
                                         audio_session_t session,
+                                        bool silenced,
                                         concurrency_type__mask_t *concurrency)
 {
-    ALOGV("startInput() input %d", input);
+
+    ALOGV("AudioPolicyManager::startInput(input:%d, session:%d, silenced:%d, concurrency:%d)",
+            input, session, silenced, *concurrency);
+
     *concurrency = API_INPUT_CONCURRENCY_NONE;
+
     ssize_t index = mInputs.indexOfKey(input);
     if (index < 0) {
         ALOGW("startInput() unknown input %d", input);
@@ -1839,12 +1844,33 @@
             return INVALID_OPERATION;
         }
 
-        Vector< sp<AudioInputDescriptor> > activeInputs = mInputs.getActiveInputs();
-        for (const auto& activeDesc : activeInputs) {
-            if (is_virtual_input_device(activeDesc->mDevice)) {
-                continue;
-            }
+        Vector<sp<AudioInputDescriptor>> activeInputs = mInputs.getActiveInputs();
 
+        // If a UID is idle and records silence and another not silenced recording starts
+        // from another UID (idle or active) we stop the current idle UID recording in
+        // favor of the new one - "There can be only one" TM
+        if (!silenced) {
+            for (const auto& activeDesc : activeInputs) {
+                if ((audioSession->flags() & AUDIO_INPUT_FLAG_MMAP_NOIRQ) != 0 &&
+                        activeDesc->getId() == inputDesc->getId()) {
+                     continue;
+                }
+
+                AudioSessionCollection activeSessions = activeDesc->getAudioSessions(
+                        true /*activeOnly*/);
+                sp<AudioSession> activeSession = activeSessions.valueAt(0);
+                if (activeSession->isSilenced()) {
+                    audio_io_handle_t activeInput = activeDesc->mIoHandle;
+                    audio_session_t activeSessionId = activeSession->session();
+                    stopInput(activeInput, activeSessionId);
+                    releaseInput(activeInput, activeSessionId);
+                    ALOGV("startInput(%d) stopping silenced input %d", input, activeInput);
+                    activeInputs = mInputs.getActiveInputs();
+                }
+            }
+        }
+
+        for (const auto& activeDesc : activeInputs) {
             if ((audioSession->flags() & AUDIO_INPUT_FLAG_MMAP_NOIRQ) != 0 &&
                     activeDesc->getId() == inputDesc->getId()) {
                 continue;
@@ -1881,10 +1907,6 @@
 
         // if capture is allowed, preempt currently active HOTWORD captures
         for (const auto& activeDesc : activeInputs) {
-            if (is_virtual_input_device(activeDesc->mDevice)) {
-                continue;
-            }
-
             if (allowConcurrentWithSoundTrigger && activeDesc->isSoundTrigger()) {
                 continue;
             }
@@ -1907,6 +1929,9 @@
     }
 #endif
 
+    // Make sure we start with the correct silence state
+    audioSession->setSilenced(silenced);
+
     // increment activity count before calling getNewInputDevice() below as only active sessions
     // are considered for device selection
     audioSession->changeActiveCount(1);
@@ -2039,7 +2064,6 @@
 void AudioPolicyManager::releaseInput(audio_io_handle_t input,
                                       audio_session_t session)
 {
-
     ALOGV("releaseInput() %d", input);
     ssize_t index = mInputs.indexOfKey(input);
     if (index < 0) {
@@ -3394,6 +3418,23 @@
     return computeVolume(stream, index, device);
 }
 
+void AudioPolicyManager::setRecordSilenced(uid_t uid, bool silenced)
+{
+    ALOGV("AudioPolicyManager:setRecordSilenced(uid:%d, silenced:%d)", uid, silenced);
+
+    Vector<sp<AudioInputDescriptor> > activeInputs = mInputs.getActiveInputs();
+    for (size_t i = 0; i < activeInputs.size(); i++) {
+        sp<AudioInputDescriptor> activeDesc = activeInputs[i];
+        AudioSessionCollection activeSessions = activeDesc->getAudioSessions(true);
+        for (size_t j = 0; j < activeSessions.size(); j++) {
+            sp<AudioSession> activeSession = activeSessions.valueAt(j);
+            if (activeSession->uid() == uid) {
+                activeSession->setSilenced(silenced);
+            }
+        }
+    }
+}
+
 status_t AudioPolicyManager::disconnectAudioSource(const sp<AudioSourceDescriptor>& sourceDesc)
 {
     ALOGV("%s handle %d", __FUNCTION__, sourceDesc->getHandle());
diff --git a/services/audiopolicy/managerdefault/AudioPolicyManager.h b/services/audiopolicy/managerdefault/AudioPolicyManager.h
index 611edec..4fd73e6 100644
--- a/services/audiopolicy/managerdefault/AudioPolicyManager.h
+++ b/services/audiopolicy/managerdefault/AudioPolicyManager.h
@@ -135,6 +135,7 @@
         // indicates to the audio policy manager that the input starts being used.
         virtual status_t startInput(audio_io_handle_t input,
                                     audio_session_t session,
+                                    bool silenced,
                                     concurrency_type__mask_t *concurrency);
 
         // indicates to the audio policy manager that the input stops being used.
@@ -235,6 +236,8 @@
         // return the strategy corresponding to a given stream type
         routing_strategy getStrategy(audio_stream_type_t stream) const;
 
+        virtual void setRecordSilenced(uid_t uid, bool silenced);
+
 protected:
         // A constructor that allows more fine-grained control over initialization process,
         // used in automatic tests.
diff --git a/services/audiopolicy/service/AudioPolicyInterfaceImpl.cpp b/services/audiopolicy/service/AudioPolicyInterfaceImpl.cpp
index 7dd6d70..680d7b9 100644
--- a/services/audiopolicy/service/AudioPolicyInterfaceImpl.cpp
+++ b/services/audiopolicy/service/AudioPolicyInterfaceImpl.cpp
@@ -362,14 +362,22 @@
 }
 
 status_t AudioPolicyService::startInput(audio_io_handle_t input,
-                                        audio_session_t session)
+                                        audio_session_t session,
+                                        audio_devices_t device,
+                                        uid_t uid,
+                                        bool *silenced)
 {
+    // If UID inactive it records silence until becoming active
+    *silenced = !mUidPolicy->isUidActive(uid) && !is_virtual_input_device(device);
+
     if (mAudioPolicyManager == NULL) {
         return NO_INIT;
     }
+
     Mutex::Autolock _l(mLock);
-    AudioPolicyInterface::concurrency_type__mask_t concurrency;
-    status_t status = mAudioPolicyManager->startInput(input, session, &concurrency);
+    AudioPolicyInterface::concurrency_type__mask_t concurrency =
+            AudioPolicyInterface::API_INPUT_CONCURRENCY_NONE;
+    status_t status = mAudioPolicyManager->startInput(input, session, *silenced, &concurrency);
 
     if (status == NO_ERROR) {
         LOG_ALWAYS_FATAL_IF(concurrency & ~AudioPolicyInterface::API_INPUT_CONCURRENCY_ALL,
diff --git a/services/audiopolicy/service/AudioPolicyService.cpp b/services/audiopolicy/service/AudioPolicyService.cpp
index af0c823..e5aed9a 100644
--- a/services/audiopolicy/service/AudioPolicyService.cpp
+++ b/services/audiopolicy/service/AudioPolicyService.cpp
@@ -28,6 +28,9 @@
 #include <utils/Log.h>
 #include <cutils/properties.h>
 #include <binder/IPCThreadState.h>
+#include <binder/ActivityManager.h>
+#include <binder/PermissionController.h>
+#include <binder/IResultReceiver.h>
 #include <utils/String16.h>
 #include <utils/threads.h>
 #include "AudioPolicyService.h"
@@ -39,6 +42,8 @@
 #include <system/audio.h>
 #include <system/audio_policy.h>
 
+#include <private/android_filesystem_config.h>
+
 namespace android {
 
 static const char kDeadlockedString[] = "AudioPolicyService may be deadlocked\n";
@@ -49,6 +54,7 @@
 
 static const nsecs_t kAudioCommandTimeoutNs = seconds(3); // 3 seconds
 
+static const String16 sManageAudioPolicyPermission("android.permission.MANAGE_AUDIO_POLICY");
 
 // ----------------------------------------------------------------------------
 
@@ -79,6 +85,9 @@
         Mutex::Autolock _l(mLock);
         mAudioPolicyEffects = audioPolicyEffects;
     }
+
+    mUidPolicy = new UidPolicy(this);
+    mUidPolicy->registerSelf();
 }
 
 AudioPolicyService::~AudioPolicyService()
@@ -92,6 +101,9 @@
 
     mNotificationClients.clear();
     mAudioPolicyEffects.clear();
+
+    mUidPolicy->unregisterSelf();
+    mUidPolicy.clear();
 }
 
 // A notification client is always registered by AudioSystem when the client process
@@ -318,6 +330,20 @@
     return NO_ERROR;
 }
 
+void AudioPolicyService::setRecordSilenced(uid_t uid, bool silenced)
+{
+    {
+        Mutex::Autolock _l(mLock);
+        if (mAudioPolicyManager) {
+            mAudioPolicyManager->setRecordSilenced(uid, silenced);
+        }
+    }
+    sp<IAudioFlinger> af = AudioSystem::get_audio_flinger();
+    if (af) {
+        af->setRecordSilenced(uid, silenced);
+    }
+}
+
 status_t AudioPolicyService::dump(int fd, const Vector<String16>& args __unused)
 {
     if (!dumpAllowed()) {
@@ -361,11 +387,210 @@
 }
 
 status_t AudioPolicyService::onTransact(
-        uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
-{
+        uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) {
+    switch (code) {
+        case SHELL_COMMAND_TRANSACTION: {
+            int in = data.readFileDescriptor();
+            int out = data.readFileDescriptor();
+            int err = data.readFileDescriptor();
+            int argc = data.readInt32();
+            Vector<String16> args;
+            for (int i = 0; i < argc && data.dataAvail() > 0; i++) {
+               args.add(data.readString16());
+            }
+            sp<IBinder> unusedCallback;
+            sp<IResultReceiver> resultReceiver;
+            status_t status;
+            if ((status = data.readNullableStrongBinder(&unusedCallback)) != NO_ERROR) {
+                return status;
+            }
+            if ((status = data.readNullableStrongBinder(&resultReceiver)) != NO_ERROR) {
+                return status;
+            }
+            status = shellCommand(in, out, err, args);
+            if (resultReceiver != nullptr) {
+                resultReceiver->send(status);
+            }
+            return NO_ERROR;
+        }
+    }
+
     return BnAudioPolicyService::onTransact(code, data, reply, flags);
 }
 
+// ------------------- Shell command implementation -------------------
+
+// NOTE: This is a remote API - make sure all args are validated
+status_t AudioPolicyService::shellCommand(int in, int out, int err, Vector<String16>& args) {
+    if (!checkCallingPermission(sManageAudioPolicyPermission, nullptr, nullptr)) {
+        return PERMISSION_DENIED;
+    }
+    if (in == BAD_TYPE || out == BAD_TYPE || err == BAD_TYPE) {
+        return BAD_VALUE;
+    }
+    if (args.size() == 3 && args[0] == String16("set-uid-state")) {
+        return handleSetUidState(args, err);
+    } else if (args.size() == 2 && args[0] == String16("reset-uid-state")) {
+        return handleResetUidState(args, err);
+    } else if (args.size() == 2 && args[0] == String16("get-uid-state")) {
+        return handleGetUidState(args, out, err);
+    } else if (args.size() == 1 && args[0] == String16("help")) {
+        printHelp(out);
+        return NO_ERROR;
+    }
+    printHelp(err);
+    return BAD_VALUE;
+}
+
+status_t AudioPolicyService::handleSetUidState(Vector<String16>& args, int err) {
+    PermissionController pc;
+    int uid = pc.getPackageUid(args[1], 0);
+    if (uid <= 0) {
+        ALOGE("Unknown package: '%s'", String8(args[1]).string());
+        dprintf(err, "Unknown package: '%s'\n", String8(args[1]).string());
+        return BAD_VALUE;
+    }
+    bool active = false;
+    if (args[2] == String16("active")) {
+        active = true;
+    } else if ((args[2] != String16("idle"))) {
+        ALOGE("Expected active or idle but got: '%s'", String8(args[2]).string());
+        return BAD_VALUE;
+    }
+    mUidPolicy->addOverrideUid(uid, active);
+    return NO_ERROR;
+}
+
+status_t AudioPolicyService::handleResetUidState(Vector<String16>& args, int err) {
+    PermissionController pc;
+    int uid = pc.getPackageUid(args[1], 0);
+    if (uid < 0) {
+        ALOGE("Unknown package: '%s'", String8(args[1]).string());
+        dprintf(err, "Unknown package: '%s'\n", String8(args[1]).string());
+        return BAD_VALUE;
+    }
+    mUidPolicy->removeOverrideUid(uid);
+    return NO_ERROR;
+}
+
+status_t AudioPolicyService::handleGetUidState(Vector<String16>& args, int out, int err) {
+    PermissionController pc;
+    int uid = pc.getPackageUid(args[1], 0);
+    if (uid < 0) {
+        ALOGE("Unknown package: '%s'", String8(args[1]).string());
+        dprintf(err, "Unknown package: '%s'\n", String8(args[1]).string());
+        return BAD_VALUE;
+    }
+    if (mUidPolicy->isUidActive(uid)) {
+        return dprintf(out, "active\n");
+    } else {
+        return dprintf(out, "idle\n");
+    }
+}
+
+status_t AudioPolicyService::printHelp(int out) {
+    return dprintf(out, "Audio policy service commands:\n"
+        "  get-uid-state <PACKAGE> gets the uid state\n"
+        "  set-uid-state <PACKAGE> <active|idle> overrides the uid state\n"
+        "  reset-uid-state <PACKAGE> clears the uid state override\n"
+        "  help print this message\n");
+}
+
+// -----------  AudioPolicyService::UidPolicy implementation ----------
+
+void AudioPolicyService::UidPolicy::registerSelf() {
+    ActivityManager am;
+    am.registerUidObserver(this, ActivityManager::UID_OBSERVER_GONE
+            | ActivityManager::UID_OBSERVER_IDLE
+            | ActivityManager::UID_OBSERVER_ACTIVE,
+            ActivityManager::PROCESS_STATE_UNKNOWN,
+            String16("audioserver"));
+}
+
+void AudioPolicyService::UidPolicy::unregisterSelf() {
+    ActivityManager am;
+    am.unregisterUidObserver(this);
+}
+
+void AudioPolicyService::UidPolicy::onUidGone(uid_t uid, __unused bool disabled) {
+    onUidIdle(uid, disabled);
+}
+
+void AudioPolicyService::UidPolicy::onUidActive(uid_t uid) {
+    {
+        Mutex::Autolock _l(mUidLock);
+        mActiveUids.insert(uid);
+    }
+    sp<AudioPolicyService> service = mService.promote();
+    if (service != nullptr) {
+        service->setRecordSilenced(uid, false);
+    }
+}
+
+void AudioPolicyService::UidPolicy::onUidIdle(uid_t uid, __unused bool disabled) {
+    bool deleted = false;
+    {
+        Mutex::Autolock _l(mUidLock);
+        if (mActiveUids.erase(uid) > 0) {
+            deleted = true;
+        }
+    }
+    if (deleted) {
+        sp<AudioPolicyService> service = mService.promote();
+        if (service != nullptr) {
+            service->setRecordSilenced(uid, true);
+        }
+    }
+}
+
+void AudioPolicyService::UidPolicy::addOverrideUid(uid_t uid, bool active) {
+    updateOverrideUid(uid, active, true);
+}
+
+void AudioPolicyService::UidPolicy::removeOverrideUid(uid_t uid) {
+    updateOverrideUid(uid, false, false);
+}
+
+void AudioPolicyService::UidPolicy::updateOverrideUid(uid_t uid, bool active, bool insert) {
+    bool wasActive = false;
+    bool isActive = false;
+    {
+        Mutex::Autolock _l(mUidLock);
+        wasActive = isUidActiveLocked(uid);
+        mOverrideUids.erase(uid);
+        if (insert) {
+            mOverrideUids.insert(std::pair<uid_t, bool>(uid, active));
+        }
+        isActive = isUidActiveLocked(uid);
+    }
+    if (wasActive != isActive) {
+        sp<AudioPolicyService> service = mService.promote();
+        if (service != nullptr) {
+            service->setRecordSilenced(uid, !isActive);
+        }
+    }
+}
+
+bool AudioPolicyService::UidPolicy::isUidActive(uid_t uid) {
+    // Non-app UIDs are considered always active
+    if (uid < FIRST_APPLICATION_UID) {
+        return true;
+    }
+    Mutex::Autolock _l(mUidLock);
+    return isUidActiveLocked(uid);
+}
+
+bool AudioPolicyService::UidPolicy::isUidActiveLocked(uid_t uid) {
+    // Non-app UIDs are considered always active
+    if (uid < FIRST_APPLICATION_UID) {
+        return true;
+    }
+    auto it = mOverrideUids.find(uid);
+    if (it != mOverrideUids.end()) {
+        return it->second;
+    }
+    return mActiveUids.find(uid) != mActiveUids.end();
+}
 
 // -----------  AudioPolicyService::AudioCommandThread implementation ----------
 
diff --git a/services/audiopolicy/service/AudioPolicyService.h b/services/audiopolicy/service/AudioPolicyService.h
index 833a230..9bc13c2 100644
--- a/services/audiopolicy/service/AudioPolicyService.h
+++ b/services/audiopolicy/service/AudioPolicyService.h
@@ -24,6 +24,7 @@
 #include <utils/Vector.h>
 #include <utils/SortedVector.h>
 #include <binder/BinderService.h>
+#include <binder/IUidObserver.h>
 #include <system/audio.h>
 #include <system/audio_policy.h>
 #include <media/IAudioPolicyService.h>
@@ -33,9 +34,13 @@
 #include "AudioPolicyEffects.h"
 #include "managerdefault/AudioPolicyManager.h"
 
+#include <unordered_map>
+#include <unordered_set>
 
 namespace android {
 
+using namespace std;
+
 // ----------------------------------------------------------------------------
 
 class AudioPolicyService :
@@ -97,7 +102,10 @@
                                      audio_port_handle_t *selectedDeviceId = NULL,
                                      audio_port_handle_t *portId = NULL);
     virtual status_t startInput(audio_io_handle_t input,
-                                audio_session_t session);
+                                audio_session_t session,
+                                audio_devices_t device,
+                                uid_t uid,
+                                bool *silenced);
     virtual status_t stopInput(audio_io_handle_t input,
                                audio_session_t session);
     virtual void releaseInput(audio_io_handle_t input,
@@ -235,6 +243,57 @@
 
             status_t dumpInternals(int fd);
 
+    // Handles binder shell commands
+    virtual status_t shellCommand(int in, int out, int err, Vector<String16>& args);
+
+    // Sets whether the given UID records only silence
+    virtual void setRecordSilenced(uid_t uid, bool silenced);
+
+    // Overrides the UID state as if it is idle
+    status_t handleSetUidState(Vector<String16>& args, int err);
+
+    // Clears the override for the UID state
+    status_t handleResetUidState(Vector<String16>& args, int err);
+
+    // Gets the UID state
+    status_t handleGetUidState(Vector<String16>& args, int out, int err);
+
+    // Prints the shell command help
+    status_t printHelp(int out);
+
+    // If recording we need to make sure the UID is allowed to do that. If the UID is idle
+    // then it cannot record and gets buffers with zeros - silence. As soon as the UID
+    // transitions to an active state we will start reporting buffers with data. This approach
+    // transparently handles recording while the UID transitions between idle/active state
+    // avoiding to get stuck in a state receiving non-empty buffers while idle or in a state
+    // receiving empty buffers while active.
+    class UidPolicy : public BnUidObserver {
+    public:
+        explicit UidPolicy(wp<AudioPolicyService> service)
+                : mService(service) {}
+
+        void registerSelf();
+        void unregisterSelf();
+
+        bool isUidActive(uid_t uid);
+
+        void onUidGone(uid_t uid, bool disabled);
+        void onUidActive(uid_t uid);
+        void onUidIdle(uid_t uid, bool disabled);
+
+        void addOverrideUid(uid_t uid, bool active);
+        void removeOverrideUid(uid_t uid);
+
+    private:
+        bool isUidActiveLocked(uid_t uid);
+        void updateOverrideUid(uid_t uid, bool active, bool insert);
+
+        Mutex mUidLock;
+        wp<AudioPolicyService> mService;
+        std::unordered_set<uid_t> mActiveUids;
+        std::unordered_map<uid_t, bool> mOverrideUids;
+    };
+
     // Thread used for tone playback and to send audio config commands to audio flinger
     // For tone playback, using a separate thread is necessary to avoid deadlock with mLock because
     // startTone() and stopTone() are normally called with mLock locked and requesting a tone start
@@ -306,7 +365,6 @@
                                                         const audio_config_base_t *deviceConfig,
                                                         audio_patch_handle_t patchHandle);
                     void        insertCommand_l(AudioCommand *command, int delayMs = 0);
-
     private:
         class AudioCommandData;
 
@@ -575,6 +633,8 @@
     // Manage all effects configured in audio_effects.conf
     sp<AudioPolicyEffects> mAudioPolicyEffects;
     audio_mode_t mPhoneState;
+
+    sp<UidPolicy> mUidPolicy;
 };
 
 } // namespace android
diff --git a/services/camera/libcameraservice/CameraService.cpp b/services/camera/libcameraservice/CameraService.cpp
index 6abfa81..5fc1fa3 100644
--- a/services/camera/libcameraservice/CameraService.cpp
+++ b/services/camera/libcameraservice/CameraService.cpp
@@ -33,14 +33,18 @@
 
 #include <android-base/macros.h>
 #include <android-base/parseint.h>
+#include <binder/ActivityManager.h>
 #include <binder/AppOpsManager.h>
 #include <binder/IPCThreadState.h>
 #include <binder/IServiceManager.h>
 #include <binder/MemoryBase.h>
 #include <binder/MemoryHeapBase.h>
+#include <binder/PermissionController.h>
 #include <binder/ProcessInfoService.h>
+#include <binder/IResultReceiver.h>
 #include <cutils/atomic.h>
 #include <cutils/properties.h>
+#include <cutils/misc.h>
 #include <gui/Surface.h>
 #include <hardware/hardware.h>
 #include <memunreachable/memunreachable.h>
@@ -165,6 +169,8 @@
 
 // ----------------------------------------------------------------------------
 
+static const String16 sManageCameraPermission("android.permission.MANAGE_CAMERA");
+
 CameraService::CameraService() :
         mEventLog(DEFAULT_EVENT_LOG_LENGTH),
         mNumberOfCameras(0), mNumberOfNormalCameras(0),
@@ -196,6 +202,9 @@
     }
 
     CameraService::pingCameraServiceProxy();
+
+    mUidPolicy = new UidPolicy(this);
+    mUidPolicy->registerSelf();
 }
 
 status_t CameraService::enumerateProviders() {
@@ -275,6 +284,7 @@
 
 CameraService::~CameraService() {
     VendorTagDescriptor::clearGlobalVendorTagDescriptor();
+    mUidPolicy->unregisterSelf();
 }
 
 void CameraService::onNewProviderRegistered() {
@@ -933,6 +943,15 @@
                 clientName8.string(), clientUid, clientPid, cameraId.string());
     }
 
+    // Make sure the UID is in an active state to use the camera
+    if (!mUidPolicy->isUidActive(callingUid)) {
+        ALOGE("Access Denial: can't use the camera from an idle UID pid=%d, uid=%d",
+            clientPid, clientUid);
+        return STATUS_ERROR_FMT(ERROR_DISABLED,
+                "Caller \"%s\" (PID %d, UID %d) cannot open camera \"%s\" from background",
+                clientName8.string(), clientUid, clientPid, cameraId.string());
+    }
+
     // Only use passed in clientPid to check permission. Use calling PID as the client PID that's
     // connected to camera service directly.
     originalClientPid = clientPid;
@@ -1969,6 +1988,30 @@
 
     // Permission checks
     switch (code) {
+        case SHELL_COMMAND_TRANSACTION: {
+            int in = data.readFileDescriptor();
+            int out = data.readFileDescriptor();
+            int err = data.readFileDescriptor();
+            int argc = data.readInt32();
+            Vector<String16> args;
+            for (int i = 0; i < argc && data.dataAvail() > 0; i++) {
+               args.add(data.readString16());
+            }
+            sp<IBinder> unusedCallback;
+            sp<IResultReceiver> resultReceiver;
+            status_t status;
+            if ((status = data.readNullableStrongBinder(&unusedCallback)) != NO_ERROR) {
+                return status;
+            }
+            if ((status = data.readNullableStrongBinder(&resultReceiver)) != NO_ERROR) {
+                return status;
+            }
+            status = shellCommand(in, out, err, args);
+            if (resultReceiver != nullptr) {
+                resultReceiver->send(status);
+            }
+            return NO_ERROR;
+        }
         case BnCameraService::NOTIFYSYSTEMEVENT: {
             if (pid != selfPid) {
                 // Ensure we're being called by system_server, or similar process with
@@ -2286,15 +2329,21 @@
     if (res != AppOpsManager::MODE_ALLOWED) {
         ALOGI("Camera %s: Access for \"%s\" revoked", mCameraIdStr.string(),
                 myName.string());
-        // Reset the client PID to allow server-initiated disconnect,
-        // and to prevent further calls by client.
-        mClientPid = getCallingPid();
-        CaptureResultExtras resultExtras; // a dummy result (invalid)
-        notifyError(hardware::camera2::ICameraDeviceCallbacks::ERROR_CAMERA_SERVICE, resultExtras);
-        disconnect();
+        block();
     }
 }
 
+void CameraService::BasicClient::block() {
+    ATRACE_CALL();
+
+    // Reset the client PID to allow server-initiated disconnect,
+    // and to prevent further calls by client.
+    mClientPid = getCallingPid();
+    CaptureResultExtras resultExtras; // a dummy result (invalid)
+    notifyError(hardware::camera2::ICameraDeviceCallbacks::ERROR_CAMERA_DISABLED, resultExtras);
+    disconnect();
+}
+
 // ----------------------------------------------------------------------------
 
 void CameraService::Client::notifyError(int32_t errorCode,
@@ -2331,6 +2380,98 @@
 }
 
 // ----------------------------------------------------------------------------
+//                  UidPolicy
+// ----------------------------------------------------------------------------
+
+void CameraService::UidPolicy::registerSelf() {
+    ActivityManager am;
+    am.registerUidObserver(this, ActivityManager::UID_OBSERVER_GONE
+            | ActivityManager::UID_OBSERVER_IDLE
+            | ActivityManager::UID_OBSERVER_ACTIVE,
+            ActivityManager::PROCESS_STATE_UNKNOWN,
+            String16("cameraserver"));
+}
+
+void CameraService::UidPolicy::unregisterSelf() {
+    ActivityManager am;
+    am.unregisterUidObserver(this);
+}
+
+void CameraService::UidPolicy::onUidGone(uid_t uid, bool disabled) {
+    onUidIdle(uid, disabled);
+}
+
+void CameraService::UidPolicy::onUidActive(uid_t uid) {
+    Mutex::Autolock _l(mUidLock);
+    mActiveUids.insert(uid);
+}
+
+void CameraService::UidPolicy::onUidIdle(uid_t uid, bool /* disabled */) {
+    bool deleted = false;
+    {
+        Mutex::Autolock _l(mUidLock);
+        if (mActiveUids.erase(uid) > 0) {
+            deleted = true;
+        }
+    }
+    if (deleted) {
+        sp<CameraService> service = mService.promote();
+        if (service != nullptr) {
+            service->blockClientsForUid(uid);
+        }
+    }
+}
+
+bool CameraService::UidPolicy::isUidActive(uid_t uid) {
+    // Non-app UIDs are considered always active
+    if (uid < FIRST_APPLICATION_UID) {
+        return true;
+    }
+    Mutex::Autolock _l(mUidLock);
+    return isUidActiveLocked(uid);
+}
+
+bool CameraService::UidPolicy::isUidActiveLocked(uid_t uid) {
+    // Non-app UIDs are considered always active
+    if (uid < FIRST_APPLICATION_UID) {
+        return true;
+    }
+    auto it = mOverrideUids.find(uid);
+    if (it != mOverrideUids.end()) {
+        return it->second;
+    }
+    return mActiveUids.find(uid) != mActiveUids.end();
+}
+
+void CameraService::UidPolicy::UidPolicy::addOverrideUid(uid_t uid, bool active) {
+    updateOverrideUid(uid, active, true);
+}
+
+void CameraService::UidPolicy::removeOverrideUid(uid_t uid) {
+    updateOverrideUid(uid, false, false);
+}
+
+void CameraService::UidPolicy::updateOverrideUid(uid_t uid, bool active, bool insert) {
+    bool wasActive = false;
+    bool isActive = false;
+    {
+        Mutex::Autolock _l(mUidLock);
+        wasActive = isUidActiveLocked(uid);
+        mOverrideUids.erase(uid);
+        if (insert) {
+            mOverrideUids.insert(std::pair<uid_t, bool>(uid, active));
+        }
+        isActive = isUidActiveLocked(uid);
+    }
+    if (wasActive != isActive && !isActive) {
+        sp<CameraService> service = mService.promote();
+        if (service != nullptr) {
+            service->blockClientsForUid(uid);
+        }
+    }
+}
+
+// ----------------------------------------------------------------------------
 //                  CameraState
 // ----------------------------------------------------------------------------
 
@@ -2791,4 +2932,92 @@
     return OK;
 }
 
+void CameraService::blockClientsForUid(uid_t uid) {
+    const auto clients = mActiveClientManager.getAll();
+    for (auto& current : clients) {
+        if (current != nullptr) {
+            const auto basicClient = current->getValue();
+            if (basicClient.get() != nullptr && basicClient->getClientUid() == uid) {
+                basicClient->block();
+            }
+        }
+    }
+}
+
+// NOTE: This is a remote API - make sure all args are validated
+status_t CameraService::shellCommand(int in, int out, int err, const Vector<String16>& args) {
+    if (!checkCallingPermission(sManageCameraPermission, nullptr, nullptr)) {
+        return PERMISSION_DENIED;
+    }
+    if (in == BAD_TYPE || out == BAD_TYPE || err == BAD_TYPE) {
+        return BAD_VALUE;
+    }
+    if (args.size() == 3 && args[0] == String16("set-uid-state")) {
+        return handleSetUidState(args, err);
+    } else if (args.size() == 2 && args[0] == String16("reset-uid-state")) {
+        return handleResetUidState(args, err);
+    } else if (args.size() == 2 && args[0] == String16("get-uid-state")) {
+        return handleGetUidState(args, out, err);
+    } else if (args.size() == 1 && args[0] == String16("help")) {
+        printHelp(out);
+        return NO_ERROR;
+    }
+    printHelp(err);
+    return BAD_VALUE;
+}
+
+status_t CameraService::handleSetUidState(const Vector<String16>& args, int err) {
+    PermissionController pc;
+    int uid = pc.getPackageUid(args[1], 0);
+    if (uid <= 0) {
+        ALOGE("Unknown package: '%s'", String8(args[1]).string());
+        dprintf(err, "Unknown package: '%s'\n", String8(args[1]).string());
+        return BAD_VALUE;
+    }
+    bool active = false;
+    if (args[2] == String16("active")) {
+        active = true;
+    } else if ((args[2] != String16("idle"))) {
+        ALOGE("Expected active or idle but got: '%s'", String8(args[2]).string());
+        return BAD_VALUE;
+    }
+    mUidPolicy->addOverrideUid(uid, active);
+    return NO_ERROR;
+}
+
+status_t CameraService::handleResetUidState(const Vector<String16>& args, int err) {
+    PermissionController pc;
+    int uid = pc.getPackageUid(args[1], 0);
+    if (uid < 0) {
+        ALOGE("Unknown package: '%s'", String8(args[1]).string());
+        dprintf(err, "Unknown package: '%s'\n", String8(args[1]).string());
+        return BAD_VALUE;
+    }
+    mUidPolicy->removeOverrideUid(uid);
+    return NO_ERROR;
+}
+
+status_t CameraService::handleGetUidState(const Vector<String16>& args, int out, int err) {
+    PermissionController pc;
+    int uid = pc.getPackageUid(args[1], 0);
+    if (uid <= 0) {
+        ALOGE("Unknown package: '%s'", String8(args[1]).string());
+        dprintf(err, "Unknown package: '%s'\n", String8(args[1]).string());
+        return BAD_VALUE;
+    }
+    if (mUidPolicy->isUidActive(uid)) {
+        return dprintf(out, "active\n");
+    } else {
+        return dprintf(out, "idle\n");
+    }
+}
+
+status_t CameraService::printHelp(int out) {
+    return dprintf(out, "Camera service commands:\n"
+        "  get-uid-state <PACKAGE> gets the uid state\n"
+        "  set-uid-state <PACKAGE> <active|idle> overrides the uid state\n"
+        "  reset-uid-state <PACKAGE> clears the uid state override\n"
+        "  help print this message\n");
+}
+
 }; // namespace android
diff --git a/services/camera/libcameraservice/CameraService.h b/services/camera/libcameraservice/CameraService.h
index e9373a6..575cebf 100644
--- a/services/camera/libcameraservice/CameraService.h
+++ b/services/camera/libcameraservice/CameraService.h
@@ -27,6 +27,7 @@
 #include <binder/AppOpsManager.h>
 #include <binder/BinderService.h>
 #include <binder/IAppOpsCallback.h>
+#include <binder/IUidObserver.h>
 #include <hardware/camera.h>
 
 #include <android/hardware/camera/common/1.0/types.h>
@@ -47,6 +48,8 @@
 #include <map>
 #include <memory>
 #include <utility>
+#include <unordered_map>
+#include <unordered_set>
 
 namespace android {
 
@@ -163,6 +166,8 @@
 
     virtual status_t    dump(int fd, const Vector<String16>& args);
 
+    virtual status_t    shellCommand(int in, int out, int err, const Vector<String16>& args);
+
     /////////////////////////////////////////////////////////////////////
     // Client functionality
 
@@ -233,6 +238,9 @@
         // Check what API level is used for this client. This is used to determine which
         // superclass this can be cast to.
         virtual bool canCastToApiClient(apiLevel level) const;
+
+        // Block the client form using the camera
+        virtual void block();
     protected:
         BasicClient(const sp<CameraService>& cameraService,
                 const sp<IBinder>& remoteCallback,
@@ -506,6 +514,37 @@
         CameraParameters mShimParams;
     }; // class CameraState
 
+    // Observer for UID lifecycle enforcing that UIDs in idle
+    // state cannot use the camera to protect user privacy.
+    class UidPolicy : public BnUidObserver {
+    public:
+        explicit UidPolicy(sp<CameraService> service)
+                : mService(service) {}
+
+        void registerSelf();
+        void unregisterSelf();
+
+        bool isUidActive(uid_t uid);
+
+        void onUidGone(uid_t uid, bool disabled);
+        void onUidActive(uid_t uid);
+        void onUidIdle(uid_t uid, bool disabled);
+
+        void addOverrideUid(uid_t uid, bool active);
+        void removeOverrideUid(uid_t uid);
+
+    private:
+        bool isUidActiveLocked(uid_t uid);
+        void updateOverrideUid(uid_t uid, bool active, bool insert);
+
+        Mutex mUidLock;
+        wp<CameraService> mService;
+        std::unordered_set<uid_t> mActiveUids;
+        std::unordered_map<uid_t, bool> mOverrideUids;
+    }; // class UidPolicy
+
+    sp<UidPolicy> mUidPolicy;
+
     // Delay-load the Camera HAL module
     virtual void onFirstRef();
 
@@ -755,6 +794,21 @@
      */
     binder::Status      getLegacyParametersLazy(int cameraId, /*out*/CameraParameters* parameters);
 
+    // Blocks all clients from the UID
+    void blockClientsForUid(uid_t uid);
+
+    // Overrides the UID state as if it is idle
+    status_t handleSetUidState(const Vector<String16>& args, int err);
+
+    // Clears the override for the UID state
+    status_t handleResetUidState(const Vector<String16>& args, int err);
+
+    // Gets the UID state
+    status_t handleGetUidState(const Vector<String16>& args, int out, int err);
+
+    // Prints the shell command help
+    status_t printHelp(int out);
+
     static int getCallingPid();
 
     static int getCallingUid();