|  | /* | 
|  | * Copyright (C) 2010 The Android Open Source Project | 
|  | * | 
|  | * Licensed under the Apache License, Version 2.0 (the "License"); | 
|  | * you may not use this file except in compliance with the License. | 
|  | * You may obtain a copy of the License at | 
|  | * | 
|  | *      http://www.apache.org/licenses/LICENSE-2.0 | 
|  | * | 
|  | * Unless required by applicable law or agreed to in writing, software | 
|  | * distributed under the License is distributed on an "AS IS" BASIS, | 
|  | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
|  | * See the License for the specific language governing permissions and | 
|  | * limitations under the License. | 
|  | */ | 
|  |  | 
|  | #define LOG_TAG "MtpProperty" | 
|  |  | 
|  | #include <inttypes.h> | 
|  | #include <cutils/compiler.h> | 
|  | #include <iomanip> | 
|  | #include <sstream> | 
|  | #include <string> | 
|  |  | 
|  | #include "MtpDataPacket.h" | 
|  | #include "MtpDebug.h" | 
|  | #include "MtpProperty.h" | 
|  | #include "MtpStringBuffer.h" | 
|  | #include "MtpUtils.h" | 
|  |  | 
|  | namespace android { | 
|  |  | 
|  | MtpProperty::MtpProperty() | 
|  | :   mCode(0), | 
|  | mType(0), | 
|  | mWriteable(false), | 
|  | mDefaultArrayLength(0), | 
|  | mDefaultArrayValues(NULL), | 
|  | mCurrentArrayLength(0), | 
|  | mCurrentArrayValues(NULL), | 
|  | mGroupCode(0), | 
|  | mFormFlag(kFormNone), | 
|  | mEnumLength(0), | 
|  | mEnumValues(NULL) | 
|  | { | 
|  | memset(&mDefaultValue, 0, sizeof(mDefaultValue)); | 
|  | memset(&mCurrentValue, 0, sizeof(mCurrentValue)); | 
|  | memset(&mMinimumValue, 0, sizeof(mMinimumValue)); | 
|  | memset(&mMaximumValue, 0, sizeof(mMaximumValue)); | 
|  | } | 
|  |  | 
|  | MtpProperty::MtpProperty(MtpPropertyCode propCode, | 
|  | MtpDataType type, | 
|  | bool writeable, | 
|  | int defaultValue) | 
|  | :   mCode(propCode), | 
|  | mType(type), | 
|  | mWriteable(writeable), | 
|  | mDefaultArrayLength(0), | 
|  | mDefaultArrayValues(NULL), | 
|  | mCurrentArrayLength(0), | 
|  | mCurrentArrayValues(NULL), | 
|  | mGroupCode(0), | 
|  | mFormFlag(kFormNone), | 
|  | mEnumLength(0), | 
|  | mEnumValues(NULL) | 
|  | { | 
|  | memset(&mDefaultValue, 0, sizeof(mDefaultValue)); | 
|  | memset(&mCurrentValue, 0, sizeof(mCurrentValue)); | 
|  | memset(&mMinimumValue, 0, sizeof(mMinimumValue)); | 
|  | memset(&mMaximumValue, 0, sizeof(mMaximumValue)); | 
|  |  | 
|  | if (defaultValue) { | 
|  | switch (type) { | 
|  | case MTP_TYPE_INT8: | 
|  | mDefaultValue.u.i8 = defaultValue; | 
|  | break; | 
|  | case MTP_TYPE_UINT8: | 
|  | mDefaultValue.u.u8 = defaultValue; | 
|  | break; | 
|  | case MTP_TYPE_INT16: | 
|  | mDefaultValue.u.i16 = defaultValue; | 
|  | break; | 
|  | case MTP_TYPE_UINT16: | 
|  | mDefaultValue.u.u16 = defaultValue; | 
|  | break; | 
|  | case MTP_TYPE_INT32: | 
|  | mDefaultValue.u.i32 = defaultValue; | 
|  | break; | 
|  | case MTP_TYPE_UINT32: | 
|  | mDefaultValue.u.u32 = defaultValue; | 
|  | break; | 
|  | case MTP_TYPE_INT64: | 
|  | mDefaultValue.u.i64 = defaultValue; | 
|  | break; | 
|  | case MTP_TYPE_UINT64: | 
|  | mDefaultValue.u.u64 = defaultValue; | 
|  | break; | 
|  | default: | 
|  | ALOGE("unknown type %04X in MtpProperty::MtpProperty", type); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | MtpProperty::~MtpProperty() { | 
|  | if (mType == MTP_TYPE_STR) { | 
|  | // free all strings | 
|  | free(mDefaultValue.str); | 
|  | free(mCurrentValue.str); | 
|  | free(mMinimumValue.str); | 
|  | free(mMaximumValue.str); | 
|  | if (mDefaultArrayValues) { | 
|  | for (uint32_t i = 0; i < mDefaultArrayLength; i++) | 
|  | free(mDefaultArrayValues[i].str); | 
|  | } | 
|  | if (mCurrentArrayValues) { | 
|  | for (uint32_t i = 0; i < mCurrentArrayLength; i++) | 
|  | free(mCurrentArrayValues[i].str); | 
|  | } | 
|  | if (mEnumValues) { | 
|  | for (uint16_t i = 0; i < mEnumLength; i++) | 
|  | free(mEnumValues[i].str); | 
|  | } | 
|  | } | 
|  | delete[] mDefaultArrayValues; | 
|  | delete[] mCurrentArrayValues; | 
|  | delete[] mEnumValues; | 
|  | } | 
|  |  | 
|  | bool MtpProperty::read(MtpDataPacket& packet) { | 
|  | uint8_t temp8; | 
|  |  | 
|  | if (!packet.getUInt16(mCode)) return false; | 
|  | bool deviceProp = isDeviceProperty(); | 
|  | if (!packet.getUInt16(mType)) return false; | 
|  | if (!packet.getUInt8(temp8)) return false; | 
|  | mWriteable = (temp8 == 1); | 
|  | switch (mType) { | 
|  | case MTP_TYPE_AINT8: | 
|  | case MTP_TYPE_AUINT8: | 
|  | case MTP_TYPE_AINT16: | 
|  | case MTP_TYPE_AUINT16: | 
|  | case MTP_TYPE_AINT32: | 
|  | case MTP_TYPE_AUINT32: | 
|  | case MTP_TYPE_AINT64: | 
|  | case MTP_TYPE_AUINT64: | 
|  | case MTP_TYPE_AINT128: | 
|  | case MTP_TYPE_AUINT128: | 
|  | mDefaultArrayValues = readArrayValues(packet, mDefaultArrayLength); | 
|  | if (!mDefaultArrayValues) return false; | 
|  | if (deviceProp) { | 
|  | mCurrentArrayValues = readArrayValues(packet, mCurrentArrayLength); | 
|  | if (!mCurrentArrayValues) return false; | 
|  | } | 
|  | break; | 
|  | default: | 
|  | if (!readValue(packet, mDefaultValue)) return false; | 
|  | if (deviceProp) { | 
|  | if (!readValue(packet, mCurrentValue)) return false; | 
|  | } | 
|  | } | 
|  | if (!deviceProp) { | 
|  | if (!packet.getUInt32(mGroupCode)) return false; | 
|  | } | 
|  | if (!packet.getUInt8(mFormFlag)) return false; | 
|  |  | 
|  | if (mFormFlag == kFormRange) { | 
|  | if (!readValue(packet, mMinimumValue)) return false; | 
|  | if (!readValue(packet, mMaximumValue)) return false; | 
|  | if (!readValue(packet, mStepSize)) return false; | 
|  | } else if (mFormFlag == kFormEnum) { | 
|  | if (!packet.getUInt16(mEnumLength)) return false; | 
|  | mEnumValues = new MtpPropertyValue[mEnumLength]; | 
|  | for (int i = 0; i < mEnumLength; i++) { | 
|  | if (!readValue(packet, mEnumValues[i])) return false; | 
|  | } | 
|  | } | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | void MtpProperty::write(MtpDataPacket& packet) { | 
|  | bool deviceProp = isDeviceProperty(); | 
|  |  | 
|  | packet.putUInt16(mCode); | 
|  | packet.putUInt16(mType); | 
|  | packet.putUInt8(mWriteable ? 1 : 0); | 
|  |  | 
|  | switch (mType) { | 
|  | case MTP_TYPE_AINT8: | 
|  | case MTP_TYPE_AUINT8: | 
|  | case MTP_TYPE_AINT16: | 
|  | case MTP_TYPE_AUINT16: | 
|  | case MTP_TYPE_AINT32: | 
|  | case MTP_TYPE_AUINT32: | 
|  | case MTP_TYPE_AINT64: | 
|  | case MTP_TYPE_AUINT64: | 
|  | case MTP_TYPE_AINT128: | 
|  | case MTP_TYPE_AUINT128: | 
|  | writeArrayValues(packet, mDefaultArrayValues, mDefaultArrayLength); | 
|  | if (deviceProp) | 
|  | writeArrayValues(packet, mCurrentArrayValues, mCurrentArrayLength); | 
|  | break; | 
|  | default: | 
|  | writeValue(packet, mDefaultValue); | 
|  | if (deviceProp) | 
|  | writeValue(packet, mCurrentValue); | 
|  | } | 
|  | if (!deviceProp) | 
|  | packet.putUInt32(mGroupCode); | 
|  | packet.putUInt8(mFormFlag); | 
|  | if (mFormFlag == kFormRange) { | 
|  | writeValue(packet, mMinimumValue); | 
|  | writeValue(packet, mMaximumValue); | 
|  | writeValue(packet, mStepSize); | 
|  | } else if (mFormFlag == kFormEnum) { | 
|  | packet.putUInt16(mEnumLength); | 
|  | for (int i = 0; i < mEnumLength; i++) | 
|  | writeValue(packet, mEnumValues[i]); | 
|  | } | 
|  | } | 
|  |  | 
|  | void MtpProperty::setDefaultValue(const uint16_t* string) { | 
|  | free(mDefaultValue.str); | 
|  | if (string) { | 
|  | MtpStringBuffer buffer(string); | 
|  | mDefaultValue.str = strdup(buffer); | 
|  | } | 
|  | else | 
|  | mDefaultValue.str = NULL; | 
|  | } | 
|  |  | 
|  | void MtpProperty::setCurrentValue(const uint16_t* string) { | 
|  | free(mCurrentValue.str); | 
|  | if (string) { | 
|  | MtpStringBuffer buffer(string); | 
|  | mCurrentValue.str = strdup(buffer); | 
|  | } | 
|  | else | 
|  | mCurrentValue.str = NULL; | 
|  | } | 
|  |  | 
|  | void MtpProperty::setCurrentValue(const char* string) { | 
|  | free(mCurrentValue.str); | 
|  | if (string) { | 
|  | MtpStringBuffer buffer(string); | 
|  | mCurrentValue.str = strdup(buffer); | 
|  | } | 
|  | else | 
|  | mCurrentValue.str = NULL; | 
|  | } | 
|  |  | 
|  | void MtpProperty::setCurrentValue(MtpDataPacket& packet) { | 
|  | free(mCurrentValue.str); | 
|  | mCurrentValue.str = NULL; | 
|  | readValue(packet, mCurrentValue); | 
|  | } | 
|  |  | 
|  | void MtpProperty::setFormRange(int min, int max, int step) { | 
|  | mFormFlag = kFormRange; | 
|  | switch (mType) { | 
|  | case MTP_TYPE_INT8: | 
|  | mMinimumValue.u.i8 = min; | 
|  | mMaximumValue.u.i8 = max; | 
|  | mStepSize.u.i8 = step; | 
|  | break; | 
|  | case MTP_TYPE_UINT8: | 
|  | mMinimumValue.u.u8 = min; | 
|  | mMaximumValue.u.u8 = max; | 
|  | mStepSize.u.u8 = step; | 
|  | break; | 
|  | case MTP_TYPE_INT16: | 
|  | mMinimumValue.u.i16 = min; | 
|  | mMaximumValue.u.i16 = max; | 
|  | mStepSize.u.i16 = step; | 
|  | break; | 
|  | case MTP_TYPE_UINT16: | 
|  | mMinimumValue.u.u16 = min; | 
|  | mMaximumValue.u.u16 = max; | 
|  | mStepSize.u.u16 = step; | 
|  | break; | 
|  | case MTP_TYPE_INT32: | 
|  | mMinimumValue.u.i32 = min; | 
|  | mMaximumValue.u.i32 = max; | 
|  | mStepSize.u.i32 = step; | 
|  | break; | 
|  | case MTP_TYPE_UINT32: | 
|  | mMinimumValue.u.u32 = min; | 
|  | mMaximumValue.u.u32 = max; | 
|  | mStepSize.u.u32 = step; | 
|  | break; | 
|  | case MTP_TYPE_INT64: | 
|  | mMinimumValue.u.i64 = min; | 
|  | mMaximumValue.u.i64 = max; | 
|  | mStepSize.u.i64 = step; | 
|  | break; | 
|  | case MTP_TYPE_UINT64: | 
|  | mMinimumValue.u.u64 = min; | 
|  | mMaximumValue.u.u64 = max; | 
|  | mStepSize.u.u64 = step; | 
|  | break; | 
|  | default: | 
|  | ALOGE("unsupported type for MtpProperty::setRange"); | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | void MtpProperty::setFormEnum(const int* values, int count) { | 
|  | mFormFlag = kFormEnum; | 
|  | delete[] mEnumValues; | 
|  | mEnumValues = new MtpPropertyValue[count]; | 
|  | mEnumLength = count; | 
|  |  | 
|  | for (int i = 0; i < count; i++) { | 
|  | int value = *values++; | 
|  | switch (mType) { | 
|  | case MTP_TYPE_INT8: | 
|  | mEnumValues[i].u.i8 = value; | 
|  | break; | 
|  | case MTP_TYPE_UINT8: | 
|  | mEnumValues[i].u.u8 = value; | 
|  | break; | 
|  | case MTP_TYPE_INT16: | 
|  | mEnumValues[i].u.i16 = value; | 
|  | break; | 
|  | case MTP_TYPE_UINT16: | 
|  | mEnumValues[i].u.u16 = value; | 
|  | break; | 
|  | case MTP_TYPE_INT32: | 
|  | mEnumValues[i].u.i32 = value; | 
|  | break; | 
|  | case MTP_TYPE_UINT32: | 
|  | mEnumValues[i].u.u32 = value; | 
|  | break; | 
|  | case MTP_TYPE_INT64: | 
|  | mEnumValues[i].u.i64 = value; | 
|  | break; | 
|  | case MTP_TYPE_UINT64: | 
|  | mEnumValues[i].u.u64 = value; | 
|  | break; | 
|  | default: | 
|  | ALOGE("unsupported type for MtpProperty::setEnum"); | 
|  | break; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | void MtpProperty::setFormDateTime() { | 
|  | mFormFlag = kFormDateTime; | 
|  | } | 
|  |  | 
|  | void MtpProperty::print() { | 
|  | std::string buffer; | 
|  | bool deviceProp = isDeviceProperty(); | 
|  | if (deviceProp) | 
|  | ALOGI("    %s (%04X)", MtpDebug::getDevicePropCodeName(mCode), mCode); | 
|  | else | 
|  | ALOGI("    %s (%04X)", MtpDebug::getObjectPropCodeName(mCode), mCode); | 
|  | ALOGI("    type %04X", mType); | 
|  | ALOGI("    writeable %s", (mWriteable ? "true" : "false")); | 
|  | buffer = "    default value: "; | 
|  | print(mDefaultValue, buffer); | 
|  | ALOGI("%s", buffer.c_str()); | 
|  | if (deviceProp) { | 
|  | buffer = "    current value: "; | 
|  | print(mCurrentValue, buffer); | 
|  | ALOGI("%s", buffer.c_str()); | 
|  | } | 
|  | switch (mFormFlag) { | 
|  | case kFormNone: | 
|  | break; | 
|  | case kFormRange: | 
|  | buffer = "    Range ("; | 
|  | print(mMinimumValue, buffer); | 
|  | buffer += ", "; | 
|  | print(mMaximumValue, buffer); | 
|  | buffer += ", "; | 
|  | print(mStepSize, buffer); | 
|  | buffer += ")"; | 
|  | ALOGI("%s", buffer.c_str()); | 
|  | break; | 
|  | case kFormEnum: | 
|  | buffer = "    Enum { "; | 
|  | for (int i = 0; i < mEnumLength; i++) { | 
|  | print(mEnumValues[i], buffer); | 
|  | buffer += " "; | 
|  | } | 
|  | buffer += "}"; | 
|  | ALOGI("%s", buffer.c_str()); | 
|  | break; | 
|  | case kFormDateTime: | 
|  | ALOGI("    DateTime\n"); | 
|  | break; | 
|  | default: | 
|  | ALOGI("    form %d\n", mFormFlag); | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | void MtpProperty::print(MtpPropertyValue& value, std::string& buffer) { | 
|  | std::ostringstream s; | 
|  | switch (mType) { | 
|  | case MTP_TYPE_INT8: | 
|  | buffer += std::to_string(value.u.i8); | 
|  | break; | 
|  | case MTP_TYPE_UINT8: | 
|  | buffer += std::to_string(value.u.u8); | 
|  | break; | 
|  | case MTP_TYPE_INT16: | 
|  | buffer += std::to_string(value.u.i16); | 
|  | break; | 
|  | case MTP_TYPE_UINT16: | 
|  | buffer += std::to_string(value.u.u16); | 
|  | break; | 
|  | case MTP_TYPE_INT32: | 
|  | buffer += std::to_string(value.u.i32); | 
|  | break; | 
|  | case MTP_TYPE_UINT32: | 
|  | buffer += std::to_string(value.u.u32); | 
|  | break; | 
|  | case MTP_TYPE_INT64: | 
|  | buffer += std::to_string(value.u.i64); | 
|  | break; | 
|  | case MTP_TYPE_UINT64: | 
|  | buffer += std::to_string(value.u.u64); | 
|  | break; | 
|  | case MTP_TYPE_INT128: | 
|  | for (auto i : value.u.i128) { | 
|  | s << std::hex << std::setfill('0') << std::uppercase << i; | 
|  | } | 
|  | buffer += s.str(); | 
|  | break; | 
|  | case MTP_TYPE_UINT128: | 
|  | for (auto i : value.u.u128) { | 
|  | s << std::hex << std::setfill('0') << std::uppercase << i; | 
|  | } | 
|  | buffer += s.str(); | 
|  | break; | 
|  | case MTP_TYPE_STR: | 
|  | buffer += value.str; | 
|  | break; | 
|  | default: | 
|  | ALOGE("unsupported type for MtpProperty::print\n"); | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | bool MtpProperty::readValue(MtpDataPacket& packet, MtpPropertyValue& value) { | 
|  | MtpStringBuffer stringBuffer; | 
|  |  | 
|  | switch (mType) { | 
|  | case MTP_TYPE_INT8: | 
|  | case MTP_TYPE_AINT8: | 
|  | if (!packet.getInt8(value.u.i8)) return false; | 
|  | break; | 
|  | case MTP_TYPE_UINT8: | 
|  | case MTP_TYPE_AUINT8: | 
|  | if (!packet.getUInt8(value.u.u8)) return false; | 
|  | break; | 
|  | case MTP_TYPE_INT16: | 
|  | case MTP_TYPE_AINT16: | 
|  | if (!packet.getInt16(value.u.i16)) return false; | 
|  | break; | 
|  | case MTP_TYPE_UINT16: | 
|  | case MTP_TYPE_AUINT16: | 
|  | if (!packet.getUInt16(value.u.u16)) return false; | 
|  | break; | 
|  | case MTP_TYPE_INT32: | 
|  | case MTP_TYPE_AINT32: | 
|  | if (!packet.getInt32(value.u.i32)) return false; | 
|  | break; | 
|  | case MTP_TYPE_UINT32: | 
|  | case MTP_TYPE_AUINT32: | 
|  | if (!packet.getUInt32(value.u.u32)) return false; | 
|  | break; | 
|  | case MTP_TYPE_INT64: | 
|  | case MTP_TYPE_AINT64: | 
|  | if (!packet.getInt64(value.u.i64)) return false; | 
|  | break; | 
|  | case MTP_TYPE_UINT64: | 
|  | case MTP_TYPE_AUINT64: | 
|  | if (!packet.getUInt64(value.u.u64)) return false; | 
|  | break; | 
|  | case MTP_TYPE_INT128: | 
|  | case MTP_TYPE_AINT128: | 
|  | if (!packet.getInt128(value.u.i128)) return false; | 
|  | break; | 
|  | case MTP_TYPE_UINT128: | 
|  | case MTP_TYPE_AUINT128: | 
|  | if (!packet.getUInt128(value.u.u128)) return false; | 
|  | break; | 
|  | case MTP_TYPE_STR: | 
|  | if (!packet.getString(stringBuffer)) return false; | 
|  | value.str = strdup(stringBuffer); | 
|  | break; | 
|  | default: | 
|  | ALOGE("unknown type %04X in MtpProperty::readValue", mType); | 
|  | return false; | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | void MtpProperty::writeValue(MtpDataPacket& packet, MtpPropertyValue& value) { | 
|  | MtpStringBuffer stringBuffer; | 
|  |  | 
|  | switch (mType) { | 
|  | case MTP_TYPE_INT8: | 
|  | case MTP_TYPE_AINT8: | 
|  | packet.putInt8(value.u.i8); | 
|  | break; | 
|  | case MTP_TYPE_UINT8: | 
|  | case MTP_TYPE_AUINT8: | 
|  | packet.putUInt8(value.u.u8); | 
|  | break; | 
|  | case MTP_TYPE_INT16: | 
|  | case MTP_TYPE_AINT16: | 
|  | packet.putInt16(value.u.i16); | 
|  | break; | 
|  | case MTP_TYPE_UINT16: | 
|  | case MTP_TYPE_AUINT16: | 
|  | packet.putUInt16(value.u.u16); | 
|  | break; | 
|  | case MTP_TYPE_INT32: | 
|  | case MTP_TYPE_AINT32: | 
|  | packet.putInt32(value.u.i32); | 
|  | break; | 
|  | case MTP_TYPE_UINT32: | 
|  | case MTP_TYPE_AUINT32: | 
|  | packet.putUInt32(value.u.u32); | 
|  | break; | 
|  | case MTP_TYPE_INT64: | 
|  | case MTP_TYPE_AINT64: | 
|  | packet.putInt64(value.u.i64); | 
|  | break; | 
|  | case MTP_TYPE_UINT64: | 
|  | case MTP_TYPE_AUINT64: | 
|  | packet.putUInt64(value.u.u64); | 
|  | break; | 
|  | case MTP_TYPE_INT128: | 
|  | case MTP_TYPE_AINT128: | 
|  | packet.putInt128(value.u.i128); | 
|  | break; | 
|  | case MTP_TYPE_UINT128: | 
|  | case MTP_TYPE_AUINT128: | 
|  | packet.putUInt128(value.u.u128); | 
|  | break; | 
|  | case MTP_TYPE_STR: | 
|  | if (value.str) | 
|  | packet.putString(value.str); | 
|  | else | 
|  | packet.putEmptyString(); | 
|  | break; | 
|  | default: | 
|  | ALOGE("unknown type %04X in MtpProperty::writeValue", mType); | 
|  | } | 
|  | } | 
|  |  | 
|  | MtpPropertyValue* MtpProperty::readArrayValues(MtpDataPacket& packet, uint32_t& length) { | 
|  | if (!packet.getUInt32(length)) return NULL; | 
|  |  | 
|  | // Fail if resulting array is over 2GB.  This is because the maximum array | 
|  | // size may be less than SIZE_MAX on some platforms. | 
|  | if ( CC_UNLIKELY( | 
|  | length == 0 || | 
|  | length >= INT32_MAX / sizeof(MtpPropertyValue)) ) { | 
|  | length = 0; | 
|  | return NULL; | 
|  | } | 
|  | MtpPropertyValue* result = new MtpPropertyValue[length]; | 
|  | for (uint32_t i = 0; i < length; i++) | 
|  | if (!readValue(packet, result[i])) { | 
|  | delete [] result; | 
|  | return NULL; | 
|  | } | 
|  | return result; | 
|  | } | 
|  |  | 
|  | void MtpProperty::writeArrayValues(MtpDataPacket& packet, MtpPropertyValue* values, uint32_t length) { | 
|  | packet.putUInt32(length); | 
|  | for (uint32_t i = 0; i < length; i++) | 
|  | writeValue(packet, values[i]); | 
|  | } | 
|  |  | 
|  | }  // namespace android |