| Jiwen 'Steve' Cai | c6fcf2f | 2018-09-27 23:34:45 -0700 | [diff] [blame] | 1 | #include <private/dvr/producer_buffer.h> | 
|  | 2 |  | 
|  | 3 | using android::pdx::LocalChannelHandle; | 
|  | 4 | using android::pdx::LocalHandle; | 
|  | 5 | using android::pdx::Status; | 
|  | 6 |  | 
|  | 7 | namespace android { | 
|  | 8 | namespace dvr { | 
|  | 9 |  | 
|  | 10 | ProducerBuffer::ProducerBuffer(uint32_t width, uint32_t height, uint32_t format, | 
|  | 11 | uint64_t usage, size_t user_metadata_size) | 
|  | 12 | : BASE(BufferHubRPC::kClientPath) { | 
|  | 13 | ATRACE_NAME("ProducerBuffer::ProducerBuffer"); | 
|  | 14 | ALOGD_IF(TRACE, | 
|  | 15 | "ProducerBuffer::ProducerBuffer: fd=%d width=%u height=%u format=%u " | 
|  | 16 | "usage=%" PRIx64 " user_metadata_size=%zu", | 
|  | 17 | event_fd(), width, height, format, usage, user_metadata_size); | 
|  | 18 |  | 
|  | 19 | auto status = InvokeRemoteMethod<BufferHubRPC::CreateBuffer>( | 
|  | 20 | width, height, format, usage, user_metadata_size); | 
|  | 21 | if (!status) { | 
|  | 22 | ALOGE( | 
|  | 23 | "ProducerBuffer::ProducerBuffer: Failed to create producer buffer: %s", | 
|  | 24 | status.GetErrorMessage().c_str()); | 
|  | 25 | Close(-status.error()); | 
|  | 26 | return; | 
|  | 27 | } | 
|  | 28 |  | 
|  | 29 | const int ret = ImportBuffer(); | 
|  | 30 | if (ret < 0) { | 
|  | 31 | ALOGE( | 
|  | 32 | "ProducerBuffer::ProducerBuffer: Failed to import producer buffer: %s", | 
|  | 33 | strerror(-ret)); | 
|  | 34 | Close(ret); | 
|  | 35 | } | 
|  | 36 | } | 
|  | 37 |  | 
|  | 38 | ProducerBuffer::ProducerBuffer(uint64_t usage, size_t size) | 
|  | 39 | : BASE(BufferHubRPC::kClientPath) { | 
|  | 40 | ATRACE_NAME("ProducerBuffer::ProducerBuffer"); | 
|  | 41 | ALOGD_IF(TRACE, "ProducerBuffer::ProducerBuffer: usage=%" PRIx64 " size=%zu", | 
|  | 42 | usage, size); | 
|  | 43 | const int width = static_cast<int>(size); | 
|  | 44 | const int height = 1; | 
|  | 45 | const int format = HAL_PIXEL_FORMAT_BLOB; | 
|  | 46 | const size_t user_metadata_size = 0; | 
|  | 47 |  | 
|  | 48 | auto status = InvokeRemoteMethod<BufferHubRPC::CreateBuffer>( | 
|  | 49 | width, height, format, usage, user_metadata_size); | 
|  | 50 | if (!status) { | 
|  | 51 | ALOGE("ProducerBuffer::ProducerBuffer: Failed to create blob: %s", | 
|  | 52 | status.GetErrorMessage().c_str()); | 
|  | 53 | Close(-status.error()); | 
|  | 54 | return; | 
|  | 55 | } | 
|  | 56 |  | 
|  | 57 | const int ret = ImportBuffer(); | 
|  | 58 | if (ret < 0) { | 
|  | 59 | ALOGE( | 
|  | 60 | "ProducerBuffer::ProducerBuffer: Failed to import producer buffer: %s", | 
|  | 61 | strerror(-ret)); | 
|  | 62 | Close(ret); | 
|  | 63 | } | 
|  | 64 | } | 
|  | 65 |  | 
|  | 66 | ProducerBuffer::ProducerBuffer(LocalChannelHandle channel) | 
|  | 67 | : BASE(std::move(channel)) { | 
|  | 68 | const int ret = ImportBuffer(); | 
|  | 69 | if (ret < 0) { | 
|  | 70 | ALOGE( | 
|  | 71 | "ProducerBuffer::ProducerBuffer: Failed to import producer buffer: %s", | 
|  | 72 | strerror(-ret)); | 
|  | 73 | Close(ret); | 
|  | 74 | } | 
|  | 75 | } | 
|  | 76 |  | 
|  | 77 | int ProducerBuffer::LocalPost(const DvrNativeBufferMetadata* meta, | 
|  | 78 | const LocalHandle& ready_fence) { | 
|  | 79 | if (const int error = CheckMetadata(meta->user_metadata_size)) | 
|  | 80 | return error; | 
|  | 81 |  | 
| Tianyu | f669f6a | 2018-10-10 15:34:32 -0700 | [diff] [blame] | 82 | // The buffer can be posted iff the buffer state for this client is gained. | 
| Tianyu Jiang | a99f911 | 2018-12-13 18:23:07 -0800 | [diff] [blame] | 83 | uint32_t current_buffer_state = | 
| Tianyu | f669f6a | 2018-10-10 15:34:32 -0700 | [diff] [blame] | 84 | buffer_state_->load(std::memory_order_acquire); | 
| Tianyu Jiang | 727ede4 | 2019-02-01 11:44:51 -0800 | [diff] [blame] | 85 | if (!BufferHubDefs::isClientGained(current_buffer_state, | 
| Tianyu | f669f6a | 2018-10-10 15:34:32 -0700 | [diff] [blame] | 86 | client_state_mask())) { | 
| Tianyu Jiang | a99f911 | 2018-12-13 18:23:07 -0800 | [diff] [blame] | 87 | ALOGE("%s: not gained, id=%d state=%" PRIx32 ".", __FUNCTION__, id(), | 
| Tianyu | f669f6a | 2018-10-10 15:34:32 -0700 | [diff] [blame] | 88 | current_buffer_state); | 
| Jiwen 'Steve' Cai | c6fcf2f | 2018-09-27 23:34:45 -0700 | [diff] [blame] | 89 | return -EBUSY; | 
|  | 90 | } | 
|  | 91 |  | 
| Tianyu Jiang | e60a4ad | 2019-01-04 14:37:23 -0800 | [diff] [blame] | 92 | // Set the producer client buffer state to released, that of all other clients | 
|  | 93 | // (both existing and non-existing clients) to posted. | 
|  | 94 | uint32_t updated_buffer_state = | 
|  | 95 | (~client_state_mask()) & BufferHubDefs::kHighBitsMask; | 
| Tianyu | f669f6a | 2018-10-10 15:34:32 -0700 | [diff] [blame] | 96 | while (!buffer_state_->compare_exchange_weak( | 
|  | 97 | current_buffer_state, updated_buffer_state, std::memory_order_acq_rel, | 
|  | 98 | std::memory_order_acquire)) { | 
| Tianyu Jiang | 727ede4 | 2019-02-01 11:44:51 -0800 | [diff] [blame] | 99 | if (!BufferHubDefs::isClientGained(current_buffer_state, | 
| Tianyu | f669f6a | 2018-10-10 15:34:32 -0700 | [diff] [blame] | 100 | client_state_mask())) { | 
|  | 101 | ALOGE( | 
|  | 102 | "%s: Failed to post the buffer. The buffer is no longer gained, " | 
| Tianyu Jiang | a99f911 | 2018-12-13 18:23:07 -0800 | [diff] [blame] | 103 | "id=%d state=%" PRIx32 ".", | 
| Tianyu | f669f6a | 2018-10-10 15:34:32 -0700 | [diff] [blame] | 104 | __FUNCTION__, id(), current_buffer_state); | 
|  | 105 | return -EBUSY; | 
|  | 106 | } | 
|  | 107 | } | 
|  | 108 |  | 
| Jiwen 'Steve' Cai | c6fcf2f | 2018-09-27 23:34:45 -0700 | [diff] [blame] | 109 | // Copy the canonical metadata. | 
|  | 110 | void* metadata_ptr = reinterpret_cast<void*>(&metadata_header_->metadata); | 
|  | 111 | memcpy(metadata_ptr, meta, sizeof(DvrNativeBufferMetadata)); | 
|  | 112 | // Copy extra user requested metadata. | 
|  | 113 | if (meta->user_metadata_ptr && meta->user_metadata_size) { | 
|  | 114 | void* metadata_src = reinterpret_cast<void*>(meta->user_metadata_ptr); | 
|  | 115 | memcpy(user_metadata_ptr_, metadata_src, meta->user_metadata_size); | 
|  | 116 | } | 
|  | 117 |  | 
|  | 118 | // Send out the acquire fence through the shared epoll fd. Note that during | 
|  | 119 | // posting no consumer is not expected to be polling on the fence. | 
|  | 120 | if (const int error = UpdateSharedFence(ready_fence, shared_acquire_fence_)) | 
|  | 121 | return error; | 
|  | 122 |  | 
| Jiwen 'Steve' Cai | c6fcf2f | 2018-09-27 23:34:45 -0700 | [diff] [blame] | 123 | return 0; | 
|  | 124 | } | 
|  | 125 |  | 
|  | 126 | int ProducerBuffer::Post(const LocalHandle& ready_fence, const void* meta, | 
|  | 127 | size_t user_metadata_size) { | 
|  | 128 | ATRACE_NAME("ProducerBuffer::Post"); | 
|  | 129 |  | 
|  | 130 | // Populate cononical metadata for posting. | 
|  | 131 | DvrNativeBufferMetadata canonical_meta; | 
|  | 132 | canonical_meta.user_metadata_ptr = reinterpret_cast<uint64_t>(meta); | 
|  | 133 | canonical_meta.user_metadata_size = user_metadata_size; | 
|  | 134 |  | 
|  | 135 | if (const int error = LocalPost(&canonical_meta, ready_fence)) | 
|  | 136 | return error; | 
|  | 137 |  | 
|  | 138 | return ReturnStatusOrError(InvokeRemoteMethod<BufferHubRPC::ProducerPost>( | 
|  | 139 | BorrowedFence(ready_fence.Borrow()))); | 
|  | 140 | } | 
|  | 141 |  | 
|  | 142 | int ProducerBuffer::PostAsync(const DvrNativeBufferMetadata* meta, | 
|  | 143 | const LocalHandle& ready_fence) { | 
|  | 144 | ATRACE_NAME("ProducerBuffer::PostAsync"); | 
|  | 145 |  | 
|  | 146 | if (const int error = LocalPost(meta, ready_fence)) | 
|  | 147 | return error; | 
|  | 148 |  | 
|  | 149 | return ReturnStatusOrError(SendImpulse(BufferHubRPC::ProducerPost::Opcode)); | 
|  | 150 | } | 
|  | 151 |  | 
|  | 152 | int ProducerBuffer::LocalGain(DvrNativeBufferMetadata* out_meta, | 
| Tianyu | 5465d6c | 2018-08-14 13:03:10 -0700 | [diff] [blame] | 153 | LocalHandle* out_fence, bool gain_posted_buffer) { | 
| Jiwen 'Steve' Cai | c6fcf2f | 2018-09-27 23:34:45 -0700 | [diff] [blame] | 154 | if (!out_meta) | 
|  | 155 | return -EINVAL; | 
|  | 156 |  | 
| Tianyu Jiang | a99f911 | 2018-12-13 18:23:07 -0800 | [diff] [blame] | 157 | uint32_t current_buffer_state = | 
| Tianyu | f669f6a | 2018-10-10 15:34:32 -0700 | [diff] [blame] | 158 | buffer_state_->load(std::memory_order_acquire); | 
| Tianyu Jiang | a99f911 | 2018-12-13 18:23:07 -0800 | [diff] [blame] | 159 | ALOGD_IF(TRACE, "%s: buffer=%d, state=%" PRIx32 ".", __FUNCTION__, id(), | 
| Tianyu | f669f6a | 2018-10-10 15:34:32 -0700 | [diff] [blame] | 160 | current_buffer_state); | 
|  | 161 |  | 
| Tianyu Jiang | 727ede4 | 2019-02-01 11:44:51 -0800 | [diff] [blame] | 162 | if (BufferHubDefs::isClientGained(current_buffer_state, | 
| Tianyu | f669f6a | 2018-10-10 15:34:32 -0700 | [diff] [blame] | 163 | client_state_mask())) { | 
| Tianyu Jiang | 8cfd42a | 2018-12-04 14:20:10 -0800 | [diff] [blame] | 164 | ALOGV("%s: already gained id=%d.", __FUNCTION__, id()); | 
| Tianyu Jiang | 60887c9 | 2018-11-14 15:52:38 -0800 | [diff] [blame] | 165 | return 0; | 
| Tianyu | 5465d6c | 2018-08-14 13:03:10 -0700 | [diff] [blame] | 166 | } | 
| Tianyu Jiang | 727ede4 | 2019-02-01 11:44:51 -0800 | [diff] [blame] | 167 | if (BufferHubDefs::isAnyClientAcquired(current_buffer_state) || | 
|  | 168 | BufferHubDefs::isAnyClientGained(current_buffer_state) || | 
|  | 169 | (BufferHubDefs::isAnyClientPosted( | 
| Tianyu Jiang | e60a4ad | 2019-01-04 14:37:23 -0800 | [diff] [blame] | 170 | current_buffer_state & | 
|  | 171 | active_clients_bit_mask_->load(std::memory_order_acquire)) && | 
| Tianyu | f669f6a | 2018-10-10 15:34:32 -0700 | [diff] [blame] | 172 | !gain_posted_buffer)) { | 
| Tianyu Jiang | a99f911 | 2018-12-13 18:23:07 -0800 | [diff] [blame] | 173 | ALOGE("%s: not released id=%d state=%" PRIx32 ".", __FUNCTION__, id(), | 
| Tianyu | f669f6a | 2018-10-10 15:34:32 -0700 | [diff] [blame] | 174 | current_buffer_state); | 
| Jiwen 'Steve' Cai | c6fcf2f | 2018-09-27 23:34:45 -0700 | [diff] [blame] | 175 | return -EBUSY; | 
|  | 176 | } | 
| Tianyu | f669f6a | 2018-10-10 15:34:32 -0700 | [diff] [blame] | 177 | // Change the buffer state to gained state. | 
| Tianyu Jiang | a99f911 | 2018-12-13 18:23:07 -0800 | [diff] [blame] | 178 | uint32_t updated_buffer_state = client_state_mask(); | 
| Tianyu | f669f6a | 2018-10-10 15:34:32 -0700 | [diff] [blame] | 179 | while (!buffer_state_->compare_exchange_weak( | 
|  | 180 | current_buffer_state, updated_buffer_state, std::memory_order_acq_rel, | 
|  | 181 | std::memory_order_acquire)) { | 
| Tianyu Jiang | 727ede4 | 2019-02-01 11:44:51 -0800 | [diff] [blame] | 182 | if (BufferHubDefs::isAnyClientAcquired(current_buffer_state) || | 
|  | 183 | BufferHubDefs::isAnyClientGained(current_buffer_state) || | 
|  | 184 | (BufferHubDefs::isAnyClientPosted( | 
| Tianyu Jiang | e60a4ad | 2019-01-04 14:37:23 -0800 | [diff] [blame] | 185 | current_buffer_state & | 
|  | 186 | active_clients_bit_mask_->load(std::memory_order_acquire)) && | 
| Tianyu | f669f6a | 2018-10-10 15:34:32 -0700 | [diff] [blame] | 187 | !gain_posted_buffer)) { | 
|  | 188 | ALOGE( | 
|  | 189 | "%s: Failed to gain the buffer. The buffer is no longer released. " | 
| Tianyu Jiang | a99f911 | 2018-12-13 18:23:07 -0800 | [diff] [blame] | 190 | "id=%d state=%" PRIx32 ".", | 
| Tianyu | f669f6a | 2018-10-10 15:34:32 -0700 | [diff] [blame] | 191 | __FUNCTION__, id(), current_buffer_state); | 
|  | 192 | return -EBUSY; | 
|  | 193 | } | 
|  | 194 | } | 
| Jiwen 'Steve' Cai | c6fcf2f | 2018-09-27 23:34:45 -0700 | [diff] [blame] | 195 |  | 
|  | 196 | // Canonical metadata is undefined on Gain. Except for user_metadata and | 
|  | 197 | // release_fence_mask. Fill in the user_metadata_ptr in address space of the | 
|  | 198 | // local process. | 
|  | 199 | if (metadata_header_->metadata.user_metadata_size && user_metadata_ptr_) { | 
|  | 200 | out_meta->user_metadata_size = | 
|  | 201 | metadata_header_->metadata.user_metadata_size; | 
|  | 202 | out_meta->user_metadata_ptr = | 
|  | 203 | reinterpret_cast<uint64_t>(user_metadata_ptr_); | 
|  | 204 | } else { | 
|  | 205 | out_meta->user_metadata_size = 0; | 
|  | 206 | out_meta->user_metadata_ptr = 0; | 
|  | 207 | } | 
|  | 208 |  | 
| Tianyu Jiang | a99f911 | 2018-12-13 18:23:07 -0800 | [diff] [blame] | 209 | uint32_t current_fence_state = fence_state_->load(std::memory_order_acquire); | 
|  | 210 | uint32_t current_active_clients_bit_mask = | 
| Tianyu | f669f6a | 2018-10-10 15:34:32 -0700 | [diff] [blame] | 211 | active_clients_bit_mask_->load(std::memory_order_acquire); | 
| Tianyu Jiang | ae9668c | 2018-12-07 15:14:47 -0800 | [diff] [blame] | 212 | // If there are release fence(s) from consumer(s), we need to return it to the | 
|  | 213 | // consumer(s). | 
| Tianyu | f669f6a | 2018-10-10 15:34:32 -0700 | [diff] [blame] | 214 | // TODO(b/112007999) add an atomic variable in metadata header in shared | 
|  | 215 | // memory to indicate which client is the last producer of the buffer. | 
|  | 216 | // Currently, assume the first client is the only producer to the buffer. | 
|  | 217 | if (current_fence_state & current_active_clients_bit_mask & | 
|  | 218 | (~BufferHubDefs::kFirstClientBitMask)) { | 
| Jiwen 'Steve' Cai | c6fcf2f | 2018-09-27 23:34:45 -0700 | [diff] [blame] | 219 | *out_fence = shared_release_fence_.Duplicate(); | 
| Tianyu Jiang | ae9668c | 2018-12-07 15:14:47 -0800 | [diff] [blame] | 220 | out_meta->release_fence_mask = current_fence_state & | 
|  | 221 | current_active_clients_bit_mask & | 
|  | 222 | (~BufferHubDefs::kFirstClientBitMask); | 
| Jiwen 'Steve' Cai | c6fcf2f | 2018-09-27 23:34:45 -0700 | [diff] [blame] | 223 | } | 
|  | 224 |  | 
| Jiwen 'Steve' Cai | c6fcf2f | 2018-09-27 23:34:45 -0700 | [diff] [blame] | 225 | return 0; | 
|  | 226 | } | 
|  | 227 |  | 
| Tianyu | 5465d6c | 2018-08-14 13:03:10 -0700 | [diff] [blame] | 228 | int ProducerBuffer::Gain(LocalHandle* release_fence, bool gain_posted_buffer) { | 
| Jiwen 'Steve' Cai | c6fcf2f | 2018-09-27 23:34:45 -0700 | [diff] [blame] | 229 | ATRACE_NAME("ProducerBuffer::Gain"); | 
|  | 230 |  | 
|  | 231 | DvrNativeBufferMetadata meta; | 
| Tianyu | 5465d6c | 2018-08-14 13:03:10 -0700 | [diff] [blame] | 232 | if (const int error = LocalGain(&meta, release_fence, gain_posted_buffer)) | 
| Jiwen 'Steve' Cai | c6fcf2f | 2018-09-27 23:34:45 -0700 | [diff] [blame] | 233 | return error; | 
|  | 234 |  | 
|  | 235 | auto status = InvokeRemoteMethod<BufferHubRPC::ProducerGain>(); | 
|  | 236 | if (!status) | 
|  | 237 | return -status.error(); | 
|  | 238 | return 0; | 
|  | 239 | } | 
|  | 240 |  | 
|  | 241 | int ProducerBuffer::GainAsync(DvrNativeBufferMetadata* out_meta, | 
| Tianyu | 5465d6c | 2018-08-14 13:03:10 -0700 | [diff] [blame] | 242 | LocalHandle* release_fence, | 
|  | 243 | bool gain_posted_buffer) { | 
| Jiwen 'Steve' Cai | c6fcf2f | 2018-09-27 23:34:45 -0700 | [diff] [blame] | 244 | ATRACE_NAME("ProducerBuffer::GainAsync"); | 
|  | 245 |  | 
| Tianyu | 5465d6c | 2018-08-14 13:03:10 -0700 | [diff] [blame] | 246 | if (const int error = LocalGain(out_meta, release_fence, gain_posted_buffer)) | 
| Jiwen 'Steve' Cai | c6fcf2f | 2018-09-27 23:34:45 -0700 | [diff] [blame] | 247 | return error; | 
|  | 248 |  | 
|  | 249 | return ReturnStatusOrError(SendImpulse(BufferHubRPC::ProducerGain::Opcode)); | 
|  | 250 | } | 
|  | 251 |  | 
|  | 252 | int ProducerBuffer::GainAsync() { | 
|  | 253 | DvrNativeBufferMetadata meta; | 
|  | 254 | LocalHandle fence; | 
|  | 255 | return GainAsync(&meta, &fence); | 
|  | 256 | } | 
|  | 257 |  | 
|  | 258 | std::unique_ptr<ProducerBuffer> ProducerBuffer::Import( | 
|  | 259 | LocalChannelHandle channel) { | 
|  | 260 | ALOGD_IF(TRACE, "ProducerBuffer::Import: channel=%d", channel.value()); | 
|  | 261 | return ProducerBuffer::Create(std::move(channel)); | 
|  | 262 | } | 
|  | 263 |  | 
|  | 264 | std::unique_ptr<ProducerBuffer> ProducerBuffer::Import( | 
|  | 265 | Status<LocalChannelHandle> status) { | 
|  | 266 | return Import(status ? status.take() | 
|  | 267 | : LocalChannelHandle{nullptr, -status.error()}); | 
|  | 268 | } | 
|  | 269 |  | 
|  | 270 | Status<LocalChannelHandle> ProducerBuffer::Detach() { | 
| Fan Xu | ddb90db | 2018-10-03 10:09:14 -0700 | [diff] [blame] | 271 | // TODO(b/112338294) remove after migrate producer buffer to binder | 
|  | 272 | ALOGW("ProducerBuffer::Detach: not supported operation during migration"); | 
|  | 273 | return {}; | 
|  | 274 |  | 
| Fan Xu | b0eec51 | 2018-10-30 11:33:15 -0700 | [diff] [blame] | 275 | // TODO(b/112338294) Keep here for reference. Remove it after new logic is | 
|  | 276 | // written. | 
| Tianyu Jiang | a99f911 | 2018-12-13 18:23:07 -0800 | [diff] [blame] | 277 | /* uint32_t buffer_state = buffer_state_->load(std::memory_order_acquire); | 
| Tianyu Jiang | 727ede4 | 2019-02-01 11:44:51 -0800 | [diff] [blame] | 278 | if (!BufferHubDefs::isClientGained( | 
| Tianyu | f669f6a | 2018-10-10 15:34:32 -0700 | [diff] [blame] | 279 | buffer_state, BufferHubDefs::kFirstClientStateMask)) { | 
| Jiwen 'Steve' Cai | c6fcf2f | 2018-09-27 23:34:45 -0700 | [diff] [blame] | 280 | // Can only detach a ProducerBuffer when it's in gained state. | 
| Tianyu Jiang | a99f911 | 2018-12-13 18:23:07 -0800 | [diff] [blame] | 281 | ALOGW("ProducerBuffer::Detach: The buffer (id=%d, state=0x%" PRIx32 | 
| Jiwen 'Steve' Cai | c6fcf2f | 2018-09-27 23:34:45 -0700 | [diff] [blame] | 282 | ") is not in gained state.", | 
|  | 283 | id(), buffer_state); | 
|  | 284 | return {}; | 
|  | 285 | } | 
|  | 286 |  | 
|  | 287 | Status<LocalChannelHandle> status = | 
|  | 288 | InvokeRemoteMethod<BufferHubRPC::ProducerBufferDetach>(); | 
|  | 289 | ALOGE_IF(!status, | 
|  | 290 | "ProducerBuffer::Detach: Failed to detach buffer (id=%d): %s.", id(), | 
|  | 291 | status.GetErrorMessage().c_str()); | 
| Fan Xu | b0eec51 | 2018-10-30 11:33:15 -0700 | [diff] [blame] | 292 | return status; */ | 
| Jiwen 'Steve' Cai | c6fcf2f | 2018-09-27 23:34:45 -0700 | [diff] [blame] | 293 | } | 
|  | 294 |  | 
|  | 295 | }  // namespace dvr | 
|  | 296 | }  // namespace android |