1e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko#include "consumer_channel.h"
2e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko
34fe60582f314e381098f8f3bc2e39c5880e9243aAlex Vakulenko#include <log/log.h>
4e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko#include <utils/Trace.h>
5e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko
6e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko#include <thread>
7e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko
8e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko#include <private/dvr/bufferhub_rpc.h>
9e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko#include "producer_channel.h"
10e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko
11e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenkousing android::pdx::BorrowedHandle;
12e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenkousing android::pdx::Channel;
13d53870c58cbb3671c2efdae3cc53850b962aa9dcCorey Tabakausing android::pdx::ErrorStatus;
14e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenkousing android::pdx::Message;
15cd52dd9f1b301854b4e1734e3741d9cef8f784b1Corey Tabakausing android::pdx::Status;
16e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenkousing android::pdx::rpc::DispatchRemoteMethod;
17e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko
18e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenkonamespace android {
19e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenkonamespace dvr {
20e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko
21e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex VakulenkoConsumerChannel::ConsumerChannel(BufferHubService* service, int buffer_id,
2252ea25cf06cef250ec73052611b48556b3fce4d5Corey Tabaka                                 int channel_id, uint64_t consumer_state_bit,
23e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko                                 const std::shared_ptr<Channel> producer)
24e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko    : BufferHubChannel(service, buffer_id, channel_id, kConsumerType),
2552ea25cf06cef250ec73052611b48556b3fce4d5Corey Tabaka      consumer_state_bit_(consumer_state_bit),
26e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko      producer_(producer) {
27e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  GetProducer()->AddConsumer(this);
28e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko}
29e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko
30e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex VakulenkoConsumerChannel::~ConsumerChannel() {
313079cb7c32421099a73e1a774b228635eeb3f189Corey Tabaka  ALOGD_IF(TRACE,
323079cb7c32421099a73e1a774b228635eeb3f189Corey Tabaka           "ConsumerChannel::~ConsumerChannel: channel_id=%d buffer_id=%d",
333079cb7c32421099a73e1a774b228635eeb3f189Corey Tabaka           channel_id(), buffer_id());
34e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko
35e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  if (auto producer = GetProducer()) {
36e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko    producer->RemoveConsumer(this);
37e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  }
38e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko}
39e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko
40e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex VakulenkoBufferHubChannel::BufferInfo ConsumerChannel::GetBufferInfo() const {
41e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  BufferHubChannel::BufferInfo info;
42e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  if (auto producer = GetProducer()) {
43e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko    // If producer has not hung up, copy most buffer info from the producer.
44e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko    info = producer->GetBufferInfo();
4552ea25cf06cef250ec73052611b48556b3fce4d5Corey Tabaka  } else {
4652ea25cf06cef250ec73052611b48556b3fce4d5Corey Tabaka    info.signaled_mask = consumer_state_bit();
47e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  }
48e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  info.id = buffer_id();
49e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  return info;
50e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko}
51e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko
52e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenkostd::shared_ptr<ProducerChannel> ConsumerChannel::GetProducer() const {
53e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  return std::static_pointer_cast<ProducerChannel>(producer_.lock());
54e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko}
55e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko
56e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenkovoid ConsumerChannel::HandleImpulse(Message& message) {
57e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  ATRACE_NAME("ConsumerChannel::HandleImpulse");
58e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  switch (message.GetOp()) {
5952ea25cf06cef250ec73052611b48556b3fce4d5Corey Tabaka    case BufferHubRPC::ConsumerAcquire::Opcode:
6052ea25cf06cef250ec73052611b48556b3fce4d5Corey Tabaka      OnConsumerAcquire(message);
6152ea25cf06cef250ec73052611b48556b3fce4d5Corey Tabaka      break;
62e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko    case BufferHubRPC::ConsumerRelease::Opcode:
63e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko      OnConsumerRelease(message, {});
64e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko      break;
65e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  }
66e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko}
67e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko
68e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenkobool ConsumerChannel::HandleMessage(Message& message) {
69e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  ATRACE_NAME("ConsumerChannel::HandleMessage");
70e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  auto producer = GetProducer();
71e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  if (!producer)
72e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko    REPLY_ERROR_RETURN(message, EPIPE, true);
73e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko
74e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  switch (message.GetOp()) {
75e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko    case BufferHubRPC::GetBuffer::Opcode:
76e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko      DispatchRemoteMethod<BufferHubRPC::GetBuffer>(
7752ea25cf06cef250ec73052611b48556b3fce4d5Corey Tabaka          *this, &ConsumerChannel::OnGetBuffer, message);
78e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko      return true;
79e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko
80e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko    case BufferHubRPC::NewConsumer::Opcode:
81e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko      DispatchRemoteMethod<BufferHubRPC::NewConsumer>(
82e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko          *producer, &ProducerChannel::OnNewConsumer, message);
83e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko      return true;
84e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko
85e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko    case BufferHubRPC::ConsumerAcquire::Opcode:
86e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko      DispatchRemoteMethod<BufferHubRPC::ConsumerAcquire>(
87e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko          *this, &ConsumerChannel::OnConsumerAcquire, message);
88e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko      return true;
89e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko
90e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko    case BufferHubRPC::ConsumerRelease::Opcode:
91e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko      DispatchRemoteMethod<BufferHubRPC::ConsumerRelease>(
92e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko          *this, &ConsumerChannel::OnConsumerRelease, message);
93e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko      return true;
94e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko
95e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko    case BufferHubRPC::ConsumerSetIgnore::Opcode:
96e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko      DispatchRemoteMethod<BufferHubRPC::ConsumerSetIgnore>(
97e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko          *this, &ConsumerChannel::OnConsumerSetIgnore, message);
98e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko      return true;
99e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko
100e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko    default:
101e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko      return false;
102e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  }
103e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko}
104e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko
10552ea25cf06cef250ec73052611b48556b3fce4d5Corey TabakaStatus<BufferDescription<BorrowedHandle>> ConsumerChannel::OnGetBuffer(
10652ea25cf06cef250ec73052611b48556b3fce4d5Corey Tabaka    Message& /*message*/) {
10752ea25cf06cef250ec73052611b48556b3fce4d5Corey Tabaka  ATRACE_NAME("ConsumerChannel::OnGetBuffer");
10852ea25cf06cef250ec73052611b48556b3fce4d5Corey Tabaka  ALOGD_IF(TRACE, "ConsumerChannel::OnGetBuffer: buffer=%d", buffer_id());
10952ea25cf06cef250ec73052611b48556b3fce4d5Corey Tabaka  if (auto producer = GetProducer()) {
11052ea25cf06cef250ec73052611b48556b3fce4d5Corey Tabaka    return {producer->GetBuffer(consumer_state_bit_)};
11152ea25cf06cef250ec73052611b48556b3fce4d5Corey Tabaka  } else {
11252ea25cf06cef250ec73052611b48556b3fce4d5Corey Tabaka    return ErrorStatus(EPIPE);
11352ea25cf06cef250ec73052611b48556b3fce4d5Corey Tabaka  }
11452ea25cf06cef250ec73052611b48556b3fce4d5Corey Tabaka}
11552ea25cf06cef250ec73052611b48556b3fce4d5Corey Tabaka
11652ea25cf06cef250ec73052611b48556b3fce4d5Corey TabakaStatus<LocalFence> ConsumerChannel::OnConsumerAcquire(Message& message) {
117e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  ATRACE_NAME("ConsumerChannel::OnConsumerAcquire");
118e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  auto producer = GetProducer();
119e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  if (!producer)
120cd52dd9f1b301854b4e1734e3741d9cef8f784b1Corey Tabaka    return ErrorStatus(EPIPE);
121e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko
122d53870c58cbb3671c2efdae3cc53850b962aa9dcCorey Tabaka  if (acquired_ || released_) {
123e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko    ALOGE(
124e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko        "ConsumerChannel::OnConsumerAcquire: Acquire when not posted: "
125d53870c58cbb3671c2efdae3cc53850b962aa9dcCorey Tabaka        "ignored=%d acquired=%d released=%d channel_id=%d buffer_id=%d",
126d53870c58cbb3671c2efdae3cc53850b962aa9dcCorey Tabaka        ignored_, acquired_, released_, message.GetChannelId(),
127d53870c58cbb3671c2efdae3cc53850b962aa9dcCorey Tabaka        producer->buffer_id());
128cd52dd9f1b301854b4e1734e3741d9cef8f784b1Corey Tabaka    return ErrorStatus(EBUSY);
129e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  } else {
13052ea25cf06cef250ec73052611b48556b3fce4d5Corey Tabaka    auto status = producer->OnConsumerAcquire(message);
131d53870c58cbb3671c2efdae3cc53850b962aa9dcCorey Tabaka    if (status) {
132d53870c58cbb3671c2efdae3cc53850b962aa9dcCorey Tabaka      ClearAvailable();
133d53870c58cbb3671c2efdae3cc53850b962aa9dcCorey Tabaka      acquired_ = true;
134d53870c58cbb3671c2efdae3cc53850b962aa9dcCorey Tabaka    }
135d53870c58cbb3671c2efdae3cc53850b962aa9dcCorey Tabaka    return status;
136e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  }
137e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko}
138e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko
139cd52dd9f1b301854b4e1734e3741d9cef8f784b1Corey TabakaStatus<void> ConsumerChannel::OnConsumerRelease(Message& message,
140cd52dd9f1b301854b4e1734e3741d9cef8f784b1Corey Tabaka                                                LocalFence release_fence) {
141e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  ATRACE_NAME("ConsumerChannel::OnConsumerRelease");
142e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  auto producer = GetProducer();
143e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  if (!producer)
144cd52dd9f1b301854b4e1734e3741d9cef8f784b1Corey Tabaka    return ErrorStatus(EPIPE);
145e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko
146d53870c58cbb3671c2efdae3cc53850b962aa9dcCorey Tabaka  if (!acquired_ || released_) {
147e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko    ALOGE(
148e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko        "ConsumerChannel::OnConsumerRelease: Release when not acquired: "
149d53870c58cbb3671c2efdae3cc53850b962aa9dcCorey Tabaka        "ignored=%d acquired=%d released=%d channel_id=%d buffer_id=%d",
150d53870c58cbb3671c2efdae3cc53850b962aa9dcCorey Tabaka        ignored_, acquired_, released_, message.GetChannelId(),
151d53870c58cbb3671c2efdae3cc53850b962aa9dcCorey Tabaka        producer->buffer_id());
152cd52dd9f1b301854b4e1734e3741d9cef8f784b1Corey Tabaka    return ErrorStatus(EBUSY);
153e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  } else {
154cd52dd9f1b301854b4e1734e3741d9cef8f784b1Corey Tabaka    auto status =
155e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko        producer->OnConsumerRelease(message, std::move(release_fence));
156d53870c58cbb3671c2efdae3cc53850b962aa9dcCorey Tabaka    if (status) {
157d53870c58cbb3671c2efdae3cc53850b962aa9dcCorey Tabaka      ClearAvailable();
158d53870c58cbb3671c2efdae3cc53850b962aa9dcCorey Tabaka      acquired_ = false;
159d53870c58cbb3671c2efdae3cc53850b962aa9dcCorey Tabaka      released_ = true;
160d53870c58cbb3671c2efdae3cc53850b962aa9dcCorey Tabaka    }
161cd52dd9f1b301854b4e1734e3741d9cef8f784b1Corey Tabaka    return status;
162e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  }
163e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko}
164e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko
165cd52dd9f1b301854b4e1734e3741d9cef8f784b1Corey TabakaStatus<void> ConsumerChannel::OnConsumerSetIgnore(Message&, bool ignored) {
166e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  ATRACE_NAME("ConsumerChannel::OnConsumerSetIgnore");
167e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  auto producer = GetProducer();
168e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  if (!producer)
169cd52dd9f1b301854b4e1734e3741d9cef8f784b1Corey Tabaka    return ErrorStatus(EPIPE);
170e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko
171e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  ignored_ = ignored;
172d53870c58cbb3671c2efdae3cc53850b962aa9dcCorey Tabaka  if (ignored_ && acquired_) {
173e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko    // Update the producer if ignore is set after the consumer acquires the
174e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko    // buffer.
175e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko    ClearAvailable();
176e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko    producer->OnConsumerIgnored();
177d53870c58cbb3671c2efdae3cc53850b962aa9dcCorey Tabaka    acquired_ = false;
178d53870c58cbb3671c2efdae3cc53850b962aa9dcCorey Tabaka    released_ = true;
179e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  }
180e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko
181cd52dd9f1b301854b4e1734e3741d9cef8f784b1Corey Tabaka  return {};
182e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko}
183e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko
184e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenkobool ConsumerChannel::OnProducerPosted() {
185e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  if (ignored_) {
186d53870c58cbb3671c2efdae3cc53850b962aa9dcCorey Tabaka    acquired_ = false;
187d53870c58cbb3671c2efdae3cc53850b962aa9dcCorey Tabaka    released_ = true;
188e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko    return false;
189e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  } else {
190d53870c58cbb3671c2efdae3cc53850b962aa9dcCorey Tabaka    acquired_ = false;
191d53870c58cbb3671c2efdae3cc53850b962aa9dcCorey Tabaka    released_ = false;
192e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko    SignalAvailable();
193e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko    return true;
194e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  }
195e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko}
196e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko
197e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenkovoid ConsumerChannel::OnProducerClosed() {
198e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  producer_.reset();
199e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  Hangup();
200e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko}
201e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko
202e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko}  // namespace dvr
203e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko}  // namespace android
204