Merge "Fix a pointer reference"
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/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> ¶ms,
+ const std::vector<C2Param*> ¶ms,
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> ¶ms,
+ const std::vector<C2Param*> ¶ms,
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> ¶ms,
+ const std::vector<C2Param*> ¶ms,
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> ¶ms,
+ const std::vector<C2Param*> ¶ms,
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> ¶ms,
+ const std::vector<C2Param*> ¶ms,
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> ¶ms,
+ const std::vector<C2Param*> ¶ms,
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> ¶ms,
+ const std::vector<C2Param*> ¶ms,
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/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();