1e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko#include "uds/service_dispatcher.h"
2e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko
3e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko#include <errno.h>
4e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko#include <log/log.h>
5e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko#include <sys/epoll.h>
6e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko#include <sys/eventfd.h>
7e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko
8e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko#include "pdx/service.h"
9e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko#include "uds/service_endpoint.h"
10e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko
11e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenkostatic const int kMaxEventsPerLoop = 128;
12e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko
13e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenkonamespace android {
14e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenkonamespace pdx {
15e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenkonamespace uds {
16e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko
17e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenkostd::unique_ptr<pdx::ServiceDispatcher> ServiceDispatcher::Create() {
18e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  std::unique_ptr<ServiceDispatcher> dispatcher{new ServiceDispatcher()};
19e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  if (!dispatcher->epoll_fd_ || !dispatcher->event_fd_) {
20e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko    dispatcher.reset();
21e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  }
22e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko
23e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  return std::move(dispatcher);
24e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko}
25e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko
26e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex VakulenkoServiceDispatcher::ServiceDispatcher() {
27e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  event_fd_.Reset(eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK));
28e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  if (!event_fd_) {
29e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko    ALOGE("Failed to create event fd because: %s\n", strerror(errno));
30e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko    return;
31e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  }
32e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko
33409c6eecdca80e95a110d63bc70b1c2dfcf49100Alex Vakulenko  epoll_fd_.Reset(epoll_create1(EPOLL_CLOEXEC));
34e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  if (!epoll_fd_) {
35e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko    ALOGE("Failed to create epoll fd because: %s\n", strerror(errno));
36e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko    return;
37e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  }
38e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko
39e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  // Use "this" as a unique pointer to distinguish the event fd from all
40e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  // the other entries that point to instances of Service.
41e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  epoll_event event;
42e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  event.events = EPOLLIN;
43e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  event.data.ptr = this;
44e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko
45e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  if (epoll_ctl(epoll_fd_.Get(), EPOLL_CTL_ADD, event_fd_.Get(), &event) < 0) {
46e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko    ALOGE("Failed to add event fd to epoll fd because: %s\n", strerror(errno));
47e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko
48e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko    // Close the fds here and signal failure to the factory method.
49e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko    event_fd_.Close();
50e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko    epoll_fd_.Close();
51e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  }
52e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko}
53e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko
54e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex VakulenkoServiceDispatcher::~ServiceDispatcher() { SetCanceled(true); }
55e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko
56e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenkoint ServiceDispatcher::ThreadEnter() {
57e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  std::lock_guard<std::mutex> autolock(mutex_);
58e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko
59e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  if (canceled_)
60e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko    return -EBUSY;
61e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko
62e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  thread_count_++;
63e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  return 0;
64e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko}
65e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko
66e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenkovoid ServiceDispatcher::ThreadExit() {
67e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  std::lock_guard<std::mutex> autolock(mutex_);
68e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  thread_count_--;
69e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  condition_.notify_one();
70e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko}
71e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko
72e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenkoint ServiceDispatcher::AddService(const std::shared_ptr<Service>& service) {
73e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  if (service->endpoint()->GetIpcTag() != Endpoint::kIpcTag)
74e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko    return -EINVAL;
75e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko
76e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  std::lock_guard<std::mutex> autolock(mutex_);
77e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko
78e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  auto* endpoint = static_cast<Endpoint*>(service->endpoint());
79e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  epoll_event event;
80e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  event.events = EPOLLIN;
81e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  event.data.ptr = service.get();
82e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko
83e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  if (epoll_ctl(epoll_fd_.Get(), EPOLL_CTL_ADD, endpoint->epoll_fd(), &event) <
84e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko      0) {
85e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko    ALOGE("Failed to add service to dispatcher because: %s\n", strerror(errno));
86e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko    return -errno;
87e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  }
88e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko
89e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  services_.push_back(service);
90e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  return 0;
91e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko}
92e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko
93e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenkoint ServiceDispatcher::RemoveService(const std::shared_ptr<Service>& service) {
94e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  if (service->endpoint()->GetIpcTag() != Endpoint::kIpcTag)
95e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko    return -EINVAL;
96e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko
97e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  std::lock_guard<std::mutex> autolock(mutex_);
98e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko
99e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  // It's dangerous to remove a service while other threads may be using it.
100e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  if (thread_count_ > 0)
101e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko    return -EBUSY;
102e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko
103e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  epoll_event dummy;  // See BUGS in man 2 epoll_ctl.
104e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko
105e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  auto* endpoint = static_cast<Endpoint*>(service->endpoint());
106e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  if (epoll_ctl(epoll_fd_.Get(), EPOLL_CTL_DEL, endpoint->epoll_fd(), &dummy) <
107e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko      0) {
108e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko    ALOGE("Failed to remove service from dispatcher because: %s\n",
109e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko          strerror(errno));
110e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko    return -errno;
111e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  }
112e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko
113e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  services_.remove(service);
114e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  return 0;
115e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko}
116e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko
117e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenkoint ServiceDispatcher::ReceiveAndDispatch() { return ReceiveAndDispatch(-1); }
118e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko
119e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenkoint ServiceDispatcher::ReceiveAndDispatch(int timeout) {
120e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  int ret = ThreadEnter();
121e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  if (ret < 0)
122e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko    return ret;
123e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko
124e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  epoll_event events[kMaxEventsPerLoop];
125e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko
126e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  int count = epoll_wait(epoll_fd_.Get(), events, kMaxEventsPerLoop, timeout);
127e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  if (count <= 0) {
128e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko    ALOGE_IF(count < 0, "Failed to wait for epoll events because: %s\n",
129e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko             strerror(errno));
130e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko    ThreadExit();
131e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko    return count < 0 ? -errno : -ETIMEDOUT;
132e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  }
133e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko
134e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  for (int i = 0; i < count; i++) {
135e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko    if (events[i].data.ptr == this) {
136e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko      ThreadExit();
137e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko      return -EBUSY;
138e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko    } else {
139e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko      Service* service = static_cast<Service*>(events[i].data.ptr);
140e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko
141e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko      ALOGI_IF(TRACE, "Dispatching message: fd=%d\n",
142e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko               static_cast<Endpoint*>(service->endpoint())->epoll_fd());
143e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko      service->ReceiveAndDispatch();
144e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko    }
145e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  }
146e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko
147e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  ThreadExit();
148e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  return 0;
149e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko}
150e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko
151e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenkoint ServiceDispatcher::EnterDispatchLoop() {
152e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  int ret = ThreadEnter();
153e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  if (ret < 0)
154e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko    return ret;
155e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko
156e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  epoll_event events[kMaxEventsPerLoop];
157e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko
158e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  while (!IsCanceled()) {
159e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko    int count = epoll_wait(epoll_fd_.Get(), events, kMaxEventsPerLoop, -1);
160e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko    if (count < 0 && errno != EINTR) {
161e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko      ALOGE("Failed to wait for epoll events because: %s\n", strerror(errno));
162e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko      ThreadExit();
163e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko      return -errno;
164e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko    }
165e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko
166e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko    for (int i = 0; i < count; i++) {
167e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko      if (events[i].data.ptr == this) {
168e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko        ThreadExit();
169e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko        return -EBUSY;
170e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko      } else {
171e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko        Service* service = static_cast<Service*>(events[i].data.ptr);
172e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko
173e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko        ALOGI_IF(TRACE, "Dispatching message: fd=%d\n",
174e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko                 static_cast<Endpoint*>(service->endpoint())->epoll_fd());
175e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko        service->ReceiveAndDispatch();
176e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko      }
177e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko    }
178e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  }
179e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko
180e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  ThreadExit();
181e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  return 0;
182e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko}
183e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko
184e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenkovoid ServiceDispatcher::SetCanceled(bool cancel) {
185e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  std::unique_lock<std::mutex> lock(mutex_);
186e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  canceled_ = cancel;
187e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko
188e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  if (canceled_ && thread_count_ > 0) {
189e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko    eventfd_write(event_fd_.Get(), 1);  // Signal threads to quit.
190e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko
191e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko    condition_.wait(lock, [this] { return !(canceled_ && thread_count_ > 0); });
192e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko
193e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko    eventfd_t value;
194e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko    eventfd_read(event_fd_.Get(), &value);  // Unsignal.
195e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko  }
196e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko}
197e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko
198e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenkobool ServiceDispatcher::IsCanceled() const { return canceled_; }
199e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko
200e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko}  // namespace uds
201e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko}  // namespace pdx
202e4eec20f6263f4a42ae462456f60ea6c4518bb0aAlex Vakulenko}  // namespace android
203