Merge changes for aosp release into master
Test: Local build
Change-Id: Ie043d33328c860b952d608b1e2ced705e2500f88
diff --git a/Android.bp b/Android.bp
new file mode 100644
index 0000000..5ad19cf
--- /dev/null
+++ b/Android.bp
@@ -0,0 +1,203 @@
+//
+// Copyright (C) 2017 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.
+
+subdirs = [
+ "common",
+ "guest",
+ "host",
+]
+
+cc_library_headers {
+ name: "cuttlefish_common_headers",
+ export_include_dirs: ["."],
+ host_supported: true,
+}
+
+// TODO(b/67435044) Update the include paths and remove this
+cc_library_headers {
+ name: "cuttlefish_glog",
+ export_include_dirs: ["common/libs"],
+ host_supported: true,
+}
+
+cc_defaults {
+ name: "cuttlefish_base",
+ gnu_extensions: false,
+ header_libs: [
+ "cuttlefish_common_headers",
+ "cuttlefish_kernel_headers",
+ ],
+ target: {
+ linux: {
+ host_ldlibs: ["-lrt"],
+ cflags: ["-DCUTTLEFISH_HOST"],
+ },
+ // We don't need 32 bit host-side builds
+ // TODO(ghartman): linux_glibc on master
+ linux_x86: {
+ enabled: false,
+ },
+ // We don't need Darwin host-side builds
+ darwin: {
+ enabled: false,
+ },
+ },
+ vendor: true,
+}
+
+// ARM code should not touch the VSoC window on an x86 CPU.
+cc_defaults {
+ name: "cuttlefish_native_isa",
+ target: {
+ android_arm: {
+ enabled: false,
+ },
+ },
+}
+
+cc_defaults {
+ name: "cuttlefish_host_only",
+ device_supported: false,
+ host_supported: true,
+ defaults: ["cuttlefish_base"],
+}
+
+cc_defaults {
+ name: "cuttlefish_host_and_guest",
+ host_supported: true,
+ defaults: ["cuttlefish_base"],
+}
+
+cc_library_shared {
+ name: "vsoc_lib",
+ srcs: [
+ "common/vsoc/lib/compat.cpp",
+ "common/vsoc/lib/e2e_test_region_layout.cpp",
+ "common/vsoc/lib/fb_bcast_layout.cpp",
+ "common/vsoc/lib/fb_bcast_region_view.cpp",
+ "common/vsoc/lib/gralloc_layout.cpp",
+ "common/vsoc/lib/lock_common.cpp",
+ "common/vsoc/lib/region_view.cpp",
+ "common/vsoc/lib/wifi_exchange_layout.cpp",
+ "common/vsoc/lib/wifi_exchange_view.cpp",
+ ],
+ header_libs: ["cuttlefish_glog"],
+ shared_libs: [
+ "libcuttlefish_fs",
+ "cuttlefish_auto_resources",
+ "libbase",
+ "liblog",
+ ],
+ target: {
+ //TODO(ghartman): linux_glibc on master
+ linux: {
+ srcs: [
+ "host/vsoc/lib/gralloc_buffer_region_view.cpp",
+ "host/vsoc/lib/host_lock.cpp",
+ "host/vsoc/lib/region_control.cpp",
+ "host/vsoc/lib/region_view.cpp",
+ ],
+ },
+ android: {
+ srcs: [
+ "guest/vsoc/lib/gralloc_region_view.cpp",
+ "guest/vsoc/lib/guest_lock.cpp",
+ "guest/vsoc/lib/region_control.cpp",
+ "guest/vsoc/lib/region_view.cpp",
+ ],
+ },
+ },
+ defaults: ["cuttlefish_host_and_guest", "cuttlefish_native_isa"],
+}
+
+cc_test_host {
+ name: "circqueue_test",
+ srcs: [
+ "common/vsoc/lib/circqueue_test.cpp",
+ ],
+ shared_libs: [
+ "vsoc_lib",
+ "libbase",
+ ],
+ defaults: ["cuttlefish_host_only"],
+}
+
+cc_test {
+ name: "vsoc_fb_bcast_region_view_test",
+ srcs: [
+ "common/vsoc/lib/fb_bcast_region_view_test.cpp",
+ ],
+ shared_libs: [
+ "vsoc_lib",
+ "libbase",
+ ],
+ target : {
+ linux_x86_64: {
+ static_libs: [
+ "libcuttlefish_host_config",
+ "libgflags",
+ ],
+ },
+ },
+ defaults: ["cuttlefish_host_and_guest", "cuttlefish_native_isa"],
+}
+
+cc_test_host {
+ name: "lock_test",
+ srcs: [
+ "common/vsoc/lib/lock_test.cpp",
+ ],
+ shared_libs: [
+ "vsoc_lib",
+ "libcuttlefish_fs",
+ "cuttlefish_auto_resources",
+ "libbase",
+ ],
+ static_libs: [
+ "libgtest_host",
+ ],
+ defaults: ["cuttlefish_host_only"],
+}
+
+cc_test_host {
+ name: "vsoc_graphics_test",
+ srcs: [
+ "common/vsoc/lib/graphics_test.cpp",
+ ],
+ shared_libs: [
+ "vsoc_lib",
+ "libbase",
+ ],
+ defaults: ["cuttlefish_host_only"],
+}
+
+cc_binary_host {
+ name: "host_region_e2e_test",
+ srcs: [
+ "host/vsoc/lib/host_region_e2e_test.cpp",
+ ],
+ shared_libs: [
+ "vsoc_lib",
+ "libcuttlefish_fs",
+ "cuttlefish_auto_resources",
+ "libbase",
+ ],
+ static_libs: [
+ "libcuttlefish_host_config",
+ "libgtest_host",
+ "libgflags",
+ ],
+ defaults: ["cuttlefish_host_only"],
+}
diff --git a/Android.mk b/Android.mk
new file mode 100644
index 0000000..c38d99b
--- /dev/null
+++ b/Android.mk
@@ -0,0 +1,17 @@
+# Copyright (C) 2017 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.
+ifneq ($(filter vsoc_x86 vsoc_x86_64, $(TARGET_DEVICE)),)
+LOCAL_PATH:= $(call my-dir)
+include $(call first-makefiles-under,$(LOCAL_PATH))
+endif
diff --git a/WORKSPACE b/WORKSPACE
deleted file mode 100644
index d192a98..0000000
--- a/WORKSPACE
+++ /dev/null
@@ -1 +0,0 @@
-workspace(name = "cuttlefish")
diff --git a/common/Android.bp b/common/Android.bp
new file mode 100644
index 0000000..646ec16
--- /dev/null
+++ b/common/Android.bp
@@ -0,0 +1,19 @@
+//
+// Copyright (C) 2017 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.
+
+subdirs = [
+ "commands",
+ "libs",
+]
diff --git a/common/commands/Android.bp b/common/commands/Android.bp
new file mode 100644
index 0000000..70311b6
--- /dev/null
+++ b/common/commands/Android.bp
@@ -0,0 +1,18 @@
+//
+// Copyright (C) 2017 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.
+
+subdirs = [
+ "wificlient",
+]
diff --git a/common/commands/wificlient/Android.bp b/common/commands/wificlient/Android.bp
new file mode 100644
index 0000000..067d085
--- /dev/null
+++ b/common/commands/wificlient/Android.bp
@@ -0,0 +1,19 @@
+cc_binary {
+ name: "wificlient",
+ srcs: [
+ "main.cc",
+ ],
+ shared_libs: [
+ "libbase",
+ "libcuttlefish_wifi",
+ "liblog",
+ "libnl",
+ ],
+ static_libs: [
+ "libgflags",
+ ],
+ header_libs: [
+ "cuttlefish_glog",
+ ],
+ defaults: ["cuttlefish_host_and_guest", "cuttlefish_native_isa"],
+}
diff --git a/common/commands/wificlient/main.cc b/common/commands/wificlient/main.cc
new file mode 100644
index 0000000..e060408
--- /dev/null
+++ b/common/commands/wificlient/main.cc
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include <gflags/gflags.h>
+#include <glog/logging.h>
+
+#include "common/libs/wifi/netlink.h"
+#include "common/libs/wifi/virtual_wifi.h"
+
+DEFINE_string(router, "cvd-wifirouter", "Path to WIFI Router Unix socket.");
+DEFINE_string(iface, "cf-wlan0", "Name of the WLAN interface to create.");
+DEFINE_string(macaddr, "00:43:56:44:80:01", "MAC address for new interface");
+DEFINE_string(wifirouter_socket, "cvd-wifirouter",
+ "Name of the wifirouter unix domain socket.");
+
+int main(int argc, char* argv[]) {
+ gflags::ParseCommandLineFlags(&argc, &argv, true);
+
+ std::unique_ptr<cvd::Netlink> nl(new cvd::Netlink(FLAGS_wifirouter_socket));
+ if (!nl->Init()) {
+ LOG(ERROR) << "Could not initialize netlink.";
+ exit(1);
+ }
+
+ std::unique_ptr<cvd::VirtualWIFI> radio(
+ new cvd::VirtualWIFI(nl.get(), FLAGS_iface, FLAGS_macaddr));
+ if (!radio->Init()) {
+ LOG(ERROR) << "Could not create radio.";
+ exit(1);
+ }
+
+ pause();
+}
diff --git a/common/libs/Android.bp b/common/libs/Android.bp
new file mode 100644
index 0000000..572e0fa
--- /dev/null
+++ b/common/libs/Android.bp
@@ -0,0 +1,23 @@
+//
+// Copyright (C) 2017 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.
+
+subdirs = [
+ "auto_resources",
+ "fs",
+ "net",
+ "threads",
+ "time",
+ "wifi",
+]
diff --git a/common/libs/auto_resources/Android.bp b/common/libs/auto_resources/Android.bp
new file mode 100644
index 0000000..2c575ce
--- /dev/null
+++ b/common/libs/auto_resources/Android.bp
@@ -0,0 +1,36 @@
+//
+// Copyright (C) 2017 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.
+
+cc_library_shared {
+ name: "cuttlefish_auto_resources",
+ srcs: [
+ "auto_resources.cpp",
+ ],
+ defaults: ["cuttlefish_host_and_guest"],
+}
+
+cc_test_host {
+ name: "auto_free_buffer_test",
+ srcs: [
+ "auto_free_buffer_test.cpp",
+ ],
+ shared_libs: [
+ "cuttlefish_auto_resources",
+ ],
+ static_libs: [
+ "libgmock",
+ ],
+ defaults: ["cuttlefish_host_only"],
+}
diff --git a/common/libs/auto_resources/BUILD b/common/libs/auto_resources/BUILD
deleted file mode 100644
index 3641e10..0000000
--- a/common/libs/auto_resources/BUILD
+++ /dev/null
@@ -1,23 +0,0 @@
-cc_library(
- name = "auto_resources",
- srcs = [
- "auto_resources.cpp",
- "auto_resources.h",
- ],
- hdrs = [
- "auto_resources.h",
- ],
- visibility = [ "//visibility:public" ],
-)
-
-cc_test(
- name = "auto_free_buffer_test",
- srcs = [
- "auto_free_buffer_test.cpp",
- ],
- deps = [
- ":auto_resources",
- "@gtest_repo//:gtest_main",
- ],
-)
-
diff --git a/common/libs/fs/Android.bp b/common/libs/fs/Android.bp
new file mode 100644
index 0000000..1522c50
--- /dev/null
+++ b/common/libs/fs/Android.bp
@@ -0,0 +1,28 @@
+//
+// Copyright (C) 2017 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.
+
+cc_library_shared {
+ name: "libcuttlefish_fs",
+ srcs: [
+ "gce_fs.cpp",
+ "shared_fd.cpp",
+ ],
+ shared_libs: [
+ "cuttlefish_auto_resources",
+ "libbase",
+ "liblog",
+ ],
+ defaults: ["cuttlefish_host_and_guest"],
+}
diff --git a/common/libs/fs/BUILD b/common/libs/fs/BUILD
deleted file mode 100644
index 2e25c4d..0000000
--- a/common/libs/fs/BUILD
+++ /dev/null
@@ -1,20 +0,0 @@
-cc_library(
- name = "fs",
- visibility = ["//visibility:public"],
- srcs = [
- "gce_fs.cpp",
- "gce_fs.h",
- "shared_fd.cpp",
- "shared_fd.h",
- "shared_select.h",
- ],
- hdrs = [
- "gce_fs.h",
- "shared_fd.h",
- "shared_select.h",
- ],
- deps = [
- "//common/libs/auto_resources",
- ],
-)
-
diff --git a/common/libs/fs/gce_fs.cpp b/common/libs/fs/gce_fs.cpp
index b2afff5..db8ce33 100644
--- a/common/libs/fs/gce_fs.cpp
+++ b/common/libs/fs/gce_fs.cpp
@@ -32,7 +32,7 @@
#include <stdlib.h>
#include <dirent.h>
-#include <glog/logging.h>
+#include "common/libs/glog/logging.h"
#define ALL_PERMS (S_ISUID | S_ISGID | S_ISVTX | S_IRWXU | S_IRWXG | S_IRWXO)
#define BUF_SIZE 64
diff --git a/common/libs/fs/shared_fd.cpp b/common/libs/fs/shared_fd.cpp
index 8028bd0..6546d0a 100644
--- a/common/libs/fs/shared_fd.cpp
+++ b/common/libs/fs/shared_fd.cpp
@@ -23,17 +23,14 @@
#include <netinet/in.h>
#include <unistd.h>
-#define LOG_TAG "SharedFD"
-
-#include <glog/logging.h>
-
#include "common/libs/auto_resources/auto_resources.h"
+#include "common/libs/glog/logging.h"
#include "common/libs/fs/shared_select.h"
// #define ENABLE_GCE_SHARED_FD_LOGGING 1
namespace {
-using avd::SharedFDSet;
+using cvd::SharedFDSet;
void MarkAll(const SharedFDSet& input, fd_set* dest, int* max_index) {
for (SharedFDSet::const_iterator it = input.begin(); it != input.end();
@@ -42,17 +39,13 @@
}
}
-void CheckMarked(fd_set* in_out_mask, fd_set* error_mask,
- SharedFDSet* in_out_set, SharedFDSet* error_set) {
+void CheckMarked(fd_set* in_out_mask, SharedFDSet* in_out_set) {
if (!in_out_set) {
return;
}
SharedFDSet save;
save.swap(in_out_set);
for (SharedFDSet::iterator it = save.begin(); it != save.end(); ++it) {
- if (error_set && (*it)->IsSet(error_mask)) {
- error_set->Set(*it);
- }
if ((*it)->IsSet(in_out_mask)) {
in_out_set->Set(*it);
}
@@ -60,7 +53,7 @@
}
} // namespace
-namespace avd {
+namespace cvd {
bool FileInstance::CopyFrom(FileInstance& in) {
AutoFreeBuffer buffer;
@@ -119,11 +112,10 @@
#if ENABLE_GCE_SHARED_FD_LOGGING
void FileInstance::Log(const char* message) {
- static int log_fd = open("/dev/null", O_WRONLY|O_APPEND|O_CREAT, 0666);
- write(log_fd, message, strlen(message));
+ LOG(INFO) << message;
}
#else
-void FileInstance::Log(const char*) { }
+void FileInstance::Log(const char*) {}
#endif
void FileInstance::Set(fd_set* dest, int* max_index) const {
@@ -136,7 +128,6 @@
FD_SET(fd_, dest);
}
-
int Select(SharedFDSet* read_set, SharedFDSet* write_set,
SharedFDSet* error_set, struct timeval* timeout) {
int max_index = 0;
@@ -152,20 +143,21 @@
}
fd_set errorfds;
FD_ZERO(&errorfds);
- int rval = TEMP_FAILURE_RETRY(select(
- max_index, &readfds, &writefds, &errorfds, timeout));
- FileInstance::Log("select\n");
if (error_set) {
- error_set->Zero();
+ MarkAll(*error_set, &errorfds, &max_index);
}
- CheckMarked(&readfds, &errorfds, read_set, error_set);
- CheckMarked(&writefds, &errorfds, write_set, error_set);
+
+ int rval = TEMP_FAILURE_RETRY(
+ select(max_index, &readfds, &writefds, &errorfds, timeout));
+ FileInstance::Log("select\n");
+ CheckMarked(&readfds, read_set);
+ CheckMarked(&writefds, write_set);
+ CheckMarked(&errorfds, error_set);
return rval;
}
-static void MakeAddress(
- const char* name, bool abstract, struct sockaddr_un* dest,
- socklen_t* len) {
+static void MakeAddress(const char* name, bool abstract,
+ struct sockaddr_un* dest, socklen_t* len) {
memset(dest, 0, sizeof(*dest));
dest->sun_family = AF_UNIX;
// sun_path is NOT expected to be nul-terminated.
@@ -198,15 +190,30 @@
return SocketLocalClient(name, false, SOCK_SEQPACKET);
}
-SharedFD SharedFD::Accept(const FileInstance& listener,
- struct sockaddr* addr, socklen_t *addrlen) {
- return SharedFD(std::shared_ptr<FileInstance>(listener.Accept(addr, addrlen)));
+SharedFD SharedFD::TimerFD(int clock, int flags) {
+ int fd = timerfd_create(clock, flags);
+ if (fd == -1) {
+ return SharedFD(std::shared_ptr<FileInstance>(new FileInstance(fd, errno)));
+ } else {
+ return SharedFD(std::shared_ptr<FileInstance>(new FileInstance(fd, 0)));
+ }
+}
+
+SharedFD SharedFD::Accept(const FileInstance& listener, struct sockaddr* addr,
+ socklen_t* addrlen) {
+ return SharedFD(
+ std::shared_ptr<FileInstance>(listener.Accept(addr, addrlen)));
}
SharedFD SharedFD::Accept(const FileInstance& listener) {
return SharedFD::Accept(listener, NULL, NULL);
}
+SharedFD SharedFD::Dup(int unmanaged_fd) {
+ int fd = dup(unmanaged_fd);
+ return SharedFD(std::shared_ptr<FileInstance>(new FileInstance(fd, errno)));
+}
+
bool SharedFD::Pipe(SharedFD* fd0, SharedFD* fd1) {
int fds[2];
int rval = pipe(fds);
@@ -218,14 +225,21 @@
return false;
}
-SharedFD SharedFD::Event() {
- return std::shared_ptr<FileInstance>(new FileInstance(eventfd(0, 0), errno));
+SharedFD SharedFD::Event(int initval, int flags) {
+ return std::shared_ptr<FileInstance>(
+ new FileInstance(eventfd(initval, flags), errno));
}
-inline bool SharedFD::SocketPair(int domain, int type, int protocol, SharedFD* fd0, SharedFD* fd1){
+SharedFD SharedFD::Epoll(int flags) {
+ return std::shared_ptr<FileInstance>(
+ new FileInstance(epoll_create1(flags), errno));
+}
+
+inline bool SharedFD::SocketPair(int domain, int type, int protocol,
+ SharedFD* fd0, SharedFD* fd1) {
int fds[2];
int rval = socketpair(domain, type, protocol, fds);
- if(rval != -1) {
+ if (rval != -1) {
(*fd0) = std::shared_ptr<FileInstance>(new FileInstance(fds[0], errno));
(*fd1) = std::shared_ptr<FileInstance>(new FileInstance(fds[1], errno));
return true;
@@ -251,49 +265,8 @@
}
}
-SharedFD SharedFD::SocketInAddrAnyServer(int in_port, int in_type) {
- errno = 0;
-
- // TODO(ender): this code is very similar to SocketSeqPacketServer
- struct sockaddr_in6 addr = {
- AF_INET6, // sin6_family
- htons(in_port), // sin6_port
- 0, // sin6_flowinfo
- in6addr_any, // sin6_addr
- 0, // sin6_scope_id
- };
-
- SharedFD rval = SharedFD::Socket(PF_INET6, in_type, 0);
- if (!rval->IsOpen()) {
- return rval;
- }
-
- int n = 1;
- if (rval->SetSockOpt(SOL_SOCKET, SO_REUSEADDR, &n, sizeof(n)) == -1) {
- LOG(ERROR) << "SetSockOpt failed " << rval->StrError();
- return SharedFD(std::shared_ptr<FileInstance>(
- new FileInstance(-1, rval->GetErrno())));
- }
- if (rval->Bind((struct sockaddr *) &addr, sizeof(addr)) == -1) {
- LOG(ERROR) << "Bind failed; port=" << in_port << ": " << rval->StrError();
- return SharedFD(std::shared_ptr<FileInstance>(
- new FileInstance(-1, rval->GetErrno())));
- }
-
- if (in_type == SOCK_STREAM) {
- // Follows the default from socket_local_server
- if (rval->Listen(1) == -1) {
- LOG(ERROR) << "Listen failed: " << rval->StrError();
- return SharedFD(std::shared_ptr<FileInstance>(
- new FileInstance(-1, rval->GetErrno())));
- }
- }
-
- return rval;
-}
-
-SharedFD SharedFD::SocketLocalClient(
- const char* name, bool abstract, int in_type) {
+SharedFD SharedFD::SocketLocalClient(const char* name, bool abstract,
+ int in_type) {
struct sockaddr_un addr;
socklen_t addrlen;
MakeAddress(name, abstract, &addr, &addrlen);
@@ -301,16 +274,47 @@
if (!rval->IsOpen()) {
return rval;
}
- if (rval->Connect((struct sockaddr *) &addr, addrlen) == -1) {
+ if (rval->Connect((struct sockaddr*)&addr, addrlen) == -1) {
LOG(ERROR) << "Connect failed; name=" << name << ": " << rval->StrError();
- return SharedFD(std::shared_ptr<FileInstance>(
- new FileInstance(-1, rval->GetErrno())));
+ return SharedFD(
+ std::shared_ptr<FileInstance>(new FileInstance(-1, rval->GetErrno())));
}
return rval;
}
-SharedFD SharedFD::SocketLocalServer(
- const char* name, bool abstract, int in_type, mode_t mode) {
+SharedFD SharedFD::SocketLocalServer(int port, int type) {
+ struct sockaddr_in addr;
+ memset(&addr, 0, sizeof(addr));
+ addr.sin_family = AF_INET;
+ addr.sin_port = htons(port);
+ addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+ SharedFD rval = SharedFD::Socket(AF_INET, type, 0);
+ if(!rval->IsOpen()) {
+ return rval;
+ }
+ int n = 1;
+ if (rval->SetSockOpt(SOL_SOCKET, SO_REUSEADDR, &n, sizeof(n)) == -1) {
+ LOG(ERROR) << "SetSockOpt failed " << rval->StrError();
+ return SharedFD(
+ std::shared_ptr<FileInstance>(new FileInstance(-1, rval->GetErrno())));
+ }
+ if(rval->Bind((struct sockaddr *) &addr, sizeof(addr)) < 0) {
+ LOG(ERROR) << "Bind failed " << rval->StrError();
+ return SharedFD(
+ std::shared_ptr<FileInstance>(new FileInstance(-1, rval->GetErrno())));
+ }
+ if (type == SOCK_STREAM) {
+ if (rval->Listen(4) < 0) {
+ LOG(ERROR) << "Listen failed " << rval->StrError();
+ return SharedFD(std::shared_ptr<FileInstance>(
+ new FileInstance(-1, rval->GetErrno())));
+ }
+ }
+ return rval;
+}
+
+SharedFD SharedFD::SocketLocalServer(const char* name, bool abstract,
+ int in_type, mode_t mode) {
// DO NOT UNLINK addr.sun_path. It does NOT have to be null-terminated.
// See man 7 unix for more details.
if (!abstract) (void)unlink(name);
@@ -326,13 +330,13 @@
int n = 1;
if (rval->SetSockOpt(SOL_SOCKET, SO_REUSEADDR, &n, sizeof(n)) == -1) {
LOG(ERROR) << "SetSockOpt failed " << rval->StrError();
- return SharedFD(std::shared_ptr<FileInstance>(
- new FileInstance(-1, rval->GetErrno())));
+ return SharedFD(
+ std::shared_ptr<FileInstance>(new FileInstance(-1, rval->GetErrno())));
}
- if (rval->Bind((struct sockaddr *) &addr, addrlen) == -1) {
+ if (rval->Bind((struct sockaddr*)&addr, addrlen) == -1) {
LOG(ERROR) << "Bind failed; name=" << name << ": " << rval->StrError();
- return SharedFD(std::shared_ptr<FileInstance>(
- new FileInstance(-1, rval->GetErrno())));
+ return SharedFD(
+ std::shared_ptr<FileInstance>(new FileInstance(-1, rval->GetErrno())));
}
/* Only the bottom bits are really the socket type; there are flags too. */
@@ -357,4 +361,4 @@
return rval;
}
-} // namespace avd
+} // namespace cvd
diff --git a/common/libs/fs/shared_fd.h b/common/libs/fs/shared_fd.h
index 1d8a956..79f5405 100644
--- a/common/libs/fs/shared_fd.h
+++ b/common/libs/fs/shared_fd.h
@@ -13,16 +13,22 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
+// TODO: We can't use std::shared_ptr on the older guests due to HALs.
+
#ifndef CUTTLEFISH_COMMON_COMMON_LIBS_FS_SHARED_FD_H_
#define CUTTLEFISH_COMMON_COMMON_LIBS_FS_SHARED_FD_H_
+#include <sys/epoll.h>
#include <sys/eventfd.h>
#include <sys/ioctl.h>
-#include <sys/socket.h>
+#include <sys/mman.h>
#include <sys/select.h>
-#include <sys/time.h>
+#include <sys/socket.h>
#include <sys/types.h>
#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/timerfd.h>
#include <sys/uio.h>
#include <sys/un.h>
@@ -54,11 +60,31 @@
* it makes it easier to convert existing code to SharedFDs and avoids the
* possibility that new POSIX functionality will lead to large refactorings.
*/
-namespace avd {
+namespace cvd {
class FileInstance;
/**
+ * Describes the fields in msghdr that are honored by the *MsgAndFDs
+ * calls.
+ */
+struct InbandMessageHeader {
+ void* msg_name;
+ socklen_t msg_namelen;
+ struct iovec* msg_iov;
+ size_t msg_iovlen;
+ int msg_flags;
+
+ void Convert(struct msghdr* dest) const {
+ dest->msg_name = msg_name;
+ dest->msg_namelen = msg_namelen;
+ dest->msg_iov = msg_iov;
+ dest->msg_iovlen = msg_iovlen;
+ dest->msg_flags = msg_flags;
+ }
+};
+
+/**
* Counted reference to a FileInstance.
*
* This is also the place where most new FileInstances are created. The creation
@@ -104,61 +130,47 @@
class SharedFD {
public:
inline SharedFD();
- SharedFD(const std::shared_ptr<FileInstance>& in) : value_(in) { }
+ SharedFD(const std::shared_ptr<FileInstance>& in) : value_(in) {}
// Reference the listener as a FileInstance to make this FD type agnostic.
- static SharedFD Accept(const FileInstance& listener,
- struct sockaddr* addr, socklen_t* addrlen);
+ static SharedFD Accept(const FileInstance& listener, struct sockaddr* addr,
+ socklen_t* addrlen);
static SharedFD Accept(const FileInstance& listener);
+ static SharedFD Dup(int unmanaged_fd);
static SharedFD GetControlSocket(const char* name);
// Returns false on failure, true on success.
static SharedFD Open(const char* pathname, int flags, mode_t mode = 0);
static bool Pipe(SharedFD* fd0, SharedFD* fd1);
- static SharedFD Event();
- static bool SocketPair(int domain, int type, int protocol, SharedFD* fd0, SharedFD* fd1);
+ static SharedFD Event(int initval = 0, int flags = 0);
+ static SharedFD Epoll(int flags = 0);
+ static bool SocketPair(int domain, int type, int protocol, SharedFD* fd0,
+ SharedFD* fd1);
static SharedFD Socket(int domain, int socket_type, int protocol);
- static SharedFD SocketInAddrAnyServer(int in_port, int in_type);
- static SharedFD SocketLocalClient(
- const char* name, bool is_abstract, int in_type);
- static SharedFD SocketLocalServer(
- const char* name, bool is_abstract, int in_type, mode_t mode);
+ static SharedFD SocketLocalClient(const char* name, bool is_abstract,
+ int in_type);
+ static SharedFD SocketLocalServer(const char* name, bool is_abstract,
+ int in_type, mode_t mode);
+ static SharedFD SocketLocalServer(int port, int type);
static SharedFD SocketSeqPacketServer(const char* name, mode_t mode);
static SharedFD SocketSeqPacketClient(const char* name);
+ static SharedFD TimerFD(int clock, int flags);
- bool operator==(const SharedFD& rhs) const {
- return value_ == rhs.value_;
- }
+ bool operator==(const SharedFD& rhs) const { return value_ == rhs.value_; }
- bool operator!=(const SharedFD& rhs) const {
- return value_ != rhs.value_;
- }
+ bool operator!=(const SharedFD& rhs) const { return value_ != rhs.value_; }
- bool operator<(const SharedFD& rhs) const {
- return value_ < rhs.value_;
- }
+ bool operator<(const SharedFD& rhs) const { return value_ < rhs.value_; }
- bool operator<=(const SharedFD& rhs) const {
- return value_ <= rhs.value_;
- }
+ bool operator<=(const SharedFD& rhs) const { return value_ <= rhs.value_; }
- bool operator>(const SharedFD& rhs) const {
- return value_ > rhs.value_;
- }
+ bool operator>(const SharedFD& rhs) const { return value_ > rhs.value_; }
- bool operator>=(const SharedFD& rhs) const {
- return value_ >= rhs.value_;
- }
+ bool operator>=(const SharedFD& rhs) const { return value_ >= rhs.value_; }
- std::shared_ptr<FileInstance> operator->() const {
- return value_;
- }
+ std::shared_ptr<FileInstance> operator->() const { return value_; }
- const avd::FileInstance& operator*() const {
- return *value_;
- }
+ const cvd::FileInstance& operator*() const { return *value_; }
- avd::FileInstance& operator*() {
- return *value_;
- }
+ cvd::FileInstance& operator*() { return *value_; }
private:
std::shared_ptr<FileInstance> value_;
@@ -180,24 +192,23 @@
class FileInstance {
// Give SharedFD access to the aliasing constructor.
friend class SharedFD;
+
public:
- virtual ~FileInstance() {
- Close();
- }
+ virtual ~FileInstance() { Close(); }
// This can't be a singleton because our shared_ptr's aren't thread safe.
static std::shared_ptr<FileInstance> ClosedInstance() {
return std::shared_ptr<FileInstance>(new FileInstance(-1, EBADF));
}
- int Bind(const struct sockaddr *addr, socklen_t addrlen) {
+ int Bind(const struct sockaddr* addr, socklen_t addrlen) {
errno = 0;
int rval = bind(fd_, addr, addrlen);
errno_ = errno;
return rval;
}
- int Connect(const struct sockaddr *addr, socklen_t addrlen) {
+ int Connect(const struct sockaddr* addr, socklen_t addrlen) {
errno = 0;
int rval = connect(fd_, addr, addrlen);
errno_ = errno;
@@ -219,6 +230,22 @@
return rval;
}
+ int EpollCtl(int op, cvd::SharedFD new_fd, struct epoll_event* event) {
+ errno = 0;
+ int rval = TEMP_FAILURE_RETRY(
+ epoll_ctl(fd_, op, new_fd->fd_, event));
+ errno_ = errno;
+ return rval;
+ }
+
+ int EpollWait(struct epoll_event* events, int maxevents, int timeout) {
+ errno = 0;
+ int rval = TEMP_FAILURE_RETRY(
+ epoll_wait(fd_, events, maxevents, timeout));
+ errno_ = errno;
+ return rval;
+ }
+
int Fchown(uid_t owner, gid_t group) {
errno = 0;
int rval = TEMP_FAILURE_RETRY(fchown(fd_, owner, group));
@@ -240,9 +267,7 @@
return rval;
}
- int GetErrno() const {
- return errno_;
- }
+ int GetErrno() const { return errno_; }
int GetSockOpt(int level, int optname, void* optval, socklen_t* optlen) {
errno = 0;
@@ -255,16 +280,14 @@
void Identify(const char* identity);
- int Ioctl(int request) {
+ int Ioctl(int request, void* val = nullptr) {
errno = 0;
- int rval = TEMP_FAILURE_RETRY(ioctl(fd_, request));
+ int rval = TEMP_FAILURE_RETRY(ioctl(fd_, request, val));
errno_ = errno;
return rval;
}
- bool IsOpen() const {
- return fd_ != -1;
- }
+ bool IsOpen() const { return fd_ != -1; }
// in probably isn't modified, but the API spec doesn't have const.
bool IsSet(fd_set* in) const;
@@ -285,6 +308,20 @@
return rval;
}
+ void* Mmap(void* addr, size_t length, int prot, int flags, off_t offset) {
+ errno = 0;
+ void* rval = mmap(addr, length, prot, flags, fd_, offset);
+ errno_ = errno;
+ return rval;
+ }
+
+ ssize_t Pread(void* buf, size_t count, off_t offset) {
+ errno = 0;
+ ssize_t rval = TEMP_FAILURE_RETRY(pread(fd_, buf, count, offset));
+ errno_ = errno;
+ return rval;
+ }
+
ssize_t Recv(void* buf, size_t len, int flags) {
errno = 0;
ssize_t rval = TEMP_FAILURE_RETRY(recv(fd_, buf, len, flags));
@@ -295,8 +332,8 @@
ssize_t RecvFrom(void* buf, size_t len, int flags, struct sockaddr* src_addr,
socklen_t* addr_len) {
errno = 0;
- ssize_t rval = TEMP_FAILURE_RETRY(recvfrom(fd_, buf, len, flags, src_addr,
- addr_len));
+ ssize_t rval =
+ TEMP_FAILURE_RETRY(recvfrom(fd_, buf, len, flags, src_addr, addr_len));
errno_ = errno;
return rval;
}
@@ -308,6 +345,36 @@
return rval;
}
+ template <size_t SZ>
+ ssize_t RecvMsgAndFDs(const struct InbandMessageHeader& msg_in, int flags,
+ SharedFD (*new_fds)[SZ]) {
+ // We need to make some modifications to land the fds. Make it clear
+ // that there are no updates to the msg being passed in during this call.
+ struct msghdr msg;
+ msg_in.Convert(&msg);
+ union {
+ char buffer[CMSG_SPACE(SZ * sizeof(int))];
+ struct cmsghdr this_aligns_buffer;
+ } u;
+ msg.msg_control = u.buffer;
+ msg.msg_controllen = sizeof(u.buffer);
+
+ cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
+ cmsg->cmsg_len = CMSG_LEN(SZ * sizeof(int));
+ cmsg->cmsg_level = SOL_SOCKET;
+ cmsg->cmsg_type = SCM_RIGHTS;
+ int* fd_array = reinterpret_cast<int*>(CMSG_DATA(cmsg));
+ for (size_t i = 0; i < SZ; ++i) {
+ fd_array[i] = -1;
+ }
+ ssize_t rval = RecvMsg(&msg, flags);
+ for (size_t i = 0; i < SZ; ++i) {
+ (*new_fds)[i] =
+ std::shared_ptr<FileInstance>(new FileInstance(fd_array[i], errno));
+ }
+ return rval;
+ }
+
ssize_t Read(void* buf, size_t count) {
errno = 0;
ssize_t rval = TEMP_FAILURE_RETRY(read(fd_, buf, count));
@@ -329,18 +396,41 @@
return rval;
}
- ssize_t SendTo(const void *buf, size_t len, int flags,
- const struct sockaddr *dest_addr, socklen_t addrlen) {
+ template <size_t SZ>
+ ssize_t SendMsgAndFDs(const struct InbandMessageHeader& msg_in, int flags,
+ const SharedFD (&fds)[SZ]) {
+ struct msghdr msg;
+ msg_in.Convert(&msg);
+ union {
+ char buffer[CMSG_SPACE(SZ * sizeof(int))];
+ struct cmsghdr this_aligns_buffer;
+ } u;
+ msg.msg_control = u.buffer;
+ msg.msg_controllen = sizeof(u.buffer);
+
+ cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
+ cmsg->cmsg_len = CMSG_LEN(SZ * sizeof(int));
+ cmsg->cmsg_level = SOL_SOCKET;
+ cmsg->cmsg_type = SCM_RIGHTS;
+ int* fd_array = reinterpret_cast<int*>(CMSG_DATA(cmsg));
+ for (int i = 0; i < SZ; ++i) {
+ fd_array[i] = fds[i]->fd_;
+ }
+ return SendMsg(&msg, flags);
+ }
+
+ ssize_t SendTo(const void* buf, size_t len, int flags,
+ const struct sockaddr* dest_addr, socklen_t addrlen) {
errno = 0;
- ssize_t rval = TEMP_FAILURE_RETRY(sendto(
- fd_, buf, len, flags, dest_addr, addrlen));
+ ssize_t rval =
+ TEMP_FAILURE_RETRY(sendto(fd_, buf, len, flags, dest_addr, addrlen));
errno_ = errno;
return rval;
}
void Set(fd_set* dest, int* max_index) const;
- int SetSockOpt(int level, int optname, const void *optval, socklen_t optlen) {
+ int SetSockOpt(int level, int optname, const void* optval, socklen_t optlen) {
errno = 0;
int rval = setsockopt(fd_, level, optname, optval, optlen);
errno_ = errno;
@@ -358,11 +448,33 @@
// buf, or a pointer to some (immutable) static string (in which case buf
// is unused).
if (out != s->strerror_buf_) {
- strncpy(out, s->strerror_buf_, sizeof(strerror_buf_));
+ strncpy(s->strerror_buf_, out, sizeof(strerror_buf_));
}
return strerror_buf_;
}
+ int TimerGet(struct itimerspec* curr_value) {
+ errno = 0;
+ int rval = timerfd_gettime(fd_, curr_value);
+ errno_ = errno;
+ return rval;
+ }
+
+ int TimerSet(int flags, const struct itimerspec* new_value,
+ struct itimerspec* old_value) {
+ errno = 0;
+ int rval = timerfd_settime(fd_, flags, new_value, old_value);
+ errno_ = errno;
+ return rval;
+ }
+
+ ssize_t Truncate(off_t length) {
+ errno = 0;
+ ssize_t rval = TEMP_FAILURE_RETRY(ftruncate(fd_, length));
+ errno_ = errno;
+ return rval;
+ }
+
ssize_t Write(const void* buf, size_t count) {
errno = 0;
ssize_t rval = TEMP_FAILURE_RETRY(write(fd_, buf, count));
@@ -382,7 +494,7 @@
identity_.PrintF("fd=%d @%p", fd, this);
}
- FileInstance* Accept(struct sockaddr* addr, socklen_t *addrlen) const {
+ FileInstance* Accept(struct sockaddr* addr, socklen_t* addrlen) const {
int fd = TEMP_FAILURE_RETRY(accept(fd_, addr, addrlen));
if (fd == -1) {
return new FileInstance(fd, errno);
@@ -400,8 +512,8 @@
/* Methods that need both a fully defined SharedFD and a fully defined
FileInstance. */
-SharedFD::SharedFD() : value_(FileInstance::ClosedInstance()) { }
+SharedFD::SharedFD() : value_(FileInstance::ClosedInstance()) {}
-}
+} // namespace cvd
#endif // CUTTLEFISH_COMMON_COMMON_LIBS_FS_SHARED_FD_H_
diff --git a/common/libs/fs/shared_fd_test.cpp b/common/libs/fs/shared_fd_test.cpp
new file mode 100644
index 0000000..3423619
--- /dev/null
+++ b/common/libs/fs/shared_fd_test.cpp
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#include "common/libs/fs/shared_fd.h"
+#include "common/libs/fs/shared_select.h"
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <gtest/gtest.h>
+
+#include <string>
+
+using cvd::InbandMessageHeader;
+using cvd::SharedFD;
+
+char hello[] = "Hello, world!";
+char pipe_message[] = "Testing the pipe";
+
+TEST(SendFD, Basic) {
+ char dirname[] = "/tmp/sfdtestXXXXXX";
+ char* socket = mkdtemp(dirname);
+ EXPECT_TRUE(socket != NULL);
+ std::string path(dirname);
+ path += "/s";
+ SharedFD server = SharedFD::SocketSeqPacketServer(path.c_str(), 0700);
+ EXPECT_TRUE(server->IsOpen());
+ int rval = fork();
+ EXPECT_NE(-1, rval);
+ if (!rval) {
+ struct iovec iov { hello, sizeof(hello) };
+ SharedFD client = SharedFD::SocketSeqPacketClient(path.c_str());
+ InbandMessageHeader hdr{};
+ hdr.msg_iov = &iov;
+ hdr.msg_iovlen = 1;
+ SharedFD fds[2];
+ SharedFD::Pipe(fds, fds + 1);
+ ssize_t rval = client->SendMsgAndFDs(hdr, 0, fds);
+ printf("SendMsg sent %zd (%s)\n", rval, client->StrError());
+ exit(0);
+ }
+ server->Listen(2);
+ SharedFD peer = SharedFD::Accept(*server);
+ EXPECT_TRUE(peer->IsOpen());
+ char buf[80];
+ struct iovec iov { buf, sizeof(buf) };
+ InbandMessageHeader hdr{};
+ hdr.msg_iov = &iov;
+ hdr.msg_iovlen = 1;
+ SharedFD fds[2];
+ peer->RecvMsgAndFDs(hdr, 0, &fds);
+ EXPECT_EQ(0, strcmp(buf, hello));
+ EXPECT_TRUE(fds[0]->IsOpen());
+ EXPECT_TRUE(fds[1]->IsOpen());
+ EXPECT_EQ(sizeof(pipe_message), fds[1]->Write(pipe_message, sizeof(pipe_message)));
+ EXPECT_EQ(sizeof(pipe_message), fds[0]->Read(buf, sizeof(buf)));
+ EXPECT_EQ(0, strcmp(buf, pipe_message));
+}
diff --git a/common/libs/fs/shared_select.h b/common/libs/fs/shared_select.h
index 1298d74..20bfb5a 100644
--- a/common/libs/fs/shared_select.h
+++ b/common/libs/fs/shared_select.h
@@ -20,7 +20,7 @@
#include "common/libs/fs/shared_fd.h"
-namespace avd {
+namespace cvd {
/**
* The SharedFD version of fdset for the Select call.
*
@@ -76,6 +76,6 @@
int Select(SharedFDSet* read_set, SharedFDSet* write_set,
SharedFDSet* error_set, struct timeval* timeout);
-} // namespace avd
+} // namespace cvd
#endif // CUTTLEFISH_COMMON_COMMON_LIBS_FS_SHARED_SELECT_H_
diff --git a/common/libs/metadata/gce_resource_location.h b/common/libs/glog/logging.h
similarity index 60%
rename from common/libs/metadata/gce_resource_location.h
rename to common/libs/glog/logging.h
index 5b7f38a..8cd4a8a 100644
--- a/common/libs/metadata/gce_resource_location.h
+++ b/common/libs/glog/logging.h
@@ -1,3 +1,4 @@
+#pragma once
/*
* Copyright (C) 2016 The Android Open Source Project
*
@@ -13,14 +14,19 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-#ifndef CUTTLEFISH_COMMON_COMMON_LIBS_METADATA_GCE_RESOURCE_LOCATION_H_
-#define CUTTLEFISH_COMMON_COMMON_LIBS_METADATA_GCE_RESOURCE_LOCATION_H_
-class GceResourceLocation {
- public:
- static const char* const kInitialMetadataPath;
- static const char* const kInitialFstabPath;
- static const char* const kDevicePersonalitiesPath;
-};
+#ifdef ANDROID
+#include <android-base/logging.h>
-#endif // CUTTLEFISH_COMMON_COMMON_LIBS_METADATA_GCE_RESOURCE_LOCATION_H_
+#if defined(CUTTLEFISH_HOST)
+using ::android::base::VERBOSE;
+using ::android::base::INFO;
+using ::android::base::WARNING;
+using ::android::base::FATAL;
+
+#define LOG_IF(LEVEL, CONDITION) if (CONDITION) LOG(LEVEL)
+
+#endif // CUTTLEFISH_HOST
+#else // DEBIAN_HOST (by elimination)
+#include <glog/logging.h>
+#endif
diff --git a/common/libs/metadata/BUILD b/common/libs/metadata/BUILD
deleted file mode 100644
index 8cc8ac0..0000000
--- a/common/libs/metadata/BUILD
+++ /dev/null
@@ -1,106 +0,0 @@
-cc_library(
- name = "constants",
- visibility = [ "//visibility:public" ],
- srcs = [
- "gce_metadata_attributes.cpp",
- "gce_metadata_attributes.h",
- "gce_resource_location.cpp",
- "gce_resource_location.h",
- ],
- hdrs = [
- "gce_metadata_attributes.h",
- "gce_resource_location.h",
- ],
-)
-
-cc_library(
- name = "initial_metadata_reader",
- visibility = [ "//visibility:public" ],
- srcs = [
- "display_properties.cpp",
- "display_properties.h",
- "initial_metadata_reader.h",
- "initial_metadata_reader_impl.cpp",
- "initial_metadata_reader_impl.h",
- "metadata_query.cpp",
- "metadata_query.h",
- ],
- hdrs = [
- "display_properties.h",
- "initial_metadata_reader.h",
- "metadata_query.h",
- ],
- copts = [
- "-I/usr/include/jsoncpp",
- ],
- linkopts = [
- "-ljsoncpp",
- ],
- deps = [
- "@glog_repo//:glog",
- ":constants",
- "//common/libs/auto_resources",
- "//common/libs/fs",
- ],
-)
-
-cc_library(
- name = "initial_metadata_reader-static",
- visibility = [ "//visibility:public" ],
- srcs = [
- "display_properties.cpp",
- "display_properties.h",
- "initial_metadata_reader.h",
- "initial_metadata_reader_impl.cpp",
- "initial_metadata_reader_impl.h",
- "metadata_query.cpp",
- "metadata_query.h",
- ],
- hdrs = [
- "display_properties.h",
- "initial_metadata_reader.h",
- "metadata_query.h",
- ],
- copts = [
- "-I/usr/include/jsoncpp",
- ],
- linkopts = [
- "-Wl,-Bstatic -ljsoncpp",
- ],
- linkstatic = 1,
- deps = [
- "@glog_repo//:glog",
- ":constants",
- "//common/libs/auto_resources",
- "//common/libs/fs",
- ],
-)
-
-
-cc_library(
- name = "partitions",
- visibility = [ "//visibility:public" ],
- srcs = [
- "get_partition_num.cpp",
- "get_partition_num.h",
- ],
- hdrs = [
- "get_partition_num.h",
- ],
- deps = [
- "//common/libs/auto_resources",
- ],
-)
-
-cc_test(
- name = "initial_metadata_reader_test",
- srcs = [
- "initial_metadata_reader_test.cpp",
- ],
- copts = [
- "-I/usr/include/jsoncpp",
- ],
- deps = [
- ":initial_metadata_reader",
- ],
-)
diff --git a/common/libs/metadata/display_properties.cpp b/common/libs/metadata/display_properties.cpp
deleted file mode 100644
index 587daff..0000000
--- a/common/libs/metadata/display_properties.cpp
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2016 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.
- */
-#include "common/libs/metadata/display_properties.h"
-
-#include <stdio.h>
-
-namespace avd {
-void DisplayProperties::Parse(const char* value) {
- if (!value) {
- return;
- }
- int xres, yres, unused_bits_per_pixel, dpi;
- int rval = sscanf(
- value, "%dx%dx%dx%d", &xres, &yres, &unused_bits_per_pixel, &dpi);
- // bits_per_pixel isn't really controllable, so do something sensible
- // if people stop setting it.
- if (rval == 3) {
- dpi = unused_bits_per_pixel;
- } else if (rval != 4) {
- return;
- }
- if ((xres < 0) || (yres < 0) || (dpi < 0)) {
- return;
- }
- x_res_ = xres;
- y_res_ = yres;
- // Bits per pixel is fixed at 32 in our devices.
- // bits_per_pixel_ = bits_per_pixel;
- dpi_ = dpi;
- default_ = false;
- config_.SetToString(value);
-}
-} // namespace avd
diff --git a/common/libs/metadata/display_properties.h b/common/libs/metadata/display_properties.h
deleted file mode 100644
index 0d02445..0000000
--- a/common/libs/metadata/display_properties.h
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2016 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 CUTTLEFISH_COMMON_COMMON_LIBS_METADATA_DISPLAY_PROPERTIES_H_
-#define CUTTLEFISH_COMMON_COMMON_LIBS_METADATA_DISPLAY_PROPERTIES_H_
-
-#include "common/libs/auto_resources/auto_resources.h"
-
-namespace avd {
-
-class DisplayProperties {
- public:
- DisplayProperties() :
- x_res_(1280),
- y_res_(720),
- bits_per_pixel_(32),
- dpi_(160),
- default_(true) {
- config_.SetToString("1280x720x32x160");
- }
-
- void Parse(const char* value);
-
- int GetXRes() const { return x_res_; }
- int GetYRes() const { return y_res_; }
- int GetBitsPerPixel() const { return bits_per_pixel_; }
- int GetDpi() const { return dpi_; }
- bool IsDefault() const { return default_; }
- const char* GetConfig() const { return config_.data(); }
-
- private:
- // Screen width in pixels
- int x_res_;
- // Screen height in pixels
- int y_res_;
- // Depth of the screen (obsolete)
- int bits_per_pixel_;
- // Pixels per inch
- int dpi_;
- // Default
- bool default_;
- // Unparsed configuration
- AutoFreeBuffer config_;
-};
-
-} // namespace avd
-#endif // CUTTLEFISH_COMMON_COMMON_LIBS_METADATA_DISPLAY_PROPERTIES_H_
diff --git a/common/libs/metadata/gce_metadata_attributes.cpp b/common/libs/metadata/gce_metadata_attributes.cpp
deleted file mode 100644
index 7ee5ac6..0000000
--- a/common/libs/metadata/gce_metadata_attributes.cpp
+++ /dev/null
@@ -1,268 +0,0 @@
-/*
- * Copyright (C) 2016 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.
- */
-#include "common/libs/metadata/gce_metadata_attributes.h"
-
-const char* const GceMetadataAttributes::kTestingPath = "testing/attributes/";
-
-const char* const GceMetadataAttributes::kInstancePath = "instance/attributes/";
-
-const char* const GceMetadataAttributes::kProjectPath = "project/attributes/";
-
-// Version of Android to boot on multiboot images.
-// Updates at boot time.
-// Example: 23_gce_x86_64_phone
-// When in doubt leave blank or use default
-const char* const GceMetadataAttributes::kAndroidVersionKey = "android_version";
-
-// Configuration of the back camera.
-// *** OBSOLETE ***
-// Updates at boot time.
-// Example: 1,768,1024,checker-sliding
-// NOTE: To escape the commas with gcloud use
-// --metadata-from-file camera_back=<(echo 1,768,1024,checker-sliding)
-const char* const GceMetadataAttributes::kBackCameraConfigKey = "camera_back";
-
-// Content of (not a path to) an extra init file
-// Updates at boot time.
-const char* const GceMetadataAttributes::kCustomInitFileKey =
- "custom_init_file";
-
-// Configuration of the display
-// *** OBSOLETE ***
-// Updates at boot time.
-// Example: 800x1280x16x213
-// Format is ${x_res}x${y_res}x${depth}x${dpi}
-// Note: depth is currently ignored. 32 is the forward-compatible value.
-const char* const GceMetadataAttributes::kDisplayConfigurationKey =
- "cfg_sta_display_resolution";
-
-// Configuration of the front camera.
-// Updates at boot time.
-// Example: 1,768,1024,checker-sliding
-// Note: To escape the commas with gcloud use
-// --metadata-from-file camera_front=<(echo 1,768,1024,checker-sliding)
-const char* const GceMetadataAttributes::kFrontCameraConfigKey = "camera_front";
-
-// Current GPS location.
-// Updates at runtime.
-// Example: -122.0840859,37.4224504,0,0,0,10
-// The fields are:
-// Logitude in degress with decimal subdegrees
-// Latitude in degress with decimal subdegrees
-// Altitude in meters
-// Heading in degress
-// Speed in meters per second
-// Precision in meters
-const char* const GceMetadataAttributes::kGpsCoordinatesKey = "gps_coordinates";
-
-// Initial locale of the device.
-// Updates at boot time.
-// Example: en_US
-// If this attribute is set any existing locale is forgotten and the new
-// one is set.
-// If no value is set then the existing locale, if any, remains.
-const char* const GceMetadataAttributes::kInitialLocaleKey =
- "cfg_sta_initial_locale";
-
-// State of Wireless LAN.
-// Updates at runtime.
-// Example: ENABLED
-// Accepted values are: ( ENABLED, DISABLED ). If value is neither of these, the
-// handler falls back to default state, DISABLED.
-const char* const GceMetadataAttributes::kWlanStateKey = "cfg_sta_wlan_state";
-
-// Configuration of the physical keyboard (if any).
-// Updates at boot time.
-// If the attribute is not specified will enable physical keyboard.
-// Values accepted:
-// 1: Use a physical keyboard.
-// All other values: disable the physical keyboard.
-const char* const GceMetadataAttributes::kPhysicalKeyboardKey =
- "physical_keyboard";
-
-// Configuration of hardware buttons.
-// *** OBSOLETE ***
-// Updates at boot time.
-// If the attribute is not specified will enable all hardware buttons.
-// Values accepted:
-// Empty string: disable all hardware buttons.
-// MENU: enable only the menu button.
-// BACK: enable only the back button.
-// HOME: enable only the home button.
-// Any combinion of the above values delimited by commas.
-const char* const GceMetadataAttributes::kPhysicalNavigationButtonsKey =
- "physical_navigation_buttons";
-
-// Path to the device node to mount as a system overlay.
-// Updates at boot time.
-// If absent no overlay is mounted.
-// Example: /dev/block/sdb1
-const char* const GceMetadataAttributes::kSystemOverlayDeviceKey =
- "system_overlay_device";
-
-// Device personality definition in JSON format.
-// Allows custom device features to be set.
-// If absent - not used.
-const char* const GceMetadataAttributes::kDevicePersonalityDefinitionKey =
- "device_personality_definition";
-
-// Device personality name.
-// Holds name (without path or extension) of the file describing device
-// features. File describing features should be located in
-// GceResourceLocation::kDevicePersonalityPath.
-// If empty, not present, or invalid falls back to 'default'.
-const char* const GceMetadataAttributes::kDevicePersonalityNameKey =
- "device_personality_name";
-
-// Controls which streaming protocol will be used by the remoter.
-// Values include:
-// appstreaming
-// vnc
-// If nothing is set the remoter behaves as if vnc is selected.
-const char* const GceMetadataAttributes::kRemotingModeKey =
- "cfg_sta_remoting_mode";
-
-// Controls the scaling used in the VNC backend. The the device is
-// configured to 320 dpi but this is set to 160, the result will be
-// as if a scaling factor of 0.5 were used.
-const char* const GceMetadataAttributes::kConsoleDpiScalingDpiKey =
- "cfg_sta_console_scaling_dpi";
-
-// Controls how the pixels will be scaled before they are sent over VNC.
-// Numbers are floating point and must be <= 1.0 If nothing is set then
-// 1.0 is assumed.
-// Note: VNC doesn't cope well with unusual scaling factors: it tends to
-// clip the right / bottom off the screen. 0.5 and 0.25 appear to be safe.
-// If nothing is set then 1.0 is assumed.
-const char* const GceMetadataAttributes::kConsoleScalingFactorKey =
- "cfg_sta_console_scaling_factor";
-
-// Controls whether metadata attribute injections are permitted from
-// within Android.
-// The only legitimate purpose of this attribute is testing.
-// Not all attributes are permitted.
-const char* const GceMetadataAttributes::kTestAllowMetadataInjectionsKey =
- "cfg_test_allow_metadata_injections";
-
-// Controls which RIL implementation will be used during next boot.
-// Values include:
-// - TESTING = use testing version of GCE RIL (if available).
-// - DEPRECATED = use previous version of GCE RIL (if available).
-// - DEFAULT / unset / other = use current version of GCE RIL.
-const char* const GceMetadataAttributes::kRilVersionKey = "ril_version";
-
-// Controls which Hardware composer implementation will be used during next
-// boot.
-// Values include:
-// - TESTING = use testing version of GCE Hardware Composer (if available).
-// - DEPRECATED = use previous version of GCE Hardware Composer (if available).
-// - DEFAULT / unset / other = use current version of GCE Hardware Composer.
-const char* const GceMetadataAttributes::kHWComposerVersionKey = "hwc_version";
-
-// Controls which VNC implementation will be used during next boot
-// Values include:
-// - TESTING = use testing version of the GCE VNC server (if available)
-// - DEPRECATED = use previous version of the GCE VNC server (if available).
-// - DEFAULT / unset / other = use current version of the GCE VNC server
-const char* const GceMetadataAttributes::kVncServerVersionKey =
- "vnc_server_version";
-
-const char* const GceMetadataAttributes::kSshKeysInstancePath =
- "instance/attributes/sshKeys";
-const char* const GceMetadataAttributes::kSshKeysProjectPath =
- "project/attributes/sshKeys";
-
-const char* const GceMetadataAttributes::kInjectedIntentInstancePath =
- "instance/attributes/t_force_intent";
-const char* const GceMetadataAttributes::kInjectedIntentProjectPath =
- "project/attributes/t_force_intent";
-
-const char* const GceMetadataAttributes::kForceCoarseOrientationChangeInstancePath =
- "instance/attributes/t_force_orientation";
-const char* const GceMetadataAttributes::kForceCoarseOrientationChangeProjectPath =
- "project/attributes/t_force_orientation";
-const char* const GceMetadataAttributes::kForceCoarseOrientationChangeTestingPath =
- "testing/attributes/t_force_orientation";
-
-const char* const GceMetadataAttributes::kPropertyMapperInstancePath =
- "instance/attributes/cfg_sta_pmap_";
-const char* const GceMetadataAttributes::kPropertyMapperProjectPath =
- "project/attributes/cfg_sta_pmap_";
-
-const char* const GceMetadataAttributes::kAccelerometerSensorInstancePath =
- "instance/attributes/t_sensor_accelerometer";
-const char* const GceMetadataAttributes::kAccelerometerSensorProjectPath =
- "project/attributes/t_sensor_accelerometer";
-
-const char* const GceMetadataAttributes::kGyroscopeSensorInstancePath =
- "instance/attributes/t_sensor_gyroscope";
-const char* const GceMetadataAttributes::kGyroscopeSensorProjectPath =
- "project/attributes/t_sensor_gyroscope";
-
-const char* const GceMetadataAttributes::kLightSensorInstancePath =
- "instance/attributes/t_sensor_light";
-const char* const GceMetadataAttributes::kLightSensorProjectPath =
- "project/attributes/t_sensor_light";
-
-const char* const GceMetadataAttributes::kMagneticFieldSensorInstancePath =
- "instance/attributes/t_sensor_magnetic_field";
-const char* const GceMetadataAttributes::kMagneticFieldSensorProjectPath =
- "project/attributes/t_sensor_magnetic_field";
-
-const char* const GceMetadataAttributes::kPressureSensorInstancePath =
- "instance/attributes/t_sensor_pressure";
-const char* const GceMetadataAttributes::kPressureSensorProjectPath =
- "project/attributes/t_sensor_pressure";
-
-const char* const GceMetadataAttributes::kProximitySensorInstancePath =
- "instance/attributes/t_sensor_proximity";
-const char* const GceMetadataAttributes::kProximitySensorProjectPath =
- "project/attributes/t_sensor_proximity";
-
-const char* const GceMetadataAttributes::kAmbientTemperatureSensorInstancePath =
- "instance/attributes/t_sensor_ambient_temperature";
-const char* const GceMetadataAttributes::kAmbientTemperatureSensorProjectPath =
- "project/attributes/t_sensor_ambient_temperature";
-
-const char* const GceMetadataAttributes::kDeviceTemperatureSensorInstancePath =
- "instance/attributes/t_sensor_device_temperature";
-const char* const GceMetadataAttributes::kDeviceTemperatureSensorProjectPath =
- "project/attributes/t_sensor_device_temperature";
-
-const char* const GceMetadataAttributes::kRelativeHumiditySensorInstancePath =
- "instance/attributes/t_sensor_relative_humidity";
-const char* const GceMetadataAttributes::kRelativeHumiditySensorProjectPath =
- "project/attributes/t_sensor_relative_humidity";
-const char* const GceMetadataAttributes::kAutoScreenshotFrequencyInstancePath =
- "instance/attributes/t_auto_screenshot_frequency";
-const char* const GceMetadataAttributes::kAutoScreenshotPrefixInstancePath =
- "instance/attributes/t_auto_screenshot_prefix";
-const char* const GceMetadataAttributes::kRebootIfMissingInstancePath =
- "instance/attributes/t_reboot_if_missing_path";
-
-const char* const GceMetadataAttributes::kPowerBatteryConfigPath =
- "instance/attributes/power_battery_config";
-
-const char* const GceMetadataAttributes::kMobileDataNetworkingConfigPath =
- "instance/attributes/mobile_data_networking_config";
-// IMEI value which must be set before the system starts to boot. If not,
-// a randomly generated value will be used by the GCE RIL module.
-const char* const GceMetadataAttributes::kIMEIConfigKey = "imei";
-
-const char* const GceMetadataAttributes::kScreenshotsDirectoryInstancePath =
- "instance/attributes/t_screenshot_dir_path";
-const char* const GceMetadataAttributes::kScreenshotsDirectoryProjectPath =
- "project/attributes/t_screenshot_dir_path";
diff --git a/common/libs/metadata/gce_metadata_attributes.h b/common/libs/metadata/gce_metadata_attributes.h
deleted file mode 100644
index ee65735..0000000
--- a/common/libs/metadata/gce_metadata_attributes.h
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright (C) 2016 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 CUTTLEFISH_COMMON_COMMON_LIBS_METADATA_GCE_METADATA_ATTRIBUTES_H_
-#define CUTTLEFISH_COMMON_COMMON_LIBS_METADATA_GCE_METADATA_ATTRIBUTES_H_
-
-// Constant string declarations for GCE metadata attributes used by the Remoter.
-class GceMetadataAttributes {
-public:
- static const char* const kTestingPath;
- static const char* const kInstancePath;
- static const char* const kProjectPath;
-
- // The InitialMetadataReader reasons in terms of keys, while the
- // MetadataService uses full paths.
- // A key represents two paths, one for the instance and one for the project.
- // When both paths are present the instance value takes precedence.
- //
- // TODO(ghartman): Refactor the MetadataService to use keys as well.
- static const char* const kAndroidVersionKey;
- static const char* const kBackCameraConfigKey;
- static const char* const kConsoleDpiScalingDpiKey;
- static const char* const kConsoleScalingFactorKey;
- static const char* const kCustomInitFileKey;
- static const char* const kDevicePersonalityDefinitionKey;
- static const char* const kDevicePersonalityNameKey;
- static const char* const kDisplayConfigurationKey;
- static const char* const kFrontCameraConfigKey;
- static const char* const kGpsCoordinatesKey;
- static const char* const kInitialLocaleKey;
- static const char* const kWlanStateKey;
- static const char* const kPhysicalKeyboardKey;
- static const char* const kPhysicalNavigationButtonsKey;
- static const char* const kRemotingModeKey;
- static const char* const kSystemOverlayDeviceKey;
- static const char* const kRilVersionKey;
- static const char* const kHWComposerVersionKey;
- static const char* const kVncServerVersionKey;
-
- static const char* const kTestAllowMetadataInjectionsKey;
-
- static const char* const kSshKeysInstancePath;
- static const char* const kSshKeysProjectPath;
- static const char* const kInjectedIntentInstancePath;
- static const char* const kInjectedIntentProjectPath;
- static const char* const kForceCoarseOrientationChangeTestingPath;
- static const char* const kForceCoarseOrientationChangeInstancePath;
- static const char* const kForceCoarseOrientationChangeProjectPath;
- static const char* const kPropertyMapperInstancePath;
- static const char* const kPropertyMapperProjectPath;
-
- static const char* const kAccelerometerSensorInstancePath;
- static const char* const kAccelerometerSensorProjectPath;
- static const char* const kGyroscopeSensorInstancePath;
- static const char* const kGyroscopeSensorProjectPath;
- static const char* const kLightSensorInstancePath;
- static const char* const kLightSensorProjectPath;
- static const char* const kMagneticFieldSensorInstancePath;
- static const char* const kMagneticFieldSensorProjectPath;
- static const char* const kPressureSensorInstancePath;
- static const char* const kPressureSensorProjectPath;
- static const char* const kProximitySensorInstancePath;
- static const char* const kProximitySensorProjectPath;
- static const char* const kDeviceTemperatureSensorInstancePath;
- static const char* const kDeviceTemperatureSensorProjectPath;
- static const char* const kAmbientTemperatureSensorInstancePath;
- static const char* const kAmbientTemperatureSensorProjectPath;
- static const char* const kRelativeHumiditySensorInstancePath;
- static const char* const kRelativeHumiditySensorProjectPath;
- static const char* const kAutoScreenshotFrequencyInstancePath;
- static const char* const kAutoScreenshotPrefixInstancePath;
- static const char* const kRebootIfMissingInstancePath;
-
- static const char* const kPowerBatteryConfigPath;
-
- static const char* const kMobileDataNetworkingConfigPath;
- static const char* const kIMEIConfigKey;
-
- static const char* const kScreenshotsDirectoryInstancePath;
- static const char* const kScreenshotsDirectoryProjectPath;
-};
-
-#endif // CUTTLEFISH_COMMON_COMMON_LIBS_METADATA_GCE_METADATA_ATTRIBUTES_H_
diff --git a/common/libs/metadata/gce_resource_location.cpp b/common/libs/metadata/gce_resource_location.cpp
deleted file mode 100644
index b9405a9..0000000
--- a/common/libs/metadata/gce_resource_location.cpp
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2016 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.
- */
-#include "common/libs/metadata/gce_resource_location.h"
-
-// Location of the file containing initial metadata.
-// Contains device configuration acquired from metadata server in json format.
-const char* const GceResourceLocation::kInitialMetadataPath =
- "/initial.metadata";
-
-// Location of the fstab file.
-const char* const GceResourceLocation::kInitialFstabPath = "/fstab.gce_x86";
-
-// Location of the folder containing device personality files.
-const char* const GceResourceLocation::kDevicePersonalitiesPath =
- "/system/etc/device_personalities";
-
diff --git a/common/libs/metadata/get_partition_num.cpp b/common/libs/metadata/get_partition_num.cpp
deleted file mode 100644
index 891b9c8..0000000
--- a/common/libs/metadata/get_partition_num.cpp
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2016 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.
- */
-#include "common/libs/metadata/get_partition_num.h"
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <errno.h>
-#include <string.h>
-#include <unistd.h>
-
-#include "common/libs/auto_resources/auto_resources.h"
-
-namespace {
-const char kDefaultPartitionsPath[] = "/partitions";
-} // namespace
-
-// Looks up the partition number for a given name.
-// Returns -1 if the partition number cannot be found for any reason.
-long GetPartitionNum(const char* name, const char* path) {
- if (!path) {
- path = kDefaultPartitionsPath;
- }
- size_t name_len = strlen(name);
- AutoCloseFILE f(fopen(path, "r"));
- if (f.IsError()) {
- printf("%s: fopen(%s) failed %s:%d (%s)", __FUNCTION__,
- path, __FILE__, __LINE__, strerror(errno));
- return -1;
- }
- char line[160];
- while (!f.IsEOF()) {
- if (!fgets(line, sizeof(line), f)) {
- if (!f.IsEOF()) {
- printf("%s: fgets failed %s:%d (%s)", __FUNCTION__,
- __FILE__, __LINE__, strerror(errno));
- }
- return -1;
- }
- if (!strncmp(name, line, name_len) && (line[name_len] == ' ')) {
- char* end;
- const char* base = line + name_len + 1;
- long rval = strtol(base, &end, 10);
- if (base != end) {
- return rval;
- } else {
- printf("%s: parse failed line=%s %s:%d", __FUNCTION__,
- line, __FILE__, __LINE__);
- }
- }
- }
- printf("%s: Could not find name=%s %s:%d", __FUNCTION__, name,
- __FILE__, __LINE__);
- return -1;
-}
diff --git a/common/libs/metadata/get_partition_num.h b/common/libs/metadata/get_partition_num.h
deleted file mode 100644
index c8dc1e6..0000000
--- a/common/libs/metadata/get_partition_num.h
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2016 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 CUTTLEFISH_COMMON_COMMON_LIBS_METADATA_GET_PARTITION_NUM_H_
-#define CUTTLEFISH_COMMON_COMMON_LIBS_METADATA_GET_PARTITION_NUM_H_
-
-// Looks up the partition number for a given name.
-// Path can be a full path to a partition number file. If null is provided the
-// system default partition file is used.
-// Returns -1 if the partition number cannot be found for any reason.
-long GetPartitionNum(const char* name, const char* path = 0);
-
-#endif // CUTTLEFISH_COMMON_COMMON_LIBS_METADATA_GET_PARTITION_NUM_H_
diff --git a/common/libs/metadata/initial_metadata_reader.h b/common/libs/metadata/initial_metadata_reader.h
deleted file mode 100644
index 5c10dde..0000000
--- a/common/libs/metadata/initial_metadata_reader.h
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2016 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 CUTTLEFISH_COMMON_COMMON_LIBS_METADATA_INITIAL_METADATA_READER_H_
-#define CUTTLEFISH_COMMON_COMMON_LIBS_METADATA_INITIAL_METADATA_READER_H_
-
-namespace avd {
-
-class DisplayProperties;
-
-// See the comments in MetadataService.h for the rules that apply to
-// project and instance values.
-class InitialMetadataReader {
- public:
- // Describes the configuration of a screen. This is here because it is shared
- // with gce_init, which can't handle some of the dependencies in the full
- // framebuffer configuration.
- static InitialMetadataReader* getInstance();
- virtual const DisplayProperties& GetDisplay() const = 0;
- virtual const char* GetInstanceHostname() const = 0;
- virtual const char* GetValueForKey(const char* key) const = 0;
-
- protected:
- InitialMetadataReader() {}
- virtual ~InitialMetadataReader() {}
- InitialMetadataReader(const InitialMetadataReader&);
- InitialMetadataReader& operator= (const InitialMetadataReader&);
-};
-
-}
-
-#endif // CUTTLEFISH_COMMON_COMMON_LIBS_METADATA_INITIAL_METADATA_READER_H_
diff --git a/common/libs/metadata/initial_metadata_reader_impl.cpp b/common/libs/metadata/initial_metadata_reader_impl.cpp
deleted file mode 100644
index 03fdc77..0000000
--- a/common/libs/metadata/initial_metadata_reader_impl.cpp
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * Copyright (C) 2016 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.
- */
-#include "common/libs/metadata/initial_metadata_reader_impl.h"
-
-#include <errno.h>
-#include <stdio.h>
-#include <iostream>
-#include <fstream>
-#include <map>
-#include <string>
-
-#include <glog/logging.h>
-#include <json/json.h>
-
-#include "common/libs/metadata/gce_metadata_attributes.h"
-#include "common/libs/metadata/gce_resource_location.h"
-
-namespace avd {
-
-InitialMetadataReaderImpl::InitialMetadataReaderImpl()
- : is_initialized_(false) {}
-
-static std::string ValueToString(Json::Value value) {
- if (value.isString()) {
- return value.asString();
- } else {
- Json::FastWriter writer;
- return writer.write(value);
- }
-}
-
-void StoreValues(Json::Value source, MetadataReaderValueMap* dest) {
- if (!source.isObject()) {
- return;
- }
- Json::Value::Members members = source.getMemberNames();
- for (Json::Value::Members::const_iterator it = members.begin();
- it != members.end(); ++it) {
- (*dest)[*it] = ValueToString(source[*it]);
- }
-}
-
-bool InitialMetadataReaderImpl::Init(const char* config_path) {
- std::ifstream ifs(config_path);
-
- if (!ifs.good()) {
- LOG(ERROR) << "Couldn't open initial metadata file.";
- return false;
- }
- // Skip over the headers.
- std::string response_line;
- while (getline(ifs, response_line, '\n')) {
- if (response_line == "\r") {
- // End of headers.
- break;
- }
- }
- // Now parse the JSON payload.
- Json::Reader reader;
- Json::Value root;
- is_initialized_ = reader.parse(ifs, root);
- // Now we need to convert the values to strings. We do this because we need
- // stable pointers to return, and libjsoncpp deallocates strings when the
- // corresponding Value goes out of scope.
- if (is_initialized_) {
- Json::Value empty(Json::objectValue);
- Json::Value source = root.get("project", empty).get("attributes", empty);
- StoreValues(source, &values_);
- source = root.get("instance", empty).get("attributes", empty);
- StoreValues(source, &values_);
- instance_hostname_ = ValueToString(
- root.get("instance", empty).get("hostname", Json::stringValue));
- } else {
- LOG(ERROR) << "Failed to parse metadata: "
- << reader.getFormattedErrorMessages();
- }
- display_.Parse(GetValueForKey(
- GceMetadataAttributes::kDisplayConfigurationKey));
- return is_initialized_;
-}
-
-const char* InitialMetadataReaderImpl::GetValueForKey(const char* key) const {
- MetadataReaderValueMap::const_iterator it = values_.find(key);
- if (it == values_.end()) {
- return NULL;
- }
- return it->second.c_str();
-}
-
-InitialMetadataReader* InitialMetadataReader::getInstance() {
- static InitialMetadataReaderImpl* instance;
- if (!instance) {
- instance = new InitialMetadataReaderImpl();
- instance->Init(GceResourceLocation::kInitialMetadataPath);
- }
- return instance;
-}
-
-}
diff --git a/common/libs/metadata/initial_metadata_reader_impl.h b/common/libs/metadata/initial_metadata_reader_impl.h
deleted file mode 100644
index ad67d45..0000000
--- a/common/libs/metadata/initial_metadata_reader_impl.h
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2016 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 CUTTLEFISH_COMMON_COMMON_LIBS_METADATA_INITIAL_METADATA_READER_IMPL_H_
-#define CUTTLEFISH_COMMON_COMMON_LIBS_METADATA_INITIAL_METADATA_READER_IMPL_H_
-
-#include <map>
-#include <string>
-
-#include "common/libs/metadata/display_properties.h"
-#include "common/libs/metadata/initial_metadata_reader.h"
-
-namespace avd {
-
-typedef std::map<std::string, std::string> MetadataReaderValueMap;
-
-class InitialMetadataReaderImpl : public InitialMetadataReader {
- public:
- InitialMetadataReaderImpl();
-
- const DisplayProperties& GetDisplay() const {
- return display_;
- }
-
- const char* GetValueForKey(const char* key) const;
- const char* GetInstanceHostname() const { return instance_hostname_.c_str(); }
- bool Init(const char* path);
-
- protected:
- bool is_initialized_;
- MetadataReaderValueMap values_;
- std::string instance_hostname_;
- DisplayProperties display_;
-};
-
-} // namespace avd
-
-#endif // CUTTLEFISH_COMMON_COMMON_LIBS_METADATA_INITIAL_METADATA_READER_IMPL_H_
diff --git a/common/libs/metadata/initial_metadata_reader_test.cpp b/common/libs/metadata/initial_metadata_reader_test.cpp
deleted file mode 100644
index ed543cd..0000000
--- a/common/libs/metadata/initial_metadata_reader_test.cpp
+++ /dev/null
@@ -1,284 +0,0 @@
-/*
- * Copyright (C) 2016 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.
- */
-#include <stdio.h>
-#include <stdlib.h>
-#include <unistd.h>
-#include <string.h>
-#include "common/libs/metadata/initial_metadata_reader_impl.h"
-#include "common/libs/metadata/gce_metadata_attributes.h"
-
-#include <json/json.h>
-
-// TODO(ghartman): Adopt external/gtest.
-
-const char* instance_value = "i_value";
-const char* project_value = "p_value";
-
-extern "C" {
-// Host builds have trouble because these symbols are not defined.
-// TODO(ghartman) Come up with better stubs. #include <atomic> created issues,
-// but perhaps there is something more portable than the gcc built-ins.
-int32_t android_atomic_inc(volatile int32_t* addr) {
- return __sync_fetch_and_add(addr, 1);
-}
-
-int32_t android_atomic_dec(volatile int32_t* addr) {
- return __sync_fetch_and_sub(addr, 1);
-}
-}
-
-void ExpectEqual(
- const char* test,
- const char* left_desc, const char* left,
- const char* right_desc, const char* right,
- const char* file, int line) {
- if (left == right) {
- return;
- }
- if (left && right && !strcmp(left, right)) {
- return;
- }
- if (!left) {
- left = "(NULL)";
- }
- if (!right) {
- right = "(NULL)";
- }
- fprintf(stderr, "%s: FAIL: strings not equal at %s:%d\n"
- " %s=\"%s\"\n"
- " %s=\"%s\"\n", test, file, line, left_desc, left, right_desc,
- right);
- exit(1);
-}
-
-#define EXPECT_EQUAL(TEST, LEFT, RIGHT) ExpectEqual(TEST, #LEFT, (LEFT), #RIGHT, (RIGHT), __FILE__, __LINE__)
-
-void ExpectNotEqual(
- const char* test,
- const char* left_desc, const char* left,
- const char* right_desc, const char* right,
- const char* file, int line) {
- if (left != right) {
- return;
- }
- if (left && right && strcmp(left, right)) {
- return;
- }
- if (!left) {
- left = "(NULL)";
- }
- if (!right) {
- right = "(NULL)";
- }
- fprintf(stderr, "%s: FAIL: strings are equal at %s:%d\n"
- " %s=\"%s\"\n"
- " %s=\"%s\"\n", test, file, line, left_desc, left, right_desc,
- right);
- exit(1);
-}
-
-void ExpectNotEqual(
- const char* test,
- const char* left_desc, int left,
- const char* right_desc, int right,
- const char* file, int line) {
- if (left != right) {
- return;
- }
- fprintf(stderr, "%s: FAIL: ints are equal at %s:%d\n"
- " %s=\"%d\"\n"
- " %s=\"%d\"\n", test, file, line, left_desc, left, right_desc,
- right);
- exit(1);
-}
-
-#define EXPECT_NOT_EQUAL(TEST, LEFT, RIGHT) ExpectNotEqual(TEST, #LEFT, (LEFT), #RIGHT, (RIGHT), __FILE__, __LINE__)
-
-void ExpectNotNull(
- const char* test, const char* left_desc, void *left,
- const char* file, int line) {
- if (left) {
- return;
- }
- fprintf(stderr, "%s: FAIL: null for %s at %s:%d\n",
- test, left_desc, file, line);
- exit(1);
-}
-
-#define EXPECT_NOT_NULL(TEST, LEFT) ExpectNotNull(TEST, #LEFT, (LEFT), __FILE__, __LINE__)
-
-class TestMetadataReader : public avd::InitialMetadataReaderImpl {
- public:
- TestMetadataReader(const char* path) : InitialMetadataReaderImpl() {
- Init(path);
- }
-};
-
-struct TestLine {
- const char* path;
- const char* key;
- const char* value;
-};
-
-struct TestCase {
- const char* expected_value;
- const char* key;
- const TestLine * lines;
-};
-
-TestLine EmptyFileLines[] = {
- {NULL, NULL, NULL},
-};
-
-const char* some_key = "some_key";
-
-TestCase EmptyFile = {
- NULL,
- some_key,
- EmptyFileLines
-};
-
-TestLine InstanceFileLines[] = {
- {GceMetadataAttributes::kInstancePath, some_key, instance_value},
- {NULL, NULL, NULL},
-};
-
-TestCase InstanceFile = {
- instance_value,
- some_key,
- InstanceFileLines
-};
-
-TestLine ProjectFileLines[] = {
- {GceMetadataAttributes::kProjectPath, some_key, project_value},
- {NULL, NULL, NULL},
-};
-
-TestCase ProjectFile = {
- project_value,
- some_key,
- ProjectFileLines
-};
-
-TestLine InstanceBeforeProjectLines[] = {
- {GceMetadataAttributes::kInstancePath, some_key, instance_value},
- {GceMetadataAttributes::kProjectPath, some_key, project_value},
- {NULL, NULL, NULL},
-};
-
-TestCase InstanceBeforeProject = {
- instance_value,
- some_key,
- InstanceBeforeProjectLines
-};
-
-TestLine ProjectBeforeInstanceLines[] = {
- {GceMetadataAttributes::kProjectPath, some_key, project_value},
- {GceMetadataAttributes::kInstancePath, some_key, instance_value},
- {NULL, NULL, NULL},
-};
-
-TestCase ProjectBeforeInstance = {
- instance_value,
- some_key,
- ProjectBeforeInstanceLines
-};
-
-TestLine ProjectSetInstanceSetEmptyLines[] = {
- {GceMetadataAttributes::kProjectPath, some_key, project_value},
- {GceMetadataAttributes::kInstancePath, some_key, ""},
- {NULL, NULL, NULL},
-};
-
-TestCase ProjectSetInstanceSetEmpty = {
- "",
- some_key,
- ProjectSetInstanceSetEmptyLines
-};
-
-void WriteLines(const char* name, const TestLine* data, int fd) {
- Json::Value root(Json::objectValue);
- while (data->path && data->key && data->value) {
- if (data->path == GceMetadataAttributes::kProjectPath) {
- root["project"]["attributes"][data->key] = data->value;
- } else if (data->path == GceMetadataAttributes::kInstancePath) {
- root["instance"]["attributes"][data->key] = data->value;
- } else {
- root[data->path][data->key] = data->value;
- }
- ++data;
- }
- int my_fd = dup(fd);
- EXPECT_NOT_EQUAL(name, my_fd, -1);
- FILE * dest = fdopen(my_fd, "w");
- EXPECT_NOT_NULL(name, dest);
- fputs("Metadata-Flavor: Google\r\n\r\n", dest);
- fputs(Json::FastWriter().write(root).c_str(), dest);
- EXPECT_NOT_EQUAL(name, fclose(dest), EOF);
-}
-
-void RunTest(const char* name, const TestCase& test) {
- char *filename = strdup("/tmp/testXXXXXX");
- EXPECT_NOT_NULL(name, filename);
- int fd = mkstemp(filename);
- EXPECT_NOT_EQUAL(name, fd, -1);
- WriteLines(name, test.lines, fd);
- TestMetadataReader* reader = new TestMetadataReader(filename);
- EXPECT_NOT_NULL(name, reader);
- EXPECT_EQUAL(name, reader->GetValueForKey(test.key), test.expected_value);
- delete reader;
- EXPECT_NOT_EQUAL(name, close(fd), -1);
- EXPECT_NOT_EQUAL(name, unlink(filename), -1);
- free(filename);
- printf("%s: PASS\n", name);
- fflush(stdout);
-}
-
-#define RUN_TEST(CONFIG) RunTest(#CONFIG, (CONFIG))
-
-TestLine SpuriousPathLines[] = {
- {"spurious_path", some_key, instance_value},
- {NULL, NULL, NULL},
-};
-
-TestCase SpuriousPath = {
- NULL,
- some_key,
- SpuriousPathLines
-};
-
-TestLine SpuriousKeyLines[] = {
- {GceMetadataAttributes::kInstancePath, "spurious", instance_value},
- {NULL, NULL, NULL},
-};
-
-TestCase SpuriousKey = {
- NULL,
- some_key,
- SpuriousKeyLines
-};
-
-int main(int /*argc*/, char* /*argv*/[]) {
- RUN_TEST(EmptyFile);
- RUN_TEST(InstanceFile);
- RUN_TEST(ProjectFile);
- RUN_TEST(InstanceBeforeProject);
- RUN_TEST(ProjectBeforeInstance);
- RUN_TEST(ProjectSetInstanceSetEmpty);
- RUN_TEST(SpuriousPath);
- RUN_TEST(SpuriousKey);
- return 0;
-}
diff --git a/common/libs/metadata/metadata_query.cpp b/common/libs/metadata/metadata_query.cpp
deleted file mode 100644
index cba93f5..0000000
--- a/common/libs/metadata/metadata_query.cpp
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2016 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.
- */
-#include "common/libs/fs/shared_fd.h"
-
-#include <glog/logging.h>
-
-#include "common/libs/metadata/metadata_query.h"
-
-namespace {
-class MetadataQueryImpl : public MetadataQuery {
- public:
- MetadataQueryImpl() {
- }
-
- ~MetadataQueryImpl() {}
-
- bool QueryServer(AutoFreeBuffer* buffer) {
- if (!client_->IsOpen()) {
- client_ = avd::SharedFD::SocketLocalClient(
- "gce_metadata", true, SOCK_STREAM);
-
- if (!client_->IsOpen()) {
- LOG(ERROR) << "Couldn't connect to metadata proxy.";
- return false;
- }
- }
-
- int32_t length;
- client_->Read(&length, sizeof(length));
-
- if ((length < 0) || (length > (1 << 20))) {
- LOG(ERROR) << "Invalid metadata length: " << length;
- client_->Close();
- return false;
- }
-
- buffer->Resize(length);
- client_->Read(buffer->data(), length);
- buffer->Resize(length + 1);
- return true;
- }
-
- private:
- avd::SharedFD client_;
-};
-} // namespace
-
-MetadataQuery* MetadataQuery::New() {
- return new MetadataQueryImpl();
-}
-
diff --git a/common/libs/metadata/metadata_query.h b/common/libs/metadata/metadata_query.h
deleted file mode 100644
index 77ceeb3..0000000
--- a/common/libs/metadata/metadata_query.h
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2016 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 CUTTLEFISH_COMMON_COMMON_LIBS_METADATA_METADATA_QUERY_H_
-#define CUTTLEFISH_COMMON_COMMON_LIBS_METADATA_METADATA_QUERY_H_
-
-#include "common/libs/auto_resources/auto_resources.h"
-
-static const int kMaxMetadataResponseBufferSize = 65536;
-
-class MetadataQuery {
- public:
- virtual ~MetadataQuery() {}
-
- // Request metadata from the server.
- // On success returns true, and current metadata in supplied buffer.
- virtual bool QueryServer(AutoFreeBuffer* buffer) = 0;
-
- static MetadataQuery* New();
-};
-
-#endif // CUTTLEFISH_COMMON_COMMON_LIBS_METADATA_METADATA_QUERY_H_
diff --git a/common/libs/net/Android.bp b/common/libs/net/Android.bp
new file mode 100644
index 0000000..8bfebdc
--- /dev/null
+++ b/common/libs/net/Android.bp
@@ -0,0 +1,28 @@
+// Copyright (C) 2017 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.
+
+cc_library_shared {
+ name: "cuttlefish_net",
+ srcs: [
+ "netlink_client.cpp",
+ "netlink_request.cpp",
+ "network_interface_manager.cpp",
+ ],
+ shared_libs: [
+ "libcuttlefish_fs",
+ "cuttlefish_auto_resources",
+ "libbase",
+ ],
+ defaults: ["cuttlefish_host_and_guest"],
+}
diff --git a/common/libs/net/netlink_client.cpp b/common/libs/net/netlink_client.cpp
new file mode 100644
index 0000000..884daca
--- /dev/null
+++ b/common/libs/net/netlink_client.cpp
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#include "common/libs/net/netlink_client.h"
+
+#include <errno.h>
+#include <linux/rtnetlink.h>
+#include <linux/sockios.h>
+#include <net/if.h>
+#include <sys/socket.h>
+
+#include "common/libs/fs/shared_fd.h"
+#include "common/libs/glog/logging.h"
+
+namespace cvd {
+namespace {
+// NetlinkClient implementation.
+// Talks to libnetlink to apply network changes.
+class NetlinkClientImpl : public NetlinkClient {
+ public:
+ NetlinkClientImpl() = default;
+ virtual ~NetlinkClientImpl() = default;
+
+ virtual bool Send(const NetlinkRequest& message);
+
+ // Initialize NetlinkClient instance.
+ // Open netlink channel and initialize interface list.
+ // Parameter |type| specifies which netlink target to address, eg.
+ // NETLINK_ROUTE.
+ // Returns true, if initialization was successful.
+ bool OpenNetlink(int type);
+
+ private:
+ bool CheckResponse(uint32_t seq_no);
+
+ SharedFD netlink_fd_;
+ sockaddr_nl address_;
+};
+
+bool NetlinkClientImpl::CheckResponse(uint32_t seq_no) {
+ uint32_t len;
+ char buf[4096];
+ struct iovec iov = { buf, sizeof(buf) };
+ struct sockaddr_nl sa;
+ struct msghdr msg = { &sa, sizeof(sa), &iov, 1, NULL, 0, 0 };
+ struct nlmsghdr *nh;
+
+ int result = netlink_fd_->RecvMsg(&msg, 0);
+ if (result < 0) {
+ LOG(ERROR) << "Netlink error: " << strerror(errno);
+ return false;
+ }
+
+ len = (uint32_t)result;
+ LOG(INFO) << "Received netlink response (" << len << " bytes)";
+
+ for (nh = (struct nlmsghdr*)buf; NLMSG_OK(nh, len); nh = NLMSG_NEXT(nh, len)) {
+ if (nh->nlmsg_seq != seq_no) {
+ // This really shouldn't happen. If we see this, it means somebody is
+ // issuing netlink requests using the same socket as us, and ignoring
+ // responses.
+ LOG(WARNING) << "Sequence number mismatch: "
+ << nh->nlmsg_seq << " != " << seq_no;
+ continue;
+ }
+
+ // This is the end of multi-part message.
+ // It indicates there's nothing more netlink wants to tell us.
+ // It also means we failed to find the response to our request.
+ if (nh->nlmsg_type == NLMSG_DONE)
+ break;
+
+ // This is the 'nlmsgerr' package carrying response to our request.
+ // It carries an 'error' value (errno) along with the netlink header info
+ // that caused this error.
+ if (nh->nlmsg_type == NLMSG_ERROR) {
+ nlmsgerr* err = reinterpret_cast<nlmsgerr*>(nh + 1);
+ if (err->error < 0) {
+ LOG(ERROR) << "Failed to complete netlink request: "
+ << "Netlink error: " << err->error
+ << ", errno: " << strerror(errno);
+ return false;
+ }
+ return true;
+ }
+ }
+
+ LOG(ERROR) << "No response from netlink.";
+ return false;
+}
+
+bool NetlinkClientImpl::Send(const NetlinkRequest& message) {
+ struct sockaddr_nl netlink_addr;
+ struct iovec netlink_iov = {
+ message.RequestData(),
+ message.RequestLength()
+ };
+ struct msghdr msg;
+ memset(&msg, 0, sizeof(msg));
+ memset(&netlink_addr, 0, sizeof(netlink_addr));
+
+ msg.msg_name = &address_;
+ msg.msg_namelen = sizeof(address_);
+ msg.msg_iov = &netlink_iov;
+ msg.msg_iovlen = sizeof(netlink_iov) / sizeof(iovec);
+
+ if (netlink_fd_->SendMsg(&msg, 0) < 0) {
+ LOG(ERROR) << "Failed to send netlink message: "
+ << strerror(errno);
+
+ return false;
+ }
+
+ return CheckResponse(message.SeqNo());
+}
+
+bool NetlinkClientImpl::OpenNetlink(int type) {
+ netlink_fd_ = SharedFD::Socket(AF_NETLINK, SOCK_RAW, type);
+ if (!netlink_fd_->IsOpen()) return false;
+
+ address_.nl_family = AF_NETLINK;
+ address_.nl_groups = 0;
+
+ netlink_fd_->Bind((struct sockaddr*)&address_, sizeof(address_));
+
+ return true;
+}
+
+class NetlinkClientFactoryImpl : public NetlinkClientFactory {
+ public:
+ NetlinkClientFactoryImpl() = default;
+ ~NetlinkClientFactoryImpl() override = default;
+
+ std::unique_ptr<NetlinkClient> New(int type) override {
+ auto client_raw = new NetlinkClientImpl();
+ // Use RVO when possible.
+ std::unique_ptr<NetlinkClient> client(client_raw);
+
+ if (!client_raw->OpenNetlink(type)) {
+ // Note: deletes client_raw.
+ client.reset();
+ }
+ return client;
+ }
+};
+
+} // namespace
+
+NetlinkClientFactory* NetlinkClientFactory::Default() {
+ static NetlinkClientFactory &factory = *new NetlinkClientFactoryImpl();
+ return &factory;
+}
+
+} // namespace cvd
diff --git a/common/libs/net/netlink_client.h b/common/libs/net/netlink_client.h
new file mode 100644
index 0000000..c8805c5
--- /dev/null
+++ b/common/libs/net/netlink_client.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2017 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 COMMON_LIBS_NET_NETLINK_CLIENT_H_
+#define COMMON_LIBS_NET_NETLINK_CLIENT_H_
+
+#include <stddef.h>
+#include <memory>
+#include <string>
+#include "common/libs/net/netlink_request.h"
+
+namespace cvd {
+
+// Abstraction of Netlink client class.
+class NetlinkClient {
+ public:
+ NetlinkClient() {}
+ virtual ~NetlinkClient() {}
+
+ // Send netlink message to kernel.
+ virtual bool Send(const NetlinkRequest& message) = 0;
+
+ private:
+ NetlinkClient(const NetlinkClient&);
+ NetlinkClient& operator= (const NetlinkClient&);
+};
+
+class NetlinkClientFactory {
+ public:
+ // Create new NetlinkClient instance of a specified type.
+ // type can be any of the NETLINK_* types (eg. NETLINK_ROUTE).
+ virtual std::unique_ptr<NetlinkClient> New(int type) = 0;
+
+ static NetlinkClientFactory* Default();
+
+ protected:
+ NetlinkClientFactory() = default;
+ virtual ~NetlinkClientFactory() = default;
+};
+
+} // namespace cvd
+
+#endif // COMMON_LIBS_NET_NETLINK_CLIENT_H_
diff --git a/common/libs/net/netlink_request.cpp b/common/libs/net/netlink_request.cpp
new file mode 100644
index 0000000..ff5b9e9
--- /dev/null
+++ b/common/libs/net/netlink_request.cpp
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#include "common/libs/net/netlink_request.h"
+
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#include <net/if.h>
+#include <string.h>
+
+#include <string>
+#include <vector>
+
+#include "common/libs/glog/logging.h"
+
+namespace cvd {
+namespace {
+uint32_t kRequestSequenceNumber = 0;
+} // namespace
+
+uint32_t NetlinkRequest::SeqNo() const {
+ return header_->nlmsg_seq;
+}
+
+void* NetlinkRequest::AppendRaw(const void* data, size_t length) {
+ void* out = request_.end();
+
+ request_.Append(data, length);
+ int pad = RTA_ALIGN(length) - length;
+ if (pad > 0) {
+ request_.Resize(request_.size() + pad);
+ }
+
+ return out;
+}
+
+void* NetlinkRequest::ReserveRaw(size_t length) {
+ void* out = request_.end();
+ request_.Resize(request_.size() + RTA_ALIGN(length));
+ return out;
+}
+
+nlattr* NetlinkRequest::AppendTag(
+ uint16_t type, const void* data, uint16_t data_length) {
+ nlattr* attr = Reserve<nlattr>();
+ attr->nla_type = type;
+ attr->nla_len = RTA_LENGTH(data_length);
+ AppendRaw(data, data_length);
+ return attr;
+}
+
+NetlinkRequest::NetlinkRequest(int32_t command, int32_t flags)
+ : request_(512),
+ header_(Reserve<nlmsghdr>()) {
+ flags |= NLM_F_ACK | NLM_F_REQUEST;
+ header_->nlmsg_flags = flags;
+ header_->nlmsg_type = command;
+ header_->nlmsg_pid = getpid();
+ header_->nlmsg_seq = kRequestSequenceNumber++;
+}
+
+NetlinkRequest::NetlinkRequest(NetlinkRequest&& other) {
+ using std::swap;
+ swap(lists_, other.lists_);
+ swap(header_, other.header_);
+ request_.Swap(other.request_);
+}
+
+void NetlinkRequest::AddString(uint16_t type, const std::string& value) {
+ AppendTag(type, value.c_str(), value.length() + 1);
+}
+
+void NetlinkRequest::AddInt32(uint16_t type, int32_t value) {
+ AppendTag(type, &value, sizeof(value));
+}
+
+void NetlinkRequest::AddInt8(uint16_t type, int8_t value) {
+ AppendTag(type, &value, sizeof(value));
+}
+
+void NetlinkRequest::AddIfInfo(int32_t if_index, bool operational) {
+ ifinfomsg* if_info = Reserve<ifinfomsg>();
+ if_info->ifi_family = AF_UNSPEC;
+ if_info->ifi_index = if_index;
+ if_info->ifi_flags = operational ? IFF_UP : 0;
+ if_info->ifi_change = IFF_UP;
+}
+
+void NetlinkRequest::AddAddrInfo(int32_t if_index) {
+ ifaddrmsg* ad_info = Reserve<ifaddrmsg>();
+ ad_info->ifa_family = AF_INET;
+ ad_info->ifa_prefixlen = 24;
+ ad_info->ifa_flags = IFA_F_PERMANENT | IFA_F_SECONDARY;
+ ad_info->ifa_scope = 0;
+ ad_info->ifa_index = if_index;
+}
+
+void NetlinkRequest::PushList(uint16_t type) {
+ int length = request_.size();
+ nlattr* list = AppendTag(type, NULL, 0);
+ lists_.push_back(std::make_pair(list, length));
+}
+
+void NetlinkRequest::PopList() {
+ if (lists_.empty()) {
+ LOG(ERROR) << "List pop with no lists left on stack.";
+ return;
+ }
+
+ std::pair<nlattr*, int> list = lists_.back();
+ lists_.pop_back();
+ list.first->nla_len = request_.size() - list.second;
+}
+
+void* NetlinkRequest::RequestData() const {
+ // Update request length before reporting raw data.
+ header_->nlmsg_len = request_.size();
+ return header_;
+}
+
+size_t NetlinkRequest::RequestLength() const {
+ return request_.size();
+}
+
+} // namespace cvd
diff --git a/common/libs/net/netlink_request.h b/common/libs/net/netlink_request.h
new file mode 100644
index 0000000..953ea00
--- /dev/null
+++ b/common/libs/net/netlink_request.h
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2017 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 COMMON_LIBS_NET_NETLINK_REQUEST_H_
+#define COMMON_LIBS_NET_NETLINK_REQUEST_H_
+
+#include <linux/netlink.h>
+#include <stddef.h>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "common/libs/auto_resources/auto_resources.h"
+
+namespace cvd {
+// Abstraction of Network link request.
+// Used to supply kernel with information about which interface needs to be
+// changed, and how.
+class NetlinkRequest {
+ public:
+ // Create new Netlink Request structure.
+ // Parameter |type| specifies netlink request type (eg. RTM_NEWLINK), while
+ // |flags| are netlink and request specific flags (eg. NLM_F_DUMP).
+ NetlinkRequest(int type, int flags);
+ NetlinkRequest(NetlinkRequest&& other);
+
+ ~NetlinkRequest() = default;
+
+ // Add an IFLA tag followed by a string.
+ // Returns true, if successful.
+ void AddString(uint16_t type, const std::string& value);
+
+ // Add an IFLA tag followed by int32.
+ // Returns true, if successful.
+ void AddInt32(uint16_t type, int32_t value);
+
+ // Add an IFLA tag followed by int8.
+ // Returns true, if successful.
+ void AddInt8(uint16_t type, int8_t value);
+
+ // Add an interface info structure.
+ // Parameter |if_index| specifies particular interface index to which the
+ // attributes following the IfInfo apply.
+ void AddIfInfo(int32_t if_index, bool is_operational);
+
+ // Add an address info to a specific interface.
+ // This method assumes the prefix length for address info is 24.
+ void AddAddrInfo(int32_t if_index);
+
+ // Creates new list.
+ // List mimmic recursive structures in a flat, contiuous representation.
+ // Each call to PushList() should have a corresponding call to PopList
+ // indicating end of sub-attribute list.
+ void PushList(uint16_t type);
+
+ // Indicates end of previously declared list.
+ void PopList();
+
+ // Request data.
+ void* RequestData() const;
+
+ // Request length.
+ size_t RequestLength() const;
+
+ // Request Sequence Number.
+ uint32_t SeqNo() const;
+
+ // Append raw data to buffer.
+ // data must not be null.
+ // Returns pointer to copied location.
+ void* AppendRaw(const void* data, size_t length);
+
+ // Reserve |length| number of bytes in the buffer.
+ // Returns pointer to reserved location.
+ void* ReserveRaw(size_t length);
+
+ // Append specialized data.
+ template <typename T> T* Append(const T& data) {
+ return static_cast<T*>(AppendRaw(&data, sizeof(T)));
+ }
+
+ // Reserve specialized data.
+ template <typename T> T* Reserve() {
+ return static_cast<T*>(ReserveRaw(sizeof(T)));
+ }
+
+ private:
+ nlattr* AppendTag(uint16_t type, const void* data, uint16_t length);
+
+ std::vector<std::pair<nlattr*, int32_t>> lists_;
+ AutoFreeBuffer request_;
+ nlmsghdr* header_;
+
+ NetlinkRequest(const NetlinkRequest&) = delete;
+ NetlinkRequest& operator= (const NetlinkRequest&) = delete;
+};
+} // namespace cvd
+#endif // COMMON_LIBS_NET_NETLINK_REQUEST_H_
diff --git a/common/libs/net/netlink_request_test.cpp b/common/libs/net/netlink_request_test.cpp
new file mode 100644
index 0000000..67b9923
--- /dev/null
+++ b/common/libs/net/netlink_request_test.cpp
@@ -0,0 +1,319 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#include "common/libs/net/netlink_client.h"
+
+#include <linux/rtnetlink.h>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <glog/logging.h>
+
+#include <iostream>
+#include <memory>
+
+using ::testing::ElementsAreArray;
+using ::testing::MatchResultListener;
+using ::testing::Return;
+
+namespace cvd {
+namespace {
+extern "C" void klog_write(int /* level */, const char* /* format */, ...) {}
+
+// Dump hex buffer to test log.
+void Dump(MatchResultListener* result_listener, const char* title,
+ const uint8_t* data, size_t length) {
+ for (size_t item = 0; item < length;) {
+ *result_listener << title;
+ do {
+ result_listener->stream()->width(2);
+ result_listener->stream()->fill('0');
+ *result_listener << std::hex << +data[item] << " ";
+ ++item;
+ } while (item & 0xf);
+ *result_listener << "\n";
+ }
+}
+
+// Compare two memory areas byte by byte, print information about first
+// difference. Dumps both bufferst to user log.
+bool Compare(MatchResultListener* result_listener,
+ const uint8_t* exp, const uint8_t* act, size_t length) {
+ for (size_t index = 0; index < length; ++index) {
+ if (exp[index] != act[index]) {
+ *result_listener << "\nUnexpected data at offset " << index << "\n";
+ Dump(result_listener, "Data Expected: ", exp, length);
+ Dump(result_listener, " Data Actual: ", act, length);
+ return false;
+ }
+ }
+
+ return true;
+}
+
+// Matcher validating Netlink Request data.
+MATCHER_P2(RequestDataIs, data, length, "Matches expected request data") {
+ size_t offset = sizeof(nlmsghdr);
+ if (offset + length != arg.RequestLength()) {
+ *result_listener << "Unexpected request length: "
+ << arg.RequestLength() - offset << " vs " << length;
+ return false;
+ }
+
+ // Note: Request begins with header (nlmsghdr). Header is not covered by this
+ // call.
+ const uint8_t* exp_data = static_cast<const uint8_t*>(
+ static_cast<const void*>(data));
+ const uint8_t* act_data = static_cast<const uint8_t*>(arg.RequestData());
+ return Compare(
+ result_listener, exp_data, &act_data[offset], length);
+}
+
+MATCHER_P4(RequestHeaderIs, length, type, flags, seq,
+ "Matches request header") {
+ nlmsghdr* header = static_cast<nlmsghdr*>(arg.RequestData());
+ if (arg.RequestLength() < sizeof(header)) {
+ *result_listener << "Malformed header: too short.";
+ return false;
+ }
+
+ if (header->nlmsg_len != length) {
+ *result_listener << "Invalid message length: "
+ << header->nlmsg_len << " vs " << length;
+ return false;
+ }
+
+ if (header->nlmsg_type != type) {
+ *result_listener << "Invalid header type: "
+ << header->nlmsg_type << " vs " << type;
+ return false;
+ }
+
+ if (header->nlmsg_flags != flags) {
+ *result_listener << "Invalid header flags: "
+ << header->nlmsg_flags << " vs " << flags;
+ return false;
+ }
+
+ if (header->nlmsg_seq != seq) {
+ *result_listener << "Invalid header sequence number: "
+ << header->nlmsg_seq << " vs " << seq;
+ return false;
+ }
+
+ return true;
+}
+} // namespace
+
+class NetlinkClientTest : public ::testing::Test {
+ void SetUp() {
+ google::InstallFailureSignalHandler();
+ }
+ protected:
+ std::unique_ptr<NetlinkClient> nl_client_;
+};
+
+TEST_F(NetlinkClientTest, BasicStringNode) {
+ constexpr uint16_t kDummyTag = 0xfce2;
+ constexpr char kLongString[] = "long string";
+
+ struct {
+ // 11 bytes of text + padding 0 + 4 bytes of header.
+ const uint16_t attr_length = 0x10;
+ const uint16_t attr_type = kDummyTag;
+ char text[sizeof(kLongString)]; // sizeof includes padding 0.
+ } expected;
+
+ memcpy(&expected.text, kLongString, sizeof(kLongString));
+
+ cvd::NetlinkRequest request(RTM_SETLINK, 0);
+ request.AddString(kDummyTag, kLongString);
+ EXPECT_THAT(request, RequestDataIs(&expected, sizeof(expected)));
+}
+
+TEST_F(NetlinkClientTest, BasicIntNode) {
+ // Basic { Dummy: Value } test.
+ constexpr uint16_t kDummyTag = 0xfce2;
+ constexpr int32_t kValue = 0x1badd00d;
+
+ struct {
+ const uint16_t attr_length = 0x8; // 4 bytes of value + 4 bytes of header.
+ const uint16_t attr_type = kDummyTag;
+ const uint32_t attr_value = kValue;
+ } expected;
+
+ cvd::NetlinkRequest request(RTM_SETLINK, 0);
+ request.AddInt32(kDummyTag, kValue);
+ EXPECT_THAT(request, RequestDataIs(&expected, sizeof(expected)));
+}
+
+TEST_F(NetlinkClientTest, SingleList) {
+ // List: { Dummy: Value}
+ constexpr uint16_t kDummyTag = 0xfce2;
+ constexpr uint16_t kListTag = 0xcafe;
+ constexpr int32_t kValue = 0x1badd00d;
+
+ struct {
+ const uint16_t list_length = 0xc;
+ const uint16_t list_type = kListTag;
+ const uint16_t attr_length = 0x8; // 4 bytes of value + 4 bytes of header.
+ const uint16_t attr_type = kDummyTag;
+ const uint32_t attr_value = kValue;
+ } expected;
+
+ cvd::NetlinkRequest request(RTM_SETLINK, 0);
+ request.PushList(kListTag);
+ request.AddInt32(kDummyTag, kValue);
+ request.PopList();
+
+ EXPECT_THAT(request, RequestDataIs(&expected, sizeof(expected)));
+}
+
+TEST_F(NetlinkClientTest, NestedList) {
+ // List1: { List2: { Dummy: Value}}
+ constexpr uint16_t kDummyTag = 0xfce2;
+ constexpr uint16_t kList1Tag = 0xcafe;
+ constexpr uint16_t kList2Tag = 0xfeed;
+ constexpr int32_t kValue = 0x1badd00d;
+
+ struct {
+ const uint16_t list1_length = 0x10;
+ const uint16_t list1_type = kList1Tag;
+ const uint16_t list2_length = 0xc;
+ const uint16_t list2_type = kList2Tag;
+ const uint16_t attr_length = 0x8;
+ const uint16_t attr_type = kDummyTag;
+ const uint32_t attr_value = kValue;
+ } expected;
+
+ cvd::NetlinkRequest request(RTM_SETLINK, 0);
+ request.PushList(kList1Tag);
+ request.PushList(kList2Tag);
+ request.AddInt32(kDummyTag, kValue);
+ request.PopList();
+ request.PopList();
+
+ EXPECT_THAT(request, RequestDataIs(&expected, sizeof(expected)));
+}
+
+TEST_F(NetlinkClientTest, ListSequence) {
+ // List1: { Dummy1: Value1}, List2: { Dummy2: Value2 }
+ constexpr uint16_t kDummy1Tag = 0xfce2;
+ constexpr uint16_t kDummy2Tag = 0xfd38;
+ constexpr uint16_t kList1Tag = 0xcafe;
+ constexpr uint16_t kList2Tag = 0xfeed;
+ constexpr int32_t kValue1 = 0x1badd00d;
+ constexpr int32_t kValue2 = 0xfee1;
+
+ struct {
+ const uint16_t list1_length = 0xc;
+ const uint16_t list1_type = kList1Tag;
+ const uint16_t attr1_length = 0x8;
+ const uint16_t attr1_type = kDummy1Tag;
+ const uint32_t attr1_value = kValue1;
+ const uint16_t list2_length = 0xc;
+ const uint16_t list2_type = kList2Tag;
+ const uint16_t attr2_length = 0x8;
+ const uint16_t attr2_type = kDummy2Tag;
+ const uint32_t attr2_value = kValue2;
+ } expected;
+
+ cvd::NetlinkRequest request(RTM_SETLINK, 0);
+ request.PushList(kList1Tag);
+ request.AddInt32(kDummy1Tag, kValue1);
+ request.PopList();
+ request.PushList(kList2Tag);
+ request.AddInt32(kDummy2Tag, kValue2);
+ request.PopList();
+
+ EXPECT_THAT(request, RequestDataIs(&expected, sizeof(expected)));
+}
+
+TEST_F(NetlinkClientTest, ComplexList) {
+ // List1: { List2: { Dummy1: Value1 }, Dummy2: Value2 }
+ constexpr uint16_t kDummy1Tag = 0xfce2;
+ constexpr uint16_t kDummy2Tag = 0xfd38;
+ constexpr uint16_t kList1Tag = 0xcafe;
+ constexpr uint16_t kList2Tag = 0xfeed;
+ constexpr int32_t kValue1 = 0x1badd00d;
+ constexpr int32_t kValue2 = 0xfee1;
+
+ struct {
+ const uint16_t list1_length = 0x18;
+ const uint16_t list1_type = kList1Tag;
+ const uint16_t list2_length = 0xc; // Note, this only covers until kValue1.
+ const uint16_t list2_type = kList2Tag;
+ const uint16_t attr1_length = 0x8;
+ const uint16_t attr1_type = kDummy1Tag;
+ const uint32_t attr1_value = kValue1;
+ const uint16_t attr2_length = 0x8;
+ const uint16_t attr2_type = kDummy2Tag;
+ const uint32_t attr2_value = kValue2;
+ } expected;
+
+ cvd::NetlinkRequest request(RTM_SETLINK, 0);
+ request.PushList(kList1Tag);
+ request.PushList(kList2Tag);
+ request.AddInt32(kDummy1Tag, kValue1);
+ request.PopList();
+ request.AddInt32(kDummy2Tag, kValue2);
+ request.PopList();
+
+ EXPECT_THAT(request, RequestDataIs(&expected, sizeof(expected)));
+}
+
+TEST_F(NetlinkClientTest, SimpleNetlinkCreateHeader) {
+ cvd::NetlinkRequest request(RTM_NEWLINK, NLM_F_CREATE | NLM_F_EXCL);
+ constexpr char kValue[] = "random string";
+ request.AddString(0, kValue); // Have something to work with.
+
+ constexpr size_t kMsgLength =
+ sizeof(nlmsghdr) + sizeof(nlattr) + RTA_ALIGN(sizeof(kValue));
+ int base_seq = request.SeqNo();
+
+ EXPECT_THAT(request, RequestHeaderIs(
+ kMsgLength,
+ RTM_NEWLINK,
+ NLM_F_ACK | NLM_F_CREATE | NLM_F_EXCL | NLM_F_REQUEST,
+ base_seq));
+
+ cvd::NetlinkRequest request2(RTM_NEWLINK, NLM_F_CREATE | NLM_F_EXCL);
+ request2.AddString(0, kValue); // Have something to work with.
+ EXPECT_THAT(request2, RequestHeaderIs(
+ kMsgLength,
+ RTM_NEWLINK,
+ NLM_F_ACK | NLM_F_CREATE | NLM_F_EXCL | NLM_F_REQUEST,
+ base_seq + 1));
+}
+
+TEST_F(NetlinkClientTest, SimpleNetlinkUpdateHeader) {
+ cvd::NetlinkRequest request(RTM_SETLINK, 0);
+ constexpr char kValue[] = "random string";
+ request.AddString(0, kValue); // Have something to work with.
+
+ constexpr size_t kMsgLength =
+ sizeof(nlmsghdr) + sizeof(nlattr) + RTA_ALIGN(sizeof(kValue));
+ int base_seq = request.SeqNo();
+
+ EXPECT_THAT(request, RequestHeaderIs(
+ kMsgLength, RTM_SETLINK, NLM_F_REQUEST | NLM_F_ACK, base_seq));
+
+ cvd::NetlinkRequest request2(RTM_SETLINK, 0);
+ request2.AddString(0, kValue); // Have something to work with.
+ EXPECT_THAT(request2, RequestHeaderIs(
+ kMsgLength, RTM_SETLINK, NLM_F_REQUEST | NLM_F_ACK, base_seq + 1));
+}
+
+} // namespace cvd
diff --git a/common/libs/net/network_interface.h b/common/libs/net/network_interface.h
new file mode 100644
index 0000000..4005f00
--- /dev/null
+++ b/common/libs/net/network_interface.h
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2017 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 GUEST_GCE_NETWORK_NETWORK_INTERFACE_H_
+#define GUEST_GCE_NETWORK_NETWORK_INTERFACE_H_
+
+#include <string>
+
+namespace cvd {
+
+// Abstraction of network interfaces.
+// This interface provides means to modify network interface parameters.
+class NetworkInterface {
+ public:
+ explicit NetworkInterface(size_t if_index)
+ : if_index_(if_index) {}
+
+ NetworkInterface() = default;
+ ~NetworkInterface() = default;
+
+ // Get network interface index.
+ size_t Index() const {
+ return if_index_;
+ }
+
+ // Set name of the network interface.
+ NetworkInterface& SetName(const std::string& new_name) {
+ name_ = new_name;
+ return *this;
+ }
+
+ // Get name of the network interface.
+ // Returns name, if previously set.
+ const std::string& Name() const {
+ return name_;
+ }
+
+ // Set operational state of the network interface (ie. whether interface is
+ // up).
+ NetworkInterface& SetOperational(bool is_operational) {
+ is_operational_ = is_operational;
+ return *this;
+ }
+
+ // Get operational state of the interface. Value of 'true' indicates interface
+ // should be 'up'.
+ bool IsOperational() const {
+ return is_operational_;
+ }
+
+ // Set IPv4 address of the network interface.
+ NetworkInterface& SetAddress(const std::string& address) {
+ ip_address_ = address;
+ return *this;
+ }
+
+ // Get IPv4 address of the network interface.
+ const std::string& Address() const {
+ return ip_address_;
+ }
+
+ // Set IPv4 broadcast address of the network interface.
+ NetworkInterface& SetBroadcastAddress(const std::string& address) {
+ bc_address_ = address;
+ return *this;
+ }
+
+ // Get IPv4 broadcast address of the network interface.
+ const std::string& BroadcastAddress() const {
+ return bc_address_;
+ }
+
+ private:
+ // Index of the network interface in the system table. 0 indicates new
+ // interface.
+ size_t if_index_ = 0;
+ // Name of the interface, e.g. "eth0".
+ std::string name_;
+ // Operational status, i.e. whether interface is up.
+ bool is_operational_ = false;
+ // IPv4 address of this interface.
+ std::string ip_address_;
+ // IPv4 broadcast address of this interface.
+ std::string bc_address_;
+
+ NetworkInterface(const NetworkInterface&);
+ NetworkInterface& operator= (const NetworkInterface&);
+};
+
+} // namespace cvd
+
+#endif // GUEST_GCE_NETWORK_NETWORK_INTERFACE_H_
diff --git a/common/libs/net/network_interface_manager.cpp b/common/libs/net/network_interface_manager.cpp
new file mode 100644
index 0000000..2e4d43f
--- /dev/null
+++ b/common/libs/net/network_interface_manager.cpp
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#include "common/libs/net/network_interface_manager.h"
+
+#include <arpa/inet.h>
+#include <linux/if_addr.h>
+#include <linux/if_link.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#include <net/if.h>
+
+#include <memory>
+
+#include "common/libs/glog/logging.h"
+#include "common/libs/net/network_interface.h"
+
+namespace cvd {
+namespace {
+NetlinkRequest BuildLinkRequest(
+ const NetworkInterface& interface) {
+ NetlinkRequest request(RTM_SETLINK, 0);
+ request.AddIfInfo(interface.Index(), interface.IsOperational());
+ if (!interface.Name().empty()) {
+ request.AddString(IFLA_IFNAME, interface.Name());
+ }
+
+ return request;
+}
+
+NetlinkRequest BuildAddrRequest(
+ const NetworkInterface& interface) {
+ NetlinkRequest request(RTM_NEWADDR, 0);
+ request.AddAddrInfo(interface.Index());
+ request.AddInt32(IFA_LOCAL, inet_addr(interface.Address().c_str()));
+ request.AddInt32(IFA_ADDRESS, inet_addr(interface.Address().c_str()));
+ request.AddInt32(IFA_BROADCAST,
+ inet_addr(interface.BroadcastAddress().c_str()));
+
+ return request;
+}
+} // namespace
+
+std::unique_ptr<NetworkInterfaceManager> NetworkInterfaceManager::New(
+ NetlinkClientFactory* nl_factory) {
+ std::unique_ptr<NetworkInterfaceManager> mgr;
+
+ if (nl_factory == NULL) {
+ nl_factory = NetlinkClientFactory::Default();
+ }
+
+ auto client = nl_factory->New(NETLINK_ROUTE);
+ if (client) {
+ mgr.reset(new NetworkInterfaceManager(std::move(client)));
+ }
+
+ return mgr;
+}
+
+NetworkInterfaceManager::NetworkInterfaceManager(
+ std::unique_ptr<NetlinkClient> nl_client)
+ : nl_client_(std::move(nl_client)) {}
+
+std::unique_ptr<NetworkInterface> NetworkInterfaceManager::Open(
+ const std::string& if_name) {
+ std::unique_ptr<NetworkInterface> iface;
+ // NOTE: do not replace this code with an IOCTL call.
+ // On SELinux enabled Androids, RILD is not permitted to execute an IOCTL
+ // and this call will fail.
+ const int32_t index = if_nametoindex(if_name.c_str());
+ if (index < 0) {
+ LOG(ERROR) << "Failed to get interface (" << if_name << ") index.";
+ return iface;
+ }
+
+ iface.reset(new NetworkInterface(index));
+ return iface;
+}
+
+bool NetworkInterfaceManager::ApplyChanges(const NetworkInterface& iface) {
+ if (!nl_client_->Send(BuildLinkRequest(iface))) return false;
+ // Terminate immediately if interface is down.
+ if (!iface.IsOperational()) return true;
+ return nl_client_->Send(BuildAddrRequest(iface));
+}
+
+} // namespace cvd
+
diff --git a/common/libs/net/network_interface_manager.h b/common/libs/net/network_interface_manager.h
new file mode 100644
index 0000000..3d6ccc5
--- /dev/null
+++ b/common/libs/net/network_interface_manager.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2017 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 COMMON_LIBS_NET_NETWORK_INTERFACE_MANAGER_H_
+#define COMMON_LIBS_NET_NETWORK_INTERFACE_MANAGER_H_
+
+#include <memory>
+#include <string>
+
+#include "common/libs/net/netlink_client.h"
+#include "common/libs/net/network_interface.h"
+
+namespace cvd {
+
+// Network interface manager class.
+// - Provides access for existing network interfaces,
+// - provides means to create new virtual interfaces.
+//
+// Example usage:
+//
+// std::unique_ptr<NetlinkClient> client(NetlinkClient::GetDefault());
+// NetworkInterfaceManager manager(client.get());
+// std::unique_ptr<NetworkInterface> iface(manager.Open("eth0"));
+//
+class NetworkInterfaceManager {
+ public:
+ // Open existing network interface.
+ //
+ // NOTE: this method does not fill in any NetworkInterface details yet.
+ std::unique_ptr<NetworkInterface> Open(const std::string& if_name);
+
+ // Apply changes made to existing network interface.
+ // This method cannot be used to instantiate new network interfaces.
+ bool ApplyChanges(const NetworkInterface& interface);
+
+ // Create new connected pair of virtual (veth) interfaces.
+ // Supplied pair of interfaces describe both endpoints' properties.
+ bool CreateVethPair(const NetworkInterface& first,
+ const NetworkInterface& second);
+
+ // Creates new NetworkInterfaceManager.
+ static std::unique_ptr<NetworkInterfaceManager> New(
+ NetlinkClientFactory* factory);
+
+ private:
+ NetworkInterfaceManager(std::unique_ptr<NetlinkClient> nl_client);
+
+ // Build (partial) netlink request.
+ bool BuildRequest(NetlinkRequest* request, const NetworkInterface& interface);
+
+ std::unique_ptr<NetlinkClient> nl_client_;
+
+ NetworkInterfaceManager(const NetworkInterfaceManager&);
+ NetworkInterfaceManager& operator= (const NetworkInterfaceManager&);
+};
+
+} // namespace cvd
+
+#endif // COMMON_LIBS_NET_NETWORK_INTERFACE_MANAGER_H_
diff --git a/common/libs/thread_safe_queue/thread_safe_queue.h b/common/libs/thread_safe_queue/thread_safe_queue.h
new file mode 100644
index 0000000..8662928
--- /dev/null
+++ b/common/libs/thread_safe_queue/thread_safe_queue.h
@@ -0,0 +1,80 @@
+#pragma once
+
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#include <mutex>
+#include <condition_variable>
+#include <deque>
+#include <utility>
+#include <iterator>
+
+namespace cvd {
+// Simple queue with Push and Pop capabilities.
+// If the max_elements argument is passed to the constructor, and Push is called
+// when the queue holds max_elements items, the max_elements_handler is called
+// with a pointer to the internal QueueImpl. The call is made while holding
+// the guarding mutex; operations on the QueueImpl will not interleave with
+// other threads calling Push() or Pop().
+// The QueueImpl type will be a SequenceContainer.
+template <typename T>
+class ThreadSafeQueue {
+ public:
+ using QueueImpl = std::deque<T>;
+ ThreadSafeQueue() = default;
+ explicit ThreadSafeQueue(std::size_t max_elements,
+ std::function<void(QueueImpl*)> max_elements_handler)
+ : max_elements_{max_elements},
+ max_elements_handler_{std::move(max_elements_handler)} {}
+
+ T Pop() {
+ std::unique_lock<std::mutex> guard(m_);
+ while (items_.empty()) {
+ new_item_.wait(guard);
+ }
+ auto t = std::move(items_.front());
+ items_.pop_front();
+ return t;
+ }
+
+ void Push(T&& t) {
+ std::lock_guard<std::mutex> guard(m_);
+ DropItemsIfAtCapacity();
+ items_.push_back(std::move(t));
+ new_item_.notify_one();
+ }
+
+ void Push(const T& t) {
+ std::lock_guard<std::mutex> guard(m_);
+ DropItemsIfAtCapacity();
+ items_.push_back(t);
+ new_item_.notify_one();
+ }
+
+ private:
+ void DropItemsIfAtCapacity() {
+ if (max_elements_ && max_elements_ == items_.size()) {
+ max_elements_handler_(&items_);
+ }
+ }
+
+ std::mutex m_;
+ std::size_t max_elements_{};
+ std::function<void(QueueImpl*)> max_elements_handler_{};
+ std::condition_variable new_item_;
+ QueueImpl items_;
+};
+} // namespace cvd
diff --git a/common/libs/threads/Android.bp b/common/libs/threads/Android.bp
new file mode 100644
index 0000000..dece9ae
--- /dev/null
+++ b/common/libs/threads/Android.bp
@@ -0,0 +1,26 @@
+//
+// Copyright (C) 2017 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.
+
+cc_test_host {
+ name: "cuttlefish_thread_test",
+ srcs: [
+ "cuttlefish_thread_test.cpp",
+ ],
+ shared_libs: [
+ "cuttlefish_time",
+ "libbase",
+ ],
+ defaults: ["cuttlefish_host_only"],
+}
diff --git a/common/libs/threads/BUILD b/common/libs/threads/BUILD
deleted file mode 100644
index ad3882e..0000000
--- a/common/libs/threads/BUILD
+++ /dev/null
@@ -1,27 +0,0 @@
-cc_library(
- name = "threads",
- srcs = [
- ],
- hdrs = [
- "pthread.h",
- "thunkers.h",
- ],
- deps = [
- "//common/libs/time",
- ],
- visibility = [ "//visibility:public" ],
-)
-
-cc_test(
- name = "threads_test",
- srcs = [
- "pthread_test.cpp",
- ],
- linkopts = [ "-lpthread" ],
- deps = [
- ":threads",
- "//common/libs/time",
- "@glog_repo//:glog",
- ],
-)
-
diff --git a/common/libs/threads/pthread.h b/common/libs/threads/cuttlefish_thread.h
similarity index 91%
rename from common/libs/threads/pthread.h
rename to common/libs/threads/cuttlefish_thread.h
index ba774b5..54e5ceb 100644
--- a/common/libs/threads/pthread.h
+++ b/common/libs/threads/cuttlefish_thread.h
@@ -1,3 +1,4 @@
+#pragma once
/*
* Copyright (C) 2016 The Android Open Source Project
*
@@ -13,10 +14,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-#ifndef CUTTLEFISH_COMMON_COMMON_LIBS_THREADS_PTHREAD_H_
-#define CUTTLEFISH_COMMON_COMMON_LIBS_THREADS_PTHREAD_H_
-// Concurreny classess for Cloud Android projects.
+// Concurreny classess for cuttlefish.
//
// These more or less mimic the interface of the C++ classes:
// Mutex is similar to std::mutex
@@ -32,7 +31,7 @@
#include <pthread.h>
#include "common/libs/time/monotonic_time.h"
-namespace avd {
+namespace cvd {
class Mutex {
friend class ConditionVariable;
@@ -95,7 +94,7 @@
return pthread_cond_wait(&cond_, mutex_->GetMutex());
}
- int WaitUntil(const avd::time::MonotonicTimePoint& tp) {
+ int WaitUntil(const cvd::time::MonotonicTimePoint& tp) {
struct timespec ts;
tp.ToTimespec(&ts);
return pthread_cond_timedwait(&cond_, mutex_->GetMutex(), &ts);
@@ -167,6 +166,4 @@
ScopedThread& operator= (const ScopedThread&);
};
-} // namespace avd
-
-#endif // CUTTLEFISH_COMMON_COMMON_LIBS_THREADS_PTHREAD_H_
+} // namespace cvd
diff --git a/common/libs/threads/pthread_test.cpp b/common/libs/threads/cuttlefish_thread_test.cpp
similarity index 87%
rename from common/libs/threads/pthread_test.cpp
rename to common/libs/threads/cuttlefish_thread_test.cpp
index 5ae33db..513262e 100644
--- a/common/libs/threads/pthread_test.cpp
+++ b/common/libs/threads/cuttlefish_thread_test.cpp
@@ -13,18 +13,17 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-#include "common/libs/threads/pthread.h"
+#include "common/libs/threads/cuttlefish_thread.h"
-#include <glog/logging.h>
-
+#include <android-base/logging.h>
#include "common/libs/threads/thunkers.h"
#include "common/libs/time/monotonic_time.h"
-using avd::ConditionVariable;
-using avd::Mutex;
-using avd::ScopedThread;
-using avd::time::MonotonicTimePoint;
-using avd::time::Milliseconds;
+using cvd::ConditionVariable;
+using cvd::Mutex;
+using cvd::ScopedThread;
+using cvd::time::MonotonicTimePoint;
+using cvd::time::Milliseconds;
static const int FINISHED = 100;
@@ -70,7 +69,7 @@
void* FastThread() {
mutex_.Lock();
- LOG_IF(FATAL, busy_ != NULL);
+ CHECK(busy_ == NULL);
busy_ = "FastThread";
SleepUntil(MonotonicTimePoint::Now() + Milliseconds(100));
stage_ = 1;
@@ -78,9 +77,9 @@
mutex_.Unlock();
SleepUntil(MonotonicTimePoint::Now() + Milliseconds(10));
mutex_.Lock();
- LOG_IF(FATAL, busy_ != NULL);
+ CHECK(busy_ == NULL);
busy_ = "FastThread";
- LOG_IF(FATAL, stage_ != 2);
+ CHECK(stage_ == 2);
stage_ = FINISHED;
busy_ = NULL;
mutex_.Unlock();
@@ -90,9 +89,9 @@
void* SlowThread() {
SleepUntil(MonotonicTimePoint::Now() + Milliseconds(50));
mutex_.Lock();
- LOG_IF(FATAL, busy_ != NULL);
+ CHECK(busy_== NULL);
busy_ = "SlowThread";
- LOG_IF(FATAL, stage_ != 1);
+ CHECK(stage_ == 1);
SleepUntil(MonotonicTimePoint::Now() + Milliseconds(100));
stage_ = 2;
busy_ = NULL;
@@ -135,12 +134,12 @@
mutex_.Unlock();
SleepUntil(MonotonicTimePoint::Now() + Milliseconds(100));
mutex_.Lock();
- LOG_IF(FATAL, signalled_ != 1);
+ CHECK(signalled_== 1);
cond_.NotifyOne();
mutex_.Unlock();
SleepUntil(MonotonicTimePoint::Now() + Milliseconds(100));
mutex_.Lock();
- LOG_IF(FATAL, signalled_ != 2);
+ CHECK(signalled_ == 2);
mutex_.Unlock();
return NULL;
}
@@ -186,7 +185,7 @@
mutex_.Unlock();
SleepUntil(MonotonicTimePoint::Now() + Milliseconds(100));
mutex_.Lock();
- LOG_IF(FATAL, signalled_ != 2);
+ CHECK(signalled_ == 2);
mutex_.Unlock();
return NULL;
}
@@ -227,7 +226,7 @@
void* SignalThread() {
SleepUntil(start_ + Milliseconds(200));
mutex_.Lock();
- LOG_IF(FATAL, stage_ != 2);
+ CHECK(stage_ == 2);
cond_.NotifyOne();
stage_ = 3;
mutex_.Unlock();
@@ -236,17 +235,17 @@
void* WaitThread() {
mutex_.Lock();
- LOG_IF(FATAL, stage_ != 0);
+ CHECK(stage_ == 0);
stage_ = 1;
cond_.WaitUntil(start_ + Milliseconds(50));
MonotonicTimePoint current(MonotonicTimePoint::Now());
- LOG_IF(FATAL, Milliseconds(current - start_).count() < 50);
- LOG_IF(FATAL, Milliseconds(current - start_).count() > 100);
+ CHECK(Milliseconds(current - start_).count() >= 50);
+ CHECK(Milliseconds(current - start_).count() <= 100);
stage_ = 2;
cond_.WaitUntil(start_ + Milliseconds(1000));
current = MonotonicTimePoint::Now();
- LOG_IF(FATAL, Milliseconds(current - start_).count() > 500);
- LOG_IF(FATAL, stage_ != 3);
+ CHECK(Milliseconds(current - start_).count() <= 500);
+ CHECK(stage_ == 3);
stage_ = FINISHED;
mutex_.Unlock();
return NULL;
@@ -258,9 +257,8 @@
MonotonicTimePoint start_;
};
-int main(int argc, char** argv) {
- ::google::InitGoogleLogging(argv[0]);
- ::google::LogToStderr();
+int main(int, char**argv) {
+ ::android::base::InitLogging(argv, android::base::StderrLogger);
MutexTest mt;
mt.Run();
NotifyOneTest nt1;
diff --git a/common/libs/threads/thread_annotations.h b/common/libs/threads/thread_annotations.h
new file mode 100644
index 0000000..3ba67e7
--- /dev/null
+++ b/common/libs/threads/thread_annotations.h
@@ -0,0 +1,79 @@
+#pragma once
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#if defined(__SUPPORT_TS_ANNOTATION__) || defined(__clang__)
+#define THREAD_ANNOTATION_ATTRIBUTE__(x) __attribute__((x))
+#else
+#define THREAD_ANNOTATION_ATTRIBUTE__(x) // no-op
+#endif
+
+#define CAPABILITY(x) \
+ THREAD_ANNOTATION_ATTRIBUTE__(capability(x))
+
+#define SCOPED_CAPABILITY \
+ THREAD_ANNOTATION_ATTRIBUTE__(scoped_lockable)
+
+#define GUARDED_BY(x) \
+ THREAD_ANNOTATION_ATTRIBUTE__(guarded_by(x))
+
+#define PT_GUARDED_BY(x) \
+ THREAD_ANNOTATION_ATTRIBUTE__(pt_guarded_by(x))
+
+#define ACQUIRED_BEFORE(...) \
+ THREAD_ANNOTATION_ATTRIBUTE__(acquired_before(__VA_ARGS__))
+
+#define ACQUIRED_AFTER(...) \
+ THREAD_ANNOTATION_ATTRIBUTE__(acquired_after(__VA_ARGS__))
+
+#define REQUIRES(...) \
+ THREAD_ANNOTATION_ATTRIBUTE__(requires_capability(__VA_ARGS__))
+
+#define REQUIRES_SHARED(...) \
+ THREAD_ANNOTATION_ATTRIBUTE__(requires_shared_capability(__VA_ARGS__))
+
+#define ACQUIRE(...) \
+ THREAD_ANNOTATION_ATTRIBUTE__(acquire_capability(__VA_ARGS__))
+
+#define ACQUIRE_SHARED(...) \
+ THREAD_ANNOTATION_ATTRIBUTE__(acquire_shared_capability(__VA_ARGS__))
+
+#define RELEASE(...) \
+ THREAD_ANNOTATION_ATTRIBUTE__(release_capability(__VA_ARGS__))
+
+#define RELEASE_SHARED(...) \
+ THREAD_ANNOTATION_ATTRIBUTE__(release_shared_capability(__VA_ARGS__))
+
+#define TRY_ACQUIRE(...) \
+ THREAD_ANNOTATION_ATTRIBUTE__(try_acquire_capability(__VA_ARGS__))
+
+#define TRY_ACQUIRE_SHARED(...) \
+ THREAD_ANNOTATION_ATTRIBUTE__(try_acquire_shared_capability(__VA_ARGS__))
+
+#define EXCLUDES(...) \
+ THREAD_ANNOTATION_ATTRIBUTE__(locks_excluded(__VA_ARGS__))
+
+#define ASSERT_CAPABILITY(x) \
+ THREAD_ANNOTATION_ATTRIBUTE__(assert_capability(x))
+
+#define ASSERT_SHARED_CAPABILITY(x) \
+ THREAD_ANNOTATION_ATTRIBUTE__(assert_shared_capability(x))
+
+#define RETURN_CAPABILITY(x) \
+ THREAD_ANNOTATION_ATTRIBUTE__(lock_returned(x))
+
+#define NO_THREAD_SAFETY_ANALYSIS \
+ THREAD_ANNOTATION_ATTRIBUTE__(no_thread_safety_analysis)
diff --git a/common/libs/time/Android.bp b/common/libs/time/Android.bp
new file mode 100644
index 0000000..a390f68
--- /dev/null
+++ b/common/libs/time/Android.bp
@@ -0,0 +1,33 @@
+//
+// Copyright (C) 2017 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.
+
+cc_library_shared {
+ name: "cuttlefish_time",
+ srcs: [
+ "monotonic_time.cpp",
+ ],
+ defaults: ["cuttlefish_host_and_guest"],
+}
+
+cc_test_host {
+ name: "monotonic_time_test",
+ srcs: [
+ "monotonic_time_test.cpp",
+ ],
+ shared_libs: [
+ "cuttlefish_time",
+ ],
+ defaults: ["cuttlefish_host_only"],
+}
diff --git a/common/libs/time/BUILD b/common/libs/time/BUILD
deleted file mode 100644
index 57c20cf..0000000
--- a/common/libs/time/BUILD
+++ /dev/null
@@ -1,23 +0,0 @@
-cc_library(
- name = "time",
- srcs = [
- "monotonic_time.cpp",
- "monotonic_time.h",
- ],
- hdrs = [
- "monotonic_time.h",
- ],
- visibility = [ "//visibility:public" ],
-)
-
-cc_test(
- name = "monotonic_time_test",
- srcs = [
- "monotonic_time_test.cpp",
- ],
- deps = [
- ":time",
- "@gtest_repo//:gtest_main",
- ],
-)
-
diff --git a/common/libs/time/monotonic_time.cpp b/common/libs/time/monotonic_time.cpp
index b12a82f..1d7bbd8 100644
--- a/common/libs/time/monotonic_time.cpp
+++ b/common/libs/time/monotonic_time.cpp
@@ -15,7 +15,7 @@
*/
#include "common/libs/time/monotonic_time.h"
-namespace avd {
+namespace cvd {
namespace time {
MonotonicTimePointFactory* MonotonicTimePointFactory::GetInstance() {
static MonotonicTimePointFactory factory;
@@ -23,4 +23,4 @@
return &factory;
}
} // namespace time
-} // namespace avd
+} // namespace cvd
diff --git a/common/libs/time/monotonic_time.h b/common/libs/time/monotonic_time.h
index be8882d..1d61ab4 100644
--- a/common/libs/time/monotonic_time.h
+++ b/common/libs/time/monotonic_time.h
@@ -13,13 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-#ifndef CUTTLEFISH_COMMON_COMMON_LIBS_TIME_MONOTONIC_TIME_H_
-#define CUTTLEFISH_COMMON_COMMON_LIBS_TIME_MONOTONIC_TIME_H_
+#pragma once
#include <stdint.h>
#include <time.h>
-namespace avd {
+namespace cvd {
namespace time {
static const int64_t kNanosecondsPerSecond = 1000000000;
@@ -280,7 +279,7 @@
};
} // namespace time
-} // namespace avd
+} // namespace cvd
/**
* Legacy support for microseconds. Use MonotonicTimePoint in new code.
@@ -288,7 +287,6 @@
static const int64_t kSecsToUsecs = static_cast<int64_t>(1000) * 1000;
static inline int64_t get_monotonic_usecs() {
- return avd::time::Microseconds(
- avd::time::MonotonicTimePoint::Now().SinceEpoch()).count();
+ return cvd::time::Microseconds(
+ cvd::time::MonotonicTimePoint::Now().SinceEpoch()).count();
}
-#endif // CUTTLEFISH_COMMON_COMMON_LIBS_TIME_MONOTONIC_TIME_H_
diff --git a/common/libs/time/monotonic_time_test.cpp b/common/libs/time/monotonic_time_test.cpp
index a99be4b..b1c07c9 100644
--- a/common/libs/time/monotonic_time_test.cpp
+++ b/common/libs/time/monotonic_time_test.cpp
@@ -18,7 +18,7 @@
#include <gtest/gtest.h>
#include <algorithm>
-using avd::time::TimeDifference;
+using cvd::time::TimeDifference;
class MonotonicTimeTest : public ::testing::Test {
public:
diff --git a/common/libs/usbforward/protocol.h b/common/libs/usbforward/protocol.h
new file mode 100644
index 0000000..c55c3b6
--- /dev/null
+++ b/common/libs/usbforward/protocol.h
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#pragma once
+
+#include <stdint.h>
+
+namespace usb_forward {
+
+// Commands that can be executed over serial port.
+// Use magic value to avoid accidental interpretation of commonly seen numbers.
+enum Command : uint32_t {
+ // Get device list.
+ // Request format:
+ // - RequestHeader{}
+ // Response format:
+ // - ResponseHeader{}
+ // - int32_t(num_devices)
+ // - num_devices times:
+ // - DeviceInfo{}
+ // - DeviceInfo.num_interfaces times:
+ // - InterfaceInfo{}
+ CmdDeviceList = 0xcfad0001,
+
+ // Attach specified device.
+ // Request format:
+ // - RequestHeader{}
+ // - AttachRequestHeader{}
+ // Response format:
+ // - ResponseHeader{}
+ CmdAttach,
+
+ // Execute command on attached USB device.
+ // Request format:
+ // - RequestHeader{}
+ // - ControlTransfer{}
+ // - if transfer direction is host -> device
+ // - uint8_t[ControlTransfer.length] data
+ // Response format:
+ // - ResponseHeader{}
+ // - if transfer direction is device -> host
+ // - int32_t(actual length)
+ // - uint8_t[actual length] bytes
+ CmdControlTransfer,
+
+ // Execute transfer on attached USB device.
+ // Request format:
+ // - RequestHeader{}
+ // - DataTransfer{}
+ // - if transfer direction is host -> device
+ // - uint8_t[DataTransfer.length] data
+ // Response format:
+ // - ResponseHeader{}
+ // - if transfer direction is host -> device
+ // - int32_t(actual length)
+ // - int32_t[actual length] bytes
+ CmdDataTransfer,
+
+ // Heartbeat is used to detect whether device is alive.
+ // This is a trivial request/response mechanism.
+ // Response status indicates whether server is ready.
+ // Request format:
+ // - RequestHeader{}
+ // Response format:
+ // - ResponseHeader{}
+ CmdHeartbeat,
+};
+
+// Status represents command execution result, using USB/IP compatible values.
+enum Status : uint32_t {
+ // StatusSuccess indicates successful command execution.
+ StatusSuccess = 0,
+
+ // StatusFailure indicates error during command execution.
+ StatusFailure = 1
+};
+
+struct RequestHeader {
+ Command command;
+ uint32_t tag;
+};
+
+struct ResponseHeader {
+ Status status;
+ uint32_t tag;
+};
+
+// DeviceInfo describes individual USB device that was found attached to the
+// bus.
+struct DeviceInfo {
+ uint16_t vendor_id;
+ uint16_t product_id;
+ uint16_t dev_version;
+ uint8_t dev_class;
+ uint8_t dev_subclass;
+ uint8_t dev_protocol;
+ uint8_t bus_id;
+ uint8_t dev_id;
+ uint8_t speed;
+ uint8_t num_configurations;
+ uint8_t num_interfaces;
+ uint8_t cur_configuration;
+} __attribute__((packed));
+
+// InterfaceInfo describes individual interface attached to a USB device.
+struct InterfaceInfo {
+ uint8_t if_class;
+ uint8_t if_subclass;
+ uint8_t if_protocol;
+ uint8_t if_reserved;
+} __attribute__((packed));
+
+// AttachRequest specifies which device on which bus needs to be attached.
+struct AttachRequest {
+ uint8_t bus_id;
+ uint8_t dev_id;
+} __attribute__((packed));
+
+// ControlTransfer specifies target bus and device along with USB request.
+struct ControlTransfer {
+ uint8_t bus_id;
+ uint8_t dev_id;
+ uint8_t type;
+ uint8_t cmd;
+ uint16_t value;
+ uint16_t index;
+ uint16_t length;
+ uint32_t timeout;
+} __attribute__((packed));
+
+// DataTransfer is used to exchange data between host and device.
+struct DataTransfer {
+ uint8_t bus_id;
+ uint8_t dev_id;
+ uint8_t endpoint_id;
+ uint8_t is_host_to_device;
+ int32_t length;
+ uint32_t timeout;
+} __attribute__((packed));
+
+} // namespace usb_forward
diff --git a/common/libs/wifi/Android.bp b/common/libs/wifi/Android.bp
new file mode 100644
index 0000000..0ce06f1
--- /dev/null
+++ b/common/libs/wifi/Android.bp
@@ -0,0 +1,20 @@
+cc_library_shared {
+ name: "libcuttlefish_wifi",
+
+ srcs: [
+ "netlink.cc",
+ "nl_client.cc",
+ "cmd.cc",
+ "virtual_wifi.cc",
+ "wr_client.cc",
+ ],
+ shared_libs: [
+ "vsoc_lib",
+ "libbase",
+ "libnl",
+ ],
+ header_libs: [
+ "cuttlefish_glog",
+ ],
+ defaults: ["cuttlefish_host_and_guest", "cuttlefish_native_isa"],
+}
diff --git a/common/libs/wifi/cmd.cc b/common/libs/wifi/cmd.cc
new file mode 100644
index 0000000..1a153cc
--- /dev/null
+++ b/common/libs/wifi/cmd.cc
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#include "common/libs/wifi/cmd.h"
+
+namespace cvd {
+
+Cmd::Cmd() : msg_(nlmsg_alloc()) {}
+
+Cmd::~Cmd() {
+ for (auto& msg : responses_) {
+ nlmsg_free(msg);
+ }
+ nlmsg_free(msg_);
+}
+
+bool Cmd::OnResponse(nl_msg* msg) {
+ // nlmsg_get increases refcount on msg, but does not return the msg
+ // so we can't exactly use it as an argument to unique_ptr.
+ nlmsg_get(msg);
+ responses_.emplace_back(msg);
+ auto hdr = nlmsg_hdr(msg);
+
+ // Kernel documentation seems to be a bit misleading on this topic saying:
+ //
+ // In multipart messages (multiple nlmsghdr headers with associated
+ // payload in one byte stream) the first and all following headers have
+ // the NLM_F_MULTI flag set, except for the last header which has the type
+ // NLMSG_DONE.
+ //
+ // In theory, that would make processing multi-part messages simple, but in
+ // practice this does not seem to be true. Specifying exit criteria solely on
+ // NLM_F_MULTI flag setting will block some, if not all calls that dump
+ // NL80211 wifi interfaces for example.
+ if (!(hdr->nlmsg_flags & NLM_F_MULTI) || (hdr->nlmsg_type == NLMSG_DONE) ||
+ (hdr->nlmsg_type == NLMSG_ERROR)) {
+ std::lock_guard<std::mutex> lock(ready_mutex_);
+ ready_signal_.notify_all();
+ return true;
+ }
+
+ return false;
+}
+
+const std::vector<nl_msg*> Cmd::Responses() const {
+ std::unique_lock<std::mutex> lock(ready_mutex_);
+ ready_signal_.wait(lock, [this]() { return responses_.size() > 0; });
+ return responses_;
+}
+
+} // namespace cvd
diff --git a/common/libs/wifi/cmd.h b/common/libs/wifi/cmd.h
new file mode 100644
index 0000000..f5d9da0
--- /dev/null
+++ b/common/libs/wifi/cmd.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#pragma once
+
+#include <condition_variable>
+#include <memory>
+#include <thread>
+#include <vector>
+
+#include <netlink/msg.h>
+
+namespace cvd {
+constexpr int kWifiSimVersion = 1;
+
+class Cmd {
+ public:
+ Cmd();
+ ~Cmd();
+
+ // Cmd() creates netlink request to be sent to kernel.
+ // Returns netlink message header structure.
+ nl_msg* Msg() const { return msg_; }
+
+ // Responses() holds execution until netlink responds to this message.
+ // Returns all netlink replies.
+ const std::vector<nl_msg*> Responses() const;
+
+ // OnResponse() handles data response from netlink.
+ // Returns value indicating 'done' state:
+ // - false, if more data is expected, or
+ // - true, if processing is complete and instance can be disposed of.
+ bool OnResponse(nl_msg* msg);
+
+ private:
+ nl_msg* msg_;
+ std::vector<nl_msg*> responses_;
+
+ mutable std::mutex ready_mutex_;
+ mutable std::condition_variable ready_signal_;
+
+ Cmd(const Cmd&) = delete;
+ Cmd& operator=(const Cmd&) = delete;
+};
+
+} // namespace cvd
diff --git a/common/libs/wifi/mac80211.h b/common/libs/wifi/mac80211.h
new file mode 100644
index 0000000..6435bcb
--- /dev/null
+++ b/common/libs/wifi/mac80211.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#pragma once
+
+#include <stdint.h>
+
+// The following definitions are required by uapi/mac80211_hwsim.h header.
+#define s8 int8_t
+#define u8 uint8_t
+#define BIT(x) (1 << (x))
+#ifndef __packed
+#define __packed __attribute__((packed))
+#endif
+
+#include <uapi/mac80211_hwsim.h>
+
+#undef __packed
+#undef BIT
+#undef u8
+#undef s8
diff --git a/common/libs/wifi/netlink.cc b/common/libs/wifi/netlink.cc
new file mode 100644
index 0000000..143a585
--- /dev/null
+++ b/common/libs/wifi/netlink.cc
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#include "common/libs/wifi/netlink.h"
+
+#include <glog/logging.h>
+#include <netlink/genl/ctrl.h>
+#include <netlink/genl/family.h>
+
+namespace cvd {
+namespace {
+constexpr char kWifiSimFamilyName[] = "MAC80211_HWSIM";
+constexpr char kNl80211FamilyName[] = "nl80211";
+} // namespace
+
+Netlink::Netlink(const std::string& wifirouter_socket)
+ : genl_(NETLINK_GENERIC), rtnl_(NETLINK_ROUTE), wrcl_(wifirouter_socket) {}
+
+bool Netlink::Init() {
+ if (!genl_.Init()) {
+ LOG(ERROR) << "Could not open Netlink Generic.";
+ return false;
+ }
+
+ if (!rtnl_.Init()) {
+ LOG(ERROR) << "Could not open Netlink Route.";
+ return false;
+ }
+
+ if (!wrcl_.Init()) {
+ LOG(ERROR) << "Could not connect to Wifi Router.";
+ return false;
+ }
+
+ // Start the thread processing asynchronous netlink responses.
+ netlink_thread_.reset(new std::thread([this]() { HandleNetlinkMessages(); }));
+
+ // Query relevant netlink families:
+ // MAC80211 family allows us to create virtual radios and corresponding
+ // interfaces.
+ mac80211_hwsim_family_ = genl_ctrl_resolve(genl_.Sock(), kWifiSimFamilyName);
+ if (mac80211_hwsim_family_ < 0) {
+ LOG(ERROR) << "Could not find virtual wifi family. Please make sure module "
+ << "'mac80211_hwsim' is loaded on your system.";
+ return false;
+ }
+ LOG(INFO) << "MAC80211_HWSIM found with family id: "
+ << mac80211_hwsim_family_;
+
+ // NL80211 family allows us to find radios and corresponding interfaces.
+ nl80211_family_ = genl_ctrl_resolve(genl_.Sock(), kNl80211FamilyName);
+ if (nl80211_family_ < 0) {
+ LOG(ERROR) << "Could not find nl80211 family. WIFI stack is unavailable.";
+ return false;
+ }
+ LOG(INFO) << "NL80211 found with family id: " << nl80211_family_;
+
+ return true;
+}
+
+void Netlink::HandleNetlinkMessages() {
+ fd_set nlfds;
+ int genl_fd = nl_socket_get_fd(GeNL().Sock());
+ int rtnl_fd = nl_socket_get_fd(RtNL().Sock());
+ int wrcl_fd = wrcl_.Sock();
+
+ int max_fd = std::max({genl_fd, rtnl_fd, wrcl_fd}) + 1;
+ while (true) {
+ FD_ZERO(&nlfds);
+ FD_SET(genl_fd, &nlfds);
+ FD_SET(rtnl_fd, &nlfds);
+ FD_SET(wrcl_fd, &nlfds);
+
+ int res = select(max_fd, &nlfds, nullptr, nullptr, nullptr);
+ if (res <= 0) continue;
+
+ if (FD_ISSET(genl_fd, &nlfds)) nl_recvmsgs_default(GeNL().Sock());
+ if (FD_ISSET(rtnl_fd, &nlfds)) nl_recvmsgs_default(RtNL().Sock());
+ if (FD_ISSET(wrcl_fd, &nlfds)) wrcl_.HandleResponses();
+ }
+}
+
+} // namespace cvd
diff --git a/common/libs/wifi/netlink.h b/common/libs/wifi/netlink.h
new file mode 100644
index 0000000..20b13c1
--- /dev/null
+++ b/common/libs/wifi/netlink.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#pragma once
+
+#include <memory>
+#include <thread>
+
+#include <netlink/genl/genl.h>
+#include "common/libs/wifi/nl_client.h"
+#include "common/libs/wifi/wr_client.h"
+
+namespace cvd {
+// Netlink provides access to relevant netlink backends and resources.
+class Netlink {
+ public:
+ Netlink(const std::string& wifirouter_socket);
+ ~Netlink() = default;
+
+ // Initialize instance of Netlink Factory.
+ bool Init();
+
+ // Getter for NETLINK_GENERIC NlClient instance.
+ NlClient& GeNL() { return genl_; }
+
+ // Getter for NETLINK_ROUTE NlClient instance.
+ NlClient& RtNL() { return rtnl_; }
+
+ WRClient& WRCL() { return wrcl_; }
+
+ // Access Family ID for MAC80211 (WIFI Simulator).
+ int FamilyMAC80211() const { return mac80211_hwsim_family_; }
+
+ // Access Family ID for NL80211 (WIFI management).
+ int FamilyNL80211() const { return nl80211_family_; }
+
+ private:
+ // Loop and process all incoming netlink messages.
+ // This function will trigger calls to NlClient's OnResponse() which handles
+ // incoming netlink messages.
+ void HandleNetlinkMessages();
+
+ NlClient genl_;
+ NlClient rtnl_;
+ WRClient wrcl_;
+
+ int mac80211_hwsim_family_ = 0;
+ int router_family_ = 0;
+ int nl80211_family_ = 0;
+
+ std::unique_ptr<std::thread> netlink_thread_;
+
+ Netlink(const Netlink&) = delete;
+ Netlink& operator=(const Netlink&) = delete;
+};
+
+} // namespace cvd
diff --git a/common/libs/wifi/nl_client.cc b/common/libs/wifi/nl_client.cc
new file mode 100644
index 0000000..5588e3d
--- /dev/null
+++ b/common/libs/wifi/nl_client.cc
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#include "common/libs/wifi/nl_client.h"
+
+#include <glog/logging.h>
+
+namespace cvd {
+
+NlClient::NlClient(int nl_type)
+ : nl_type_(nl_type),
+ callback_(nullptr, [](nl_cb* cb) { free(cb); }),
+ sock_(nullptr, [](nl_sock* sock) { free(sock); }) {}
+
+bool NlClient::Init() {
+ // Set up netlink callbacks.
+ callback_.reset(nl_cb_alloc(NL_CB_CUSTOM));
+ if (!callback_) {
+ LOG(ERROR) << "Could not create netlink callback.";
+ return false;
+ }
+
+ // Register callback that will receive asynchronous messages from netlink.
+ nl_cb_set(callback_.get(), NL_CB_MSG_IN, NL_CB_CUSTOM,
+ [](nl_msg* msg, void* data) {
+ NlClient* self = static_cast<NlClient*>(data);
+ return self->OnResponse(msg);
+ },
+ this);
+
+ // Open Netlink target.
+ sock_.reset(nl_socket_alloc_cb(callback_.get()));
+ if (!sock_) {
+ LOG(ERROR) << "Could not create netlink socket. Are you root?";
+ return false;
+ }
+
+ if (nl_connect(sock_.get(), nl_type_) < 0) {
+ LOG(ERROR) << "Could not connect to netlink. Are you root?";
+ return false;
+ }
+
+ return true;
+}
+
+void NlClient::Send(Cmd* msg) {
+ std::lock_guard<std::mutex> guard(in_flight_mutex_);
+ // nl_send_auto sets sequence number (if defaults to NL_AUTO_SEQ).
+ // Make sure to execute this while in critical section to ensure we have time
+ // to set up callback before we receive response.
+ nl_send_auto(sock_.get(), msg->Msg());
+ auto seq = nlmsg_hdr(msg->Msg())->nlmsg_seq;
+ in_flight_[seq] = msg;
+}
+
+// Handle asynchronous messages & responses from netlink.
+int NlClient::OnResponse(nl_msg* msg) {
+ nlmsghdr* header = nlmsg_hdr(msg);
+ int seq = header->nlmsg_seq;
+
+ // Find & invoke corresponding callback, if any.
+ std::lock_guard<std::mutex> guard(in_flight_mutex_);
+ auto pos = in_flight_.find(seq);
+ if (pos != in_flight_.end()) {
+ if (pos->second->OnResponse(msg)) {
+ // Erase command if reports it's done.
+ in_flight_.erase(seq);
+ }
+ } else if (default_handler_) {
+ default_handler_(msg);
+ }
+
+ return NL_OK;
+}
+
+void NlClient::SetDefaultHandler(std::function<void(nl_msg*)> cb) {
+ std::lock_guard<std::mutex> guard(in_flight_mutex_);
+ default_handler_ = std::move(cb);
+}
+
+nl_sock* NlClient::Sock() const { return sock_.get(); }
+
+} // namespace cvd
diff --git a/common/libs/wifi/nl_client.h b/common/libs/wifi/nl_client.h
new file mode 100644
index 0000000..2b5a57f
--- /dev/null
+++ b/common/libs/wifi/nl_client.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#pragma once
+
+#include <functional>
+#include <map>
+#include <memory>
+#include <mutex>
+
+#include <netlink/genl/genl.h>
+
+#include "common/libs/wifi/cmd.h"
+
+namespace cvd {
+
+class NlClient {
+ public:
+ NlClient(int nl_type);
+ ~NlClient() = default;
+
+ // Init this client: set up callback & open socket.
+ bool Init();
+
+ // Get netlink socket used for sending and receiving messages.
+ nl_sock* Sock() const;
+
+ // Send message to netlink. Supplied callback will be invoked when response is
+ // received.
+ void Send(Cmd* msg);
+
+ // Set callback receiving all asynchronous messages and responses that do not
+ // have any proper recipient.
+ // This is useful in situations, where netlink sends asynchronous event
+ // notifications, such as new MAC80211 HWSIM frame.
+ void SetDefaultHandler(std::function<void(nl_msg*)> cb);
+
+ private:
+ // Receive & dispatch netlink response.
+ int OnResponse(nl_msg* msg);
+
+ int nl_type_;
+
+ std::unique_ptr<nl_cb, void (*)(nl_cb*)> callback_;
+ std::unique_ptr<nl_sock, void (*)(nl_sock*)> sock_;
+ std::mutex in_flight_mutex_;
+ std::map<uint32_t, Cmd*> in_flight_;
+ std::function<void(nl_msg*)> default_handler_;
+
+ NlClient(const NlClient&) = delete;
+ NlClient& operator=(const NlClient&) = delete;
+};
+
+} // namespace cvd
diff --git a/common/libs/wifi/router.h b/common/libs/wifi/router.h
new file mode 100644
index 0000000..9a5ab93
--- /dev/null
+++ b/common/libs/wifi/router.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#pragma once
+
+namespace cvd {
+// Commands recognized by WIFIRouter netlink family.
+enum {
+ // WIFIROUTER_CMD_REGISTER is used by client to request notifications for
+ // packets sent from an interface with specific MAC address. Recognized
+ // attributes:
+ // - WIFIROUTER_ATTR_MAC - MAC address (byte array) of interface to receive
+ // notifications for.
+ WIFIROUTER_CMD_REGISTER,
+
+ // WIFIROUTER_CMD_NOTIFY is issued by the server to notify clients for every
+ // new WIFIROUTER packet the client is registered for. Comes with attributes:
+ // - WIFIROUTER_ATTR_MAC - MAC address of interface that received packet,
+ // - WIFIROUTER_ATTR_PACKET - content of the MAC80211_HWSIM packet.
+ WIFIROUTER_CMD_NOTIFY,
+};
+
+// Attributes recognized by WIFIRouter netlink family.
+enum {
+ // Don't use attribute 0 to avoid parsing malformed message.
+ WIFIROUTER_ATTR_UNSPEC,
+
+ // MAC address representing interface from which the packet originated.
+ WIFIROUTER_ATTR_MAC,
+
+ // MAC80211_HWSIM packet content.
+ WIFIROUTER_ATTR_PACKET,
+
+ // Keep this last.
+ WIFIROUTER_ATTR_MAX
+};
+
+} // namespace cvd
+
diff --git a/common/libs/wifi/virtual_wifi.cc b/common/libs/wifi/virtual_wifi.cc
new file mode 100644
index 0000000..365209e
--- /dev/null
+++ b/common/libs/wifi/virtual_wifi.cc
@@ -0,0 +1,295 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include "common/libs/wifi/virtual_wifi.h"
+
+#include <fstream>
+
+#include <glog/logging.h>
+#include <linux/nl80211.h>
+#include <netlink/genl/ctrl.h>
+
+#include "common/libs/wifi/cmd.h"
+#include "common/libs/wifi/mac80211.h"
+#include "common/libs/wifi/router.h"
+
+namespace cvd {
+namespace {
+// Create new HWSIM Radio.
+// Returns newly created HWSIM radio number, or negative errno code.
+int CreateHWSIM(Netlink* nl, const std::string& wiphy_name) {
+ Cmd msg;
+
+ if (!genlmsg_put(msg.Msg(), NL_AUTO_PID, NL_AUTO_SEQ, nl->FamilyMAC80211(), 0,
+ NLM_F_REQUEST, HWSIM_CMD_NEW_RADIO, kWifiSimVersion) ||
+ nla_put_string(msg.Msg(), HWSIM_ATTR_RADIO_NAME, wiphy_name.c_str()) ||
+ nla_put_flag(msg.Msg(), HWSIM_ATTR_DESTROY_RADIO_ON_CLOSE)) {
+ LOG(ERROR) << "Could not create new radio request.";
+ return -1;
+ }
+
+ nl->GeNL().Send(&msg);
+
+ // Responses() pauses until netlink responds to previously sent message.
+ for (auto* r : msg.Responses()) {
+ auto hdr = nlmsg_hdr(r);
+ if (hdr->nlmsg_type == NLMSG_ERROR) {
+ nlmsgerr* err = static_cast<nlmsgerr*>(nlmsg_data(hdr));
+ return err->error;
+ }
+ }
+
+ LOG(ERROR) << "Unknown or no response from netlink.";
+ return -1;
+}
+
+// Destroy existing HWSIM Radio.
+int DeleteHWSIM(Netlink* nl, int hwsim_number) {
+ Cmd msg;
+
+ if (!genlmsg_put(msg.Msg(), NL_AUTO_PID, NL_AUTO_SEQ, nl->FamilyMAC80211(), 0,
+ NLM_F_REQUEST, HWSIM_CMD_DEL_RADIO, kWifiSimVersion) ||
+ nla_put_u32(msg.Msg(), HWSIM_ATTR_RADIO_ID, hwsim_number)) {
+ LOG(ERROR) << "Could not create del radio request.";
+ return -1;
+ }
+
+ nl->GeNL().Send(&msg);
+
+ // Responses() pauses until netlink responds to previously sent message.
+ for (auto* r : msg.Responses()) {
+ auto hdr = nlmsg_hdr(r);
+ if (hdr->nlmsg_type == NLMSG_ERROR) {
+ nlmsgerr* err = static_cast<nlmsgerr*>(nlmsg_data(hdr));
+ return err->error;
+ }
+ }
+
+ LOG(ERROR) << "Unknown or no response from netlink.";
+ return -1;
+}
+
+// Get WIPHY index number associated with a specified name.
+// Note: WIPHY number is not the same as HWSIM number:
+// - the former identifies physical radio in the system,
+// - the latter identifies simulated radio in the system.
+// TODO(ender): we can get the interface number from sysfs, but
+// the information we receive from HWSIM is not enough to get the
+// wiphy # from the system directly. Update this when there is a better
+// way to acquire radio number.
+int GetWIPHYIndex(const std::string& wiphy_name) {
+ int number;
+ std::ifstream file("/sys/class/ieee80211/" + wiphy_name + "/index");
+ file >> number;
+ return number;
+}
+
+// Get WLAN interface index associated with specific WIPHY index.
+// Returns interface index (> 0) or negative value indicating errno code.
+int GetWiphyInterface(Netlink* nl, uint32_t wiphy_index) {
+ Cmd msg;
+
+ if (!genlmsg_put(msg.Msg(), NL_AUTO_PID, NL_AUTO_SEQ, nl->FamilyNL80211(), 0,
+ NLM_F_REQUEST | NLM_F_DUMP, NL80211_CMD_GET_INTERFACE, 0)) {
+ LOG(ERROR) << "Could not create interface query.";
+ return -1;
+ }
+
+ nl->GeNL().Send(&msg);
+
+ // Responses() pauses until netlink responds to previously sent message.
+ for (auto* r : msg.Responses()) {
+ auto hdr = nlmsg_hdr(r);
+ if (hdr->nlmsg_type == NLMSG_ERROR) {
+ nlmsgerr* err = static_cast<nlmsgerr*>(nlmsg_data(hdr));
+ LOG(ERROR) << "Could not query wiphy: " << strerror(-err->error);
+ return err->error;
+ }
+
+ // Last message in entire series.
+ if (hdr->nlmsg_type == NLMSG_DONE) break;
+
+ // !DONE && !ERROR => content.
+ // Decode attributes supplied by netlink.
+ // the genlmsg_parse puts each attribute in a respective slot in an array,
+ // so we have to preallocate enough space.
+ struct nlattr* attrs[NL80211_ATTR_MAX + 1];
+ auto err = genlmsg_parse(hdr, 0, attrs, NL80211_ATTR_MAX, nullptr);
+
+ // Return error if response could not be parsed. This is actually quite
+ // serious.
+ if (err < 0) {
+ LOG(ERROR) << "Could not process netlink response: " << strerror(-err);
+ return err;
+ }
+
+ // Check if we have WIPHY attribute in response -- and if it's the relevant
+ // one.
+ auto wiphy = attrs[NL80211_ATTR_WIPHY];
+ if (wiphy != nullptr && nla_get_u32(wiphy) == wiphy_index) {
+ auto number = attrs[NL80211_ATTR_IFINDEX];
+
+ if (number != nullptr) {
+ return nla_get_u32(number);
+ }
+ }
+ }
+
+ LOG(INFO) << "No interfaces found for wiphy " << wiphy_index;
+ return -1;
+}
+
+// Set WLAN interface name.
+// Uses Netlink Route to alter interface attributes (currently: name).
+bool SetWLANInterface(Netlink* nl, int iface_index, const std::string& name,
+ const uint8_t* address) {
+ Cmd msg;
+
+ ifinfomsg ifm{};
+ ifm.ifi_index = iface_index;
+
+ if (!nlmsg_put(msg.Msg(), NL_AUTO_PID, NL_AUTO_SEQ, RTM_SETLINK, 0,
+ NLM_F_REQUEST) ||
+ nlmsg_append(msg.Msg(), &ifm, sizeof(ifm), 0) ||
+ nla_put_string(msg.Msg(), IFLA_IFNAME, name.c_str()) ||
+ nla_put(msg.Msg(), IFLA_ADDRESS, MAX_ADDR_LEN, address)) {
+ LOG(ERROR) << "Could not create interface update.";
+ return false;
+ }
+
+ nl->RtNL().Send(&msg);
+
+ // Responses() pauses until netlink responds to previously sent message.
+ for (auto* r : msg.Responses()) {
+ auto hdr = nlmsg_hdr(r);
+ if (hdr->nlmsg_type == NLMSG_ERROR) {
+ nlmsgerr* err = static_cast<nlmsgerr*>(nlmsg_data(hdr));
+ if (err->error < 0) {
+ LOG(ERROR) << "Failed to update iface " << iface_index
+ << ": " << strerror(-err->error);
+ }
+ return err->error == 0;
+ }
+ }
+
+ LOG(ERROR) << "Unknown or no response from netlink.";
+ return -1;
+}
+
+bool RegisterForRouterNotifications(Netlink* nl, uint8_t* mac_addr) {
+ Cmd msg;
+
+ if (!genlmsg_put(msg.Msg(), NL_AUTO_PID, NL_AUTO_SEQ, 0, 0,
+ NLM_F_REQUEST, WIFIROUTER_CMD_REGISTER, 0) ||
+ nla_put(msg.Msg(), WIFIROUTER_ATTR_MAC, MAX_ADDR_LEN, mac_addr)) {
+ LOG(ERROR) << "Could not create wifirouter register message.";
+ return false;
+ }
+
+ nl->WRCL().Send(&msg);
+
+ // Responses() pauses until netlink responds to previously sent message.
+ for (auto* r : msg.Responses()) {
+ auto hdr = nlmsg_hdr(r);
+ if (hdr->nlmsg_type == NLMSG_ERROR) {
+ nlmsgerr* err = static_cast<nlmsgerr*>(nlmsg_data(hdr));
+ if (err->error < 0) {
+ LOG(ERROR) << "Failed to register with wifi router: "
+ << strerror(err->error);
+ }
+ return err->error == 0;
+ }
+ }
+
+ LOG(ERROR) << "Unknown or no response from wifi router.";
+ return -1;
+}
+
+} // namespace
+
+VirtualWIFI::~VirtualWIFI() {
+ LOG(INFO) << "Deleting virtual wifi: " << hwsim_number_;
+ if (hwsim_number_ > 0) {
+ auto res = DeleteHWSIM(nl_, hwsim_number_);
+ if (res < 0) {
+ LOG(ERROR) << "Could not delete radio: " << strerror(-res);
+ }
+ hwsim_number_ = 0;
+ }
+ LOG(INFO) << "Done.";
+}
+
+bool VirtualWIFI::Init() {
+ // Dummy variable is used with sscanf to determine mac address is well formed
+ // (that is: there's no trailing string content).
+ char dummy;
+
+ if (sscanf(addr_.c_str(), "%2hhx:%2hhx:%2hhx:%2hhx:%2hhx:%2hhx%c",
+ &mac_addr_[0], &mac_addr_[1], &mac_addr_[2], &mac_addr_[3],
+ &mac_addr_[4], &mac_addr_[5], &dummy) != 6) {
+ LOG(ERROR) << "Malformed MAC address: " << addr_;
+ return false;
+ }
+
+ std::string phy = name_ + "phy";
+ // Each WLAN device consists of two sides:
+ // - WIPHY is the "radio" side,
+ // - WLAN is the "interface" side.
+ // Radios have more physical properties, while WLAN have more logical /
+ // interface properties. Each radio can have more than one WLAN.
+
+ // 1. Create new MAC80211 HWSIM radio.
+ LOG(INFO) << "Creating virtual radio: " << phy;
+ hwsim_number_ = CreateHWSIM(nl_, phy);
+ if (hwsim_number_ <= 0) {
+ LOG(ERROR) << "Could not create HWSIM: " << strerror(-hwsim_number_);
+ return false;
+ }
+
+ // 2. Acquire the WIPHY radio number created with HWSIM radio.
+ LOG(INFO) << "Querying WIPHY number for: " << phy;
+ wiphy_number_ = GetWIPHYIndex(phy);
+ if (wiphy_number_ <= 0) {
+ LOG(ERROR) << "Could not create WIPHY.";
+ return false;
+ }
+
+ // 3. Query interface index.
+ LOG(INFO) << "Querying WIFI number for: " << wiphy_number_;
+ iface_number_ = GetWiphyInterface(nl_, wiphy_number_);
+ if (iface_number_ <= 0) {
+ LOG(ERROR) << "Could not query interface details.";
+ return false;
+ }
+
+ // 4. Apply requested interface name.
+ LOG(INFO) << "Updating interface name to: " << name_;
+ if (!SetWLANInterface(nl_, iface_number_, name_, &mac_addr_[0])) {
+ LOG(ERROR) << "Could not update wlan interface name.";
+ return false;
+ }
+
+ // 5. Register with wifi router.
+ LOG(INFO) << "Registering for notifications for: " << addr_;
+ if (!RegisterForRouterNotifications(nl_, mac_addr_)) {
+ LOG(ERROR) << "Could not register with wifi router.";
+ return false;
+ }
+
+ return true;
+}
+
+} // namespace cvd
diff --git a/common/libs/wifi/virtual_wifi.h b/common/libs/wifi/virtual_wifi.h
new file mode 100644
index 0000000..fac70e0
--- /dev/null
+++ b/common/libs/wifi/virtual_wifi.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#pragma once
+
+#include <memory>
+#include <string>
+
+#include <netinet/in.h>
+#include <linux/netdevice.h>
+
+#include "common/libs/wifi/netlink.h"
+
+namespace cvd {
+
+// VirtualWIFI is an abstraction of an (individual) virtual WLAN device.
+// A virtual WLAN is a composition of the three elements:
+// - HWSIM RADIO, or an instance of a virtual MAC80211 device; this instance is
+// later used to determine origin of the 802.11 frames (ie. which virtual
+// interface was used to send them),
+// - WIPHY, or Radio that is recognized by Linux kernel; these instances are
+// *named* representations of the HWSIM radios and can be used to identify
+// associated WLAN interface,
+// - WLAN, or WIFI Interface, which is directly used network stack and tools.
+//
+// Typically, Cuttlefish guests will run with just one VirtualWIFI instance, but
+// the host will need (typically) one per Guest instance. This is dictated by
+// the fact that at most one user-space daemon can listen for MAC80211 packets
+// at any given time.
+class VirtualWIFI {
+ public:
+ VirtualWIFI(Netlink* nl, const std::string& name, const std::string& macaddr)
+ : nl_(nl), name_(name), addr_(macaddr) {}
+ ~VirtualWIFI();
+
+ const uint8_t* MacAddr() const { return &mac_addr_[0]; }
+ const std::string& Name() const { return name_; }
+
+ bool Init();
+
+ private:
+ Netlink* nl_;
+ std::string name_;
+
+ // MAC address associated with primary WLAN interface.
+ // This is the only way to identify origin of the packets.
+ // Sadly, if MAC Address is altered manually at runtime, we
+ // will stop working.
+ std::string addr_;
+
+ // NOTE: this has to be MAX_ADDR_LEN, even if we occupy fewer bytes.
+ // Netlink requires this to be full length.
+ uint8_t mac_addr_[MAX_ADDR_LEN];
+
+ // HWSIM number is required to identify HWSIM device that we want destroyed
+ // when we no longer need it.
+ int hwsim_number_ = 0;
+
+ // WIPHY and WIFI interface numbers. Useful for local operations, such as
+ // renaming interface.
+ int wiphy_number_ = 0;
+ int iface_number_ = 0;
+
+ VirtualWIFI(const VirtualWIFI&) = delete;
+ VirtualWIFI& operator=(const VirtualWIFI&) = delete;
+};
+
+} // namespace cvd
diff --git a/common/libs/wifi/wr_client.cc b/common/libs/wifi/wr_client.cc
new file mode 100644
index 0000000..65fd170
--- /dev/null
+++ b/common/libs/wifi/wr_client.cc
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#include "common/libs/wifi/wr_client.h"
+
+#include <glog/logging.h>
+
+namespace cvd {
+namespace {
+const int kMaxSupportedPacketSize = getpagesize();
+} // namespace
+WRClient::WRClient(const std::string& address) : address_(address) {}
+
+bool WRClient::Init() {
+ // Sadly, we can't use SharedFD, because we need access to raw file
+ // descriptor.
+
+ struct sockaddr_un addr {};
+ addr.sun_family = AF_UNIX;
+ memcpy(addr.sun_path + 1, address_.c_str(), address_.size());
+ socklen_t len = offsetof(struct sockaddr_un, sun_path) + address_.size() + 1;
+ socket_ = socket(AF_UNIX, SOCK_SEQPACKET, 0);
+ if (socket_ < 0) {
+ LOG(ERROR) << "socket() failed: " << strerror(errno);
+ return false;
+ }
+
+ auto res = connect(socket_, reinterpret_cast<sockaddr*>(&addr), len);
+ if (res < 0) {
+ LOG(ERROR) << "Could not connect to wifi router: " << strerror(errno);
+ return false;
+ }
+
+ return true;
+}
+
+void WRClient::Send(Cmd* msg) {
+ std::lock_guard<std::mutex> guard(in_flight_mutex_);
+ // Make sure to execute this while in critical section to ensure we have time
+ // to set up seq number & callback before we receive response.
+ auto hdr = nlmsg_hdr(msg->Msg());
+ int seq = in_flight_last_seq_++;
+ // Do not use 0 for sequence numbers. 0 is reserved for async notifications.
+ if (!in_flight_last_seq_) in_flight_last_seq_ = 1;
+
+ hdr->nlmsg_seq = seq;
+ send(socket_, hdr, hdr->nlmsg_len, MSG_NOSIGNAL);
+ in_flight_[seq] = msg;
+}
+
+// Handle asynchronous messages & responses from netlink.
+void WRClient::HandleResponses() {
+ std::unique_ptr<uint8_t[]> buf(new uint8_t[kMaxSupportedPacketSize]);
+
+ auto size = recv(socket_, buf.get(), kMaxSupportedPacketSize, 0);
+ if (size <= 0) {
+ LOG(FATAL) << "No data from WIFI Router - likely dead: " << strerror(errno);
+ return;
+ }
+
+ auto hdr = reinterpret_cast<nlmsghdr*>(buf.get());
+ if (size != hdr->nlmsg_len) {
+ LOG(FATAL) << "Malformed message from WIFI Router.";
+ return;
+ }
+
+ int seq = hdr->nlmsg_seq;
+ std::unique_ptr<nl_msg, void (*)(nl_msg*)> nlmsg(
+ nlmsg_convert(hdr), [](nl_msg* m) { nlmsg_free(m); });
+
+ // Find & invoke corresponding callback, if any.
+ std::lock_guard<std::mutex> guard(in_flight_mutex_);
+ auto pos = in_flight_.find(seq);
+ if (pos != in_flight_.end()) {
+ if (pos->second->OnResponse(nlmsg.get())) {
+ // Erase command if reports it's done.
+ in_flight_.erase(seq);
+ }
+ } else if (default_handler_) {
+ default_handler_(nlmsg.get());
+ }
+}
+
+void WRClient::SetDefaultHandler(std::function<void(nl_msg*)> cb) {
+ std::lock_guard<std::mutex> guard(in_flight_mutex_);
+ default_handler_ = std::move(cb);
+}
+
+int WRClient::Sock() const { return socket_; }
+
+} // namespace cvd
diff --git a/common/libs/wifi/wr_client.h b/common/libs/wifi/wr_client.h
new file mode 100644
index 0000000..71a6438
--- /dev/null
+++ b/common/libs/wifi/wr_client.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#pragma once
+
+#include <functional>
+#include <map>
+#include <memory>
+#include <mutex>
+#include <string>
+
+#include <netlink/msg.h>
+
+#include "common/libs/fs/shared_fd.h"
+#include "common/libs/wifi/cmd.h"
+
+namespace cvd {
+
+class WRClient {
+ public:
+ WRClient(const std::string& socket_address);
+ ~WRClient() = default;
+
+ // Init this client: open socket to wifi router.
+ bool Init();
+
+ // Get wifirouter socket used for sending and receiving messages.
+ int Sock() const;
+
+ // Send message to wifi router.
+ void Send(Cmd* msg);
+
+ // Handle incoming responses from wifi router.
+ void HandleResponses();
+
+ // Set callback receiving all asynchronous messages and responses that do not
+ // have any proper recipient.
+ void SetDefaultHandler(std::function<void(nl_msg*)> cb);
+
+ private:
+ // Receive & dispatch netlink response.
+
+ std::string address_;
+ int socket_ = 0;
+ std::mutex in_flight_mutex_;
+ // Do not use 0 as a sequence number. 0 is reserved for asynchronous
+ // notifications.
+ int in_flight_last_seq_ = 1;
+ std::map<uint32_t, Cmd*> in_flight_;
+ std::function<void(nl_msg*)> default_handler_;
+
+ WRClient(const WRClient&) = delete;
+ WRClient& operator=(const WRClient&) = delete;
+};
+
+} // namespace cvd
diff --git a/common/vsoc/lib/circqueue_impl.h b/common/vsoc/lib/circqueue_impl.h
new file mode 100644
index 0000000..154dbbb
--- /dev/null
+++ b/common/vsoc/lib/circqueue_impl.h
@@ -0,0 +1,222 @@
+#pragma once
+
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include <string.h>
+
+#include "common/vsoc/shm/circqueue.h"
+#include "common/vsoc/lib/region_signaling_interface.h"
+
+namespace {
+// Increases the given index until it is naturally aligned for T.
+template <typename T>
+uintptr_t align(uintptr_t index) {
+ return (index + sizeof(T) - 1) & ~(sizeof(T) - 1);
+}
+} // namespace
+
+namespace vsoc {
+class RegionSignalingInterface;
+namespace layout {
+
+template <uint32_t SizeLog2>
+void CircularQueueBase<SizeLog2>::CopyInRange(const char* buffer_in,
+ const Range& t) {
+ size_t bytes = t.end_idx - t.start_idx;
+ uint32_t index = t.start_idx & (BufferSize - 1);
+ if (index + bytes < BufferSize) {
+ memcpy(buffer_ + index, buffer_in, bytes);
+ } else {
+ size_t part1_size = BufferSize - index;
+ size_t part2_size = bytes - part1_size;
+ memcpy(buffer_ + index, buffer_in, part1_size);
+ memcpy(buffer_, buffer_in + part1_size, part2_size);
+ }
+}
+
+template <uint32_t SizeLog2>
+void CircularQueueBase<SizeLog2>::CopyOutRange(const Range& t,
+ char* buffer_out) {
+ uint32_t index = t.start_idx & (BufferSize - 1);
+ size_t total_size = t.end_idx - t.start_idx;
+ if (index + total_size <= BufferSize) {
+ memcpy(buffer_out, buffer_ + index, total_size);
+ } else {
+ uint32_t part1_size = BufferSize - index;
+ uint32_t part2_size = total_size - part1_size;
+ memcpy(buffer_out, buffer_ + index, part1_size);
+ memcpy(buffer_out + part1_size, buffer_, part2_size);
+ }
+}
+
+template <uint32_t SizeLog2>
+void CircularQueueBase<SizeLog2>::WaitForDataLocked(
+ RegionSignalingInterface* r) {
+ while (1) {
+ uint32_t o_w_pub = w_pub_;
+ // We don't have data. Wait until some appears and try again
+ if (r_released_ != o_w_pub) {
+ return;
+ }
+ lock_.Unlock();
+ r->WaitForSignal(&w_pub_, o_w_pub);
+ lock_.Lock();
+ }
+}
+
+template <uint32_t SizeLog2>
+intptr_t CircularQueueBase<SizeLog2>::WriteReserveLocked(
+ RegionSignalingInterface* r,
+ size_t bytes,
+ Range* t,
+ bool non_blocking) {
+ // Can't write more than the buffer will hold
+ if (bytes > BufferSize) {
+ return -ENOSPC;
+ }
+ while (true) {
+ t->start_idx = w_pub_;
+ uint32_t o_r_release = r_released_;
+ size_t available = BufferSize - t->start_idx + o_r_release;
+ if (available >= bytes) {
+ break;
+ }
+ if (non_blocking) {
+ return -EWOULDBLOCK;
+ }
+ // If we can't write at the moment wait for a reader to release
+ // some bytes.
+ lock_.Unlock();
+ r->WaitForSignal(&r_released_, o_r_release);
+ lock_.Lock();
+ }
+ t->end_idx = t->start_idx + bytes;
+ return t->end_idx - t->start_idx;
+}
+
+template <uint32_t SizeLog2>
+intptr_t CircularByteQueue<SizeLog2>::Read(RegionSignalingInterface* r, char* buffer_out,
+ size_t max_size) {
+ this->lock_.Lock();
+ this->WaitForDataLocked(r);
+ Range t;
+ t.start_idx = this->r_released_;
+ t.end_idx = this->w_pub_;
+ // The lock is still held here...
+ // Trim the range if we got more than the reader wanted
+ if ((t.end_idx - t.start_idx) > max_size) {
+ t.end_idx = t.start_idx + max_size;
+ }
+ this->CopyOutRange(t, buffer_out);
+ this->r_released_ = t.end_idx;
+ this->lock_.Unlock();
+ layout::Sides side;
+ side.value_ = layout::Sides::Both;
+ r->SendSignal(side, &this->r_released_);
+ return t.end_idx - t.start_idx;
+}
+
+template <uint32_t SizeLog2>
+intptr_t CircularByteQueue<SizeLog2>::Write(
+ RegionSignalingInterface* r,
+ const char* buffer_in,
+ size_t bytes,
+ bool non_blocking) {
+ Range range;
+ this->lock_.Lock();
+ intptr_t rval = this->WriteReserveLocked(r, bytes, &range, non_blocking);
+ if (rval < 0) {
+ this->lock_.Unlock();
+ return rval;
+ }
+ this->CopyInRange(buffer_in, range);
+ // We can't publish until all of the previous write allocations where
+ // published.
+ this->w_pub_ = range.end_idx;
+ this->lock_.Unlock();
+ layout::Sides side;
+ side.value_ = layout::Sides::Both;
+ r->SendSignal(side, &this->w_pub_);
+ return bytes;
+}
+
+template <uint32_t SizeLog2, uint32_t MaxPacketSize>
+intptr_t CircularPacketQueue<SizeLog2, MaxPacketSize>::CalculateBufferedSize(
+ size_t payload) {
+ return align<uint32_t>(sizeof(uint32_t) + payload);
+}
+
+template <uint32_t SizeLog2, uint32_t MaxPacketSize>
+intptr_t CircularPacketQueue<SizeLog2, MaxPacketSize>::Read(RegionSignalingInterface* r,
+ char* buffer_out,
+ size_t max_size) {
+ this->lock_.Lock();
+ this->WaitForDataLocked(r);
+ uint32_t packet_size = *reinterpret_cast<uint32_t*>(
+ this->buffer_ + (this->r_released_ & (this->BufferSize - 1)));
+ if (packet_size > max_size) {
+ this->lock_.Unlock();
+ return -ENOSPC;
+ }
+ Range t;
+ t.start_idx = this->r_released_ + sizeof(uint32_t);
+ t.end_idx = t.start_idx + packet_size;
+ this->CopyOutRange(t, buffer_out);
+ this->r_released_ += this->CalculateBufferedSize(packet_size);
+ this->lock_.Unlock();
+ layout::Sides side;
+ side.value_ = layout::Sides::Both;
+ r->SendSignal(side, &this->r_released_);
+ return packet_size;
+}
+
+template <uint32_t SizeLog2, uint32_t MaxPacketSize>
+intptr_t CircularPacketQueue<SizeLog2, MaxPacketSize>::Write(
+ RegionSignalingInterface* r,
+ const char* buffer_in,
+ uint32_t bytes,
+ bool non_blocking) {
+ if (bytes > MaxPacketSize) {
+ return -ENOSPC;
+ }
+ Range range;
+ size_t buffered_size = this->CalculateBufferedSize(bytes);
+ this->lock_.Lock();
+ intptr_t rval =
+ this->WriteReserveLocked(r, buffered_size, &range, non_blocking);
+ if (rval < 0) {
+ this->lock_.Unlock();
+ return rval;
+ }
+ Range header = range;
+ header.end_idx = header.start_idx + sizeof(uint32_t);
+ uint32_t sizeof_uint32_t = sizeof(uint32_t);
+ Range payload{static_cast<uint32_t>(range.start_idx + sizeof(uint32_t)),
+ static_cast<uint32_t>(
+ range.start_idx + sizeof(uint32_t) + bytes)};
+ this->CopyInRange(reinterpret_cast<const char*>(&bytes), header);
+ this->CopyInRange(buffer_in, payload);
+ this->w_pub_ = range.end_idx;
+ this->lock_.Unlock();
+ layout::Sides side;
+ side.value_ = layout::Sides::Both;
+ r->SendSignal(side, &this->w_pub_);
+ return bytes;
+}
+
+} // namespace layout
+} // namespace vsoc
diff --git a/common/vsoc/lib/circqueue_test.cpp b/common/vsoc/lib/circqueue_test.cpp
new file mode 100644
index 0000000..34aca33
--- /dev/null
+++ b/common/vsoc/lib/circqueue_test.cpp
@@ -0,0 +1,327 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#include <thread>
+#include <unistd.h>
+
+#include <gtest/gtest.h>
+
+#include "common/vsoc/lib/circqueue_impl.h"
+#include "common/vsoc/lib/mock_region_view.h"
+
+#define EXPECT_BLOCK(region, tid) \
+ EXPECT_TRUE(region.IsBlocking(tid))
+
+namespace {
+
+constexpr int kQueueSizeLog2 = 16;
+constexpr int kQueueCapacity = 1 << kQueueSizeLog2;
+constexpr int kMaxPacketSize = 1024;
+
+constexpr int kNumReadingThread = 5;
+constexpr int kNumWritingThread = 5;
+
+struct CircQueueTestRegionLayout : public vsoc::layout::RegionLayout {
+ vsoc::layout::CircularByteQueue<kQueueSizeLog2> byte_queue;
+ vsoc::layout::CircularPacketQueue<kQueueSizeLog2, kMaxPacketSize> packet_queue;
+};
+
+typedef vsoc::test::MockRegionView<CircQueueTestRegionLayout> CircQueueRegionView;
+
+class CircQueueTest : public ::testing::Test {
+ protected:
+ virtual void SetUp() {
+ region_.Open();
+ }
+
+ CircQueueRegionView region_;
+};
+
+intptr_t ReadBytes(CircQueueRegionView* region, int bytes) {
+ char buffer_out[bytes];
+ CircQueueTestRegionLayout* layout = region->data();
+ return layout->byte_queue.Read(region, buffer_out, bytes);
+}
+
+intptr_t WriteBytes(CircQueueRegionView* region, int bytes) {
+ char buffer_in[bytes];
+ CircQueueTestRegionLayout* layout = region->data();
+ return layout->byte_queue.Write(region, buffer_in, bytes);
+}
+
+intptr_t ReadPacket(CircQueueRegionView* region, int max_size) {
+ char buffer_out[max_size];
+ CircQueueTestRegionLayout* layout = region->data();
+ return layout->packet_queue.Read(region, buffer_out, max_size);
+}
+
+intptr_t WritePacket(CircQueueRegionView* region, int packet_size) {
+ char buffer_in[packet_size];
+ CircQueueTestRegionLayout* layout = region->data();
+ return layout->packet_queue.Write(region, buffer_in, packet_size);
+}
+
+void ReadBytesInChunk(CircQueueRegionView* region, int total_size, int chuck_size) {
+ char buffer_out[chuck_size];
+ CircQueueTestRegionLayout* layout = region->data();
+ int total_read = 0;
+ int remaining = total_size;
+ while (remaining >= chuck_size) {
+ int ret = layout->byte_queue.Read(region, buffer_out, chuck_size);
+ total_read += ret;
+ remaining -= ret;
+ }
+ if (remaining > 0) {
+ total_read += layout->byte_queue.Write(region, buffer_out, remaining);
+ }
+ EXPECT_EQ(total_read, total_size);
+}
+
+void WriteBytesInChunk(CircQueueRegionView* region, int total_size, int chuck_size) {
+ char buffer_in[chuck_size];
+ CircQueueTestRegionLayout* layout = region->data();
+ int total_write = 0;
+ int remaining = total_size;
+ while (remaining >= chuck_size) {
+ int ret = layout->byte_queue.Write(region, buffer_in, chuck_size);
+ total_write += ret;
+ remaining -= ret;
+ }
+ if (remaining > 0) {
+ total_write += layout->byte_queue.Write(region, buffer_in, remaining);
+ }
+ EXPECT_EQ(total_write, total_size);
+}
+
+void ReadManyPackets(CircQueueRegionView* region, int num_packets, int packet_size) {
+ char buffer_out[packet_size];
+ CircQueueTestRegionLayout* layout = region->data();
+ int total_read = 0;
+ int remaining = num_packets;
+ while (remaining > 0) {
+ int ret = layout->packet_queue.Read(region, buffer_out, packet_size);
+ total_read += ret;
+ remaining--;
+ }
+ EXPECT_EQ(total_read, num_packets * packet_size);
+}
+
+void WriteManyPackets(CircQueueRegionView* region, int num_packets, int packet_size) {
+ char buffer_in[packet_size];
+ CircQueueTestRegionLayout* layout = region->data();
+ int total_write = 0;
+ int remaining = num_packets;
+ while (remaining > 0) {
+ int ret = layout->packet_queue.Write(region, buffer_in, packet_size);
+ total_write += ret;
+ remaining--;
+ }
+ EXPECT_EQ(total_write, num_packets * packet_size);
+}
+
+// ByteQueue Tests
+
+// Test writing bytes
+TEST_F(CircQueueTest, ByteQueueSimpleWrite) {
+ const int num_bytes = 8;
+ EXPECT_EQ(num_bytes, WriteBytes(&this->region_, num_bytes));
+}
+
+// Test reading bytes
+TEST_F(CircQueueTest, ByteQueueSimpleRead) {
+ const int num_bytes = 8;
+ EXPECT_EQ(num_bytes, WriteBytes(&this->region_, num_bytes));
+ EXPECT_EQ(num_bytes, ReadBytes(&this->region_, num_bytes));
+}
+
+// Test reading on an empty queue. Expect blocking.
+TEST_F(CircQueueTest, ByteQueueReadOnEmpty) {
+ const int num_bytes = 8;
+
+ // Spawn a thread to read from queue. Expect it to block.
+ std::thread reading_thread(ReadBytes, &this->region_, num_bytes);
+ EXPECT_BLOCK(region_, reading_thread.get_id());
+
+ // Write expected bytes in so that we can clean up properly.
+ std::thread writing_thread(WriteBytes, &this->region_, num_bytes);
+ writing_thread.join();
+
+ reading_thread.join();
+}
+
+// Test writing on a full queue. Expect blocking.
+TEST_F(CircQueueTest, ByteQueueWriteOnFull) {
+ // Fill the queue.
+ const int capacity_bytes = kQueueCapacity;
+ EXPECT_EQ(capacity_bytes, WriteBytes(&this->region_, capacity_bytes));
+
+ // Now the queue is full, any further write would block.
+ const int num_bytes = 8;
+ std::thread writing_thread(WriteBytes, &this->region_, num_bytes);
+ EXPECT_BLOCK(region_, writing_thread.get_id());
+
+ // Read the extra bytes out so that we can clean up properly.
+ std::thread reading_thread(ReadBytes, &this->region_, num_bytes);
+ reading_thread.join();
+
+ writing_thread.join();
+}
+
+// Test if bytes being read out are the same as ones being written in.
+TEST_F(CircQueueTest, ByteQueueContentIntegrity) {
+ const int num_bytes = 8;
+ CircQueueTestRegionLayout* layout = this->region_.data();
+
+ char buffer_in[num_bytes] = {'a'};
+ layout->byte_queue.Write(&this->region_, buffer_in, num_bytes);
+
+ char buffer_out[num_bytes] = {'b'};
+ layout->byte_queue.Read(&this->region_, buffer_out, num_bytes);
+
+ for (int i=0; i<num_bytes; i++) {
+ EXPECT_EQ(buffer_in[i], buffer_out[i]);
+ }
+}
+
+// Test writing more bytes than capacity
+TEST_F(CircQueueTest, ByteQueueWriteTooManyBytes) {
+ const int extra_bytes = 8;
+ const int num_bytes = kQueueCapacity + extra_bytes;
+ EXPECT_EQ(-ENOSPC, WriteBytes(&this->region_, num_bytes));
+}
+
+// Test multiple bytes read/write
+TEST_F(CircQueueTest, ByteQueueMultipleReadWrite) {
+ const int chunk_size = 7;
+ const int total_size = 3.3 * kQueueCapacity;
+ std::vector<std::thread> reading_threads;
+ std::vector<std::thread> writing_threads;
+ for (int i=0; i<kNumReadingThread; i++) {
+ reading_threads.emplace_back(
+ std::thread(ReadBytesInChunk, &this->region_, total_size, chunk_size));
+ }
+ for (int i=0; i<kNumWritingThread; i++) {
+ writing_threads.emplace_back(
+ std::thread(WriteBytesInChunk, &this->region_, total_size, chunk_size));
+ }
+ std::for_each(reading_threads.begin(), reading_threads.end(), [](std::thread& t) { t.join(); });
+ std::for_each(writing_threads.begin(), writing_threads.end(), [](std::thread& t) { t.join(); });
+}
+
+// PacketQueue Tests
+
+// Test writing packet
+TEST_F(CircQueueTest, PacketQueueSimpleWrite) {
+ const int packet_size = 8;
+ EXPECT_EQ(packet_size, WritePacket(&this->region_, packet_size));
+}
+
+// Test reading packet
+TEST_F(CircQueueTest, PacketQueueSimpleRead) {
+ const int packet_size = 8;
+ EXPECT_EQ(packet_size, WritePacket(&this->region_, packet_size));
+ EXPECT_EQ(packet_size, ReadPacket(&this->region_, packet_size));
+}
+
+// Test reading on an empty queue. Expect blocking.
+TEST_F(CircQueueTest, PacketQueueReadOnEmpty) {
+ const int packet_size = 8;
+
+ // Spawn a thread to read from queue. Expect it to block.
+ std::thread reading_thread(ReadPacket, &this->region_, packet_size);
+ EXPECT_BLOCK(region_, reading_thread.get_id());
+
+ // Write expected bytes in so that we can clean up properly.
+ std::thread writing_thread(WritePacket, &this->region_, packet_size);
+ writing_thread.join();
+
+ reading_thread.join();
+}
+
+// Test writing on a full queue. Expect blocking.
+TEST_F(CircQueueTest, PacketQueueWriteOnFull) {
+ // Fill the queue.
+ const int packet_size = kMaxPacketSize;
+ int capacity_bytes = kQueueCapacity;
+ while (capacity_bytes >= packet_size) {
+ EXPECT_EQ(packet_size, WritePacket(&this->region_, packet_size));
+ capacity_bytes -= (packet_size + sizeof(uint32_t));
+ }
+
+ // Now the queue is full, any further write would block.
+ std::thread writing_thread(WritePacket, &this->region_, packet_size);
+ EXPECT_BLOCK(region_, writing_thread.get_id());
+
+ // Read the extra bytes out so that we can clean up properly.
+ std::thread reading_thread(ReadPacket, &this->region_, packet_size);
+ reading_thread.join();
+
+ writing_thread.join();
+}
+
+// Test if packet being read out are the same as one being written in.
+TEST_F(CircQueueTest, PacketQueueContentIntegrity) {
+ const int packet_size = 8;
+ CircQueueTestRegionLayout* layout = this->region_.data();
+
+ char buffer_in[packet_size] = {'a'};
+ layout->packet_queue.Write(&this->region_, buffer_in, packet_size);
+
+ char buffer_out[packet_size] = {'b'};
+ layout->packet_queue.Read(&this->region_, buffer_out, packet_size);
+
+ for (int i=0; i<packet_size; i++) {
+ EXPECT_EQ(buffer_in[i], buffer_out[i]);
+ }
+}
+
+// Test writing packet larger than capacity
+TEST_F(CircQueueTest, PacketQueueWriteTooLargePacket) {
+ const int extra_bytes = 8;
+ const int packet_size = kQueueCapacity + extra_bytes;
+ EXPECT_EQ(-ENOSPC, WritePacket(&this->region_, packet_size));
+}
+
+// Test reading packet larger than can handle
+TEST_F(CircQueueTest, PacketQueueReadTooLargePacket) {
+ const int extra_bytes = 8;
+ const int small_packet = 8;
+ const int large_packet = small_packet + extra_bytes;
+
+ WritePacket(&this->region_, large_packet);
+ char buffer_out[small_packet];
+ CircQueueTestRegionLayout* layout = this->region_.data();
+ EXPECT_EQ(-ENOSPC, layout->packet_queue.Read(&this->region_, buffer_out, small_packet));
+}
+
+// Test multiple packets read/write
+TEST_F(CircQueueTest, PacketQueueMultipleReadWrite) {
+ const int packet_size = kMaxPacketSize;
+ const int num_packets = 1.5 * (kQueueCapacity / packet_size);
+ std::vector<std::thread> reading_threads;
+ std::vector<std::thread> writing_threads;
+ for (int i=0; i<kNumReadingThread; i++) {
+ reading_threads.emplace_back(
+ std::thread(ReadManyPackets, &this->region_, num_packets, packet_size));
+ }
+ for (int i=0; i<kNumWritingThread; i++) {
+ writing_threads.emplace_back(
+ std::thread(WriteManyPackets, &this->region_, num_packets, packet_size));
+ }
+ std::for_each(reading_threads.begin(), reading_threads.end(), [](std::thread& t) { t.join(); });
+ std::for_each(writing_threads.begin(), writing_threads.end(), [](std::thread& t) { t.join(); });
+}
+
+} // namespace
diff --git a/common/vsoc/lib/compat.cpp b/common/vsoc/lib/compat.cpp
new file mode 100644
index 0000000..3b8deb2
--- /dev/null
+++ b/common/vsoc/lib/compat.cpp
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include "common/vsoc/lib/compat.h"
+
+#include <sys/syscall.h>
+#include <unistd.h>
+
+namespace vsoc {
+
+#ifdef CUTTLEFISH_HOST
+uint32_t gettid() {
+ thread_local uint32_t tid = syscall(SYS_gettid);
+ return tid;
+}
+#endif
+}
diff --git a/common/vsoc/lib/compat.h b/common/vsoc/lib/compat.h
new file mode 100644
index 0000000..8f3355c
--- /dev/null
+++ b/common/vsoc/lib/compat.h
@@ -0,0 +1,28 @@
+#pragma once
+
+/*
+ * Copyright (C) 2017 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.
+ */
+
+// Utilities to make the host and the guest a bit more compatible
+
+#include <stdint.h>
+
+namespace vsoc {
+#ifdef CUTTLEFISH_HOST
+// Things that are missing on the host
+uint32_t gettid();
+#endif
+}
diff --git a/common/vsoc/lib/e2e_test_region_layout.cpp b/common/vsoc/lib/e2e_test_region_layout.cpp
new file mode 100644
index 0000000..67a5435
--- /dev/null
+++ b/common/vsoc/lib/e2e_test_region_layout.cpp
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2017 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 some of the string constants associated with the region layout.
+#include "common/vsoc/shm/e2e_test_region_layout.h"
+
+namespace vsoc {
+namespace layout {
+namespace e2e_test {
+
+const char* E2EPrimaryTestRegionLayout::region_name = "e2e_primary";
+const char
+ E2EPrimaryTestRegionLayout::guest_pattern[E2EMemoryFill::kOwnedFieldSize] =
+ "primary guest e2e string";
+const char
+ E2EPrimaryTestRegionLayout::host_pattern[E2EMemoryFill::kOwnedFieldSize] =
+ "primary host e2e string";
+
+const char* E2ESecondaryTestRegionLayout::region_name = "e2e_secondary";
+const char E2ESecondaryTestRegionLayout::guest_pattern
+ [E2EMemoryFill::kOwnedFieldSize] = "secondary guest e2e string";
+const char
+ E2ESecondaryTestRegionLayout::host_pattern[E2EMemoryFill::kOwnedFieldSize] =
+ "secondary host e2e string";
+
+const char* E2EUnfindableRegionLayout::region_name = "e2e_must_not_exist";
+
+const char* E2EManagedTestRegionLayout::region_name = "e2e_managed";
+
+const char* E2EManagerTestRegionLayout::region_name = "e2e_manager";
+
+} // namespace e2e_test
+} // namespace layout
+} // namespace vsoc
diff --git a/common/vsoc/lib/e2e_test_region_view.h b/common/vsoc/lib/e2e_test_region_view.h
new file mode 100644
index 0000000..079eb88
--- /dev/null
+++ b/common/vsoc/lib/e2e_test_region_view.h
@@ -0,0 +1,72 @@
+#pragma once
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#include "common/vsoc/lib/typed_region_view.h"
+#include "common/vsoc/shm/e2e_test_region_layout.h"
+
+namespace vsoc {
+template <typename Layout>
+class E2ERegionView : public vsoc::TypedRegionView<Layout> {
+ public:
+ const char* guest_string(size_t index) const {
+ return make_nonvolatile(this->data().data[index].guest_writable);
+ }
+
+ const char* host_string(size_t index) const {
+ return make_nonvolatile(this->data().data[index].host_writable);
+ }
+
+ bool set_guest_string(size_t index, const char* value) {
+ strcpy(make_nonvolatile(this->data()->data[index].guest_writable), value);
+ return true;
+ }
+
+ bool set_host_string(size_t index, const char* value) {
+ strcpy(make_nonvolatile(this->data()->data[index].host_writable), value);
+ return true;
+ }
+
+ size_t string_size() const {
+ return Layout::NumFillRecords(this->control_->region_data_size());
+ }
+
+ void guest_status(vsoc::layout::e2e_test::E2ETestStage stage) {
+ this->data()->guest_status.set_value(stage);
+ }
+
+ void host_status(vsoc::layout::e2e_test::E2ETestStage stage) {
+ this->data()->host_status.set_value(stage);
+ }
+
+ protected:
+ /**
+ * The string functions have problems with volatile pointers, so
+ * this function casts them away.
+ */
+ template <typename T>
+ static T* make_nonvolatile(volatile T* in) {
+ return (T*)in;
+ }
+};
+
+using E2EPrimaryRegionView =
+ vsoc::E2ERegionView<layout::e2e_test::E2EPrimaryTestRegionLayout>;
+using E2ESecondaryRegionView =
+ vsoc::E2ERegionView<layout::e2e_test::E2ESecondaryTestRegionLayout>;
+using E2EUnfindableRegionView =
+ vsoc::E2ERegionView<layout::e2e_test::E2EUnfindableRegionLayout>;
+}; // namespace vsoc
diff --git a/common/vsoc/lib/fb_bcast_layout.cpp b/common/vsoc/lib/fb_bcast_layout.cpp
new file mode 100644
index 0000000..41565d2
--- /dev/null
+++ b/common/vsoc/lib/fb_bcast_layout.cpp
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#include "common/vsoc/shm/fb_bcast_layout.h"
+
+namespace vsoc {
+namespace layout {
+
+namespace framebuffer {
+
+const char* FBBroadcastLayout::region_name = "fb_broadcast";
+
+} // framebuffer_bcast
+} // layout
+} // vsoc
diff --git a/common/vsoc/lib/fb_bcast_region_view.cpp b/common/vsoc/lib/fb_bcast_region_view.cpp
new file mode 100644
index 0000000..3afd9a3
--- /dev/null
+++ b/common/vsoc/lib/fb_bcast_region_view.cpp
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include "common/vsoc/lib/fb_bcast_region_view.h"
+
+#include "common/libs/glog/logging.h"
+#include "common/vsoc/lib/lock_guard.h"
+
+using vsoc::framebuffer::FBBroadcastRegionView;
+
+// We can use a locking protocol because we decided that the streamer should
+// have more priority than the hwcomposer, so it's OK to block the hwcomposer
+// waiting for the streamer to complete, while the streamer will only block on
+// the hwcomposer when it's ran out of work to do and needs to get more from the
+// hwcomposer.
+void FBBroadcastRegionView::BroadcastNewFrame(uint32_t seq_num,
+ vsoc_reg_off_t frame_offset) {
+ {
+ auto lock_guard(make_lock_guard(&data()->bcast_lock));
+ data()->seq_num = seq_num;
+ data()->frame_offset = frame_offset;
+ }
+ // Signaling after releasing the lock may cause spurious wake ups.
+ // Signaling while holding the lock may cause the just-awaken listener to
+ // block immediately trying to acquire the lock.
+ // The former is less costly and slightly less likely to happen.
+ layout::Sides side;
+ side.value_ = layout::Sides::Both;
+ SendSignal(side, &data()->seq_num);
+}
+
+vsoc_reg_off_t FBBroadcastRegionView::WaitForNewFrameSince(uint32_t* last_seq_num) {
+ static std::unique_ptr<RegionWorker> worker = StartWorker();
+ // It's ok to read seq_num here without holding the lock because the lock will
+ // be acquired immediately after so we'll block if necessary to wait for the
+ // critical section in BroadcastNewFrame to complete.
+ // Also, the call to WaitForSignal receives a pointer to seq_num (so the
+ // compiler should not optimize it out) and includes a memory barrier
+ // (FUTEX_WAIT).
+ while (data()->seq_num == *last_seq_num) {
+ // Don't hold the lock when waiting for a signal, will deadlock.
+ WaitForSignal(&data()->seq_num, *last_seq_num);
+ }
+
+ {
+ auto lock_guard(make_lock_guard(&data()->bcast_lock));
+ *last_seq_num = data()->seq_num;
+ return data()->frame_offset;
+ }
+}
diff --git a/common/vsoc/lib/fb_bcast_region_view.h b/common/vsoc/lib/fb_bcast_region_view.h
new file mode 100644
index 0000000..26bc588
--- /dev/null
+++ b/common/vsoc/lib/fb_bcast_region_view.h
@@ -0,0 +1,75 @@
+#pragma once
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include "common/vsoc/lib/typed_region_view.h"
+#include "common/vsoc/shm/fb_bcast_layout.h"
+#include "common/vsoc/shm/graphics.h"
+#include "uapi/vsoc_shm.h"
+
+namespace vsoc {
+namespace framebuffer {
+
+class FBBroadcastRegionView : public vsoc::TypedRegionView<
+ vsoc::layout::framebuffer::FBBroadcastLayout> {
+ public:
+ // Screen width in pixels
+ int x_res() const {
+ return data().x_res;
+ }
+
+ // Screen height in pixels
+ int y_res() const {
+ return data().y_res;
+ }
+
+ // Dots per inch
+ int dpi() const {
+ return data().dpi;
+ }
+
+ // Refresh rate in Hertz
+ int refresh_rate_hz() const {
+ // TODO(jemoreira): region_->data()->refresh_rate_hz;
+ return kFbRefreshRateHz;
+ }
+
+ uint32_t pixel_format() const {
+ return kFbPixelFormat;
+ }
+
+ uint32_t bytes_per_pixel() const {
+ return vsoc::PixelFormatProperties<kFbPixelFormat>::bytes_per_pixel;
+ }
+
+ // Broadcasts a new frame. Meant to be used exclusively by the hwcomposer.
+ // Zero is an invalid frame_num, used to indicate that there is no frame
+ // available. It's expected that frame_num increases monotonically over time,
+ // but there is no hard requirement for this.
+ // frame_offset is the offset of the current frame in the gralloc region.
+ void BroadcastNewFrame(uint32_t frame_num, vsoc_reg_off_t frame_offset);
+
+ // Waits for a new frame (one with a different seq_num than last one we saw).
+ // Returns the offset of the new frame or zero if there was an error, stores
+ // the new sequential number in *last_seq_num.
+ vsoc_reg_off_t WaitForNewFrameSince(uint32_t* last_seq_num);
+
+ private:
+ static constexpr uint32_t kFbPixelFormat = vsoc::VSOC_PIXEL_FORMAT_RGBA_8888;
+ static constexpr int kFbRefreshRateHz = 60;
+};
+} // namespace framebuffer
+} // namespace vsoc
diff --git a/common/vsoc/lib/fb_bcast_region_view_test.cpp b/common/vsoc/lib/fb_bcast_region_view_test.cpp
new file mode 100644
index 0000000..0921058
--- /dev/null
+++ b/common/vsoc/lib/fb_bcast_region_view_test.cpp
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#include "fb_bcast_region_view.h"
+#include "host/libs/config/host_config.h"
+#include <stdio.h>
+
+using vsoc::framebuffer::FBBroadcastRegionView;
+
+int main() {
+ uint32_t frame_num = 0;
+ vsoc_reg_off_t offset = 0;
+ FBBroadcastRegionView region;
+#if defined(CUTTLEFISH_HOST)
+ auto rval = region.Open(vsoc::GetDomain().c_str());
+#else
+ auto rval = region.Open();
+#endif
+ if (!rval) {
+ fprintf(stderr, "Error opening region\n");
+ return 1;
+ }
+
+ while (1) {
+ offset = region.WaitForNewFrameSince(&frame_num);
+ printf("Signaled frame_num = %d, offset = 0x%x\n", frame_num, offset);
+ }
+
+ return 0;
+}
diff --git a/common/vsoc/lib/gralloc_layout.cpp b/common/vsoc/lib/gralloc_layout.cpp
new file mode 100644
index 0000000..9be790a
--- /dev/null
+++ b/common/vsoc/lib/gralloc_layout.cpp
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2017 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 some of the string constants associated with the region layout.
+#include "common/vsoc/shm/gralloc_layout.h"
+
+namespace vsoc {
+namespace layout {
+
+namespace gralloc {
+
+const char* GrallocManagerLayout::region_name = "gralloc_manager";
+const char* GrallocBufferLayout::region_name = "gralloc_memory";
+
+} // gralloc
+} // layout
+} // vsoc
diff --git a/common/vsoc/lib/graphics_common.h b/common/vsoc/lib/graphics_common.h
new file mode 100644
index 0000000..d2db6c4
--- /dev/null
+++ b/common/vsoc/lib/graphics_common.h
@@ -0,0 +1,28 @@
+#pragma once
+
+/*
+ * Copyright (C) 2017 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.
+ */
+#include "common/vsoc/shm/graphics.h"
+
+namespace vsoc {
+
+// Returns BytesPerPixel given a PixelFormat. Code should rely on this rather
+// than accessing the mask directly.
+static inline std::size_t BytesPerPixel(PixelFormat in) {
+ return 1 + (in >> PixelFormatBase::SubformatSize) &
+ (PixelFormatBase::MaxBytesPerPixel - 1);
+}
+} // vsoc
diff --git a/common/vsoc/lib/graphics_test.cpp b/common/vsoc/lib/graphics_test.cpp
new file mode 100644
index 0000000..b43e56e
--- /dev/null
+++ b/common/vsoc/lib/graphics_test.cpp
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#include "common/vsoc/shm/graphics.h"
+
+#include <gtest/gtest.h>
+
+TEST(GraphicsTest, Basic) {
+ // This is a shared memory layout and is mostly tested via static asserts.
+ // If this can compile then the test has passed.
+}
diff --git a/common/vsoc/lib/lock_common.cpp b/common/vsoc/lib/lock_common.cpp
new file mode 100644
index 0000000..ed7962e
--- /dev/null
+++ b/common/vsoc/lib/lock_common.cpp
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#include "common/vsoc/shm/lock.h"
+
+#include "common/libs/glog/logging.h"
+#include "common/vsoc/lib/compat.h"
+#include "common/vsoc/lib/region_view.h"
+
+#include <stdlib.h>
+
+using vsoc::layout::Sides;
+
+namespace {
+
+const uint32_t LockFree = 0U;
+const uint32_t GuestWaitingFlag = (Sides::Guest << 30);
+const uint32_t HostWaitingFlag = (Sides::Host << 30);
+const uint32_t OurWaitingFlag = (Sides::OurSide << 30);
+static_assert(GuestWaitingFlag, "GuestWaitingFlag is 0");
+static_assert(HostWaitingFlag, "HostWaitingFlag is 0");
+static_assert((GuestWaitingFlag & HostWaitingFlag) == 0,
+ "Waiting flags should not share bits");
+
+// const uint32_t ReservedForRobustLocksFlag = 0x20000000U;
+
+// PID_MAX_LIMIT appears to be 0x00400000U, so we're probably ok here
+const uint32_t OwnerMask = 0x1FFFFFFFU;
+}; // namespace
+
+namespace vsoc {
+/**
+ * This is a generic synchronization primitive that provides space for the
+ * owner of the lock to write platform-specific information.
+ */
+bool vsoc::layout::WaitingLockBase::TryLock(uint32_t tid,
+ uint32_t* expected_out) {
+ uint32_t masked_tid = tid & OwnerMask;
+ uint32_t expected = LockFree;
+ while (1) {
+ // First try to lock assuming that the mutex is free
+ if (lock_uint32_.compare_exchange_strong(expected, masked_tid)) {
+ // We got the lock.
+ return true;
+ }
+ // We didn't get the lock and our wait flag is already set. It's safe to
+ // try to sleep
+ if (expected & OurWaitingFlag) {
+ *expected_out = expected;
+ return false;
+ }
+ // Attempt to set the wait flag. This will fail if the lock owner changes.
+ while (1) {
+ uint32_t add_wait_flag = expected | OurWaitingFlag;
+ if (lock_uint32_.compare_exchange_strong(expected, add_wait_flag)) {
+ // We set the waiting flag. Try to sleep.
+ *expected_out = expected;
+ return false;
+ }
+ // The owner changed, but we at least we got the wait flag.
+ // Try sleeping
+ if (expected & OurWaitingFlag) {
+ *expected_out = expected;
+ return false;
+ }
+ // Special case: the lock was just freed. Stop trying to set the
+ // waiting flag and try to grab the lock.
+ if (expected == LockFree) {
+ break;
+ }
+ // The owner changed and we have no wait flag
+ // Try to set the wait flag again
+ }
+ // This only happens if the lock was freed while we attempt the set the
+ // wait flag. Try to grab the lock again
+ }
+ // Never reached.
+}
+
+layout::Sides vsoc::layout::WaitingLockBase::UnlockCommon(uint32_t tid) {
+ uint32_t expected_state = lock_uint32_;
+
+ // We didn't hold the lock. This process is insane and must die before it
+ // does damage.
+ if ((tid ^ expected_state) & OwnerMask) {
+ LOG(FATAL) << tid << " unlocking " << this << " owned by "
+ << expected_state;
+ }
+ // If contention is just starting this may fail twice (once for each bit)
+ // expected_state updates on each failure. When this finishes we have
+ // one bit for each waiter
+ while (1) {
+ if (lock_uint32_.compare_exchange_strong(expected_state, LockFree)) {
+ break;
+ }
+ }
+ if ((expected_state ^ tid) & OwnerMask) {
+ LOG(FATAL) << "Lock owner of " << this << " changed from " << tid << " to "
+ << expected_state << " during unlock";
+ }
+ Sides rval;
+ rval.value_ = expected_state & (GuestWaitingFlag | HostWaitingFlag) >> 30;
+ return rval;
+}
+
+void layout::GuestAndHostLock::Lock(RegionView* region) {
+ uint32_t* uaddr = reinterpret_cast<uint32_t*>(&lock_uint32_);
+ uint32_t expected;
+ uint32_t tid = gettid();
+ while (1) {
+ if (TryLock(tid, &expected)) {
+ return;
+ }
+ region->WaitForSignal(uaddr, expected);
+ }
+}
+
+void layout::GuestAndHostLock::Unlock(RegionView* region) {
+ uint32_t* uaddr = reinterpret_cast<uint32_t*>(&lock_uint32_);
+ region->SendSignal(UnlockCommon(gettid()), uaddr);
+}
+
+} // namespace vsoc
diff --git a/common/vsoc/lib/lock_guard.h b/common/vsoc/lib/lock_guard.h
new file mode 100644
index 0000000..371cf52
--- /dev/null
+++ b/common/vsoc/lib/lock_guard.h
@@ -0,0 +1,82 @@
+#pragma once
+
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include "common/vsoc/shm/lock.h"
+
+namespace vsoc {
+
+class RegionView;
+
+/*
+ * Implements std::lock_guard like functionality for the vsoc locks.
+ */
+
+template <typename Lock>
+class LockGuard {
+ public:
+ explicit LockGuard(Lock* lock) : lock_(lock) { lock_->Lock(); }
+
+ LockGuard(LockGuard<Lock>&& o) noexcept {
+ lock_ = o.lock_;
+ o.lock_ = nullptr;
+ }
+
+ LockGuard(const LockGuard<Lock>&) = delete;
+ LockGuard<Lock>& operator=(const LockGuard<Lock>&) = delete;
+
+ ~LockGuard() {
+ if (lock_) {
+ lock_->Unlock();
+ }
+ }
+
+ private:
+ Lock* lock_;
+};
+
+template <>
+class LockGuard<::vsoc::layout::GuestAndHostLock> {
+ using Lock = ::vsoc::layout::GuestAndHostLock;
+
+ public:
+ LockGuard(Lock* lock, RegionView* region) : lock_(lock), region_(region) {
+ lock_->Lock(region_);
+ }
+
+ LockGuard(LockGuard<Lock>&& o) noexcept {
+ lock_ = o.lock_;
+ o.lock_ = nullptr;
+ region_ = o.region_;
+ o.region_ = nullptr;
+ }
+
+ LockGuard(const LockGuard<Lock>&) = delete;
+ LockGuard<Lock>& operator=(const LockGuard<Lock>&) = delete;
+
+ ~LockGuard() {
+ if (lock_) {
+ lock_->Unlock(region_);
+ }
+ }
+
+ private:
+ Lock* lock_;
+ RegionView* region_;
+};
+
+} // namespace vsoc
diff --git a/common/vsoc/lib/lock_test.cpp b/common/vsoc/lib/lock_test.cpp
new file mode 100644
index 0000000..d09b5a4
--- /dev/null
+++ b/common/vsoc/lib/lock_test.cpp
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#include "common/vsoc/shm/lock.h"
+
+#include <thread>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+#include "common/vsoc/lib/region_view.h"
+
+#ifdef CUTTLEFISH_HOST
+using MyLock = vsoc::layout::HostLock;
+#else
+using MyLock = vsoc::layout::GuestLock;
+#endif
+
+class SimpleLocker {
+ public:
+ enum State {
+ BEFORE_EXECUTION,
+ BEFORE_LOCK,
+ IN_CRITICAL_SECTION,
+ DONE,
+ JOINED
+ };
+
+ explicit SimpleLocker(MyLock* lock)
+ : lock_(lock), thread_(&SimpleLocker::Work, this) {}
+
+ void Work() {
+ state_ = BEFORE_LOCK;
+ lock_->Lock();
+ state_ = IN_CRITICAL_SECTION;
+ InCriticalSection();
+ lock_->Unlock();
+ state_ = DONE;
+ }
+
+ void InCriticalSection() {}
+
+ void Join() {
+ thread_.join();
+ state_ = JOINED;
+ }
+
+ protected:
+ MyLock* lock_;
+ volatile State state_{BEFORE_EXECUTION};
+ std::thread thread_;
+};
+
+TEST(LockTest, Basic) {
+ // In production regions are always 0 filled on allocation. That's not
+ // true on the stack, so initialize the lock when we declare it.
+ MyLock lock{};
+ SimpleLocker a(&lock);
+ SimpleLocker b(&lock);
+ a.Join();
+ b.Join();
+}
diff --git a/common/vsoc/lib/mock_region_view.h b/common/vsoc/lib/mock_region_view.h
new file mode 100644
index 0000000..b6951f7
--- /dev/null
+++ b/common/vsoc/lib/mock_region_view.h
@@ -0,0 +1,118 @@
+#pragma once
+
+/*
+ * Copyright (C) 2017 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.
+ */
+#include <linux/futex.h>
+#include <sys/syscall.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <chrono>
+#include <condition_variable>
+#include <mutex>
+#include <thread>
+#include <unordered_map>
+
+#include "common/vsoc/lib/region_signaling_interface.h"
+
+namespace vsoc {
+namespace test {
+
+/**
+ * MockRegionView mocks a region in the shared memory window by calloc and
+ * futex. It supports only one-sided signal as in it doesn't do anything on
+ * sending or waiting interrupt. This is to test if a particular layout
+ * behaves correctly when there are multiple threads accessing it.
+ */
+template <typename Layout>
+class MockRegionView : public vsoc::RegionSignalingInterface {
+ public:
+ explicit MockRegionView() {};
+ virtual ~MockRegionView() {
+ if (region_base_) {
+ free(region_base_);
+ region_base_ = nullptr;
+ }
+ };
+
+ bool Open() {
+ region_base_ = calloc(sizeof(Layout), 1);
+ return !region_base_;
+ };
+
+ virtual void SendSignal(vsoc::layout::Sides /* sides_to_signal */,
+ uint32_t* uaddr) {
+ syscall(SYS_futex, reinterpret_cast<int32_t*>(uaddr), FUTEX_WAKE, -1,
+ nullptr, nullptr, 0);
+ }
+
+ virtual void WaitForSignal(uint32_t* uaddr, uint32_t expected_value) {
+ {
+ std::lock_guard<std::mutex> guard(mutex_);
+ if (tid_to_addr_.find(std::this_thread::get_id()) != tid_to_addr_.end()) {
+ // Same thread tries to wait
+ return;
+ }
+ tid_to_addr_.emplace(std::this_thread::get_id(), uaddr);
+ map_changed_.notify_one();
+ }
+
+ syscall(SYS_futex, uaddr, FUTEX_WAIT, expected_value, nullptr, nullptr, 0);
+
+ {
+ std::lock_guard<std::mutex> guard(mutex_);
+ tid_to_addr_.erase(std::this_thread::get_id());
+ }
+ }
+
+ // Mock methods from TypedRegionView
+ Layout* data() { return reinterpret_cast<Layout*>(region_base_); };
+
+ // Check wait status on a specificy thread
+ bool IsBlocking(std::thread::id tid) {
+ while(1) {
+ std::unique_lock<std::mutex> lock(mutex_);
+ if (tid_to_addr_.find(tid) != tid_to_addr_.end()) {
+ return true;
+ }
+ // Allow some time as tid map might not be updated yet
+ while(tid_to_addr_.find(tid) == tid_to_addr_.end()) {
+ if (map_changed_.wait_for(
+ lock, std::chrono::seconds(kWaitTimeoutInSec)) ==
+ std::cv_status::timeout) {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+
+ private:
+ // Timeout to avoid a race on checking if a thread is blocked
+ static constexpr int kWaitTimeoutInSec = 5;
+
+ void *region_base_{};
+ std::mutex mutex_;
+ std::condition_variable map_changed_;
+ std::unordered_map<std::thread::id, uint32_t*> tid_to_addr_;
+};
+
+template <typename Layout>
+constexpr int MockRegionView<Layout>::kWaitTimeoutInSec;
+
+
+} // namespace test
+} // namespace vsoc
diff --git a/common/vsoc/lib/region_control.h b/common/vsoc/lib/region_control.h
new file mode 100644
index 0000000..82464aa
--- /dev/null
+++ b/common/vsoc/lib/region_control.h
@@ -0,0 +1,89 @@
+#pragma once
+
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include <stdint.h>
+#include <memory>
+#include "uapi/vsoc_shm.h"
+
+namespace vsoc {
+
+/**
+ * Base class for side-specific utility functions that work on regions.
+ * The methods in this class do not assume that the region is mapped in memory.
+ * This makes is appropriate for ManagedRegions and certain low-level tests
+ * of VSoC shared memory. Most other users will want to use TypedRegions with
+ * a defined RegionLayout.
+ *
+ * This class is not directly instantiable because it must be specialized with
+ * additional fields for the host and guest.
+ */
+class RegionControl {
+ public:
+ virtual ~RegionControl() {
+ if (region_base_) {
+ munmap(region_base_, region_size());
+ }
+ region_base_ = nullptr;
+ }
+
+#if defined(CUTTLEFISH_HOST)
+ static std::shared_ptr<RegionControl> Open(const char* region_name,
+ const char* domain);
+#else
+ static std::shared_ptr<RegionControl> Open(const char* region_name);
+#endif
+
+ const vsoc_device_region& region_desc() const { return region_desc_; }
+
+ // Returns the size of the entire region, including the signal tables.
+ uint32_t region_size() const {
+ return region_desc_.region_end_offset - region_desc_.region_begin_offset;
+ }
+
+ // Returns the size of the region that is usable for region-specific data.
+ uint32_t region_data_size() const {
+ return region_size() - region_desc_.offset_of_region_data;
+ }
+
+ // Creates a FdScopedPermission. Returns the file descriptor or -1 on
+ // failure. FdScopedPermission is not supported on the host, so -1 is
+ // always returned there.
+ virtual int CreateFdScopedPermission(const char* managed_region_name,
+ vsoc_reg_off_t owner_offset,
+ uint32_t owned_value,
+ vsoc_reg_off_t begin_offset,
+ vsoc_reg_off_t end_offset) = 0;
+
+ // Interrupt our peer, causing it to scan the outgoing_signal_table
+ virtual bool InterruptPeer() = 0;
+
+ // Wake the local signal table scanner. Primarily used during shutdown
+ virtual void InterruptSelf() = 0;
+
+ // Maps the entire region at an address, returning a pointer to the mapping
+ virtual void* Map() = 0;
+
+ // Wait for an interrupt from our peer
+ virtual void WaitForInterrupt() = 0;
+
+ protected:
+ RegionControl() {}
+ void* region_base_{};
+ vsoc_device_region region_desc_{};
+};
+} // namespace vsoc
diff --git a/common/vsoc/lib/region_signaling_interface.h b/common/vsoc/lib/region_signaling_interface.h
new file mode 100644
index 0000000..9adffa5
--- /dev/null
+++ b/common/vsoc/lib/region_signaling_interface.h
@@ -0,0 +1,54 @@
+#pragma once
+
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include <stdlib.h>
+
+#include "common/vsoc/shm/base.h"
+
+namespace vsoc {
+
+/**
+ * Interface that defines signaling and waiting for signal.
+ */
+class RegionSignalingInterface {
+ public:
+ virtual ~RegionSignalingInterface() {};
+
+ // Post a signal to the guest, the host, or both.
+ // See futex(2) FUTEX_WAKE for details.
+ //
+ // sides_to_signal: controls where the signal is sent
+ //
+ // signal_addr: the memory location to signal. Must be within the region.
+ virtual void SendSignal(layout::Sides sides_to_signal, uint32_t* signal_addr) = 0;
+
+ // This implements the following:
+ // if (*signal_addr == last_observed_value)
+ // wait_for_signal_at(signal_addr);
+ //
+ // Note: the caller still needs to check the value at signal_addr because
+ // this function may return early for reasons that are implementation-defined.
+ // See futex(2) FUTEX_WAIT for details.
+ //
+ // signal_addr: the memory that will be signaled. Must be within the region.
+ //
+ // last_observed_value: the value that motivated the calling code to wait.
+ virtual void WaitForSignal(uint32_t* signal_addr, uint32_t last_observed_value) = 0;
+};
+
+} // namespace vsoc
diff --git a/common/vsoc/lib/region_view.cpp b/common/vsoc/lib/region_view.cpp
new file mode 100644
index 0000000..fbd0393
--- /dev/null
+++ b/common/vsoc/lib/region_view.cpp
@@ -0,0 +1,185 @@
+#include "common/vsoc/lib/region_view.h"
+
+#include <linux/futex.h>
+#include <sys/mman.h>
+#include <sys/syscall.h>
+
+#include "common/libs/glog/logging.h"
+
+namespace {
+const uint32_t UADDR_OFFSET_MASK = 0xFFFFFFFC;
+const uint32_t UADDR_OFFSET_ROUND_TRIP_FLAG = 1;
+} // namespace
+
+using vsoc::layout::Sides;
+
+vsoc::RegionWorker::RegionWorker(RegionView* region)
+ : region_(region), thread_(&vsoc::RegionWorker::Work, this) {}
+
+void vsoc::RegionWorker::Work() {
+ while (!stopping_) {
+ region_->WaitForInterrupt();
+ if (stopping_) {
+ return;
+ }
+ region_->ProcessSignalsFromPeer([](uint32_t* uaddr){
+ syscall(SYS_futex, uaddr, FUTEX_WAKE, -1, nullptr, nullptr, 0);
+ });
+ }
+}
+
+vsoc::RegionWorker::~RegionWorker() {
+ stopping_ = true;
+ region_->InterruptSelf();
+ thread_.join();
+}
+
+vsoc::RegionView::~RegionView() {
+ // region_base_ is borrowed here. It's owned by control_, which is
+ // responsible for unmapping the memory
+ region_base_ = nullptr;
+}
+
+#if defined(CUTTLEFISH_HOST)
+bool vsoc::RegionView::Open(const char* name, const char* domain) {
+ control_ = vsoc::RegionControl::Open(name, domain);
+ if (!control_) {
+ return false;
+ }
+ region_base_ = control_->Map();
+ return region_base_ != nullptr;
+}
+#else
+bool vsoc::RegionView::Open(const char* name) {
+ control_ = vsoc::RegionControl::Open(name);
+ if (!control_) {
+ return false;
+ }
+ region_base_ = control_->Map();
+ return region_base_ != nullptr;
+}
+#endif
+
+// Interrupt our peer, causing it to scan the outgoing_signal_table
+bool vsoc::RegionView::MaybeInterruptPeer() {
+ if (region_offset_to_pointer<std::atomic<uint32_t>>(
+ outgoing_signal_table().interrupt_signalled_offset)
+ ->exchange(1)) {
+ return false;
+ }
+ return control_->InterruptPeer();
+}
+
+// Wait for an interrupt from our peer
+void vsoc::RegionView::WaitForInterrupt() {
+ while (1) {
+ if (region_offset_to_pointer<std::atomic<uint32_t>>(
+ incoming_signal_table().interrupt_signalled_offset)
+ ->exchange(0)) {
+ return;
+ }
+ control_->WaitForInterrupt();
+ }
+}
+
+void vsoc::RegionView::ProcessSignalsFromPeer(
+ std::function<void(uint32_t*)> signal_handler) {
+ const vsoc_signal_table_layout& table = incoming_signal_table();
+ const size_t num_offsets = (1 << table.num_nodes_lg2);
+ std::atomic<uint32_t>* offsets =
+ region_offset_to_pointer<std::atomic<uint32_t>>(
+ table.futex_uaddr_table_offset);
+ for (size_t i = 0; i < num_offsets; ++i) {
+ uint32_t offset = offsets[i].exchange(0);
+ if (offset) {
+ bool round_trip = offset & UADDR_OFFSET_ROUND_TRIP_FLAG;
+ uint32_t* uaddr =
+ region_offset_to_pointer<uint32_t>(offset & UADDR_OFFSET_MASK);
+ signal_handler(uaddr);
+ if (round_trip) {
+ SendSignalToPeer(uaddr, false);
+ }
+ }
+ }
+}
+
+void vsoc::RegionView::SendSignal(Sides sides_to_signal, uint32_t* uaddr) {
+ if (sides_to_signal.value_ & Sides::Peer) {
+ // If we should also be signalling our side set the round trip flag on
+ // the futex signal.
+ bool round_trip = sides_to_signal.value_ & Sides::OurSide;
+ SendSignalToPeer(reinterpret_cast<uint32_t*>(uaddr), round_trip);
+ // Return without signaling our waiters to give the other side a chance
+ // to run.
+ return;
+ }
+ if (sides_to_signal.value_ & Sides::OurSide) {
+ syscall(SYS_futex, reinterpret_cast<int32_t*>(uaddr), FUTEX_WAKE, -1,
+ nullptr, nullptr, 0);
+ }
+}
+
+void vsoc::RegionView::SendSignalToPeer(uint32_t* uaddr, bool round_trip) {
+ const vsoc_signal_table_layout& table = outgoing_signal_table();
+ std::atomic<uint32_t>* offsets =
+ region_offset_to_pointer<std::atomic<uint32_t>>(
+ table.futex_uaddr_table_offset);
+ // maximum index in the node that can hold an offset;
+ const size_t max_index = (1 << table.num_nodes_lg2) - 1;
+ uint32_t offset = pointer_to_region_offset(uaddr);
+ if (offset & ~UADDR_OFFSET_MASK) {
+ LOG(FATAL) << "uaddr offset is not naturally aligned " << uaddr;
+ }
+ // Guess at where this offset should go in the table.
+ // Do this before we set the round-trip flag.
+ size_t hash = (offset >> 2) & max_index;
+ if (round_trip) {
+ offset |= UADDR_OFFSET_ROUND_TRIP_FLAG;
+ }
+ while (1) {
+ uint32_t expected = 0;
+ if (offsets[hash].compare_exchange_strong(expected, offset)) {
+ // We stored the offset. Send the interrupt.
+ this->MaybeInterruptPeer();
+ break;
+ }
+ // We didn't store, but the value was already in the table with our flag.
+ // Return without interrupting.
+ if (expected == offset) {
+ return;
+ }
+ // Hash collision. Try again in a different node
+ if ((expected & UADDR_OFFSET_MASK) != (offset & UADDR_OFFSET_MASK)) {
+ hash = (hash + 1) & max_index;
+ continue;
+ }
+ // Our offset was in the bucket, but the flags didn't match.
+ // We're done if the value in the node had the round trip flag set.
+ if (expected & UADDR_OFFSET_ROUND_TRIP_FLAG) {
+ return;
+ }
+ // We wanted the round trip flag, but the value in the bucket didn't set it.
+ // Do a second swap to try to set it.
+ if (offsets[hash].compare_exchange_strong(expected, offset)) {
+ // It worked. We're done.
+ return;
+ }
+ if (expected == offset) {
+ // expected was the offset without the flag. After the swap it has the
+ // the flag. This means that some other thread set the flag, so
+ // we're done.
+ return;
+ }
+ // Something about the offset changed. We need to go around again, because:
+ // our peer processed the old entry
+ // another thread may have stolen the node while we were distracted
+ }
+}
+
+std::unique_ptr<vsoc::RegionWorker> vsoc::RegionView::StartWorker() {
+ return std::unique_ptr<vsoc::RegionWorker>(new vsoc::RegionWorker(this));
+}
+
+void vsoc::RegionView::WaitForSignal(uint32_t* uaddr, uint32_t expected_value) {
+ syscall(SYS_futex, uaddr, FUTEX_WAIT, expected_value, nullptr, nullptr, 0);
+}
diff --git a/common/vsoc/lib/region_view.h b/common/vsoc/lib/region_view.h
new file mode 100644
index 0000000..5210924
--- /dev/null
+++ b/common/vsoc/lib/region_view.h
@@ -0,0 +1,206 @@
+#pragma once
+
+/*
+ * Copyright (C) 2017 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.
+ */
+
+// Object that represents a region on the Host
+
+#include <stdlib.h>
+#include <sys/mman.h>
+#include <atomic>
+#include <cstdint>
+
+#include <functional>
+#include <thread>
+
+#include "common/libs/fs/shared_fd.h"
+#include "common/libs/glog/logging.h"
+#include "common/vsoc/lib/lock_guard.h"
+#include "common/vsoc/lib/region_control.h"
+#include "common/vsoc/lib/region_signaling_interface.h"
+#include "common/vsoc/shm/base.h"
+#include "uapi/vsoc_shm.h"
+
+namespace vsoc {
+
+class RegionView;
+
+/**
+ * Represents a task that is tied to a RegionView.
+ *
+ * This is currently used for the task that forwards futexes across the
+ * shared memory window.
+ */
+class RegionWorker {
+ public:
+ explicit RegionWorker(RegionView* region);
+ ~RegionWorker();
+ void Work();
+
+ protected:
+ RegionView* region_;
+ std::thread thread_;
+ volatile bool stopping_{};
+};
+
+/**
+ * Base class to access a mapped region in VSoC shared memory.
+ * This class holds the methods that depends on the region's memory having an
+ * address. The RegionControl class holds the methods that can be invoked
+ * without mapping the region.
+ */
+class RegionView : public RegionSignalingInterface {
+ public:
+ virtual ~RegionView();
+
+#if defined(CUTTLEFISH_HOST)
+ bool Open(const char* region_name, const char* domain);
+#else
+ bool Open(const char* region_name);
+#endif
+
+ // Returns a pointer to the table that will be scanned for signals
+ const vsoc_signal_table_layout& incoming_signal_table();
+
+ // Returns a pointer to the table that will be used to post signals
+ const vsoc_signal_table_layout& outgoing_signal_table();
+
+ // Returns true iff an interrupt is queued in the signal table
+ bool HasIncomingInterrupt() {
+ return *region_offset_to_pointer<std::atomic<uint32_t>>(
+ incoming_signal_table().interrupt_signalled_offset);
+ }
+
+ // Wake any threads waiting for an interrupt. This is generally used during
+ // shutdown.
+ void InterruptSelf() { control_->InterruptSelf(); }
+
+ // Interrupt our peer if an interrupt is not already on the way.
+ // Returns true if the interrupt was sent, false if an interrupt was already
+ // pending.
+ bool MaybeInterruptPeer();
+
+ // Scan in the incoming signal table, issuing futex calls for any posted
+ // signals and then reposting them to the peer if they were round-trip
+ // signals.
+ //
+ // signal_handler: An action to perform on every offset signalled by our
+ // peer, usually a FUTEX_WAKE call, but can be customized for other
+ // purposes.
+ void ProcessSignalsFromPeer(
+ std::function<void(uint32_t*)> signal_handler);
+
+ // Post a signal to the guest, the host, or both.
+ // See futex(2) FUTEX_WAKE for details.
+ //
+ // sides_to_signal: controls where the signal is sent
+ //
+ // signal_addr: the memory location to signal. Must be within the region.
+ void SendSignal(layout::Sides sides_to_signal, uint32_t* signal_addr);
+
+ // Post a signal to our peer for a specific memeory location.
+ // See futex(2) FUTEX_WAKE for details.
+ //
+ // signal_addr: the memory location to signal. Must be within the region.
+ //
+ // round_trip: true if there may be waiters on both sides of the shared
+ // memory.
+ void SendSignalToPeer(uint32_t* signal_addr, bool round_trip);
+
+ // Waits until an interrupt appears on this region, then clears the
+ // interrupted flag and returns.
+ void WaitForInterrupt();
+
+ // This implements the following:
+ // if (*signal_addr == last_observed_value)
+ // wait_for_signal_at(signal_addr);
+ //
+ // Note: the caller still needs to check the value at signal_addr because
+ // this function may return early for reasons that are implementation-defined.
+ // See futex(2) FUTEX_WAIT for details.
+ //
+ // signal_addr: the memory that will be signaled. Must be within the region.
+ //
+ // last_observed_value: the value that motivated the calling code to wait.
+ void WaitForSignal(uint32_t* signal_addr, uint32_t last_observed_value);
+
+ // Starts the signal table scanner. This must be invoked by subclasses, which
+ // must store the returned unique_ptr as a class member.
+ std::unique_ptr<RegionWorker> StartWorker();
+
+ // Returns a pointer to the start of region data that is cast to the given
+ // type. Initializers that run in the launcher use this to get a typed view of the region. Most other cases should be handled via TypedRegionView.
+ template <typename LayoutType>
+ LayoutType* GetLayoutPointer() {
+ return this->region_offset_to_pointer<LayoutType>(
+ control_->region_desc().offset_of_region_data);
+ }
+
+ // Helper functions for lock guards. This approach has two advantages:
+ //
+ // o Callers don't have to be refactored when lock types change
+ // o The region pointer can be provided automatically
+ template <typename T>
+ LockGuard<T> make_lock_guard(T* lock) {
+ return LockGuard<T>(lock);
+ }
+
+ LockGuard<::vsoc::layout::GuestAndHostLock> make_lock_guard(
+ ::vsoc::layout::GuestAndHostLock* l) {
+ return LockGuard<::vsoc::layout::GuestAndHostLock>(l, this);
+ }
+
+ protected:
+ template <typename T>
+ T* region_offset_to_pointer(vsoc_reg_off_t offset) {
+ if (offset > control_->region_size()) {
+ LOG(FATAL) << __FUNCTION__ << ": " << offset << " not in region @"
+ << region_base_;
+ }
+ return reinterpret_cast<T*>(reinterpret_cast<uintptr_t>(region_base_) +
+ offset);
+ }
+
+ template <typename T>
+ const T& region_offset_to_reference(vsoc_reg_off_t offset) const {
+ if (offset > control_->region_size()) {
+ LOG(FATAL) << __FUNCTION__ << ": " << offset << " not in region @"
+ << region_base_;
+ }
+ return *reinterpret_cast<const T*>(
+ reinterpret_cast<uintptr_t>(region_base_) + offset);
+ }
+
+ // Calculates an offset based on a pointer in the region. Crashes if the
+ // pointer isn't in the region.
+ // This is mostly for the RegionView's internal plumbing. Use TypedRegionView
+ // and RegionLayout to avoid this in most cases.
+ template <typename T>
+ vsoc_reg_off_t pointer_to_region_offset(T* ptr) {
+ vsoc_reg_off_t rval = reinterpret_cast<uintptr_t>(ptr) -
+ reinterpret_cast<uintptr_t>(region_base_);
+ if (rval > control_->region_size()) {
+ LOG(FATAL) << __FUNCTION__ << ": " << ptr << " not in region @"
+ << region_base_;
+ }
+ return rval;
+ }
+
+ std::shared_ptr<RegionControl> control_;
+ void* region_base_{};
+};
+
+} // namespace vsoc
diff --git a/common/vsoc/lib/single_sided_signal.h b/common/vsoc/lib/single_sided_signal.h
new file mode 100644
index 0000000..c32361a
--- /dev/null
+++ b/common/vsoc/lib/single_sided_signal.h
@@ -0,0 +1,45 @@
+#pragma once
+
+/*
+ * Copyright (C) 2017 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.
+ */
+
+// Signaling mechanism that allows threads to signal changes to shared
+// memory and to wait for signals.
+
+namespace vsoc {
+/**
+ * Defines the strategy for signaling among threads on a single kernel.
+ */
+namespace SingleSidedSignal {
+/**
+ * Waits for a signal, assuming the the word at addr matches expected_state.
+ * Will return immediately if the value does not match.
+ * Callers must be equipped to cope with spurious returns.
+ */
+static void AwaitSignal(uint32_t expected_state, uint32_t* uaddr) {
+ syscall(SYS_futex, uaddr, FUTEX_WAIT, expected_state, nullptr, nullptr, 0);
+}
+
+/**
+ * Sends a signal to every thread in AwaitSignal() using the address in
+ * uaddr.
+ */
+static void Signal(std::atomic<uint32_t>* uaddr) {
+ syscall(SYS_futex, reinterpret_cast<int32_t*>(uaddr), FUTEX_WAKE, -1, nullptr,
+ nullptr, 0);
+}
+} // namespace SingleSidedSignal
+} // namespace vsoc
diff --git a/common/vsoc/lib/typed_region_view.h b/common/vsoc/lib/typed_region_view.h
new file mode 100644
index 0000000..ec16c4d
--- /dev/null
+++ b/common/vsoc/lib/typed_region_view.h
@@ -0,0 +1,63 @@
+#pragma once
+
+/*
+ * Copyright (C) 2017 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.
+ */
+
+// Object that represents a region on the Host
+
+#include "common/vsoc/lib/region_view.h"
+
+#include <map>
+#include <mutex>
+#include <string>
+
+namespace vsoc {
+
+/**
+ * This class adds methods that depend on the Region's type.
+ * This may be directly constructed. However, it may be more effective to
+ * subclass it, adding region-specific methods.
+ *
+ * Layout should be VSoC shared memory compatible, defined in common/vsoc/shm,
+ * and should have a constant string region name.
+ */
+template <typename LayoutType>
+class TypedRegionView : public RegionView {
+ public:
+ using Layout = LayoutType;
+
+ /* Returns a pointer to the region with a type that matches the layout */
+ LayoutType* data() {
+ return this->GetLayoutPointer<LayoutType>();
+ }
+
+ const LayoutType& data() const {
+ return this->region_offset_to_reference<LayoutType>(
+ control_->region_desc().offset_of_region_data);
+ }
+
+#if defined(CUTTLEFISH_HOST)
+ bool Open(const char* domain) {
+ return RegionView::Open(LayoutType::region_name, domain);
+ }
+#else
+ bool Open() {
+ return RegionView::Open(LayoutType::region_name);
+ }
+#endif
+};
+
+} // namespace vsoc
diff --git a/common/vsoc/lib/wifi_exchange_layout.cpp b/common/vsoc/lib/wifi_exchange_layout.cpp
new file mode 100644
index 0000000..12e9577
--- /dev/null
+++ b/common/vsoc/lib/wifi_exchange_layout.cpp
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#include "common/vsoc/shm/wifi_exchange_layout.h"
+
+namespace vsoc {
+namespace layout {
+namespace wifi {
+const char* WifiExchangeLayout::region_name = "wifi_exchange";
+} // namespace wifi
+} // namespace layout
+} // namespace vsoc
diff --git a/common/vsoc/lib/wifi_exchange_view.cpp b/common/vsoc/lib/wifi_exchange_view.cpp
new file mode 100644
index 0000000..23e0bc6
--- /dev/null
+++ b/common/vsoc/lib/wifi_exchange_view.cpp
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#include "common/vsoc/lib/wifi_exchange_view.h"
+
+#include "common/vsoc/lib/circqueue_impl.h"
+
+namespace vsoc {
+namespace wifi {
+
+bool WifiExchangeView::Send(const void* buffer, size_t length) {
+#ifdef CUTTLEFISH_HOST
+ return data()->guest_ingress.Write(this, static_cast<const char*>(buffer),
+ length) == length;
+#else
+ return data()->guest_egress.Write(this, static_cast<const char*>(buffer),
+ length) == length;
+#endif
+}
+
+intptr_t WifiExchangeView::Recv(void* buffer, size_t max_length) {
+#ifdef CUTTLEFISH_HOST
+ return data()->guest_egress.Read(this, static_cast<char*>(buffer),
+ max_length);
+#else
+ return data()->guest_ingress.Read(this, static_cast<char*>(buffer),
+ max_length);
+#endif
+}
+
+void WifiExchangeView::SetGuestMACAddress(const uint8_t* mac_address) {
+ memcpy(data()->mac_address, mac_address, ETH_ALEN);
+}
+
+void WifiExchangeView::GetGuestMACAddress(uint8_t* mac_address) {
+ memcpy(mac_address, data()->mac_address, ETH_ALEN);
+}
+
+} // namespace wifi
+} // namespace vsoc
diff --git a/common/vsoc/lib/wifi_exchange_view.h b/common/vsoc/lib/wifi_exchange_view.h
new file mode 100644
index 0000000..83d7e6a
--- /dev/null
+++ b/common/vsoc/lib/wifi_exchange_view.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#pragma once
+
+#include "common/vsoc/lib/typed_region_view.h"
+#include "common/vsoc/shm/wifi_exchange_layout.h"
+#include "uapi/vsoc_shm.h"
+
+namespace vsoc {
+namespace wifi {
+
+class WifiExchangeView
+ : public vsoc::TypedRegionView<vsoc::layout::wifi::WifiExchangeLayout> {
+ public:
+ // Send netlink packet to peer.
+ // returns true, if operation was successful.
+ bool Send(const void* buffer, size_t length);
+
+ // Receive netlink packet from peer.
+ // Returns number of bytes read, or negative value, if failed.
+ intptr_t Recv(void* buffer, size_t max_length);
+
+ // Set guest MAC address.
+ void SetGuestMACAddress(const uint8_t* mac_address);
+ void GetGuestMACAddress(uint8_t* mac_address);
+
+ void SetConfigReady();
+ void WaitConfigReady();
+};
+
+} // namespace wifi
+} // namespace vsoc
diff --git a/common/vsoc/shm/base.h b/common/vsoc/shm/base.h
new file mode 100644
index 0000000..abac492
--- /dev/null
+++ b/common/vsoc/shm/base.h
@@ -0,0 +1,86 @@
+#pragma once
+/*
+ * Copyright (C) 2017 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.
+ */
+
+// Base macros for all layout structures.
+
+#include <type_traits>
+#include "common/vsoc/shm/version.h"
+
+// ShmTypeValidator provides meaningful information about the type size
+// mismatch in compilation error messages, eg.
+//
+// error:
+// static_assert failed "Class size changed, update the version"
+// static_assert(Current == Expected,
+// note: in instantiation of template class
+// 'ShmTypeValidator<vsoc::layout::myclass::ClassName, 1232, 1240>'
+// requested here ASSERT_SHM_COMPATIBLE(ClassName, myclass);
+//
+template<typename Type, size_t Current, size_t Expected>
+struct ShmTypeValidator {
+ static_assert(Current == Expected,
+ "Class size changed, update the version");
+ static_assert(std::is_trivial<Type>(),
+ "Class uses features that are unsafe");
+ static constexpr bool valid = (Current == Expected);
+};
+
+#define ASSERT_SHM_COMPATIBLE(T, R) \
+ static_assert( \
+ ShmTypeValidator<T, sizeof(T), vsoc::layout::version_info::R::T##_size> \
+ ::valid, "Compilation error. Please fix above errors and retry.")
+
+#define ASSERT_SHM_CONSTANT_VALUE(T, R) \
+ static_assert(T == vsoc::layout::version_info::R::constant_values::T, \
+ "Constant value changed")
+
+namespace vsoc {
+namespace layout {
+
+/**
+ * Memory is shared between Guest and Host kernels. In some cases we need
+ * flag to indicate which side we're on. In those cases we'll use this
+ * simple structure.
+ *
+ * These are carefully formatted to make Guest and Host a bitfield.
+ */
+struct Sides {
+ static const uint32_t NoSides = 0;
+ static const uint32_t Guest = 1;
+ static const uint32_t Host = 2;
+ static const uint32_t Both = 3;
+#ifdef CUTTLEFISH_HOST
+ static const uint32_t OurSide = Host;
+ static const uint32_t Peer = Guest;
+#else
+ static const uint32_t OurSide = Guest;
+ static const uint32_t Peer = Host;
+#endif
+
+ uint32_t value_;
+};
+ASSERT_SHM_COMPATIBLE(Sides, multi_region);
+
+/**
+ * Base class for all region layout structures.
+ */
+class RegionLayout {
+};
+ASSERT_SHM_COMPATIBLE(RegionLayout, multi_region);
+
+} // namespace layout
+} // namespace vsoc
diff --git a/common/vsoc/shm/circqueue.h b/common/vsoc/shm/circqueue.h
new file mode 100644
index 0000000..5422b8e
--- /dev/null
+++ b/common/vsoc/shm/circqueue.h
@@ -0,0 +1,170 @@
+#pragma once
+
+/*
+ * Copyright (C) 2017 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.
+ */
+
+// Memory layout for byte-oriented circular queues
+
+#include <atomic>
+#include <cstdint>
+
+#include "common/vsoc/shm/base.h"
+#include "common/vsoc/shm/lock.h"
+
+namespace vsoc {
+class RegionSignalingInterface;
+namespace layout {
+
+/**
+ * Base classes for all spinlock protected circular queues.
+ * This class should be embedded in the per-region data structure that is used
+ * as the parameter to TypedRegion.
+ */
+template <uint32_t SizeLog2>
+class CircularQueueBase {
+ CircularQueueBase() = delete;
+ CircularQueueBase(const CircularQueueBase&) = delete;
+ CircularQueueBase& operator=(const CircularQueueBase&) = delete;
+ protected:
+ /**
+ * Specifies a part of the queue. Note, the given indexes must be masked
+ * before they can be used against buffer_
+ */
+ struct Range {
+ // Points to the first bytes that is part of the range
+ uint32_t start_idx;
+ // Points to the first byte that is not in the range. This is similar to
+ // the STL end iterator.
+ uint32_t end_idx;
+ };
+ static const uintptr_t BufferSize = (1 << SizeLog2);
+
+ /**
+ * Copy bytes from buffer_in into the part of the queue specified by Range.
+ */
+ void CopyInRange(const char* buffer_in, const Range& t);
+
+ /**
+ * Copy the bytes specified by range to the given buffer. They caller must
+ * ensure that the buffer is large enough to hold the content of the range.
+ */
+ void CopyOutRange(const Range& t, char* buffer_out);
+
+ /**
+ * Wait until data becomes available in the queue. The caller must have
+ * called Lock() before invoking this. The caller must call Unlock()
+ * after this returns.
+ */
+ void WaitForDataLocked(RegionSignalingInterface* r);
+
+ /**
+ * Reserve space in the queue for writing. The caller must have called Lock()
+ * before invoking this. The caller must call Unlock() after this returns.
+ * Indexes pointing to the reserved space will be placed in range.
+ * On success this returns bytes.
+ * On failure a negative errno indicates the problem. -ENOSPC indicates that
+ * bytes > the queue size, -EWOULDBLOCK indicates that the call would block
+ * waiting for space but was requested non bloking.
+ */
+ intptr_t WriteReserveLocked(RegionSignalingInterface* r,
+ size_t bytes,
+ Range* t,
+ bool non_blocking);
+
+ // Note: Both of these fields may hold values larger than the buffer size,
+ // they should be interpreted modulo the buffer size. This fact along with the
+ // buffer size being a power of two greatly simplyfies the index calculations.
+ // Advances when a reader has finished with buffer space
+ uint32_t r_released_;
+ // Advances when buffer space is filled and ready for a reader
+ uint32_t w_pub_;
+ // Spinlock that protects the region. 0 means unlocked
+ SpinLock lock_;
+ // The actual memory in the buffer
+ char buffer_[BufferSize];
+};
+using CircularQueueBase64k = CircularQueueBase<16>;
+ASSERT_SHM_COMPATIBLE(CircularQueueBase64k, multi_region);
+
+/**
+ * Byte oriented circular queue. Reads will always return some data, but
+ * may return less data than requested. Writes will always write all of the
+ * data or return an error.
+ */
+template <uint32_t SizeLog2>
+class CircularByteQueue : public CircularQueueBase<SizeLog2> {
+ public:
+ /**
+ * Read at most max_size bytes from the qeueue, placing them in buffer_out
+ */
+ intptr_t Read(RegionSignalingInterface* r, char* buffer_out, std::size_t max_size);
+ /**
+ * Write all of the given bytes into the queue. If non_blocking isn't set the
+ * call may block until there is enough available space in the queue. On
+ * success the return value will match bytes. On failure a negative errno is
+ * returned. -ENOSPC: If the queue size is smaller than the number of bytes to
+ * write. -EWOULDBLOCK: If non_blocking is true and there is not enough free
+ * space.
+ */
+ intptr_t Write(RegionSignalingInterface* r,
+ const char* buffer_in,
+ std::size_t bytes,
+ bool non_blocking = false);
+
+ protected:
+ using Range = typename CircularQueueBase<SizeLog2>::Range;
+};
+using CircularByteQueue64k = CircularByteQueue<16>;
+ASSERT_SHM_COMPATIBLE(CircularByteQueue64k, multi_region);
+
+/**
+ * Packet oriented circular queue. Reads will either return data or an error.
+ * Each return from read corresponds to a call to write and returns all of the
+ * data from that corresponding Write().
+ */
+template <uint32_t SizeLog2, uint32_t MaxPacketSize>
+class CircularPacketQueue : public CircularQueueBase<SizeLog2> {
+ public:
+ /**
+ * Read a single packet from the queue, placing its data into buffer_out.
+ * If max_size indicates that buffer_out cannot hold the entire packet
+ * this function will return -ENOSPC.
+ */
+ intptr_t Read(RegionSignalingInterface* r, char* buffer_out, std::size_t max_size);
+
+ /**
+ * Writes [buffer_in, buffer_in + bytes) to the queue.
+ * If the number of bytes to be written exceeds the size of the queue
+ * -ENOSPC will be returned.
+ * If non_blocking is true and there is not enogh free space on the queue to
+ * write all the data -EWOULDBLOCK will be returned.
+ */
+ intptr_t Write(RegionSignalingInterface* r,
+ const char* buffer_in,
+ uint32_t bytes,
+ bool non_blocking = false);
+
+ protected:
+ static_assert(CircularQueueBase<SizeLog2>::BufferSize >= MaxPacketSize,
+ "Buffer is too small to hold the maximum sized packet");
+ using Range = typename CircularQueueBase<SizeLog2>::Range;
+ intptr_t CalculateBufferedSize(size_t payload);
+};
+using CircularPacketQueue64k = CircularPacketQueue<16, 1024>;
+ASSERT_SHM_COMPATIBLE(CircularPacketQueue64k, multi_region);
+
+} // namespace layout
+} // namespace vsoc
diff --git a/common/vsoc/shm/e2e_test_region_layout.h b/common/vsoc/shm/e2e_test_region_layout.h
new file mode 100644
index 0000000..f0a24e9
--- /dev/null
+++ b/common/vsoc/shm/e2e_test_region_layout.h
@@ -0,0 +1,166 @@
+#pragma once
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#include <cstdint>
+#include "common/vsoc/shm/base.h"
+#include "common/vsoc/shm/version.h"
+
+// Memory layout for a region that supports end-to-end (E2E) testing of
+// shared memory regions. This verifies that all sorts of things work along the
+// the path:
+//
+// host libraries <-> ivshmem server <-> kernel <-> guest libraries
+//
+// This is intentionally not a unit test. The primary source of errors along
+// this path is a misunderstanding and/or inconsistency in one of the
+// interfaces. Introducing mocks would allow these errors to go undetected.
+// Another way of looking at it is that the mocks would end up being a
+// a copy-and-paste job, making a series of change-detector tests.
+//
+// These tests are actually run on every device boot to verify that things are
+// ok.
+
+namespace vsoc {
+namespace layout {
+
+namespace e2e_test {
+
+/**
+ * Flags that are used to indicate test status. Some of the latter testing
+ * stages rely on initializion that must be done on the peer.
+ */
+enum E2ETestStage {
+ // No tests have passed
+ E2E_STAGE_NONE = 0,
+ // This side has finished writing its pattern to the region
+ E2E_MEMORY_FILLED = 1,
+ // This side has confirmed that it can see its peer's writes to the region
+ E2E_PEER_MEMORY_READ = 2,
+};
+
+/**
+ * Structure that grants permission to write in the region to either the guest
+ * or the host. This size of these fields is arbitrary.
+ */
+struct E2EMemoryFill {
+ static const std::size_t kOwnedFieldSize = 32;
+
+ // The compiler must not attempt to optimize away reads and writes to the
+ // shared memory window. This is pretty typical when dealing with devices
+ // doing memory mapped I/O.
+ char host_writable[kOwnedFieldSize];
+ char guest_writable[kOwnedFieldSize];
+};
+ASSERT_SHM_COMPATIBLE(E2EMemoryFill, e2e_test);
+
+/**
+ * Structure that grants permission to write in the region to either the guest
+ * or the host. This size of these fields is arbitrary.
+ */
+class E2ETestStageRegister {
+ public:
+ E2ETestStage value() const {
+ E2ETestStage rval = static_cast<E2ETestStage>(value_);
+ return rval;
+ }
+
+ void set_value(E2ETestStage new_value) { value_ = new_value; }
+
+ protected:
+ // The compiler must not attempt to optimize away reads and writes to the
+ // shared memory window. This is pretty typical when dealing with devices
+ // doing memory mapped I/O.
+ uint32_t value_;
+};
+ASSERT_SHM_COMPATIBLE(E2ETestStageRegister, e2e_test);
+
+/**
+ * Describes the layout of the regions used for the end-to-end test. There
+ * are multiple regions: primary and secondary, so some details like the region
+ * name must wait until later.
+ */
+class E2ETestRegionLayout : public ::vsoc::layout::RegionLayout {
+ public:
+ /**
+ * Computes how many E2EMemoryFill records we need to cover the region.
+ * Covering the entire region during the test ensures that everything is
+ * mapped and coherent between guest and host.
+ */
+ static std::size_t NumFillRecords(std::size_t region_size) {
+ if (region_size < sizeof(E2ETestRegionLayout)) {
+ return 0;
+ }
+ // 1 + ... An array of size 1 is allocated in the E2ETestRegion.
+ // TODO(ghartman): AddressSanitizer may find this sort of thing to be
+ // alarming.
+ return 1 +
+ (region_size - sizeof(E2ETestRegionLayout)) / sizeof(E2EMemoryFill);
+ }
+ // The number of test stages that have completed on the guest
+ // Later host tests will wait on this
+ E2ETestStageRegister guest_status;
+ // The number of test stages that have completed on the host
+ // Later guest tests will wait on this
+ E2ETestStageRegister host_status;
+ // These fields are used to test the signaling mechanism.
+ uint32_t host_to_guest_signal;
+ uint32_t guest_to_host_signal;
+ // There rest of the region will be filled by guest_host_strings.
+ // We actually use more than one of these, but we can't know how many
+ // until we examine the region.
+ E2EMemoryFill data[1];
+};
+ASSERT_SHM_COMPATIBLE(E2ETestRegionLayout, e2e_test);
+
+struct E2EPrimaryTestRegionLayout : public E2ETestRegionLayout {
+ static const char* region_name;
+ static const char guest_pattern[E2EMemoryFill::kOwnedFieldSize];
+ static const char host_pattern[E2EMemoryFill::kOwnedFieldSize];
+};
+ASSERT_SHM_COMPATIBLE(E2EPrimaryTestRegionLayout, e2e_test);
+
+struct E2ESecondaryTestRegionLayout : public E2ETestRegionLayout {
+ static const char* region_name;
+ static const char guest_pattern[E2EMemoryFill::kOwnedFieldSize];
+ static const char host_pattern[E2EMemoryFill::kOwnedFieldSize];
+};
+ASSERT_SHM_COMPATIBLE(E2ESecondaryTestRegionLayout, e2e_test);
+
+/**
+ * Defines an end-to-end region with a name that should never be configured.
+ */
+struct E2EUnfindableRegionLayout : public E2ETestRegionLayout {
+ static const char* region_name;
+};
+ASSERT_SHM_COMPATIBLE(E2EUnfindableRegionLayout, e2e_test);
+
+struct E2EManagedTestRegionLayout : public RegionLayout {
+ static const char* region_name;
+ uint32_t val; // Not needed, here only to avoid an empty struct.
+};
+ASSERT_SHM_COMPATIBLE(E2EManagedTestRegionLayout, e2e_test);
+
+struct E2EManagerTestRegionLayout : public RegionLayout {
+ static const char* region_name;
+ typedef E2EManagedTestRegionLayout ManagedRegion;
+ uint32_t data[4]; // We don't need more than 4 for the tests
+};
+ASSERT_SHM_COMPATIBLE(E2EManagerTestRegionLayout, e2e_test);
+
+} // namespace e2e_test
+} // namespace layout
+} // namespace vsoc
diff --git a/common/vsoc/shm/fb_bcast_layout.h b/common/vsoc/shm/fb_bcast_layout.h
new file mode 100644
index 0000000..67c229b
--- /dev/null
+++ b/common/vsoc/shm/fb_bcast_layout.h
@@ -0,0 +1,50 @@
+#pragma once
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include "common/vsoc/shm/base.h"
+#include "common/vsoc/shm/lock.h"
+#include "common/vsoc/shm/version.h"
+
+// Memory layout for the hwcomposer and hwcomposer broadcast regions
+
+namespace vsoc {
+namespace layout {
+
+namespace framebuffer {
+
+struct FBBroadcastLayout : public RegionLayout {
+ static const char* region_name;
+ // Display properties
+ uint32_t x_res;
+ uint32_t y_res;
+ uint16_t dpi;
+ uint16_t refresh_rate_hz;
+
+ // The frame sequential number
+ uint32_t seq_num;
+ // The offset in the gralloc buffer region of the current frame buffer.
+ uint32_t frame_offset;
+ // Protects access to the frame offset and sequential number.
+ // See the region implementation for more details.
+ SpinLock bcast_lock;
+};
+ASSERT_SHM_COMPATIBLE(FBBroadcastLayout, framebuffer);
+
+} // namespace framebuffer
+
+} // namespace layout
+} // namespace vsoc
diff --git a/common/vsoc/shm/gralloc_layout.h b/common/vsoc/shm/gralloc_layout.h
new file mode 100644
index 0000000..54f5589
--- /dev/null
+++ b/common/vsoc/shm/gralloc_layout.h
@@ -0,0 +1,70 @@
+#pragma once
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include "common/vsoc/shm/base.h"
+#include "common/vsoc/shm/graphics.h"
+#include "common/vsoc/shm/lock.h"
+#include "common/vsoc/shm/version.h"
+
+// Memory layout for the gralloc manager region.
+
+namespace vsoc {
+namespace layout {
+
+namespace gralloc {
+
+struct BufferEntry {
+ uint32_t owned_by;
+ uint32_t buffer_begin;
+ uint32_t buffer_end;
+
+ PixelFormatRegister pixel_format;
+ uint32_t stride;
+ uint32_t width;
+ uint32_t height;
+
+ // A size of 28 is causing different layouts when GrallocManagerLayout is
+ // compiled in host and guest sides
+ uint32_t padding;
+
+ uint32_t buffer_size() {
+ return buffer_end - buffer_begin;
+ }
+};
+ASSERT_SHM_COMPATIBLE(BufferEntry, gralloc);
+
+struct GrallocBufferLayout : public RegionLayout {
+ static const char* region_name;
+};
+ASSERT_SHM_COMPATIBLE(GrallocBufferLayout, gralloc);
+
+struct GrallocManagerLayout : public RegionLayout {
+ static const char* region_name;
+ typedef GrallocBufferLayout ManagedRegion;
+
+ uint32_t allocated_buffer_memory;
+ uint32_t buffer_count;
+ // Make sure this isn't the first field
+ GuestLock new_buffer_lock;
+ // Needs to be last field
+ BufferEntry buffers_table[1];
+};
+ASSERT_SHM_COMPATIBLE(GrallocManagerLayout, gralloc);
+
+} // namespace gralloc
+} // namespace layout
+} // namespace vsoc
diff --git a/common/vsoc/shm/graphics.h b/common/vsoc/shm/graphics.h
new file mode 100644
index 0000000..feaf7f8
--- /dev/null
+++ b/common/vsoc/shm/graphics.h
@@ -0,0 +1,170 @@
+#pragma once
+
+/*
+ * Copyright (C) 2017 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.
+ */
+
+// Memory layout for primitive graphics types.
+
+// The vsoc::layout namespace indicates that these are shared memory structure
+// definitions. The #include's given above are strictly limited, as are the
+// types that can be referenced below.
+
+#include <cstdint>
+
+#include "common/vsoc/shm/base.h"
+#include "common/vsoc/shm/version.h"
+
+namespace vsoc {
+
+// The enumerations for VSoC pixel formats are laid out so that hardware to
+// parse bytes per pixel without relying an a exhaustive list of pixel formats.
+// These constants define the fields involved.
+namespace PixelFormatConst {
+ static const uint32_t BytesPerPixelSize = 3;
+ static const uint32_t SubformatSize = 3;
+ static const uint32_t MaxBytesPerPixel = (1 << BytesPerPixelSize);
+ static const uint32_t MaxSubformat = (1 << SubformatSize) - 1;
+};
+
+
+// Builds (statically) a new pixel format enumeration value given constant
+// bytes per pixel.
+template <uint32_t BYTES, uint32_t SUB_FORMAT>
+struct PixelFormatBuilder {
+ static_assert(BYTES > 0, "Too few bytes");
+ static_assert(BYTES <= PixelFormatConst::MaxBytesPerPixel, "Too many bytes");
+ static_assert(SUB_FORMAT <= PixelFormatConst::MaxSubformat,
+ "Too many subformats");
+ static const uint32_t value = ((BYTES - 1) << PixelFormatConst::SubformatSize) | SUB_FORMAT;
+};
+
+template <uint32_t FORMAT>
+struct PixelFormatProperties {
+ // No static asserts since all int32_t values are (technically) valid pixel formats?
+ static const uint32_t bytes_per_pixel = (FORMAT >> PixelFormatConst::SubformatSize) + 1;
+};
+
+// Contains all of the pixel formats currently supported by this VSoC. The
+// enumeration serves multiple purposes:
+//
+// * The compile will warn (or error) if we switch on PixelFormat and don't
+// handly all of the cases.
+//
+// * Code can use PixelFormat to describe paramaters, making APIs a bit more
+// self-documenting.
+//
+// * Observant reviewers can verify that the same pixel value is not assigned
+// to multiple formats. Keep the enums in numerical order below to
+// make this easier.
+enum PixelFormat {
+ VSOC_PIXEL_FORMAT_UNINITIALIZED = PixelFormatBuilder<1,0>::value,
+ VSOC_PIXEL_FORMAT_BLOB = PixelFormatBuilder<1,1>::value,
+
+ VSOC_PIXEL_FORMAT_RGB_565 = PixelFormatBuilder<2,0>::value,
+ VSOC_PIXEL_FORMAT_YV12 = PixelFormatBuilder<2,1>::value,
+ VSOC_PIXEL_FORMAT_YCbCr_420_888 = PixelFormatBuilder<2,2>::value,
+
+ VSOC_PIXEL_FORMAT_RGB_888 = PixelFormatBuilder<3,0>::value,
+
+ VSOC_PIXEL_FORMAT_RGBA_8888 = PixelFormatBuilder<4,0>::value,
+ VSOC_PIXEL_FORMAT_RGBX_8888 = PixelFormatBuilder<4,1>::value,
+ VSOC_PIXEL_FORMAT_BGRA_8888 = PixelFormatBuilder<4,2>::value,
+
+ // VSOC_PIXEL_FORMAT_IMPLEMENTATION_DEFINED intentionally left out. The HALs
+ // should choose one of the defined contrete types.
+ //
+ // The following formats are defined in various platform versions, but don't
+ // seem to be used. If we encounter them it's ok to add them to the table.
+ // This does not necessitate a version change.
+ //
+ // The following have been in the framework for a long time:
+ //
+ // VSOC_PIXEL_FORMAT_YCrCb_420_SP
+ // VSOC_PIXEL_FORMAT_YCbCr_422_SP
+ //
+ // The following were added in JB_MR2:
+ //
+ // VSOC_PIXEL_FORMAT_YCbCr_420_888
+ // VSOC_PIXEL_FORMAT_Y8
+ // VSOC_PIXEL_FORMAT_Y16
+ //
+ // The following were added in L:
+ //
+ // VSOC_PIXEL_FORMAT_RAW_OPAQUE
+ // VSOC_PIXEL_FORMAT_RAW16 (also known as RAW_SENSOR. Define only RAW16)
+ // VSOC_PIXEL_FORMAT_RAW10
+ //
+ // The following were added in L MR1:
+ //
+ // VSOC_PIXEL_FORMAT_YCbCr_444_888
+ // VSOC_PIXEL_FORMAT_YCbCr_422_888
+ // VSOC_PIXEL_FORMAT_RAW12
+ // VSOC_PIXEL_FORMAT_FLEX_RGBA_8888
+ // VSOC_PIXEL_FORMAT_FLEX_RGB_888
+ //
+ // These pixel formats were removed in later framework versions. Implement
+ // only if absolutely necessary.
+ //
+ // Support was dropped in K for:
+ //
+ // VSOC_PIXEL_FORMAT_RGBA_5551
+ // VSOC_PIXEL_FORMAT_RGBA_4444
+ //
+ // Supported only in K, L, and LMR1:
+ //
+ // VSOC_PIXEL_FORMAT_sRGB_X_8888
+ // VSOC_PIXEL_FORMAT_sRGB_A_8888
+};
+ASSERT_SHM_CONSTANT_VALUE(VSOC_PIXEL_FORMAT_UNINITIALIZED, multi_region);
+ASSERT_SHM_CONSTANT_VALUE(VSOC_PIXEL_FORMAT_BLOB, multi_region);
+ASSERT_SHM_CONSTANT_VALUE(VSOC_PIXEL_FORMAT_RGB_565, multi_region);
+ASSERT_SHM_CONSTANT_VALUE(VSOC_PIXEL_FORMAT_YV12, multi_region);
+ASSERT_SHM_CONSTANT_VALUE(VSOC_PIXEL_FORMAT_YCbCr_420_888, multi_region);
+ASSERT_SHM_CONSTANT_VALUE(VSOC_PIXEL_FORMAT_RGB_888, multi_region);
+ASSERT_SHM_CONSTANT_VALUE(VSOC_PIXEL_FORMAT_RGBA_8888, multi_region);
+ASSERT_SHM_CONSTANT_VALUE(VSOC_PIXEL_FORMAT_RGBX_8888, multi_region);
+ASSERT_SHM_CONSTANT_VALUE(VSOC_PIXEL_FORMAT_BGRA_8888, multi_region);
+
+namespace layout {
+
+// VSoC memory layout for a register that accepts a single pixel format.
+// The value is volatile to ensure that the compiler does not eliminate stores.
+struct PixelFormatRegister {
+ volatile PixelFormat value_;
+};
+ASSERT_SHM_COMPATIBLE(PixelFormatRegister, multi_region);
+
+// Register layout for a mask giving different PixelFormats. Reserve enough
+// space to allow for future expansion. For example, we may well end with
+// a 12 bit per channel format in the future.
+struct PixelFormatMaskRegister {
+ volatile uint64_t value_;
+
+ bool HasValue(PixelFormat in) {
+ return !!(value_ & (uint64_t(1) << in));
+ }
+};
+ASSERT_SHM_COMPATIBLE(PixelFormatMaskRegister, multi_region);
+
+// Ensure that the mask is large enough to hold the highest encodable
+// pixel format.
+static_assert(PixelFormatBuilder<
+ PixelFormatConst::MaxBytesPerPixel,
+ PixelFormatConst::MaxSubformat>::value <
+ 8 * sizeof(PixelFormatMaskRegister),
+ "Largest pixel format does not fit in mask");
+} // layout
+} // vsoc
diff --git a/common/vsoc/shm/lock.h b/common/vsoc/shm/lock.h
new file mode 100644
index 0000000..2cae5bc
--- /dev/null
+++ b/common/vsoc/shm/lock.h
@@ -0,0 +1,195 @@
+#pragma once
+
+/*
+ * Copyright (C) 2017 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.
+ */
+
+// Memory layout for locks of all types.
+
+// The vsoc::layout namespace indicates that these are shared memory structure
+// definitions. The #include's given above are strictly limited, as are the
+// types that can be referenced below.
+
+// For _mm_pause()
+#include <x86intrin.h>
+
+#include <atomic>
+#include <cstdint>
+
+#include <linux/futex.h>
+#include <sys/syscall.h>
+
+#include <unistd.h>
+
+#include "common/vsoc/shm/base.h"
+#include "common/vsoc/shm/version.h"
+
+// Host userspace, guest userspace, and the guest kernel must all agree on
+// the relationship between std::atomic and atomic_t. That's hard to do without
+// examining assembly, and we can't really examing atomic_t outside of the
+// kernel tree, but we can at least assert that the host and the guest
+// agree on a size.
+static_assert(sizeof(std::atomic<uint32_t>) == 4, "std::atomic size mismatch");
+
+namespace vsoc {
+
+class RegionView;
+
+namespace layout {
+
+/**
+ * Lock that causes threads to busy loop rather than sleeping.
+ * This lock should never be used when the amount of work in the critical
+ * section cannot be bounded.
+ */
+class SpinLock {
+ public:
+ /**
+ * Acquire the spinlock on the queue. This will effectively block all
+ * readers and writers.
+ */
+ void Lock() {
+ while (lock_.exchange(1)) {
+ _mm_pause();
+ }
+ }
+
+ /**
+ * Release the spinlock.
+ */
+ void Unlock() {
+ lock_ = 0;
+ }
+
+ protected:
+ std::atomic<uint32_t> lock_;
+};
+ASSERT_SHM_COMPATIBLE(SpinLock, multi_region);
+
+/**
+ * This is a generic synchronization primitive that provides space for the
+ * owner of the lock to write platform-specific information.
+ */
+class WaitingLockBase {
+ protected:
+ // Common code to handle locking
+ // Must be called with the kernel's thread id
+ // Returns true if the lock was acquired. In this case the value in
+ // expected_vlaue is undefined.
+ // Returns false if locking failed. The value discovered in the lock word
+ // is returned in expected_value, and should probably be used in a conditional
+ // sleep.
+ bool TryLock(uint32_t tid, uint32_t* expected_value);
+
+ // Common code to handle unlocking.
+ // Must be called with the kernel's thread id
+ // Returns sides that should be signalled or 0
+ Sides UnlockCommon(uint32_t tid);
+
+ // Non-zero values in this word indicate that the lock is in use.
+ // This is 32 bits for compatibility with futex()
+ std::atomic<uint32_t> lock_uint32_;
+
+ // Pad so we line up with glib's pthread_mutex_t and can share the same queue.
+ // These fields may be redefined at any point in the future. They should not
+ // be used.
+ private:
+// These fields are known to be unused and are provided for compatibility
+// with glibc's locks.
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wunused-private-field"
+ uint32_t reserved_1_;
+ char reserved_2_[16];
+ // Provide scratch space for the owner of the lock. The content of this space
+ // is undefined when the lock is acquired. The owner may write to and read
+ // from it while it holds the lock, but must relinquish control before
+ // releasing the lock.
+ //
+ // This is intended to support Linux robust futexes. See the documentation
+ // in the kernel tree:
+ // Documentation/robust-futex-ABI.txt
+ public:
+ int64_t owner_scratch_[2];
+#pragma clang diagnostic pop
+};
+ASSERT_SHM_COMPATIBLE(WaitingLockBase, multi_region);
+
+/**
+ * GuestLocks can be acquired and released only on the guest. They reside
+ * in the shared memory window because mutiple guest processes may need
+ * to coordinate activities in certain shared memory regions.
+ *
+ * Representing this as a concrete type allows for some optimizations when
+ * signalling on the lock.
+ */
+class GuestLock : public WaitingLockBase {
+ public:
+#ifndef CUTTLEFISH_HOST
+ void Lock();
+ void Unlock();
+#endif
+};
+ASSERT_SHM_COMPATIBLE(GuestLock, multi_region);
+
+/**
+ * HostLocks can be acquired and released only on the host. They reside
+ * in the shared memory window because mutiple host processes may need
+ * to coordinate activities in certain shared memory regions.
+ *
+ * Representing this as a concrete type allows for some optimizations when
+ * signalling on the lock.
+ */
+class HostLock : public WaitingLockBase {
+ public:
+#ifdef CUTTLEFISH_HOST
+ void Lock();
+ void Unlock();
+#endif
+};
+ASSERT_SHM_COMPATIBLE(HostLock, multi_region);
+
+/**
+ * GuestAndHostLocks can be acquired and released on either side of the
+ * shared memory window. The locks attempt to enforce fairness by using
+ * a round-trip signal:
+ *
+ * When a guest releases a lock this code sends a signal to wake the host,
+ * but not other guest waiters.
+ *
+ * The wake handler on the host wakes up and local waiters and then reposts
+ * the signal to the guest.
+ *
+ * When the guest receives the signal from the host it then wakes ups
+ * any waiters.
+ *
+ * A similar scenario applies when the host releases a lock with guest waiters.
+ *
+ * Signalling across the shared memory window twice has non-trivial cost.
+ * There are some optimizations in the code to prevent the full round-trip
+ * if the process releasing the lock can confirm that there are no waiters on
+ * the other side.
+ *
+ * Representing this as a concrete type allows for some optimizations when
+ * signalling on the lock.
+ */
+class GuestAndHostLock : public WaitingLockBase {
+ public:
+ void Lock(RegionView*);
+ void Unlock(RegionView*);
+};
+ASSERT_SHM_COMPATIBLE(GuestAndHostLock, multi_region);
+
+} // namespace layout
+} // namespace vsoc
diff --git a/common/vsoc/shm/version.h b/common/vsoc/shm/version.h
new file mode 100644
index 0000000..54baf34
--- /dev/null
+++ b/common/vsoc/shm/version.h
@@ -0,0 +1,147 @@
+#pragma once
+
+/*
+ * Copyright (C) 2017 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.
+ */
+
+// Version information for structures that are present in VSoC shared memory
+// windows. The proper use of this file will:
+//
+// * ensure that the guest and host builds agree on the sizes of the shared
+// structures.
+//
+// * provides a single version code for the entire vsoc layout, assuming
+// that reviewers excercise some care.
+//
+//
+// Use:
+//
+// Every new class / structure in the shm folder needs to add a size
+// entry here, #include the base.h file, and add a ASSERT_SHM_COMPATIBLE
+// instantiation just below the class definition,
+//
+// For templatized classes / structs the author should choose a fixed size,
+// create a using alias, and instantiate the checks on the alias.
+// See CircularByteQueue64k for an example of this usage.
+//
+// Note to reviewers:
+//
+// It is probably ok to approve new additions here without forcing a
+// a version change.
+//
+// However, the version must increment for any change in the value of a
+// constant.
+//
+// #ifdef, etc is absolutely forbidden in this file and highly discouraged
+// in the other vsoc/shm files.
+
+#include <cstdint>
+
+namespace vsoc {
+namespace layout {
+namespace version_info {
+namespace multi_region {
+
+namespace {
+// Increment this for any change in size in the fundamental structures listed
+// below. If the size of any of these structures changes we must examine all
+// of the regions bumping the version number if they used the fields in their
+// definition.
+const uint32_t version = 0;
+} // namespace
+
+static const std::size_t Base_size = 1;
+static const std::size_t CircularQueueBase64k_size = 65548;
+static const std::size_t CircularByteQueue64k_size = 65548;
+static const std::size_t CircularPacketQueue64k_size = 65548;
+static const std::size_t GuestLock_size = 40;
+static const std::size_t GuestAndHostLock_size = 40;
+static const std::size_t HostLock_size = 40;
+static const std::size_t PixelFormatRegister_size = 4;
+static const std::size_t PixelFormatMaskRegister_size = 8;
+static const std::size_t RegionLayout_size = 1;
+static const std::size_t Sides_size = 4;
+static const std::size_t SpinLock_size = 4;
+static const std::size_t WaitingLockBase_size = 40;
+
+namespace constant_values {
+static const uint32_t VSOC_PIXEL_FORMAT_UNINITIALIZED = 0;
+static const uint32_t VSOC_PIXEL_FORMAT_BLOB = 1;
+
+static const uint32_t VSOC_PIXEL_FORMAT_RGB_565 = 8;
+static const uint32_t VSOC_PIXEL_FORMAT_YV12 = 9;
+static const uint32_t VSOC_PIXEL_FORMAT_YCbCr_420_888 = 10;
+
+static const uint32_t VSOC_PIXEL_FORMAT_RGB_888 = 16;
+
+static const uint32_t VSOC_PIXEL_FORMAT_RGBA_8888 = 24;
+static const uint32_t VSOC_PIXEL_FORMAT_RGBX_8888 = 25;
+static const uint32_t VSOC_PIXEL_FORMAT_BGRA_8888 = 26;
+} // namespace constant_values
+} // namespace multi_region
+
+// Versioning information for gralloc_layout.h
+// Changes to these structures will affect only the gralloc region
+namespace gralloc {
+namespace {
+const uint32_t version = 0;
+}
+static const std::size_t BufferEntry_size = 32;
+static const std::size_t GrallocManagerLayout_size = 80;
+static const std::size_t GrallocBufferLayout_size = 1;
+} // namespace gralloc
+
+// Versioning information for fb_bcast_layout.h
+// Changes to these structures will affect only the framebuffer broadcast region
+namespace framebuffer {
+namespace {
+const uint32_t version = 1;
+}
+static const std::size_t FBBroadcastLayout_size = 24;
+} // namespace framebuffer
+
+// Versioning information for wifi_layout.h
+namespace wifi {
+namespace {
+constexpr uint32_t version = 0;
+} // namespace
+constexpr size_t WifiExchangeLayout_size =
+ 65548 + // sizeof(CircularPacketQueue<16, 8192>) - forward
+ 65548 + // sizeof(CircularPacketQueue<16, 8192>) - reverse
+ 4 + // Lock config_lock_
+ 2 + // bool config_ready_ (and even address alignment)
+ 6; // uint8_t[6] MAC address.
+} // namespace wifi
+
+
+// Versioning information for e2e_test_region.h
+// Changes to these structures will affect only the e2e_test_region
+namespace e2e_test {
+namespace {
+const uint32_t version = 1;
+}
+static const std::size_t E2EManagerTestRegionLayout_size = 16;
+static const std::size_t E2EMemoryFill_size = 64;
+static const std::size_t E2EPrimaryTestRegionLayout_size = 80;
+static const std::size_t E2ESecondaryTestRegionLayout_size = 80;
+static const std::size_t E2ETestRegionLayout_size = 80;
+static const std::size_t E2ETestStageRegister_size = 4;
+static const std::size_t E2EUnfindableRegionLayout_size = 80;
+static const std::size_t E2EManagedTestRegionLayout_size = 4;
+} // namespace e2e_test
+
+} // namespace version_info
+} // namespace layout
+} // namespace vsoc
diff --git a/common/vsoc/shm/wifi_exchange_layout.h b/common/vsoc/shm/wifi_exchange_layout.h
new file mode 100644
index 0000000..10888cd
--- /dev/null
+++ b/common/vsoc/shm/wifi_exchange_layout.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#pragma once
+#include <linux/if_ether.h>
+
+#include "common/vsoc/shm/base.h"
+#include "common/vsoc/shm/circqueue.h"
+#include "common/vsoc/shm/lock.h"
+#include "common/vsoc/shm/version.h"
+
+// Memory layout for wifi packet exchange region.
+namespace vsoc {
+namespace layout {
+namespace wifi {
+
+struct WifiExchangeLayout : public RegionLayout {
+ // Traffic originating from host that proceeds towards guest.
+ CircularPacketQueue<16, 8192> guest_ingress;
+ // Traffic originating from guest that proceeds towards host.
+ CircularPacketQueue<16, 8192> guest_egress;
+
+ // config_lock_ manages access to configuration section
+ SpinLock config_lock_;
+ // config_ready_ indicates whether config section is ready to be accessed.
+ bool config_ready_;
+ // Desired MAC address for guest device.
+ uint8_t mac_address[ETH_ALEN];
+
+ static const char* region_name;
+};
+
+ASSERT_SHM_COMPATIBLE(WifiExchangeLayout, wifi);
+
+} // namespace wifi
+} // namespace layout
+} // namespace vsoc
diff --git a/guest/Android.bp b/guest/Android.bp
new file mode 100644
index 0000000..ee8105d
--- /dev/null
+++ b/guest/Android.bp
@@ -0,0 +1,18 @@
+//
+// Copyright (C) 2017 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.
+
+subdirs = [
+ "commands",
+]
diff --git a/guest/commands/Android.bp b/guest/commands/Android.bp
new file mode 100644
index 0000000..cbd09e8
--- /dev/null
+++ b/guest/commands/Android.bp
@@ -0,0 +1,19 @@
+//
+// Copyright (C) 2017 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.
+
+subdirs = [
+ "wifirouter",
+]
+
diff --git a/guest/commands/audio/Android.mk b/guest/commands/audio/Android.mk
new file mode 100644
index 0000000..035c7d3
--- /dev/null
+++ b/guest/commands/audio/Android.mk
@@ -0,0 +1,64 @@
+# Copyright (C) 2017 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.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+ifeq (0, $(shell test $(PLATFORM_SDK_VERSION) -ge 21; echo $$?))
+LOCAL_MODULE_RELATIVE_PATH := hw
+else
+LOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)/hw
+endif
+LOCAL_MODULE_TAGS := optional
+LOCAL_MULTILIB := first
+
+LOCAL_SHARED_LIBRARIES := \
+ liblog \
+ libcutils \
+ cuttlefish_auto_resources \
+ libcuttlefish_fs \
+ cuttlefish_time
+
+LOCAL_SRC_FILES := \
+ audio_hal.cpp \
+ vsoc_audio.cpp \
+ vsoc_audio_input_stream.cpp \
+ vsoc_audio_output_stream.cpp \
+ vsoc_audio_message.cpp
+
+LOCAL_C_INCLUDES := \
+ device/google/cuttlefish_common \
+ $(VSOC_STLPORT_INCLUDES) \
+ frameworks/native/include/media/hardware \
+ $(call include-path-for, audio)
+
+LOCAL_STATIC_LIBRARIES := \
+ libcutils \
+ libcuttlefish_remoter_framework \
+ $(VSOC_STLPORT_STATIC_LIBS)
+
+LOCAL_CFLAGS := \
+ -Wall -Werror -Wno-parentheses -Wno-missing-field-initializers \
+ -DLOG_TAG=\"VSoC-Audio\" \
+ $(VSOC_VERSION_CFLAGS)
+
+# Work-around for the non-standard language feautures used in
+# system/media/audio/include/system/audio.h
+LOCAL_CLANG_CFLAGS := -Wno-gnu-designator
+
+LOCAL_MODULE := audio.primary.vsoc
+LOCAL_VENDOR_MODULE := true
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/guest/commands/audio/audio_hal.cpp b/guest/commands/audio/audio_hal.cpp
new file mode 100644
index 0000000..1b05d40
--- /dev/null
+++ b/guest/commands/audio/audio_hal.cpp
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#include "guest/commands/audio/vsoc_audio.h"
+
+#include "guest/commands/audio/audio_hal.h"
+#include "guest/libs/platform_support/api_level_fixes.h"
+
+static hw_module_methods_t hal_module_methods = {
+ VSOC_STATIC_INITIALIZER(open) cvd::GceAudio::Open,
+};
+
+
+audio_module HAL_MODULE_INFO_SYM = {
+ VSOC_STATIC_INITIALIZER(common) {
+ VSOC_STATIC_INITIALIZER(tag) HARDWARE_MODULE_TAG,
+ VSOC_STATIC_INITIALIZER(module_api_version) AUDIO_MODULE_API_VERSION_0_1,
+ VSOC_STATIC_INITIALIZER(hal_api_version) HARDWARE_HAL_API_VERSION,
+ VSOC_STATIC_INITIALIZER(id) AUDIO_HARDWARE_MODULE_ID,
+ VSOC_STATIC_INITIALIZER(name) "GCE Audio HW HAL",
+ VSOC_STATIC_INITIALIZER(author) "The Android Open Source Project",
+ VSOC_STATIC_INITIALIZER(methods) &hal_module_methods,
+ },
+};
diff --git a/guest/commands/audio/audio_hal.h b/guest/commands/audio/audio_hal.h
new file mode 100644
index 0000000..36d47cb
--- /dev/null
+++ b/guest/commands/audio/audio_hal.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#pragma once
+
+#define AUDIO_DEBUG 0
+
+#if AUDIO_DEBUG
+# define D(...) ALOGD(__VA_ARGS__)
+#else
+# define D(...) ((void)0)
+#endif
+
+#include <errno.h>
+#include <string.h>
+#include <cutils/log.h>
+
+#include <hardware/hardware.h>
+#include <system/audio.h>
+#include <hardware/audio.h>
diff --git a/guest/commands/audio/policy/Android.mk b/guest/commands/audio/policy/Android.mk
new file mode 100644
index 0000000..da72309
--- /dev/null
+++ b/guest/commands/audio/policy/Android.mk
@@ -0,0 +1,45 @@
+# Copyright (C) 2016 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.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+ifeq (0, $(shell test $(PLATFORM_SDK_VERSION) -ge 21; echo $$?))
+LOCAL_MODULE_RELATIVE_PATH := hw
+else
+LOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)/hw
+endif
+LOCAL_MODULE_TAGS := optional
+LOCAL_MULTILIB := first
+
+LOCAL_SHARED_LIBRARIES := \
+ liblog \
+ libcutils
+
+LOCAL_SRC_FILES := \
+ vsoc_audio_policy_hal.cpp
+
+LOCAL_C_INCLUDES := \
+ device/google/cuttlefish_common \
+ $(VSOC_STLPORT_INCLUDES) \
+ frameworks/native/include/media/hardware \
+ $(call include-path-for, audio)
+
+LOCAL_CFLAGS := -Wall -DLOG_TAG=\"VSoC-AudioPolicy\"
+
+LOCAL_MODULE := audio_policy.vsoc
+LOCAL_VENDOR_MODULE := true
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/guest/commands/audio/policy/vsoc_audio_policy_hal.cpp b/guest/commands/audio/policy/vsoc_audio_policy_hal.cpp
new file mode 100644
index 0000000..047e27d
--- /dev/null
+++ b/guest/commands/audio/policy/vsoc_audio_policy_hal.cpp
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+#include <errno.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <hardware/hardware.h>
+#include <system/audio.h>
+#include <system/audio_policy.h>
+#include <hardware/audio_policy.h>
+
+#include "guest/commands/audio/policy/vsoc_audio_policy_hal.h"
+
+namespace cvd {
+
+int GceAudioPolicy::Create(
+ const audio_policy_device* device,
+ audio_policy_service_ops* aps_ops,
+ void* service, audio_policy** ap) {
+ D("%s", __FUNCTION__);
+ audio_policy_device* dev;
+ gce_audio_policy* dap;
+ int ret;
+
+ *ap = NULL;
+
+ if (!service || !aps_ops) {
+ return -EINVAL;
+ }
+
+ dap = (gce_audio_policy*) calloc(1, sizeof(*dap));
+ if (!dap) {
+ return -ENOMEM;
+ }
+
+ dap->policy.set_device_connection_state =
+ &GceAudioPolicy::SetDeviceConnectionState;
+ dap->policy.get_device_connection_state =
+ &GceAudioPolicy::GetDeviceConnectionState;
+ dap->policy.set_phone_state = &GceAudioPolicy::SetPhoneState;
+ dap->policy.set_ringer_mode = &GceAudioPolicy::SetRingerMode;
+ dap->policy.set_force_use = &GceAudioPolicy::SetForceUse;
+ dap->policy.get_force_use = &GceAudioPolicy::GetForceUse;
+ dap->policy.set_can_mute_enforced_audible =
+ &GceAudioPolicy::SetCanMuteEnforcedAudible;
+ dap->policy.init_check = &GceAudioPolicy::InitCheck;
+ dap->policy.get_output = &GceAudioPolicy::GetOutput;
+ dap->policy.start_output = &GceAudioPolicy::StartOutput;
+ dap->policy.stop_output = &GceAudioPolicy::StopOutput;
+ dap->policy.release_output = &GceAudioPolicy::ReleaseOutput;
+ dap->policy.get_input = &GceAudioPolicy::GetInput;
+ dap->policy.start_input = &GceAudioPolicy::StartInput;
+ dap->policy.stop_input = &GceAudioPolicy::StopInput;
+ dap->policy.release_input = &GceAudioPolicy::ReleaseInput;
+ dap->policy.init_stream_volume = &GceAudioPolicy::InitStreamVolume;
+ dap->policy.set_stream_volume_index = &GceAudioPolicy::SetStreamVolumeIndex;
+ dap->policy.get_stream_volume_index = &GceAudioPolicy::GetStreamVolumeIndex;
+ dap->policy.set_stream_volume_index_for_device =
+ &GceAudioPolicy::SetStreamVolumeIndexForDevice;
+ dap->policy.get_stream_volume_index_for_device =
+ &GceAudioPolicy::GetStreamVolumeIndexForDevice;
+ dap->policy.get_strategy_for_stream = &GceAudioPolicy::GetStrategyForStream;
+ dap->policy.get_devices_for_stream = &GceAudioPolicy::GetDevicesForStream;
+ dap->policy.get_output_for_effect = &GceAudioPolicy::GetOutputForEffect;
+ dap->policy.register_effect = &GceAudioPolicy::RegisterEffect;
+ dap->policy.unregister_effect = &GceAudioPolicy::UnregisterEffect;
+ dap->policy.set_effect_enabled = &GceAudioPolicy::SetEffectEnabled;
+ dap->policy.is_stream_active = &GceAudioPolicy::IsStreamActive;
+ dap->policy.dump = &GceAudioPolicy::Dump;
+#ifdef ENABLE_OFFLOAD
+ dap->policy.is_offload_supported = &GceAudioPolicy::IsOffloadSupported;
+#endif
+
+ dap->service = service;
+ dap->aps_ops = aps_ops;
+
+ *ap = &dap->policy;
+ return 0;
+}
+
+
+int GceAudioPolicy::Destroy(const audio_policy_device* ap_dev,
+ audio_policy* ap) {
+ D("%s", __FUNCTION__);
+ free(ap);
+ return 0;
+}
+
+
+int GceAudioPolicy::Close(hw_device_t* device) {
+ D("%s", __FUNCTION__);
+ free(device);
+ return 0;
+}
+
+
+int GceAudioPolicy::Open(
+ const hw_module_t* module, const char* name, hw_device_t** device) {
+ D("%s", __FUNCTION__);
+ audio_policy_device* dev;
+
+ *device = NULL;
+
+ if (strcmp(name, AUDIO_POLICY_INTERFACE) != 0) {
+ return -EINVAL;
+ }
+
+ dev = (audio_policy_device*) calloc(1, sizeof(*dev));
+ if (!dev) {
+ return -ENOMEM;
+ }
+
+ dev->common.tag = HARDWARE_DEVICE_TAG;
+ dev->common.version = 0;
+ dev->common.module = (hw_module_t*) module;
+ dev->common.close = &GceAudioPolicy::Close;
+ dev->create_audio_policy = &GceAudioPolicy::Create;
+ dev->destroy_audio_policy = &GceAudioPolicy::Destroy;
+
+ *device = &dev->common;
+
+ return 0;
+}
+
+}
+
+static hw_module_methods_t gce_audio_policy_module_methods = {
+ .open = &cvd::GceAudioPolicy::Open,
+};
+
+
+audio_policy_module HAL_MODULE_INFO_SYM = {
+ .common = {
+ .tag = HARDWARE_MODULE_TAG,
+ .version_major = 1,
+ .version_minor = 0,
+ .id = AUDIO_POLICY_HARDWARE_MODULE_ID,
+ .name = "GCE Audio Policy HAL",
+ .author = "The Android Open Source Project",
+ .methods = &gce_audio_policy_module_methods,
+ },
+};
diff --git a/guest/commands/audio/policy/vsoc_audio_policy_hal.h b/guest/commands/audio/policy/vsoc_audio_policy_hal.h
new file mode 100644
index 0000000..365d1c4
--- /dev/null
+++ b/guest/commands/audio/policy/vsoc_audio_policy_hal.h
@@ -0,0 +1,244 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+#pragma once
+
+#include <errno.h>
+#include <string.h>
+#include <cutils/log.h>
+
+#include <hardware/hardware.h>
+#include <system/audio.h>
+#include <hardware/audio.h>
+
+#define AUDIO_DEBUG 1
+
+#if AUDIO_DEBUG
+# define D(...) ALOGD(__VA_ARGS__)
+#else
+# define D(...) ((void)0)
+#endif
+
+namespace cvd {
+
+struct gce_audio_policy {
+ audio_policy policy;
+
+ audio_policy_service_ops* aps_ops;
+ void* service;
+};
+
+
+class GceAudioPolicy {
+ public:
+ GceAudioPolicy() {}
+ ~GceAudioPolicy() {}
+
+ static int Open(
+ const hw_module_t* module, const char* name, hw_device_t** device);
+ static int Create(const audio_policy_device* device,
+ audio_policy_service_ops* aps_ops, void* service,
+ audio_policy** ap);
+ static int Destroy(const audio_policy_device* ap_dev,
+ audio_policy* ap);
+ static int Close(hw_device_t* device);
+
+ static int SetDeviceConnectionState(
+ audio_policy* pol, audio_devices_t device,
+ audio_policy_dev_state_t state, const char* device_address) {
+ ALOGE("%s: not supported", __FUNCTION__);
+ return -ENOSYS;
+ }
+
+ static audio_policy_dev_state_t GetDeviceConnectionState(
+ const audio_policy* pol, audio_devices_t device,
+ const char* device_address) {
+ ALOGE("%s: not supported", __FUNCTION__);
+ return AUDIO_POLICY_DEVICE_STATE_UNAVAILABLE;
+ }
+
+ static void SetPhoneState(audio_policy* pol, audio_mode_t state) {
+ ALOGE("%s: not supported", __FUNCTION__);
+ }
+
+ static void SetRingerMode(audio_policy* pol, uint32_t mode,
+ uint32_t mask) {
+ ALOGW("%s: deprecated", __FUNCTION__);
+ }
+
+ static void SetForceUse(
+ audio_policy* pol, audio_policy_force_use_t usage,
+ audio_policy_forced_cfg_t config) {
+ ALOGE("%s: not supported", __FUNCTION__);
+ }
+
+ static audio_policy_forced_cfg_t GetForceUse(
+ const audio_policy* pol, audio_policy_force_use_t usage) {
+ ALOGE("%s: not supported", __FUNCTION__);
+ return AUDIO_POLICY_FORCE_NONE;
+ }
+
+ static void SetCanMuteEnforcedAudible(
+ audio_policy* pol, bool can_mute) {
+ ALOGE("%s: not supported", __FUNCTION__);
+ }
+
+ static int InitCheck(const audio_policy* pol) {
+ ALOGE("%s: not supported", __FUNCTION__);
+ return 0;
+ }
+
+ static audio_io_handle_t GetOutput(
+ audio_policy* pol, audio_stream_type_t stream,
+ uint32_t sampling_rate, audio_format_t format,
+ audio_channel_mask_t channelMask, audio_output_flags_t flags
+#ifdef ENABLE_OFFLOAD
+ , const audio_offload_info_t* info
+#endif
+ ) {
+ ALOGE("%s: not supported", __FUNCTION__);
+ return 0;
+ }
+
+ static int StartOutput(audio_policy* pol, audio_io_handle_t output,
+ audio_stream_type_t stream, int session) {
+ ALOGE("%s: not supported", __FUNCTION__);
+ return -ENOSYS;
+ }
+
+ static int StopOutput(audio_policy* pol, audio_io_handle_t output,
+ audio_stream_type_t stream, int session) {
+ ALOGE("%s: not supported", __FUNCTION__);
+ return -ENOSYS;
+ }
+
+ static void ReleaseOutput(
+ audio_policy* pol, audio_io_handle_t output) {
+ ALOGE("%s: not supported", __FUNCTION__);
+ }
+
+ static audio_io_handle_t GetInput(
+ audio_policy* pol, audio_source_t inputSource,
+ uint32_t sampling_rate, audio_format_t format,
+ audio_channel_mask_t channelMask, audio_in_acoustics_t acoustics) {
+ ALOGE("%s: not supported", __FUNCTION__);
+ return 0;
+ }
+
+ static int StartInput(audio_policy* pol, audio_io_handle_t input) {
+ ALOGE("%s: not supported", __FUNCTION__);
+ return -ENOSYS;
+ }
+
+ static int StopInput(audio_policy* pol, audio_io_handle_t input) {
+ ALOGE("%s: not supported", __FUNCTION__);
+ return -ENOSYS;
+ }
+
+ static void ReleaseInput(
+ audio_policy* pol, audio_io_handle_t input) {
+ ALOGE("%s: not supported", __FUNCTION__);
+ }
+
+ static void InitStreamVolume(audio_policy* pol,
+ audio_stream_type_t stream, int index_min,
+ int index_max) {
+ ALOGE("%s: not supported", __FUNCTION__);
+ }
+
+ static int SetStreamVolumeIndex(audio_policy* pol,
+ audio_stream_type_t stream, int index) {
+ ALOGE("%s: not supported", __FUNCTION__);
+ return -ENOSYS;
+ }
+
+ static int GetStreamVolumeIndex(const audio_policy* pol,
+ audio_stream_type_t stream, int* index) {
+ ALOGE("%s: not supported", __FUNCTION__);
+ return -ENOSYS;
+ }
+
+ static int SetStreamVolumeIndexForDevice(
+ audio_policy* pol, audio_stream_type_t stream,
+ int index, audio_devices_t device) {
+ ALOGE("%s: not supported", __FUNCTION__);
+ return -ENOSYS;
+ }
+
+ static int GetStreamVolumeIndexForDevice(
+ const audio_policy* pol, audio_stream_type_t stream,
+ int* index, audio_devices_t device) {
+ ALOGE("%s: not supported", __FUNCTION__);
+ return -ENOSYS;
+ }
+
+ static uint32_t GetStrategyForStream(const audio_policy* pol,
+ audio_stream_type_t stream) {
+ ALOGE("%s: not supported", __FUNCTION__);
+ return 0;
+ }
+
+ static audio_devices_t GetDevicesForStream(const audio_policy* pol,
+ audio_stream_type_t stream) {
+ ALOGE("%s: not supported", __FUNCTION__);
+ return 0;
+ }
+
+ static audio_io_handle_t GetOutputForEffect(
+ audio_policy* pol, const effect_descriptor_s* desc) {
+ ALOGE("%s: not supported", __FUNCTION__);
+ return 0;
+ }
+
+ static int RegisterEffect(
+ audio_policy* pol, const effect_descriptor_s* desc,
+ audio_io_handle_t output, uint32_t strategy, int session, int id) {
+ ALOGE("%s: not supported", __FUNCTION__);
+ return -ENOSYS;
+ }
+
+ static int UnregisterEffect(audio_policy* pol, int id) {
+ ALOGE("%s: not supported", __FUNCTION__);
+ return -ENOSYS;
+ }
+
+ static int SetEffectEnabled(audio_policy* pol, int id, bool enabled) {
+ ALOGE("%s: not supported", __FUNCTION__);
+ return -ENOSYS;
+ }
+
+ static bool IsStreamActive(
+ const audio_policy* pol, audio_stream_type_t stream,
+ uint32_t in_past_ms) {
+ ALOGE("%s: not supported", __FUNCTION__);
+ return false;
+ }
+
+ static int Dump(const audio_policy* pol, int fd) {
+ ALOGE("%s: not supported", __FUNCTION__);
+ return -ENOSYS;
+ }
+
+#ifdef ENABLE_OFFLOAD
+ static bool IsOffloadSupported(const audio_policy* pol,
+ const audio_offload_info_t* info) {
+ ALOGE("%s: not supported", __FUNCTION__);
+ return false;
+ }
+#endif
+};
+
+}
+
diff --git a/guest/commands/audio/simulated_buffer.h b/guest/commands/audio/simulated_buffer.h
new file mode 100644
index 0000000..5ea31c2
--- /dev/null
+++ b/guest/commands/audio/simulated_buffer.h
@@ -0,0 +1,315 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#pragma once
+
+#include <stdint.h>
+#include <unistd.h>
+#include <time.h>
+
+#include "common/libs/time/monotonic_time.h"
+
+/**
+ * This abstract class simulates a buffer that either fills or empties at
+ * a specified rate.
+ *
+ * The simulated buffer automatically fills or empties at a specific rate.
+ *
+ * An item is the thing contained in the simulated buffer. Items are moved
+ * in and out of the buffer without subdivision.
+ *
+ * An integral number of items must arrive / depart in each second.
+ * This number is stored in items_per_second_
+ *
+ * items_per_second * 2000000000 must fit within an int64_t. This
+ * works if items_per_second is represented by an int32.
+ *
+ * The base class does have the concept of capacity, but doesn't use it.
+ * It is included here to simplify unit testing.
+ *
+ * For actual use, see SimulatedInputBuffer and SimulatedOutputBuffer below.
+ */
+class SimulatedBufferBase {
+ public:
+ static inline int64_t divide_and_round_up(int64_t q, int64_t d) {
+ return q / d + ((q % d) != 0);
+ }
+
+ SimulatedBufferBase(
+ int32_t items_per_second,
+ int64_t simulated_item_capacity,
+ cvd::time::MonotonicTimePointFactory* clock =
+ cvd::time::MonotonicTimePointFactory::GetInstance()) :
+ clock_(clock),
+ current_item_num_(0),
+ base_item_num_(0),
+ simulated_item_capacity_(simulated_item_capacity),
+ items_per_second_(items_per_second),
+ initialize_(true),
+ paused_(false) { }
+
+ virtual ~SimulatedBufferBase() { }
+
+ int64_t GetCurrentItemNum() {
+ Update();
+ return current_item_num_;
+ }
+
+ const cvd::time::MonotonicTimePoint GetLastUpdatedTime() const {
+ return current_time_;
+ }
+
+ // Sleep for the given amount of time. Subclasses may override this to use
+ // different sleep calls.
+ // Sleep is best-effort. The code assumes that the acutal sleep time may be
+ // greater or less than the time requested.
+ virtual void SleepUntilTime(const cvd::time::MonotonicTimePoint& in) {
+ struct timespec ts;
+ in.ToTimespec(&ts);
+ clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &ts, NULL);
+ }
+
+ // The time counter may not start at 0. Concrete classes should call this
+ // to allow the buffer simulation to read the current time number and
+ // initialize its internal state.
+ virtual void Init() {
+ if (initialize_) {
+ clock_->FetchCurrentTime(&base_time_);
+ current_time_ = base_time_;
+ initialize_ = false;
+ }
+ }
+
+ virtual void Update() {
+ if (initialize_) {
+ Init();
+ }
+ cvd::time::MonotonicTimePoint now;
+ clock_->FetchCurrentTime(&now);
+ // We can't call FetchCurrentTime() in the constuctor because a subclass may
+ // want to override it, so we initialze the times to 0. If we detect this
+ // case go ahead and initialize to a current timestamp.
+ if (paused_) {
+ base_time_ += now - current_time_;
+ current_time_ = now;
+ return;
+ }
+ // Avoid potential overflow by limiting the scaling to one time second.
+ // There is no round-off error here because the bases are adjusted for full
+ // seconds.
+ // There is no issue with int64 overflow because 2's compliment subtraction
+ // is immune to overflow.
+ // However, this does assume that kNanosecondsPerSecond * items_per_second_
+ // fits in an int64.
+ cvd::time::Seconds seconds(now - base_time_);
+ base_time_ += seconds;
+ base_item_num_ += seconds.count() * items_per_second_;
+ current_time_ = now;
+ current_item_num_ =
+ cvd::time::Nanoseconds(now - base_time_).count() *
+ items_per_second_ / cvd::time::kNanosecondsPerSecond +
+ base_item_num_;
+ }
+
+ // If set to true new items will not be created.
+ bool SetPaused(bool new_state) {
+ bool rval = paused_;
+ Update();
+ paused_ = new_state;
+ return rval;
+ }
+
+ // Calculate the TimePoint that corresponds to an item.
+ // Caution: This may not return a correct time for items in the past.
+ cvd::time::MonotonicTimePoint CalculateItemTime(int64_t item) {
+ int64_t seconds = (item - base_item_num_) / items_per_second_;
+ int64_t new_base_item_num = base_item_num_ + seconds * items_per_second_;
+ return base_time_ + cvd::time::Seconds(seconds) +
+ cvd::time::Nanoseconds(divide_and_round_up(
+ (item - new_base_item_num) *
+ cvd::time::kNanosecondsPerSecond,
+ items_per_second_));
+ }
+
+ // Sleep until the given item number is generated. If the generator is
+ // paused unpause it to make the sleep finite.
+ void SleepUntilItem(int64_t item) {
+ if (paused_) {
+ SetPaused(false);
+ }
+ cvd::time::MonotonicTimePoint desired_time =
+ CalculateItemTime(item);
+ while (1) {
+ Update();
+ if (current_item_num_ - item >= 0) {
+ return;
+ }
+ SleepUntilTime(desired_time);
+ }
+ }
+
+ protected:
+ // Source of the timepoints.
+ cvd::time::MonotonicTimePointFactory* clock_;
+ // Time when the other values in the structure were updated.
+ cvd::time::MonotonicTimePoint current_time_;
+ // Most recent time when there was no round-off error between the clock and
+ // items.
+ cvd::time::MonotonicTimePoint base_time_;
+ // Number of the current item.
+ int64_t current_item_num_;
+ // Most recent item number where there was no round-off error between the
+ // clock and items.
+ int64_t base_item_num_;
+ // Simulated_Item_Capacity of the buffer in items.
+ int64_t simulated_item_capacity_;
+ // Number of items that are created in 1s. A typical number would be 48000.
+ int32_t items_per_second_;
+ bool initialize_;
+ // If true then don't generate new items.
+ bool paused_;
+};
+
+/**
+ * This is a simulation of an output buffer that drains at a constant rate.
+ */
+class SimulatedOutputBuffer : public SimulatedBufferBase {
+ public:
+ SimulatedOutputBuffer(
+ int64_t item_rate,
+ int64_t simulated_item_capacity,
+ cvd::time::MonotonicTimePointFactory* clock =
+ cvd::time::MonotonicTimePointFactory::GetInstance()) :
+ SimulatedBufferBase(item_rate, simulated_item_capacity, clock) {
+ output_buffer_item_num_ = current_item_num_;
+ }
+
+ void Update() override {
+ SimulatedBufferBase::Update();
+ if ((output_buffer_item_num_ - current_item_num_) < 0) {
+ // We ran out of items at some point in the past. However, the
+ // output capactiy can't be negative.
+ output_buffer_item_num_ = current_item_num_;
+ }
+ }
+
+ int64_t AddToOutputBuffer(int64_t num_new_items, bool block) {
+ Update();
+ // The easy case: num_new_items fit in the bucket.
+ if ((output_buffer_item_num_ + num_new_items - current_item_num_) <=
+ simulated_item_capacity_) {
+ output_buffer_item_num_ += num_new_items;
+ return num_new_items;
+ }
+ // If we're non-blocking accept enough items to fill the output.
+ if (!block) {
+ int64_t used = current_item_num_ + simulated_item_capacity_ -
+ output_buffer_item_num_;
+ output_buffer_item_num_ = current_item_num_ + simulated_item_capacity_;
+ return used;
+ }
+ int64_t new_output_buffer_item_num = output_buffer_item_num_ + num_new_items;
+ SleepUntilItem(new_output_buffer_item_num - simulated_item_capacity_);
+ output_buffer_item_num_ = new_output_buffer_item_num;
+ return num_new_items;
+ }
+
+ int64_t GetNextOutputBufferItemNum() {
+ Update();
+ return output_buffer_item_num_;
+ }
+
+ cvd::time::MonotonicTimePoint GetNextOutputBufferItemTime() {
+ Update();
+ return CalculateItemTime(output_buffer_item_num_);
+ }
+
+ int64_t GetOutputBufferSize() {
+ Update();
+ return output_buffer_item_num_ - current_item_num_;
+ }
+
+ void Drain() {
+ SleepUntilItem(output_buffer_item_num_);
+ }
+
+ protected:
+ int64_t output_buffer_item_num_;
+};
+
+/**
+ * Simulates an input buffer that fills at a constant rate.
+ */
+class SimulatedInputBuffer : public SimulatedBufferBase {
+ public:
+ SimulatedInputBuffer(
+ int64_t item_rate,
+ int64_t simulated_item_capacity,
+ cvd::time::MonotonicTimePointFactory* clock =
+ cvd::time::MonotonicTimePointFactory::GetInstance()) :
+ SimulatedBufferBase(item_rate, simulated_item_capacity, clock) {
+ input_buffer_item_num_ = current_item_num_;
+ lost_input_items_ = 0;
+ }
+
+ void Update() override {
+ SimulatedBufferBase::Update();
+ if ((current_item_num_ - input_buffer_item_num_) >
+ simulated_item_capacity_) {
+ // The buffer overflowed at some point in the past. Account for the lost
+ // times.
+ int64_t new_input_buffer_item_num =
+ current_item_num_ - simulated_item_capacity_;
+ lost_input_items_ +=
+ new_input_buffer_item_num - input_buffer_item_num_;
+ input_buffer_item_num_ = new_input_buffer_item_num;
+ }
+ }
+
+ int64_t RemoveFromInputBuffer(int64_t num_items_wanted, bool block) {
+ Update();
+ if (!block) {
+ int64_t num_items_available = current_item_num_ - input_buffer_item_num_;
+ if (num_items_available < num_items_wanted) {
+ input_buffer_item_num_ += num_items_available;
+ return num_items_available;
+ } else {
+ input_buffer_item_num_ += num_items_wanted;
+ return num_items_wanted;
+ }
+ }
+ // Calculate the item number that is being claimed. Sleep until it appears.
+ // Advancing input_buffer_item_num_ causes a negative value to be compared
+ // to the capacity, effectively disabling the overflow detection code
+ // in Update().
+ input_buffer_item_num_ += num_items_wanted;
+ while (input_buffer_item_num_ - current_item_num_ > 0) {
+ SleepUntilItem(input_buffer_item_num_);
+ }
+ return num_items_wanted;
+ }
+
+ int64_t GetLostInputItems() {
+ Update();
+ int64_t rval = lost_input_items_;
+ lost_input_items_ = 0;
+ return rval;
+ }
+
+ protected:
+ int64_t input_buffer_item_num_;
+ int64_t lost_input_items_;
+};
+
diff --git a/guest/commands/audio/vsoc_audio.cpp b/guest/commands/audio/vsoc_audio.cpp
new file mode 100644
index 0000000..68c896d
--- /dev/null
+++ b/guest/commands/audio/vsoc_audio.cpp
@@ -0,0 +1,496 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#include "guest/commands/audio/audio_hal.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/time.h>
+#include <fcntl.h>
+
+extern "C" {
+#include <cutils/str_parms.h>
+}
+
+#include "common/libs/auto_resources/auto_resources.h"
+#include "common/libs/fs/shared_select.h"
+#include "common/libs/threads/cuttlefish_thread.h"
+#include "common/libs/threads/thunkers.h"
+#include "guest/commands/audio/vsoc_audio.h"
+#include "guest/commands/audio/vsoc_audio_input_stream.h"
+#include "guest/commands/audio/vsoc_audio_output_stream.h"
+#include "guest/libs/platform_support/api_level_fixes.h"
+#include "guest/libs/remoter/remoter_framework_pkt.h"
+
+using cvd::LockGuard;
+using cvd::Mutex;
+
+namespace cvd {
+
+namespace {
+template <typename F> struct HWDeviceThunker :
+ ThunkerBase<hw_device_t, GceAudio, F>{};
+template <typename F> struct AudioThunker :
+ ThunkerBase<audio_hw_device, GceAudio, F>{};
+template <typename F> struct AudioThreadThunker :
+ ThunkerBase<void, GceAudio, F>{};
+}
+
+GceAudio::~GceAudio() { }
+
+int GceAudio::Close() {
+ D("GceAudio::%s", __FUNCTION__);
+ {
+ LockGuard<Mutex> guard(lock_);
+ for (std::list<GceAudioOutputStream*>::iterator it = output_list_.begin();
+ it != output_list_.end(); ++it) {
+ delete *it;
+ }
+ for (input_map_t::iterator it = input_map_.begin();
+ it != input_map_.end(); ++it) {
+ delete it->second;
+ }
+ }
+ // Make certain that the listener thread wakes up
+ cvd::SharedFD temp_client =
+ cvd::SharedFD::SocketSeqPacketClient(
+ gce_audio_message::kAudioHALSocketName);
+ uint64_t dummy_val = 1;
+ terminate_listener_thread_event_->Write(&dummy_val, sizeof dummy_val);
+ pthread_join(listener_thread_, NULL);
+ delete this;
+ return 0;
+}
+
+cvd::SharedFD GceAudio::GetAudioFd() {
+ LockGuard<Mutex> guard(lock_);
+ return audio_data_socket_;
+}
+
+size_t GceAudio::GetInputBufferSize(const audio_config*) const {
+ return IN_BUFFER_BYTES;
+}
+
+uint32_t GceAudio::GetSupportedDevices() const {
+ return AUDIO_DEVICE_OUT_EARPIECE |
+ AUDIO_DEVICE_OUT_SPEAKER |
+ AUDIO_DEVICE_OUT_DEFAULT |
+ AUDIO_DEVICE_IN_COMMUNICATION |
+ AUDIO_DEVICE_IN_BUILTIN_MIC |
+ AUDIO_DEVICE_IN_WIRED_HEADSET |
+ AUDIO_DEVICE_IN_VOICE_CALL |
+ AUDIO_DEVICE_IN_DEFAULT;
+}
+
+int GceAudio::InitCheck() const {
+ D("GceAudio::%s", __FUNCTION__);
+ return 0;
+}
+
+int GceAudio::SetMicMute(bool state) {
+ D("GceAudio::%s", __FUNCTION__);
+ LockGuard<Mutex> guard(lock_);
+ mic_muted_ = state;
+ return 0;
+}
+
+int GceAudio::GetMicMute(bool *state) const {
+ D("GceAudio::%s", __FUNCTION__);
+ LockGuard<Mutex> guard(lock_);
+ *state = mic_muted_;
+ return 0;
+}
+
+int GceAudio::OpenInputStream(audio_io_handle_t handle,
+ audio_devices_t devices,
+ audio_config *config,
+ audio_stream_in **stream_in,
+ audio_input_flags_t /*flags*/,
+ const char * /*address*/,
+ audio_source_t /*source*/) {
+ GceAudioInputStream* new_stream;
+ int rval = GceAudioInputStream::Open(
+ this, handle, devices, *config, &new_stream);
+ uint32_t stream_number;
+ if (new_stream) {
+ LockGuard<Mutex> guard(lock_);
+ stream_number = next_stream_number_++;
+ input_map_[stream_number] = new_stream;
+ }
+ // This should happen after the lock is released, hence the double check
+ if (new_stream) {
+ SendStreamUpdate(new_stream->GetStreamDescriptor(
+ stream_number, gce_audio_message::OPEN_INPUT_STREAM), MSG_DONTWAIT);
+ }
+ *stream_in = new_stream;
+ return rval;
+}
+
+
+void GceAudio::CloseInputStream(audio_stream_in *stream) {
+ GceAudioInputStream* astream = static_cast<GceAudioInputStream*>(stream);
+ gce_audio_message descriptor;
+ {
+ LockGuard<Mutex> guard(lock_);
+ // TODO(ghartman): This could be optimized if stream knew it's number.
+ for (input_map_t::iterator it = input_map_.begin();
+ it != input_map_.end(); ++it) {
+ if (it->second == stream) {
+ descriptor = it->second->GetStreamDescriptor(
+ it->first, gce_audio_message::CLOSE_INPUT_STREAM);
+ input_map_.erase(it);
+ break;
+ }
+ }
+ }
+ SendStreamUpdate(descriptor, MSG_DONTWAIT);
+ delete astream;
+}
+
+
+int GceAudio::OpenOutputStream(audio_io_handle_t handle,
+ audio_devices_t devices,
+ audio_output_flags_t flags,
+ audio_config *config,
+ audio_stream_out **stream_out,
+ const char * /*address*/) {
+ GceAudioOutputStream* new_stream;
+ int rval;
+ {
+ LockGuard<Mutex> guard(lock_);
+ rval = GceAudioOutputStream::Open(
+ this, handle, devices, flags, config, next_stream_number_++,
+ &new_stream);
+ if (new_stream) {
+ output_list_.push_back(new_stream);
+ }
+ }
+ if (new_stream) {
+ SendStreamUpdate(new_stream->GetStreamDescriptor(
+ gce_audio_message::OPEN_OUTPUT_STREAM), MSG_DONTWAIT);
+ }
+ *stream_out = new_stream;
+ return rval;
+}
+
+void GceAudio::CloseOutputStream(audio_stream_out *stream) {
+ GceAudioOutputStream* astream = static_cast<GceAudioOutputStream*>(stream);
+ gce_audio_message close;
+ {
+ LockGuard<Mutex> guard(lock_);
+ output_list_.remove(astream);
+ close = astream->GetStreamDescriptor(
+ gce_audio_message::CLOSE_OUTPUT_STREAM);
+ }
+ SendStreamUpdate(close, MSG_DONTWAIT);
+ delete astream;
+}
+
+int GceAudio::Dump(int fd) const {
+ LockGuard<Mutex> guard(lock_);
+ VSOC_FDPRINTF(
+ fd,
+ "\nadev_dump:\n"
+ "\tmic_mute: %s\n"
+ "\tnum_outputs: %zu\n"
+ "\tnum_inputs: %zu\n\n",
+ mic_muted_ ? "true": "false",
+ output_list_.size(), input_map_.size());
+
+ for (std::list<GceAudioOutputStream*>::const_iterator it =
+ output_list_.begin();
+ it != output_list_.end(); ++it) {
+ (*it)->common.dump(&(*it)->common, fd);
+ }
+
+ for (input_map_t::const_iterator it = input_map_.begin();
+ it != input_map_.end(); ++it) {
+ (*it).second->common.dump(&(*it).second->common, fd);
+ }
+
+ return 0;
+}
+
+ssize_t GceAudio::SendMsg(const msghdr& msg, int flags) {
+ cvd::SharedFD fd = GetAudioFd();
+ if (!fd->IsOpen()) {
+ return 0;
+ }
+ return fd->SendMsg(&msg, flags);
+}
+
+ssize_t GceAudio::SendStreamUpdate(
+ const gce_audio_message& stream_info, int flags) {
+ msghdr msg;
+ iovec msg_iov[1];
+ msg_iov[0].iov_base = const_cast<gce_audio_message*>(&stream_info);
+ msg_iov[0].iov_len = sizeof(gce_audio_message);
+ msg.msg_name = NULL;
+ msg.msg_namelen = 0;
+ msg.msg_iov = msg_iov;
+ msg.msg_iovlen = arraysize(msg_iov);
+ msg.msg_control = NULL;
+ msg.msg_controllen = 0;
+ msg.msg_flags = 0;
+ return SendMsg(msg, flags);
+}
+
+int GceAudio::SetVoiceVolume(float volume) {
+ D("GceAudio::%s: set voice volume %f", __FUNCTION__, volume);
+ voice_volume_ = volume;
+ return 0;
+}
+
+int GceAudio::SetMasterVolume(float volume) {
+ D("GceAudio::%s: set master volume %f", __FUNCTION__, volume);
+ master_volume_ = volume;
+ return 0;
+}
+
+int GceAudio::GetMasterVolume(float* volume) {
+ D("GceAudio::%s: get master volume %f", __FUNCTION__, master_volume_);
+ *volume = master_volume_;
+ return 0;
+}
+
+int GceAudio::SetMasterMute(bool muted) {
+ D("GceAudio::%s: set master muted %d", __FUNCTION__, muted);
+ master_muted_ = muted;
+ return 0;
+}
+
+int GceAudio::GetMasterMute(bool* muted) {
+ D("GceAudio::%s: get master muted %d", __FUNCTION__, master_muted_);
+ *muted = master_muted_;
+ return 0;
+}
+
+int GceAudio::SetMode(audio_mode_t mode) {
+ D("GceAudio::%s: new mode %d", __FUNCTION__, mode);
+ mode_ = mode;
+ return 0;
+}
+
+void* GceAudio::Listener() {
+ // TODO(ghartman): Consider tightening the mode on this later.
+ audio_listener_socket_ = cvd::SharedFD::SocketSeqPacketServer(
+ gce_audio_message::kAudioHALSocketName, 0777);
+ if (!audio_listener_socket_->IsOpen()) {
+ ALOGE("GceAudio::%s: Could not listen for audio connections. (%s).",
+ __FUNCTION__, audio_listener_socket_->StrError());
+ return NULL;
+ }
+ ALOGI("GceAudio::%s: Listening for audio connections at %s",
+ __FUNCTION__, gce_audio_message::kAudioHALSocketName);
+ remoter_request_packet announce;
+ remoter_request_packet_init(&announce, kRemoterHALReady, 0);
+ announce.send_response = 0;
+ strncpy(announce.params.hal_ready_params.unix_socket,
+ gce_audio_message::kAudioHALSocketName,
+ sizeof(announce.params.hal_ready_params.unix_socket));
+ AutoCloseFileDescriptor remoter_socket(remoter_connect());
+ if (remoter_socket.IsError()) {
+ ALOGI("GceAudio::%s: Couldn't connect to remoter to register HAL (%s).",
+ __FUNCTION__, strerror(errno));
+ } else {
+ int err = remoter_do_single_request_with_socket(
+ remoter_socket, &announce, NULL);
+ if (err == -1) {
+ ALOGI("GceAudio::%s: HAL registration failed after connect (%s).",
+ __FUNCTION__, strerror(errno));
+ } else {
+ ALOGI("GceAudio::%s: HAL registered with the remoter", __FUNCTION__);
+ }
+ }
+ while (true) {
+ // Poll for new connections or the terminatation event.
+ // The listener is non-blocking. We send to at most one client. If a new
+ // client comes in disconnect the old one.
+ cvd::SharedFDSet fd_set;
+ fd_set.Set(audio_listener_socket_);
+ fd_set.Set(terminate_listener_thread_event_);
+ if (cvd::Select(&fd_set, NULL, NULL, NULL) <= 0) {
+ // There's no timeout, so 0 shouldn't happen.
+ ALOGE("GceAudio::%s: Error using shared Select", __FUNCTION__);
+ break;
+ }
+ if (fd_set.IsSet(terminate_listener_thread_event_)) {
+ break;
+ }
+ LOG_FATAL_IF(fd_set.IsSet(audio_listener_socket_),
+ "No error in Select() but nothing ready to read");
+ cvd::SharedFD fd = cvd::SharedFD::Accept(
+ *audio_listener_socket_);
+ if (!fd->IsOpen()) {
+ continue;
+ }
+ std::list<gce_audio_message> streams;
+ {
+ LockGuard<Mutex> guard(lock_);
+ // Do not do I/O while holding the lock. It could block the HAL
+ // implementation.
+ // Register the fd before dropping the lock to ensure that every
+ // active stream will appear when we first connect.
+ // Some output streams may appear twice if an open is active
+ // during the connect.
+ audio_data_socket_ = fd;
+ for (std::list<GceAudioOutputStream*>::iterator it = output_list_.begin();
+ it != output_list_.end(); ++it) {
+ streams.push_back((*it)->GetStreamDescriptor(
+ gce_audio_message::OPEN_OUTPUT_STREAM));
+ }
+ for (input_map_t::iterator it = input_map_.begin();
+ it != input_map_.end(); ++it) {
+ streams.push_back(it->second->GetStreamDescriptor(
+ it->first, gce_audio_message::OPEN_INPUT_STREAM));
+ }
+ }
+ for (std::list<gce_audio_message>::iterator it = streams.begin();
+ it != streams.end(); ++it) {
+ // We're willing to block because this is independent of the HAL
+ // implementation. We also don't want to forget to mention the input
+ // streams.
+ if (SendStreamUpdate(*it, 0) < 0) {
+ ALOGE("GceAudio::%s: Failed to announce open stream (%s)",
+ __FUNCTION__, fd->StrError());
+ }
+ }
+ }
+ return NULL;
+}
+
+int GceAudio::Open(const hw_module_t* module, const char* name,
+ hw_device_t** device) {
+ D("GceAudio::%s", __FUNCTION__);
+
+ if (strcmp(name, AUDIO_HARDWARE_INTERFACE)) {
+ ALOGE("GceAudio::%s: invalid module name %s (expected %s)",
+ __FUNCTION__, name, AUDIO_HARDWARE_INTERFACE);
+ return -EINVAL;
+ }
+
+ GceAudio* rval = new GceAudio;
+ int err = pthread_create(
+ &rval->listener_thread_, NULL,
+ AudioThreadThunker<void*()>::call<&GceAudio::Listener>, rval);
+ if (err) {
+ ALOGE("GceAudio::%s: Unable to start listener thread (%s)", __FUNCTION__,
+ strerror(err));
+ }
+ rval->common.tag = HARDWARE_DEVICE_TAG;
+ rval->common.version = version_;
+ rval->common.module = const_cast<hw_module_t *>(module);
+ rval->common.close = HWDeviceThunker<int()>::call<&GceAudio::Close>;
+
+#if !defined(AUDIO_DEVICE_API_VERSION_2_0)
+ // This HAL entry is supported only on AUDIO_DEVICE_API_VERSION_1_0.
+ // In fact, with version 2.0 the device numbers were orgainized in a
+ // way that makes the return value nonsense.
+ // Skipping the assignment is ok: the memset in the constructor already
+ // put a NULL here.
+ rval->get_supported_devices =
+ AudioThunker<uint32_t()>::call<&GceAudio::GetSupportedDevices>;
+#endif
+ rval->init_check = AudioThunker<int()>::call<&GceAudio::InitCheck>;
+
+ rval->set_voice_volume =
+ AudioThunker<int(float)>::call<&GceAudio::SetVoiceVolume>;
+ rval->set_master_volume =
+ AudioThunker<int(float)>::call<&GceAudio::SetMasterVolume>;
+ rval->get_master_volume =
+ AudioThunker<int(float*)>::call<&GceAudio::GetMasterVolume>;
+
+#if defined(AUDIO_DEVICE_API_VERSION_2_0)
+ rval->set_master_mute =
+ AudioThunker<int(bool)>::call<&GceAudio::SetMasterMute>;
+ rval->get_master_mute =
+ AudioThunker<int(bool*)>::call<&GceAudio::GetMasterMute>;
+#endif
+
+ rval->set_mode = AudioThunker<int(audio_mode_t)>::call<&GceAudio::SetMode>;
+ rval->set_mic_mute = AudioThunker<int(bool)>::call<&GceAudio::SetMicMute>;
+ rval->get_mic_mute =
+ AudioThunker<int(bool*)>::call<&GceAudio::GetMicMute>;
+
+ rval->set_parameters =
+ AudioThunker<int(const char*)>::call<&GceAudio::SetParameters>;
+ rval->get_parameters =
+ AudioThunker<char*(const char*)>::call<&GceAudio::GetParameters>;
+
+ rval->get_input_buffer_size =
+ AudioThunker<size_t(const audio_config*)>::call<
+ &GceAudio::GetInputBufferSize>;
+
+ rval->open_input_stream =
+ AudioThunker<GceAudio::OpenInputStreamHAL_t>::call<
+ &GceAudio::OpenInputStreamCurrentHAL>;
+ rval->close_input_stream =
+ AudioThunker<void(audio_stream_in*)>::call<&GceAudio::CloseInputStream>;
+
+ rval->open_output_stream =
+ AudioThunker<GceAudio::OpenOutputStreamHAL_t>::call<
+ &GceAudio::OpenOutputStreamCurrentHAL>;
+ rval->close_output_stream =
+ AudioThunker<void(audio_stream_out*)>::call<&GceAudio::CloseOutputStream>;
+
+ rval->dump = AudioThunker<int(int)>::call<&GceAudio::Dump>;
+
+ *device = &rval->common;
+ return 0;
+}
+
+int GceAudio::SetParameters(const char *kvpairs) {
+ ALOGE("GceAudio::%s: not implemented", __FUNCTION__);
+ if (kvpairs) D("GceAudio::%s: kvpairs %s", __FUNCTION__, kvpairs);
+ return 0;
+}
+
+
+char* GceAudio::GetParameters(const char *keys) const {
+ ALOGE("GceAudio::%s: not implemented", __FUNCTION__);
+ if (keys) D("GceAudio::%s: kvpairs %s", __FUNCTION__, keys);
+ return strdup("");
+}
+
+int GceAudio::SetStreamParameters(
+ struct audio_stream *stream, const char *kv_pairs) {
+ struct str_parms *parms = str_parms_create_str(kv_pairs);
+ if (!parms) {
+ return 0;
+ }
+ int sample_rate;
+ if (str_parms_get_int(parms, AUDIO_PARAMETER_STREAM_SAMPLING_RATE,
+ &sample_rate) >= 0) {
+ stream->set_sample_rate(stream, sample_rate);
+ }
+ int format;
+ if (str_parms_get_int(parms, AUDIO_PARAMETER_STREAM_FORMAT,
+ &format) >= 0) {
+ stream->set_format(stream, static_cast<audio_format_t>(format));
+ }
+ int routing;
+ if (str_parms_get_int(parms, AUDIO_PARAMETER_STREAM_ROUTING,
+ &routing) >= 0) {
+ stream->set_device(stream, static_cast<audio_devices_t>(routing));
+ }
+ if (str_parms_get_int(parms, AUDIO_PARAMETER_STREAM_INPUT_SOURCE,
+ &routing) >= 0) {
+ stream->set_device(stream, static_cast<audio_devices_t>(routing));
+ }
+ str_parms_destroy(parms);
+ return 0;
+}
+
+}
diff --git a/guest/commands/audio/vsoc_audio.h b/guest/commands/audio/vsoc_audio.h
new file mode 100644
index 0000000..7e23bf7
--- /dev/null
+++ b/guest/commands/audio/vsoc_audio.h
@@ -0,0 +1,320 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#pragma once
+
+#include <list>
+#include <map>
+
+#include "common/libs/fs/shared_fd.h"
+#include "common/libs/threads/cuttlefish_thread.h"
+#include "guest/commands/audio/audio_hal.h"
+#include "guest/commands/audio/vsoc_audio_input_stream.h"
+#include "guest/commands/audio/vsoc_audio_message.h"
+#include "guest/libs/platform_support/api_level_fixes.h"
+
+namespace cvd {
+
+class GceAudioInputStream;
+class GceAudioOutputStream;
+
+class GceAudio : public audio_hw_device {
+ public:
+ // This common code manipulates the parameters of input and output streams.
+ static int SetStreamParameters(struct audio_stream *, const char *);
+
+ ~GceAudio();
+
+ // Non-HAL methods that are part of the GCE implementation.
+ // Most of these are used by the input and output streams.
+
+ // Returns true if the microphone is muted. Used by input streams.
+ bool IsMicrophoneMuted() {
+ cvd::LockGuard<cvd::Mutex> guard(lock_);
+ return mic_muted_;
+ }
+
+ // Retrieves the SharedFD of the process accepting audio data.
+ // Returns a non-open fd if no process is listening (the normal case).
+ cvd::SharedFD GetAudioFd();
+
+ // Send a message to the connected streamer.
+ // Returns:
+ // 0 if there is no streamer.
+ // >0 if the message was sent.
+ // -1 if there was an error.
+ ssize_t SendMsg(const msghdr&, int flags);
+
+ // Sends a stream update to the connected streamer.
+ // Stream updates have no frames. Use SendMsg if the message has frames.
+ // 0 if there is no streamer.
+ // >0 if the message was sent.
+ // -1 if there was an error.
+ ssize_t SendStreamUpdate(
+ const gce_audio_message& stream_info, int flags);
+
+ // Callbacks for the Android audio_module HAL interface.
+ // Most of the comments below are copied from
+ // libhardware/include/hardware/audio.h
+ //
+ // Where the is a conflict the comments there apply.
+ // By default these methods return 0 on success -<errno> for failure.
+
+ // Opens the device.
+ static int Open(const hw_module_t* module, const char* name,
+ hw_device_t** device);
+
+ // Closes the device, closing any open input streams and output streams.
+ int Close();
+
+ // Closes the input stream, throwing away any data in the buffer.
+ void CloseInputStream(audio_stream_in* stream);
+
+ // Closes the output stream without waiting for the buffer to clear.
+ void CloseOutputStream(audio_stream_out* stream);
+
+ // Creates an audio patch between several source and sink ports.
+ // The handle is allocated by the HAL and should be unique for this
+ // audio HAL module.
+ // TODO(ghartman): Implement this as part of HAL 3.0
+ //int CreateAudioPatch(unsigned int num_sources,
+ // const struct audio_port_config *sources,
+ // unsigned int num_sinks,
+ // const struct audio_port_config *sinks,
+ // audio_patch_handle_t *handle);
+
+ // dumps the state of the audio hardware to the given fd.
+ // This information can be retrieved using the dumpsys utility.
+ int Dump(int fd) const;
+
+ // Fills the list of supported attributes for a given audio port.
+ // As input, "port" contains the information (type, role, address etc...)
+ // needed by the HAL to identify the port.
+ // As output, "port" contains possible attributes (sampling rates, formats,
+ // channel masks, gain controllers...) for this port.
+ // TODO(ghartman): Implement this as part of HAL 3.0
+ // int GetAudioPort(struct audio_port *port);
+
+ // Sets audio port configuration
+ // TODO(ghartman): Implement this as part of HAL 3.0
+ // int SetAudioPortConfig(const struct audio_port_config *config);
+
+ size_t GetInputBufferSize(const audio_config*) const;
+
+ // Gets the current master volume value for the HAL, if the HAL supports
+ // master volume control. AudioFlinger will query this value from the
+ // primary audio HAL when the service starts and use the value for setting
+ // the initial master volume across all HALs. HALs which do not support
+ // this method may leave it set to NULL.
+ int GetMasterVolume(float* /*volume*/);
+
+ // Get the current master mute status for the HAL, if the HAL supports
+ // master mute control. AudioFlinger will query this value from the primary
+ // audio HAL when the service starts and use the value for setting the
+ // initial master mute across all HALs. HALs which do not support this
+ // method may leave it set to NULL.
+ int GetMasterMute(bool* muted);
+
+ // Gets the audio mute status for the microphone.
+ int GetMicMute(bool* state) const;
+
+ // Retrieves the global audio parameters.
+ // TODO(ghartman): Implement this.
+ char* GetParameters(const char* keys) const;
+
+ // Enumerates what devices are supported by each audio_hw_device
+ // implementation.
+ // Return value is a bitmask of 1 or more values of audio_devices_t
+ // used by audio flinger.
+ // NOTE: audio HAL implementations starting with
+ // AUDIO_DEVICE_API_VERSION_2_0 do not implement this function.
+ // AUDIO_DEVICE_API_VERSION_2_0 was the current version as of JB-MR1
+ // All supported devices should be listed in audio_policy.conf
+ // file and the audio policy manager must choose the appropriate
+ // audio module based on information in this file.
+ uint32_t GetSupportedDevices() const;
+
+ // Checks to see if the audio hardware interface has been initialized.
+ // Always returns 0 to indicate success, but -ENODEV is also allowed to
+ // indicate failure.
+ int InitCheck() const;
+
+ // Creates an additional hardware input stream.
+ // Additional parameters were added in the 3.0 version of the HAL.
+ // These defaults make it easier to implement a cross-branch device.
+ int OpenInputStream(
+ audio_io_handle_t handle,
+ audio_devices_t devices, audio_config *config,
+ audio_stream_in **stream_in,
+ audio_input_flags_t flags = AUDIO_INPUT_FLAG_NONE,
+ const char* address = 0,
+ audio_source_t source = AUDIO_SOURCE_DEFAULT);
+
+ // Creates an additional output stream.
+ // The "address" parameter qualifies the "devices" audio device type if
+ // needed. On GCE we ignore it for now because we simulate a single SoC
+ // hw devices.
+ //
+ // The format format depends on the device type:
+ // Bluetooth devices use the MAC address of the device in the form
+ // "00:11:22:AA:BB:CC"
+ // USB devices use the ALSA card and device numbers in the form
+ // "card=X;device=Y"
+ // Other devices may use a number or any other string.
+ int OpenOutputStream(
+ audio_io_handle_t handle,
+ audio_devices_t devices, audio_output_flags_t flags,
+ audio_config* config, audio_stream_out** stream_out,
+ const char* address = 0);
+
+ // Releases an audio patch.
+ // TODO(ghartman): Implement this as part of HAL 3.0
+ //int ReleaseAudioPatch(audio_patch_handle_t handle);
+
+ // Sets the audio mute status for all audio activities. If any value other
+ // than 0 is returned, the software mixer will emulate this capability.
+ // The GCE implementation always returns 0.
+ int SetMasterMute(bool muted);
+
+ // Sets the audio volume for all audio activities other than voice call.
+ // Range between 0.0 and 1.0. If any value other than 0 is returned,
+ // the software mixer will emulate this capability.
+ // The GCE implementation always returns 0.
+ int SetMasterVolume(float volume);
+
+ // Sets the audio mute status for the microphone.
+ int SetMicMute(bool state);
+
+ // set_mode is called when the audio mode changes. AUDIO_MODE_NORMAL mode
+ // is for standard audio playback, AUDIO_MODE_RINGTONE when a ringtone is
+ // playing, and AUDIO_MODE_IN_CALL when a call is in progress.
+ int SetMode(audio_mode_t mode);
+
+ // Sets the global audio parameters.
+ // TODO(ghartman): Create a sensible implementation.
+ int SetParameters(const char* kvpairs);
+
+ // Sets the audio volume of a voice call. Range is between 0.0 and 1.0
+ int SetVoiceVolume(float volume);
+
+
+ private:
+ // Main routine for a thread that listens for incoming streamer connections.
+ void* Listener();
+ // HAL 3.0 modifies the signatures of OpenInputStream and OpenOutputStream.
+ // We don't want to fork the implementation, and we don't want #ifdefs all
+ // over the code. The current implementation defines OpenInputStream and
+ // OpenOutputStream with default values for the paramteres that were added,
+ // and then generates a HAL-specific wrapper to be used in the function
+ // table.
+#if defined(AUDIO_DEVICE_API_VERSION_3_0)
+ typedef int OpenInputStreamHAL_t(
+ audio_io_handle_t, audio_devices_t, audio_config*, audio_stream_in**,
+ audio_input_flags_t, const char*, audio_source_t);
+
+ int OpenInputStreamCurrentHAL(
+ audio_io_handle_t a, audio_devices_t b, audio_config* c,
+ audio_stream_in** d, audio_input_flags_t e, const char* f,
+ audio_source_t g) {
+ return OpenInputStream(a, b, c, d, e, f, g);
+ }
+
+ typedef int OpenOutputStreamHAL_t(
+ audio_io_handle_t, audio_devices_t, audio_output_flags_t,
+ audio_config*, audio_stream_out**,
+ const char*);
+
+ int OpenOutputStreamCurrentHAL(
+ audio_io_handle_t a, audio_devices_t b, audio_output_flags_t c,
+ audio_config* d, audio_stream_out** e,
+ const char* f) {
+ return OpenOutputStream(a, b, c, d, e, f);
+ }
+#else
+ typedef int OpenInputStreamHAL_t(
+ audio_io_handle_t, audio_devices_t, audio_config*, audio_stream_in**);
+
+ int OpenInputStreamCurrentHAL(
+ audio_io_handle_t a, audio_devices_t b, audio_config* c,
+ audio_stream_in** d) {
+ return OpenInputStream(a, b, c, d);
+ }
+
+ typedef int OpenOutputStreamHAL_t(
+ audio_io_handle_t, audio_devices_t, audio_output_flags_t,
+ audio_config*, audio_stream_out**);
+
+ int OpenOutputStreamCurrentHAL(
+ audio_io_handle_t a, audio_devices_t b, audio_output_flags_t c,
+ audio_config* d, audio_stream_out** e) {
+ return OpenOutputStream(a, b, c, d, e);
+ }
+#endif
+
+ //TODO(ghartman): Update this when we support 3.0.
+#if defined(AUDIO_DEVICE_API_VERSION_2_0)
+ static const unsigned int version_ = AUDIO_DEVICE_API_VERSION_2_0;
+#else
+ static const unsigned int version_ = AUDIO_DEVICE_API_VERSION_1_0;
+#endif
+
+ // Thread to handle new connections.
+ pthread_t listener_thread_;
+ // Event to indicate that the listener thread should terminate.
+ cvd::SharedFD terminate_listener_thread_event_;
+ // The listener socket, which is polled for new connections.
+ // TODO(ghartman): Consider using a thread.
+ cvd::SharedFD audio_listener_socket_;
+ // Lock to protect the data below.
+ mutable cvd::Mutex lock_;
+ // The data socket for the current streamer. Typically -1.
+ // The behavior of the HAL should not be affected by the presence or absence
+ // of the streamer.
+ cvd::SharedFD audio_data_socket_;
+ // State that is managed at the device level.
+ float voice_volume_;
+ float master_volume_;
+ bool master_muted_;
+ bool mic_muted_;
+ audio_mode_t mode_;
+ // There can be multiple input and output streams. This field is used
+ // to assign each one a unique identifier.
+ // TODO(ghartman): This can wrap after 2^32 streams. Ideally we should check
+ // the output_list_ to ensure that the stream number hasn't been assigned.
+ // However, streams don't really appear and disapper that often.
+ // We use the same counter for both input and output streams to make things
+ // a little easier on the client.
+ uint32_t next_stream_number_;
+ // List of the currently active output streams.
+ // Used to clean things up Close()
+ std::list<GceAudioOutputStream *> output_list_;
+ // List of the currently active input streams.
+ // Used to clean things up Close()
+ typedef std::map<uint32_t, GceAudioInputStream *> input_map_t;
+ input_map_t input_map_;
+
+ GceAudio() :
+ audio_hw_device(),
+ terminate_listener_thread_event_(cvd::SharedFD::Event()),
+ voice_volume_(0.0),
+ master_volume_(0.0),
+ master_muted_(false),
+ mic_muted_(false),
+ mode_(AUDIO_MODE_NORMAL),
+ next_stream_number_(1) { }
+};
+
+}
+
diff --git a/guest/commands/audio/vsoc_audio_input_stream.cpp b/guest/commands/audio/vsoc_audio_input_stream.cpp
new file mode 100644
index 0000000..9802d2a
--- /dev/null
+++ b/guest/commands/audio/vsoc_audio_input_stream.cpp
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <cstdint>
+
+extern "C"{
+#include <cutils/str_parms.h>
+}
+
+#include "common/libs/auto_resources/auto_resources.h"
+#include "common/libs/threads/thunkers.h"
+#include "guest/commands/audio/audio_hal.h"
+#include "guest/commands/audio/vsoc_audio.h"
+#include "guest/commands/audio/vsoc_audio_input_stream.h"
+#include "guest/libs/platform_support/api_level_fixes.h"
+
+namespace cvd {
+
+namespace {
+template <typename F> struct Thunker :
+ ThunkerBase<audio_stream, GceAudioInputStream, F>{};
+template <typename F> struct InThunker :
+ ThunkerBase<audio_stream_in, GceAudioInputStream, F>{};
+}
+
+#if defined(AUDIO_DEVICE_API_VERSION_3_0)
+static inline size_t GceAudioFrameSize(const audio_stream_in* s) {
+ return audio_stream_in_frame_size(s);
+}
+#elif defined(AUDIO_DEVICE_API_VERSION_2_0)
+static inline size_t GceAudioFrameSize(const audio_stream_in* s) {
+
+ return audio_stream_frame_size(&s->common);
+}
+#else
+static inline size_t GceAudioFrameSize(audio_stream_in* s) {
+
+ return audio_stream_frame_size(&s->common);
+}
+#endif
+
+GceAudioInputStream::GceAudioInputStream(
+ cvd::GceAudio* dev, audio_devices_t devices, const audio_config& config)
+ : audio_stream_in(),
+ dev_(dev),
+ config_(config),
+ gain_(0.0),
+ device_(devices) {
+ common.get_sample_rate =
+ Thunker<uint32_t()>::call<&GceAudioInputStream::GetSampleRate>;
+ common.set_sample_rate =
+ Thunker<int(uint32_t)>::call<&GceAudioInputStream::SetSampleRate>;
+ common.get_buffer_size =
+ Thunker<size_t()>::call<&GceAudioInputStream::GetBufferSize>;
+ common.get_channels =
+ Thunker<audio_channel_mask_t()>::call<&GceAudioInputStream::GetChannels>;
+ common.get_device =
+ Thunker<audio_devices_t()>::call<&GceAudioInputStream::GetDevice>;
+ common.set_device =
+ Thunker<int(audio_devices_t)>::call<&GceAudioInputStream::SetDevice>;
+ common.get_format =
+ Thunker<audio_format_t()>::call<&GceAudioInputStream::GetFormat>;
+ common.set_format =
+ Thunker<int(audio_format_t)>::call<&GceAudioInputStream::SetFormat>;
+ common.standby =
+ Thunker<int()>::call<&GceAudioInputStream::Standby>;
+ common.dump =
+ Thunker<int(int)>::call<&GceAudioInputStream::Dump>;
+ common.set_parameters = GceAudio::SetStreamParameters;
+ common.get_parameters =
+ Thunker<char*(const char *)>::call<&GceAudioInputStream::GetParameters>;
+ common.add_audio_effect =
+ Thunker<int(effect_handle_t)>::call<&GceAudioInputStream::AddAudioEffect>;
+ common.remove_audio_effect = Thunker<int(effect_handle_t)>::call<
+ &GceAudioInputStream::RemoveAudioEffect>;
+ set_gain = InThunker<int(float)>::call<&GceAudioInputStream::SetGain>;
+ read = InThunker<ssize_t(void*, size_t)>::call<
+ &GceAudioInputStream::Read>;
+ get_input_frames_lost = InThunker<uint32_t()>::call<
+ &GceAudioInputStream::GetInputFramesLost>;
+ frame_size_ = GceAudioFrameSize(this);
+ buffer_model_.reset(
+ new SimulatedInputBuffer(config_.sample_rate, GetBufferSize() /
+ frame_size_));
+ reported_lost_frames_ = 0;
+}
+
+gce_audio_message GceAudioInputStream::GetStreamDescriptor(
+ uint32_t stream_number, gce_audio_message::message_t event) {
+ gce_audio_message rval;
+ rval.message_type = event;
+ rval.stream_number = stream_number;
+ rval.frame_num = buffer_model_->GetCurrentItemNum();
+ rval.time_presented =
+ buffer_model_->GetLastUpdatedTime().SinceEpoch().GetTS();
+ rval.frame_rate = config_.sample_rate;
+ rval.channel_mask = config_.channel_mask;
+ rval.format = config_.format;
+ rval.frame_size = frame_size_;
+ return rval;
+}
+
+int GceAudioInputStream::Open(GceAudio* dev,
+ audio_io_handle_t /*handle*/,
+ audio_devices_t devices,
+ const audio_config& config,
+ GceAudioInputStream** stream_in) {
+ D("GceAudioInputStream::%s", __FUNCTION__);
+ *stream_in = new GceAudioInputStream(dev, devices, config);
+ return 0;
+}
+
+int GceAudioInputStream::SetFormat(audio_format_t format) {
+ config_.format = format;
+ frame_size_ = GceAudioFrameSize(this);
+ return 0;
+}
+
+int GceAudioInputStream::Dump(int fd) const {
+ D("GceAudioInputStream::%s", __FUNCTION__);
+ VSOC_FDPRINTF(
+ fd,
+ "\tInputSteam Dump:\n"
+ "\t\tsample rate: %u\n"
+ "\t\tbuffer size: %zu\n"
+ "\t\tchannel mask: %08x\n"
+ "\t\tformat: %d\n"
+ "\t\tdevice: %08x\n"
+ "\t\taudio dev: %p\n\n",
+ GetSampleRate(), GetBufferSize(),
+ GetChannels(), GetFormat(), device_, dev_);
+ return 0;
+}
+
+int GceAudioInputStream::SetSampleRate(uint32_t sample_rate) {
+ if (sample_rate != config_.sample_rate) {
+ config_.sample_rate = sample_rate;
+ buffer_model_.reset(
+ new SimulatedInputBuffer(sample_rate, GetBufferSize() / frame_size_));
+ reported_lost_frames_ = 0;
+ }
+ return 0;
+}
+
+char* GceAudioInputStream::GetParameters(const char* keys) const {
+ D("GceAudioInputStream::%s", __FUNCTION__);
+ if (keys) D("GceAudioInputStream::%s keys %s", __FUNCTION__, keys);
+
+ str_parms* query = str_parms_create_str(keys);
+ str_parms* reply = str_parms_create();
+
+ char value[256];
+ int ret = str_parms_get_str(query, AUDIO_PARAMETER_STREAM_ROUTING,
+ value, sizeof(value));
+ char* str;
+ if (ret >= 0) {
+ str_parms_add_int(reply, AUDIO_PARAMETER_STREAM_ROUTING, device_);
+ str = strdup(str_parms_to_str(reply));
+ } else {
+ str = strdup(keys);
+ }
+ str_parms_destroy(query);
+ str_parms_destroy(reply);
+ return str;
+}
+
+
+ssize_t GceAudioInputStream::Read(void* buffer, size_t bytes) {
+ int64_t available = buffer_model_->RemoveFromInputBuffer(
+ bytes / frame_size_, false) * frame_size_;
+ ssize_t rval = available;
+ if ((rval != available) || (rval < 0)) {
+ ALOGE("GceAudioInputStream:%s got bad value from "
+ "RemoveFromInputBuffer %" PRId64, __FUNCTION__, available);
+ return -1;
+ }
+ memset(buffer, 0, rval);
+ return rval;
+}
+
+}
diff --git a/guest/commands/audio/vsoc_audio_input_stream.h b/guest/commands/audio/vsoc_audio_input_stream.h
new file mode 100644
index 0000000..1f8cb4d
--- /dev/null
+++ b/guest/commands/audio/vsoc_audio_input_stream.h
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#pragma once
+
+#include <memory>
+
+#include "guest/commands/audio/audio_hal.h"
+#include "guest/commands/audio/simulated_buffer.h"
+#include "guest/commands/audio/vsoc_audio_message.h"
+
+namespace cvd {
+
+namespace {
+static const int IN_BUFFER_BYTES = 4096;
+}
+
+class GceAudio;
+
+// Defines static callback functions for generic_stream_in HAL interface.
+class GceAudioInputStream : public audio_stream_in {
+ public:
+ // These methods are internal to the GCE audio implementation.
+ // Factory for new input streams.
+ static int Open(
+ GceAudio* dev, audio_io_handle_t handle,
+ audio_devices_t devices, const audio_config& config,
+ GceAudioInputStream** stream_in);
+
+ // Gets a description of this stream
+ gce_audio_message GetStreamDescriptor(
+ uint32_t stream_number, gce_audio_message::message_t event);
+
+ // audio_stream_in implementation. These definitions follow the ones
+ // in hardware/libhardware/include/hardware/audio.h
+
+ // Returns the sampling rate in Hz - eg. 44100.
+ uint32_t GetSampleRate() const { return config_.sample_rate; }
+
+ // Sets the sample rate
+ // no direct calls from JB and later, but called indirectly from
+ // GceAudio::SetStreamParamters when it finds
+ // AUDIO_PARAMETER_STREAM_SAMPLING_RATE
+ int SetSampleRate(uint32_t rate);
+
+ // Returns the size of input/output buffer in bytes for this stream - eg.
+ // 4800.
+ // It should be a multiple of the frame size. See also get_input_buffer_size
+ size_t GetBufferSize() const {
+ return IN_BUFFER_BYTES;
+ }
+
+ // Returns the channel mask -
+ // e.g. AUDIO_CHANNEL_OUT_STEREO or AUDIO_CHANNEL_IN_STEREO
+ audio_channel_mask_t GetChannels() const {
+ return config_.channel_mask;
+ }
+
+ // Returns the audio format - e.g. AUDIO_FORMAT_PCM_16_BIT
+ audio_format_t GetFormat() const {
+ return config_.format;
+ }
+
+ // Sets the audio format
+ // no direct calls from JB and later, but called indirectly from
+ // GceAudio::SetStreamParamters when it finds
+ // AUDIO_PARAMETER_STREAM_FORMAT
+ int SetFormat(audio_format_t format);
+
+ // Puts the audio hardware input/output into standby mode.
+ // Driver should exit from standby mode at the next I/O operation.
+ // Returns 0 on success and <0 on failure.
+ int Standby() { return 0; }
+
+ // Dumps the state of the audio input/output device
+ int Dump(int fd) const;
+
+ // Returns the set of device(s) which this stream is connected to
+ audio_devices_t GetDevice() const {
+ return device_;
+ }
+
+ // Sets the device this stream is connected to.
+ // no direct calls from JB and later, but called indirectly from
+ // GceAudio::SetStreamParamters when it finds
+ // AUDIO_PARAMETER_STREAM_ROUTING for both input and output.
+ // AUDIO_PARAMETER_STREAM_INPUT_SOURCE is an additional information used by
+ // input streams only.
+ int SetDevice(audio_devices_t device) { device_ = device; return 0; }
+
+ // sets audio stream parameters. The function accepts a list of
+ // parameter key value pairs in the form: key1=value1;key2=value2;...
+ //
+ // Some keys are reserved for standard parameters (See AudioParameter class)
+ //
+ // If the implementation does not accept a parameter change while
+ // the output is active but the parameter is acceptable otherwise, it must
+ // return -ENOSYS.
+ // The audio flinger will put the stream in standby and then change the
+ // parameter value.
+ // Uses GceAudio::SetStreamParameters
+
+ // Returns a pointer to a heap allocated string. The caller is responsible
+ // for freeing the memory for it using free().
+ char* GetParameters(const char* keys) const;
+
+ int AddAudioEffect(effect_handle_t /*effect*/) const {
+ return 0;
+ }
+
+ int RemoveAudioEffect(effect_handle_t /*effect*/) const {
+ return 0;
+ }
+
+ // Input stream specific methods
+
+ // Sets the input gain for the audio driver. This method is for
+ // for future use as of M.
+ int SetGain(float gain) {
+ gain_ = gain;
+ return 0;
+ }
+
+ // Reads audio buffer in from audio driver. Returns number of bytes read, or
+ // a negative android::status_t. If at least one frame was read prior to the error,
+ // read should return that byte count and then return an error in the
+ // subsequent call.
+ ssize_t Read(void* buffer, size_t bytes);
+
+ // Return the amount of input frames lost in the audio driver since the
+ // last call of this function.
+ // Audio driver is expected to reset the value to 0 and restart counting
+ // upon returning the current value by this function call.
+ // Such loss typically occurs when the user space process is blocked
+ // longer than the capacity of audio driver buffers.
+ //
+ // Unit: the number of input audio frames
+ uint32_t GetInputFramesLost() {
+ int64_t cur_lost_frames = buffer_model_->GetLostInputItems();
+ uint32_t rval = cur_lost_frames - reported_lost_frames_;
+ reported_lost_frames_ = cur_lost_frames;
+ return rval;
+ }
+
+ private:
+ GceAudioInputStream(cvd::GceAudio* dev, audio_devices_t devices,
+ const audio_config& config);
+ std::unique_ptr<SimulatedInputBuffer> buffer_model_;
+ cvd::GceAudio *dev_;
+ audio_config config_;
+ float gain_;
+ audio_devices_t device_;
+ size_t frame_size_;
+ int64_t reported_lost_frames_;
+};
+
+}
diff --git a/guest/commands/audio/vsoc_audio_message.cpp b/guest/commands/audio/vsoc_audio_message.cpp
new file mode 100644
index 0000000..30ef532
--- /dev/null
+++ b/guest/commands/audio/vsoc_audio_message.cpp
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#include "guest/commands/audio/vsoc_audio_message.h"
+
+const char* gce_audio_message::kAudioHALSocketName =
+ "/var/run/media/audio_hal_socket";
diff --git a/guest/commands/audio/vsoc_audio_message.h b/guest/commands/audio/vsoc_audio_message.h
new file mode 100644
index 0000000..4e02f91
--- /dev/null
+++ b/guest/commands/audio/vsoc_audio_message.h
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#pragma once
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <time.h>
+#include <system/audio.h>
+
+struct gce_audio_message {
+ static const char* kAudioHALSocketName;
+ static const size_t kMaxAudioFrameLen = 65536;
+ enum message_t {
+ UNKNOWN = 0,
+ DATA_SAMPLES = 1,
+ OPEN_INPUT_STREAM = 2,
+ OPEN_OUTPUT_STREAM = 3,
+ CLOSE_INPUT_STREAM = 4,
+ CLOSE_OUTPUT_STREAM = 5,
+ CONTROL_PAUSE = 100
+ };
+ // Size of the header + data. Used to frame when we're on TCP.
+ size_t total_size;
+ // Size of the audio header
+ size_t header_size;
+ message_t message_type;
+ // Identifier for the stream.
+ uint32_t stream_number;
+ // HAL assigned frame number, starts from 0.
+ int64_t frame_num;
+ // MONOTONIC_TIME when these frames were presented to the HAL.
+ timespec time_presented;
+ // Sample rate from the audio configuration.
+ uint32_t frame_rate;
+ // Channel mask from the audio configuration.
+ audio_channel_mask_t channel_mask;
+ // Format from the audio configuration.
+ audio_format_t format;
+ // Size of each frame in bytes.
+ size_t frame_size;
+ // Number of frames that were presented to the HAL.
+ size_t num_frames_presented;
+ // Number of frames that the HAL accepted.
+ // For blocking audio this will be the same as num_frames.
+ // For non-blocking audio this may be less.
+ size_t num_frames_accepted;
+ // Count of the number of packets that were dropped because they would
+ // have blocked the HAL or exceeded the maximum message size.
+ size_t num_packets_dropped;
+ // Count of the number of packets that were shortened to fit within
+ // kMaxAudioFrameLen.
+ size_t num_packets_shortened;
+ // num_frames_presented (not num_frames_accepted) will follow here.
+
+ gce_audio_message() :
+ total_size(sizeof(gce_audio_message)),
+ header_size(sizeof(gce_audio_message)),
+ message_type(UNKNOWN),
+ stream_number(0),
+ frame_num(0),
+ frame_rate(0),
+ channel_mask(0),
+ format(AUDIO_FORMAT_DEFAULT),
+ frame_size(0),
+ num_frames_presented(0),
+ num_frames_accepted(0),
+ num_packets_dropped(0),
+ num_packets_shortened(0) {
+ time_presented.tv_sec = 0;
+ time_presented.tv_nsec = 0;
+ }
+};
diff --git a/guest/commands/audio/vsoc_audio_output_stream.cpp b/guest/commands/audio/vsoc_audio_output_stream.cpp
new file mode 100644
index 0000000..f32a676
--- /dev/null
+++ b/guest/commands/audio/vsoc_audio_output_stream.cpp
@@ -0,0 +1,349 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#include "guest/commands/audio/vsoc_audio_message.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <memory>
+
+#include <cutils/sockets.h>
+extern "C"{
+#include <cutils/str_parms.h>
+}
+
+#include "common/libs/auto_resources/auto_resources.h"
+#include "common/libs/threads/thunkers.h"
+#include "common/libs/time/monotonic_time.h"
+#include "guest/commands/audio/audio_hal.h"
+#include "guest/commands/audio/vsoc_audio.h"
+#include "guest/commands/audio/vsoc_audio_output_stream.h"
+#include "guest/libs/platform_support/api_level_fixes.h"
+#include "guest/libs/remoter/remoter_framework_pkt.h"
+
+#if defined(AUDIO_DEVICE_API_VERSION_3_0)
+static inline size_t GceAudioFrameSize(const audio_stream_out* s) {
+ return audio_stream_out_frame_size(s);
+}
+#elif defined(AUDIO_DEVICE_API_VERSION_2_0)
+static inline size_t GceAudioFrameSize(const audio_stream_out* s) {
+
+ return audio_stream_frame_size(&s->common);
+}
+#else
+static inline size_t GceAudioFrameSize(audio_stream_out* s) {
+
+ return audio_stream_frame_size(&s->common);
+}
+#endif
+
+namespace cvd {
+
+const size_t GceAudioOutputStream::kOutBufferSize;
+const size_t GceAudioOutputStream::kOutLatency;
+
+namespace {
+template <typename F> struct Thunker :
+ ThunkerBase<audio_stream, GceAudioOutputStream, F>{};
+
+template <typename F> struct OutThunker :
+ ThunkerBase<audio_stream_out, GceAudioOutputStream, F>{};
+}
+
+GceAudioOutputStream::GceAudioOutputStream(GceAudio* dev) :
+ audio_stream_out(),
+ dev_(dev),
+ device_(AUDIO_DEVICE_OUT_DEFAULT),
+ frame_count_(0),
+ left_volume_(0.0),
+ right_volume_(0.0) { }
+
+int GceAudioOutputStream::Dump(int fd) const {
+ D("GceAudioOutputStream::%s", __FUNCTION__);
+ VSOC_FDPRINTF(
+ fd,
+ "\tout_dump:\n"
+ "\t\tsample rate: %u\n"
+ "\t\tbuffer size: %zu\n"
+ "\t\tchannel mask: %08x\n"
+ "\t\tformat: %d\n"
+ "\t\tdevice: %08x\n"
+ "\t\taudio dev: %p\n\n",
+ GetSampleRate(),
+ GetBufferSize(),
+ GetChannels(),
+ GetFormat(),
+ device_,
+ dev_);
+ return 0;
+}
+
+int GceAudioOutputStream::GetNextWriteTimestamp(int64_t* nstime) const {
+ *nstime = cvd::time::Nanoseconds(
+ buffer_->GetNextOutputBufferItemTime().SinceEpoch()).count();
+ return 0;
+}
+
+namespace {
+struct StrParmsDestroyer {
+ void operator()(str_parms* parms) const {
+ if (parms) {
+ str_parms_destroy(parms);
+ }
+ }
+};
+
+typedef std::unique_ptr<str_parms, StrParmsDestroyer> StrParmsPtr;
+}
+
+int GceAudioOutputStream::SetParameters(const char* kv_pairs) {
+ int err = 0;
+ StrParmsPtr parms(str_parms_create_str(kv_pairs));
+ {
+ int fmt = 0;
+ if (str_parms_get_int(parms.get(), AUDIO_PARAMETER_STREAM_FORMAT, &fmt)
+ == 0) {
+ SetFormat(static_cast<audio_format_t>(fmt));
+ }
+ }
+ {
+ int sample_rate = 0;
+ if (str_parms_get_int(parms.get(), AUDIO_PARAMETER_STREAM_SAMPLING_RATE,
+ &sample_rate) == 0) {
+ SetSampleRate(static_cast<uint32_t>(sample_rate));
+ }
+ }
+ {
+ int routing = 0;
+ if (str_parms_get_int(parms.get(), AUDIO_PARAMETER_STREAM_ROUTING,
+ &routing) == 0) {
+ device_ = static_cast<uint32_t>(routing);
+ }
+ }
+ {
+ int channels = 0;
+ if (str_parms_get_int(parms.get(), AUDIO_PARAMETER_STREAM_CHANNELS,
+ &channels) == 0) {
+ message_header_.channel_mask = static_cast<audio_channel_mask_t>(channels);
+ }
+ }
+ {
+ int frame_count = 0;
+ if (str_parms_get_int(parms.get(), AUDIO_PARAMETER_STREAM_FRAME_COUNT,
+ &frame_count) == 0) {
+ frame_count_ = static_cast<size_t>(frame_count);
+ }
+ }
+ {
+ int input_source = 0;
+ if (str_parms_get_int(parms.get(), AUDIO_PARAMETER_STREAM_INPUT_SOURCE,
+ &input_source) == 0){
+ ALOGE("GceAudioOutputStream::%s AUDIO_PARAMETER_STREAM_INPUT_SOURCE"
+ " passed to an output stream", __FUNCTION__);
+ err = -EINVAL;
+ }
+ }
+ return err;
+}
+
+void GceAudioOutputStream::AddIntIfKeyPresent(
+ /*const */ str_parms* query, str_parms* reply, const char* key, int value) {
+ if (str_parms_get_str(query, key, NULL, 0) >= 0) {
+ str_parms_add_int(reply, key, value);
+ }
+}
+
+
+char* GceAudioOutputStream::GetParameters(const char* keys) const {
+ D("GceAudioOutputStream::%s", __FUNCTION__);
+ if (keys) D("%s keys %s", __FUNCTION__, keys);
+
+ StrParmsPtr query(str_parms_create_str(keys));
+ StrParmsPtr reply(str_parms_create());
+
+ AddIntIfKeyPresent(query.get(), reply.get(),
+ AUDIO_PARAMETER_STREAM_FORMAT,
+ static_cast<int>(GetFormat()));
+ AddIntIfKeyPresent(query.get(), reply.get(),
+ AUDIO_PARAMETER_STREAM_SAMPLING_RATE,
+ static_cast<int>(GetSampleRate()));
+ AddIntIfKeyPresent(query.get(), reply.get(),
+ AUDIO_PARAMETER_STREAM_ROUTING,
+ static_cast<int>(device_));
+ AddIntIfKeyPresent(query.get(), reply.get(),
+ AUDIO_PARAMETER_STREAM_CHANNELS,
+ static_cast<int>(message_header_.channel_mask));
+ AddIntIfKeyPresent(query.get(), reply.get(),
+ AUDIO_PARAMETER_STREAM_FRAME_COUNT,
+ static_cast<int>(frame_count_));
+
+ char *str = str_parms_to_str(reply.get());
+ return str;
+}
+
+int GceAudioOutputStream::GetRenderPosition(uint32_t* dsp_frames) const {
+ *dsp_frames = buffer_->GetCurrentItemNum();
+ return 0;
+}
+
+ssize_t GceAudioOutputStream::Write(const void* buffer, size_t length) {
+ // We're always the blocking case for now.
+ static const bool blocking = true;
+ message_header_.frame_size = frame_size_;
+ frame_count_ += message_header_.num_frames_presented = length / frame_size_;
+ message_header_.message_type = gce_audio_message::DATA_SAMPLES;
+ // First do a nonblocking add
+ int64_t frames_accepted_without_blocking = buffer_->AddToOutputBuffer(
+ message_header_.num_frames_presented, false);
+ // This seems backward, but adding the items to the buffer first
+ // allows us to calculate the right frame number in the case of underflow.
+ message_header_.frame_num =
+ buffer_->GetNextOutputBufferItemNum() - frames_accepted_without_blocking;
+ message_header_.time_presented =
+ buffer_->GetLastUpdatedTime().SinceEpoch().GetTS();
+ // We want to send the message before blocking. If we're in blocking mode
+ // we will accept all of the frames.
+ if (blocking) {
+ message_header_.num_frames_accepted =
+ message_header_.num_frames_presented;
+ } else {
+ message_header_.num_frames_accepted = frames_accepted_without_blocking;
+ }
+ // Never exceed the maximum packet size, as defined by the interface.
+ // Clip off any frames that we can't transmit and increment the clipped
+ // count.
+ size_t transmitted_frame_size = length;
+ if (length > gce_audio_message::kMaxAudioFrameLen) {
+ transmitted_frame_size = gce_audio_message::kMaxAudioFrameLen;
+ message_header_.num_packets_shortened++;
+ }
+ message_header_.total_size =
+ sizeof(message_header_) + transmitted_frame_size;
+ // Now send the message. Do not block if the receiver isn't ready
+ // If this is a blocking write we will block after we have attempted to
+ // send the data to the receiver.
+ msghdr msg;
+ iovec msg_iov[2];
+ // We need a cast here because iov_base is defined non-const to support
+ // recvmsg et.al.
+ // There is no danger here:sendmsg does not write to the buffer.
+ msg_iov[0].iov_base = &message_header_;
+ msg_iov[0].iov_len = sizeof(message_header_);
+ msg_iov[1].iov_base = const_cast<void*>(buffer);
+ msg_iov[1].iov_len = transmitted_frame_size;
+ msg.msg_name = NULL;
+ msg.msg_namelen = 0;
+ msg.msg_iov = msg_iov;
+ msg.msg_iovlen = arraysize(msg_iov);
+ msg.msg_control = NULL;
+ msg.msg_controllen = 0;
+ msg.msg_flags = 0;
+ if (dev_->SendMsg(msg, MSG_DONTWAIT) < 0) {
+ message_header_.num_packets_dropped++;
+ }
+ if (!blocking) {
+ return frames_accepted_without_blocking * frame_size_;
+ }
+ if ((message_header_.num_frames_presented) >
+ static_cast<size_t>(frames_accepted_without_blocking)) {
+ buffer_->AddToOutputBuffer(
+ message_header_.num_frames_presented -
+ frames_accepted_without_blocking, true);
+ }
+ return message_header_.num_frames_presented * frame_size_;
+}
+
+int GceAudioOutputStream::Open(
+ GceAudio* dev, audio_io_handle_t /*handle*/,
+ audio_devices_t devices, audio_output_flags_t /*flags*/,
+ audio_config* config, uint32_t stream_number,
+ GceAudioOutputStream** stream_out) {
+ D("GceAudioOutputStream::%s", __FUNCTION__);
+ *stream_out = NULL;
+ // Deleted by Close(); UniquePtr holds until end of Open().
+ std::unique_ptr<GceAudioOutputStream> out(
+ new GceAudioOutputStream(dev));
+ out->message_header_.stream_number = stream_number;
+ out->message_header_.format = config->format;
+ out->message_header_.channel_mask = config->channel_mask;
+ out->message_header_.frame_rate = config->sample_rate;
+ out->frame_count_ =
+#if VSOC_PLATFORM_SDK_AFTER(K)
+ config->frame_count;
+#else
+ 0;
+#endif
+ out->common.get_sample_rate =
+ Thunker<uint32_t()>::call<&GceAudioOutputStream::GetSampleRate>;
+ out->common.set_sample_rate =
+ Thunker<int(uint32_t)>::call<&GceAudioOutputStream::SetSampleRate>;
+ out->common.get_buffer_size =
+ Thunker<size_t()>::call<&GceAudioOutputStream::GetBufferSize>;
+ out->common.get_channels =
+ Thunker<audio_channel_mask_t()>::call<
+ &GceAudioOutputStream::GetChannels>;
+ out->common.get_format = Thunker<audio_format_t()>::call<
+ &GceAudioOutputStream::GetFormat>;
+ out->common.set_format = Thunker<int(audio_format_t)>::call<
+ &GceAudioOutputStream::SetFormat>;
+ out->common.standby = Thunker<int()>::call<&GceAudioOutputStream::Standby>;
+ out->common.dump = Thunker<int(int)>::call<&GceAudioOutputStream::Dump>;
+ out->common.get_device = Thunker<audio_devices_t()>::call<
+ &GceAudioOutputStream::GetDevice>;
+ out->common.set_device = Thunker<int(audio_devices_t)>::call<
+ &GceAudioOutputStream::SetDevice>;
+ out->common.set_parameters =
+ Thunker<int(const char*)>::call<
+ &GceAudioOutputStream::SetParameters>;
+ out->common.get_parameters =
+ Thunker<char*(const char *)>::call<
+ &GceAudioOutputStream::GetParameters>;
+ out->common.add_audio_effect =
+ Thunker<int(effect_handle_t)>::call<
+ &GceAudioOutputStream::AddAudioEffect>;
+ out->common.remove_audio_effect =
+ Thunker<int(effect_handle_t)>::call<
+ &GceAudioOutputStream::RemoveAudioEffect>;
+ out->get_latency =
+ OutThunker<uint32_t()>::call<
+ &GceAudioOutputStream::GetLatency>;
+ out->set_volume =
+ OutThunker<int(float, float)>::call<&GceAudioOutputStream::SetVolume>;
+ out->write =
+ OutThunker<ssize_t(const void*, size_t)>::call<
+ &GceAudioOutputStream::Write>;
+ out->get_render_position =
+ OutThunker<int(uint32_t*)>::call<
+ &GceAudioOutputStream::GetRenderPosition>;
+ out->get_next_write_timestamp =
+ OutThunker<int(int64_t*)>::call<
+ &GceAudioOutputStream::GetNextWriteTimestamp>;
+ out->device_ = devices;
+ out->frame_size_ = GceAudioFrameSize(out.get());
+
+ int64_t item_capacity =
+ out->frame_size_ == 0 ? 0 : out->GetBufferSize() / out->frame_size_;
+ if (item_capacity == 0) {
+ ALOGE("Attempt to create GceAudioOutputStream with frame_size_ of 0.");
+ return -EINVAL;
+ }
+ out->buffer_.reset(
+ new SimulatedOutputBuffer(
+ config->sample_rate, item_capacity));
+ *stream_out = out.release();
+ return 0;
+}
+
+} // namespace cvd
diff --git a/guest/commands/audio/vsoc_audio_output_stream.h b/guest/commands/audio/vsoc_audio_output_stream.h
new file mode 100644
index 0000000..ecd609f
--- /dev/null
+++ b/guest/commands/audio/vsoc_audio_output_stream.h
@@ -0,0 +1,313 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#pragma once
+
+#include <memory>
+
+#include "guest/commands/audio/audio_hal.h"
+#include "guest/commands/audio/simulated_buffer.h"
+#include "guest/commands/audio/vsoc_audio_message.h"
+
+namespace cvd {
+
+// Defines static callback functions for the audio_stream and audio_stream_out
+// interfaces in libhardware/include/hardware/audio.h
+//
+// Where the is a conflict the comments there apply.
+// By default these methods return 0 on success -<errno> for failure.
+class GceAudioOutputStream : public audio_stream_out {
+ public:
+ // Factory method for a new output stream.
+ static int Open(GceAudio* dev, audio_io_handle_t handle,
+ audio_devices_t devices, audio_output_flags_t flags,
+ audio_config* config, uint32_t stream_number,
+ GceAudioOutputStream** stream_out);
+
+ gce_audio_message GetStreamDescriptor(
+ gce_audio_message::message_t message_type) const {
+ gce_audio_message rval = message_header_;
+ rval.total_size = sizeof(rval);
+ rval.header_size = sizeof(rval);
+ rval.message_type = message_type;
+ rval.num_frames_presented = 0;
+ rval.num_frames_accepted = 0;
+ return rval;
+ }
+
+ // Method from audio_stream, listed in order of appearance.
+ // TODO(ghartman): Consider moving these if they could be shared with
+ // gce_audio_input_stream.
+
+
+ // Returns the sampling rate in Hz - eg. 44100.
+ uint32_t GetSampleRate() const {
+ return message_header_.frame_rate;
+ }
+
+ // Sets the sample rate
+ // AUDIO_PARAMETER_STREAM_SAMPLING_RATE
+ int SetSampleRate(uint32_t sample_rate) {
+ if (sample_rate != message_header_.frame_rate) {
+ message_header_.frame_rate = sample_rate;
+ // TODO(ghartman): The output buffer should be quantized at about 192
+ // bytes for better fidelity. Do this by passing
+ // frame_rate * frame_size / 192 and then rescaling the outputs.
+ // Or we could always create a quantized wrapper of the buffer...
+ buffer_.reset(
+ new SimulatedOutputBuffer(
+ sample_rate, GetBufferSize() / frame_size_));
+ }
+ return 0;
+ }
+
+ // Returns the size of input/output buffer in bytes for this stream.
+ // eg. 4800.
+ // It should be a multiple of the frame size. See also GetInputBufferSize.
+ size_t GetBufferSize() const {
+ return kOutBufferSize;
+ }
+
+ // Returns the channel mask -
+ // e.g. AUDIO_CHANNEL_OUT_STEREO or AUDIO_CHANNEL_IN_STEREO
+ audio_channel_mask_t GetChannels() const {
+ return message_header_.channel_mask;
+ }
+
+ // Returns the audio format - e.g. AUDIO_FORMAT_PCM_16_BIT
+ audio_format_t GetFormat() const {
+ return message_header_.format;
+ }
+
+ // Sets the audio format.
+ // Unused as of JB - use set_parameters with key
+ // AUDIO_PARAMETER_STREAM_FORMAT
+ int SetFormat(audio_format_t format) {
+ message_header_.format = format;
+ return 0;
+ }
+
+ // Puts the audio hardware input/output into standby mode.
+ // Driver should exit from standby mode at the next I/O operation.
+ // Returns 0 on success and <0 on failure.
+ // TODO(ghartman): This should reset some of the frame counts.
+ int Standby() {
+ return 0;
+ }
+
+ // dumps the state of the audio hardware to the given fd.
+ // This information can be retrieved using the dumpsys utility.
+ int Dump(int fd) const;
+
+ // Returns the set of device(s) which this stream is connected to.
+ // TODO(ghartman): Implement this.
+ audio_devices_t GetDevice() const { return device_; }
+
+ // Not directly called from JB forward.
+ // Called indirectly from SetParameters with the key
+ // AUDIO_PARAMETER_STREAM_ROUTING
+ int SetDevice(audio_devices_t device) { device_ = device; return 0; }
+
+ // Sets audio stream parameters. The function accepts a list of
+ // parameter key value pairs in the form: key1=value1;key2=value2;...
+ //
+ // Some keys are reserved for standard parameters (See AudioParameter class)
+ //
+ // If the implementation does not accept a parameter change while
+ // the output is active but the parameter is acceptable otherwise, it must
+ // return -ENOSYS.
+ //
+ // The audio flinger will put the stream in standby and then change the
+ // parameter value.
+ int SetParameters(const char* kv_pairs);
+
+ // Gets audio stream parameters. The function accepts a list of
+ // keys in the form: key1=value1;key2=value2;...
+ //
+ // Returns a pointer to a heap allocated string. The caller is responsible
+ // for freeing the memory for it using free().
+ // TODO(ghartman): Implement this.
+ char* GetParameters(const char* keys) const;
+
+ // TODO(ghartman): Implement this.
+ int AddAudioEffect(effect_handle_t /*effect*/) const {
+ static unsigned int printed = 0; // printed every 2^32-th call.
+ ALOGE_IF(!printed++, "%s: not implemented", __FUNCTION__);
+ return 0;
+ }
+
+ // TODO(ghartman): Implement this.
+ int RemoveAudioEffect(effect_handle_t /*effect*/) const {
+ static unsigned int printed = 0; // printed every 2^32-th call.
+ ALOGE_IF(!printed++, "%s: not implemented", __FUNCTION__);
+ return 0;
+ }
+
+ // Methods defined in audio_stream_out
+
+ // Returns the audio hardware driver estimated latency in milliseconds.
+ // TODO(ghartman): Calculate this based on the format and the quantum.
+ uint32_t GetLatency() const {
+ return kOutLatency;
+ }
+
+ // Use this method in situations where audio mixing is done in the
+ // hardware. This method serves as a direct interface with hardware,
+ // allowing you to directly set the volume as apposed to via the framework.
+ // This method might produce multiple PCM outputs or hardware accelerated
+ // codecs, such as MP3 or AAC.
+ //
+ // Note that GCE simulates hardware mixing.
+ int SetVolume(float left_volume, float right_volume) {
+ left_volume_ = left_volume;
+ right_volume_ = right_volume;
+ return 0;
+ }
+
+ // Write audio buffer to driver. Returns number of bytes written, or a
+ // negative android::status_t. If at least one frame was written successfully prior
+ // to the error the driver will return that successful (short) byte count
+ // and then return an error in the subsequent call.
+ //
+ // If SetCallback() has previously been called to enable non-blocking mode
+ // the Write() is not allowed to block. It must write only the number of
+ // bytes that currently fit in the driver/hardware buffer and then return
+ // this byte count. If this is less than the requested write size the
+ // callback function must be called when more space is available in the
+ // driver/hardware buffer.
+ ssize_t Write(const void* buffer, size_t bytes);
+
+ // Returns the number of audio frames written by the audio dsp to DAC since
+ // the output has exited standby
+ // TODO(ghartman): Implement zeroing this in Standby().
+ int GetRenderPosition(uint32_t* dsp_frames) const;
+
+ // Gets the local time at which the next write to the audio driver will be
+ // presented. The units are microseconds, where the epoch is decided by the
+ // local audio HAL.
+ //
+ // The GCE implementation uses CLOCK_MONOTONIC, which also happens to line
+ // up with LocalTime.
+ int GetNextWriteTimestamp(int64_t*) const;
+
+ // Turns on non-blocking mode and sets the callback function for notifying
+ // completion of non-blocking write and drain.
+ // Calling this function implies that all future Write() and Drain()
+ // must be non-blocking and use the callback to signal completion.
+ //
+ // TODO(ghartman): Implement this URGENTLY.
+ //
+ // int SetCallback(stream_callback_t callback, void *cookie);
+
+ // Notifies to the audio driver to stop playback however the queued buffers
+ // are retained by the hardware. Useful for implementing pause/resume. Empty
+ // implementation if not supported however should be implemented for hardware
+ // with non-trivial latency. In the pause state audio hardware could still be
+ // using power. User may consider calling suspend after a timeout.
+ //
+ // Implementation of this function is mandatory for offloaded playback.
+ //
+ // TODO(ghartman): Implement this URGENTLY. There is already support in
+ // SimulatedBuffer.
+ // int Pause();
+
+ // Notifies to the audio driver to resume playback following a pause.
+ // Returns error if called without matching pause.
+ //
+ // Implementation of this function is mandatory for offloaded playback.
+ //
+ // TODO(ghartman): Implement this URGENTLY.
+ //
+ // int Resume();
+
+ // Requests notification when data buffered by the driver/hardware has
+ // been played. If set_callback() has previously been called to enable
+ // non-blocking mode, the drain() must not block, instead it should return
+ // quickly and completion of the drain is notified through the callback.
+ // If set_callback() has not been called, the drain() must block until
+ // completion.
+ //
+ // If type==AUDIO_DRAIN_ALL, the drain completes when all previously written
+ // data has been played.
+ //
+ // If type==AUDIO_DRAIN_EARLY_NOTIFY, the drain completes shortly before all
+ // data for the current track has played to allow time for the framework
+ // to perform a gapless track switch.
+ //
+ // Drain must return immediately on stop() and flush() call
+ //
+ // Implementation of this function is mandatory for offloaded playback.
+ //
+ // TODO(ghartman): Implement this URGENTLY.
+ //
+ // int Drain(audio_drain_type_t type);
+
+ // Notifies to the audio driver to flush the queued data. Stream must already
+ // be paused before calling Flush().
+ //
+ // Implementation of this function is mandatory for offloaded playback.
+ //
+ // TODO(ghartman): Implement this URGENTLY.
+ //
+ // int Flush();
+
+ // Returns a recent count of the number of audio frames presented to an
+ // external observer. This excludes frames which have been written but are
+ // still in the pipeline.
+ //
+ // The count is not reset to zero when output enters standby.
+ // Also returns the value of CLOCK_MONOTONIC as of this presentation count.
+ // The returned count is expected to be 'recent',
+ // but does not need to be the most recent possible value.
+ // However, the associated time should correspond to whatever count is
+ // returned.
+ //
+ // Example: assume that N+M frames have been presented, where M is a
+ // 'small' number.
+ // Then it is permissible to return N instead of N+M,
+ // and the timestamp should correspond to N rather than N+M.
+ // The terms 'recent' and 'small' are not defined.
+ // They reflect the quality of the implementation.
+ //
+ // 3.0 and higher only.
+ //
+ // TODO(ghartman): Implement this URGENTLY.
+ //
+ // int GetPresentationPosition(uint64_t *frames, struct timespec *timestamp);
+
+ private:
+ // If key is present in query, add key=value; to reply.
+ // query should be pointer to const, but the str_parms functions aren't
+ // const-correct, so neither is this.
+ static void AddIntIfKeyPresent(
+ /*const*/ str_parms* query, str_parms* reply, const char* key, int value);
+
+
+ explicit GceAudioOutputStream(cvd::GceAudio*);
+
+ static const size_t kOutBufferSize = 3840;
+ static const size_t kOutLatency = 2;
+
+ gce_audio_message message_header_;
+ std::unique_ptr<SimulatedOutputBuffer> buffer_;
+ cvd::GceAudio *dev_;
+ audio_devices_t device_;
+ size_t frame_size_;
+ size_t frame_count_;
+ float left_volume_;
+ float right_volume_;
+};
+
+}
diff --git a/guest/commands/usbforward/Android.mk b/guest/commands/usbforward/Android.mk
new file mode 100644
index 0000000..db65f85
--- /dev/null
+++ b/guest/commands/usbforward/Android.mk
@@ -0,0 +1,40 @@
+# Copyright (C) 2017 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.
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+LOCAL_MODULE := usbforward
+LOCAL_MODULE_TAGS := optional
+LOCAL_SRC_FILES := \
+ main.cpp \
+ usb_server.cpp \
+ transport_request.cpp
+
+LOCAL_C_INCLUDES := \
+ device/google/cuttlefish_common \
+ device/google/cuttlefish_kernel
+
+LOCAL_SHARED_LIBRARIES := \
+ cuttlefish_auto_resources \
+ libcuttlefish_fs \
+ libusb \
+ libbase \
+ liblog
+
+LOCAL_CFLAGS += -DLOG_TAG=\"UsbForward\"
+
+LOCAL_MULTILIB := first
+LOCAL_VENDOR_MODULE := true
+include $(BUILD_EXECUTABLE)
+
diff --git a/guest/commands/usbforward/main.cpp b/guest/commands/usbforward/main.cpp
new file mode 100644
index 0000000..0ccf479
--- /dev/null
+++ b/guest/commands/usbforward/main.cpp
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#include <cutils/log.h>
+#include <stdio.h>
+#include <libusb/libusb.h>
+
+#include "common/libs/fs/shared_fd.h"
+#include "guest/commands/usbforward/usb_server.h"
+
+int main(int argc, char* argv[]) {
+ if (argc == 1) {
+ printf("Usage: %s <virtio_channel>\n", argv[0]);
+ return 1;
+ }
+
+ cvd::SharedFD fd = cvd::SharedFD::Open(argv[1], O_RDWR | O_NOCTTY);
+ if (!fd->IsOpen()) {
+ ALOGE("Could not open %s: %s", argv[1], fd->StrError());
+ return 1;
+ }
+
+ usb_forward::USBServer server(fd);
+ server.Serve();
+ ALOGE("Terminated.");
+
+ libusb_exit(nullptr);
+ return 1;
+}
diff --git a/guest/commands/usbforward/transport_request.cpp b/guest/commands/usbforward/transport_request.cpp
new file mode 100644
index 0000000..9f994e9
--- /dev/null
+++ b/guest/commands/usbforward/transport_request.cpp
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include "guest/commands/usbforward/transport_request.h"
+
+#include <cutils/log.h>
+
+namespace usb_forward {
+
+TransportRequest::TransportRequest(std::shared_ptr<libusb_device_handle> handle,
+ CallbackType callback,
+ const ControlTransfer& transfer)
+ : handle_{std::move(handle)},
+ callback_{std::move(callback)},
+ is_control_{true},
+ transfer_{libusb_alloc_transfer(0), libusb_free_transfer} {
+ // NOTE: libusb places setup structure as part of user data!
+ buffer_.reset(new uint8_t[transfer.length + LIBUSB_CONTROL_SETUP_SIZE]);
+
+ // NOTE: libusb places a structure of size LIBUSB_CONTROL_SETUP_SIZE directly
+ // in the data buffer.
+ libusb_fill_control_setup(buffer_.get(), transfer.type, transfer.cmd,
+ transfer.value, transfer.index, transfer.length);
+
+ // NOTE: despite libusb requires user to allocate buffer large enough to
+ // accommodate SETUP structure and actual data, it requires user to provide
+ // only data length here, while setup length is added internally.
+ libusb_fill_control_transfer(transfer_.get(), handle_.get(), buffer_.get(),
+ OnTransferComplete, this, transfer.timeout);
+}
+
+TransportRequest::TransportRequest(std::shared_ptr<libusb_device_handle> handle,
+ CallbackType callback,
+ const DataTransfer& transfer)
+ : handle_{std::move(handle)},
+ callback_{std::move(callback)},
+ is_control_{false},
+ transfer_{libusb_alloc_transfer(0), libusb_free_transfer} {
+ buffer_.reset(new uint8_t[transfer.length]);
+ libusb_fill_bulk_transfer(
+ transfer_.get(), handle_.get(),
+ transfer.endpoint_id | (transfer.is_host_to_device ? LIBUSB_ENDPOINT_OUT
+ : LIBUSB_ENDPOINT_IN),
+ buffer_.get(), transfer.length, OnTransferComplete, this,
+ transfer.timeout);
+}
+
+uint8_t* TransportRequest::Buffer() {
+ if (is_control_) {
+ return &buffer_[LIBUSB_CONTROL_SETUP_SIZE];
+ } else {
+ return buffer_.get();
+ }
+}
+
+bool TransportRequest::Submit() {
+ if (handle_) {
+ auto err = libusb_submit_transfer(transfer_.get());
+ if (err != 0) {
+ ALOGE("libusb transfer failed: %d", err);
+ }
+ return err == 0;
+ } else {
+ ALOGE("Initiated transfer, but device not opened.");
+ return false;
+ }
+}
+
+void TransportRequest::OnTransferComplete(libusb_transfer* req) {
+ auto treq = static_cast<TransportRequest*>(req->user_data);
+ treq->callback_(req->status == 0, treq->Buffer(), req->actual_length);
+}
+
+} // namespace usb_forward
diff --git a/guest/commands/usbforward/transport_request.h b/guest/commands/usbforward/transport_request.h
new file mode 100644
index 0000000..1f76bbe
--- /dev/null
+++ b/guest/commands/usbforward/transport_request.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#pragma once
+
+#include <functional>
+#include <memory>
+
+#include <stdint.h>
+#include <libusb/libusb.h>
+#include "common/libs/usbforward/protocol.h"
+
+namespace usb_forward {
+
+// TransportRequest represents a libusb asynchronous transport request.
+// This class encapsulates everything that is necessary to complete
+// transfer.
+class TransportRequest final {
+ public:
+ // CallbackType describes what kind of function can receive call when this
+ // asynchronous call is complete.
+ // Parameters passed to callback, in order:
+ // - success indicator (true = success),
+ // - buffer with data (in or out),
+ // - actual length transferred.
+ using CallbackType = std::function<void(bool, const uint8_t*, int32_t)>;
+
+ TransportRequest(std::shared_ptr<libusb_device_handle> device,
+ CallbackType callback, const ControlTransfer& transfer);
+ TransportRequest(std::shared_ptr<libusb_device_handle> device,
+ CallbackType callback, const DataTransfer& transfer);
+ ~TransportRequest() = default;
+
+ uint8_t* Buffer();
+
+ // Submit sends an asynchronous data exchange requests.
+ // Returns true only if operation was successful. At this point
+ // ownership of this structure is passed to libusb and user
+ // must not release the underlying structure.
+ bool Submit();
+
+ // Executes corresponding callback with execution results.
+ // This is a static call to ensure that the callback being invoked
+ // can dispose of this instance.
+ static void OnTransferComplete(libusb_transfer* req);
+
+ private:
+ std::shared_ptr<libusb_device_handle> handle_;
+ CallbackType callback_;
+ bool is_control_;
+ std::unique_ptr<libusb_transfer, void (*)(libusb_transfer*)> transfer_;
+ std::unique_ptr<uint8_t[]> buffer_;
+
+ TransportRequest(const TransportRequest& other) = delete;
+ TransportRequest& operator=(const TransportRequest& other) = delete;
+};
+
+} // namespace usb_forward
diff --git a/guest/commands/usbforward/usb_server.cpp b/guest/commands/usbforward/usb_server.cpp
new file mode 100644
index 0000000..119c77e
--- /dev/null
+++ b/guest/commands/usbforward/usb_server.cpp
@@ -0,0 +1,405 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+// #undef NDEBUG
+
+#include "guest/commands/usbforward/usb_server.h"
+
+#include <string>
+#include <vector>
+#include <strings.h>
+#include <cutils/log.h>
+#include <libusb/libusb.h>
+#include "common/libs/fs/shared_select.h"
+#include "common/libs/usbforward/protocol.h"
+#include "guest/commands/usbforward/transport_request.h"
+
+namespace usb_forward {
+namespace {
+// USBServer exports device kExportedVendorID:kExportedProductID to the server.
+// We will not support exporting multiple USB devices as there's no practical
+// need for this.
+constexpr uint16_t kExportedVendorID = 0x18d1;
+constexpr uint16_t kExportedProductID = 0x4ee7;
+
+// Use default BUS and DEVICE IDs so that it's easier to attach over USB/IP.
+constexpr uint8_t kDefaultBusID = 1;
+constexpr uint8_t kDefaultDevID = 1;
+
+std::shared_ptr<libusb_device_handle> GetDevice() {
+ std::shared_ptr<libusb_device_handle> res(
+ libusb_open_device_with_vid_pid(nullptr, kExportedVendorID,
+ kExportedProductID),
+ [](libusb_device_handle* h) {
+ // Apparently, deleter is called even on an uninitialized shared_ptr.
+ if (h != nullptr) {
+ libusb_release_interface(h, 0);
+ libusb_close(h);
+ }
+ });
+
+ if (res) libusb_claim_interface(res.get(), 0);
+
+ return res;
+}
+
+} // anonymous namespace
+
+bool USBServer::GetDeviceInfo(
+ DeviceInfo* info, std::vector<InterfaceInfo>* ifaces) {
+ if (!handle_) return false;
+
+ // This function does not modify the reference count of the returned device,
+ // so do not feel compelled to unreference it when you are done.
+ libusb_device* dev = libusb_get_device(handle_.get());
+
+ libusb_device_descriptor desc;
+ libusb_config_descriptor* conf;
+ memset(info, 0, sizeof(*info));
+
+ int res = libusb_get_device_descriptor(dev, &desc);
+ if (res < 0) {
+ // This shouldn't really happen.
+ ALOGE("libusb_get_device_descriptor failed %d", res);
+ return false;
+ }
+
+ res = libusb_get_active_config_descriptor(dev, &conf);
+ if (res < 0) {
+ // This shouldn't really happen.
+ ALOGE("libusb_get_active_config_descriptor failed %d", res);
+ libusb_free_config_descriptor(conf);
+ return false;
+ }
+
+ info->vendor_id = desc.idVendor;
+ info->product_id = desc.idProduct;
+ info->dev_version = desc.bcdDevice;
+ info->dev_class = desc.bDeviceClass;
+ info->dev_subclass = desc.bDeviceSubClass;
+ info->dev_protocol = desc.bDeviceProtocol;
+ info->speed = libusb_get_device_speed(dev);
+ info->num_configurations = desc.bNumConfigurations;
+ info->num_interfaces = conf->bNumInterfaces;
+ info->cur_configuration = conf->bConfigurationValue;
+ info->bus_id = kDefaultBusID;
+ info->dev_id = kDefaultDevID;
+
+ if (ifaces != nullptr) {
+ for (int ifidx = 0; ifidx < conf->bNumInterfaces; ++ifidx) {
+ const libusb_interface& iface = conf->interface[ifidx];
+ for (int altidx = 0; altidx < iface.num_altsetting; ++altidx) {
+ const libusb_interface_descriptor& alt = iface.altsetting[altidx];
+ ifaces->push_back(InterfaceInfo{alt.bInterfaceClass,
+ alt.bInterfaceSubClass,
+ alt.bInterfaceProtocol, 0});
+ }
+ }
+ }
+ libusb_free_config_descriptor(conf);
+ return true;
+}
+
+USBServer::USBServer(const cvd::SharedFD& fd)
+ : fd_{fd},
+ device_event_fd_{cvd::SharedFD::Event(0, 0)},
+ thread_event_fd_{cvd::SharedFD::Event(0, 0)} {}
+
+void USBServer::HandleDeviceList(uint32_t tag) {
+ // Iterate all devices and send structure for every found device.
+ // Write header: number of devices.
+ DeviceInfo info;
+ std::vector<InterfaceInfo> ifaces;
+ bool found = GetDeviceInfo(&info, &ifaces);
+
+ cvd::LockGuard<cvd::Mutex> lock(write_mutex_);
+ ResponseHeader rsp{StatusSuccess, tag};
+ fd_->Write(&rsp, sizeof(rsp));
+ if (found) {
+ uint32_t cnt = 1;
+ fd_->Write(&cnt, sizeof(cnt));
+ fd_->Write(&info, sizeof(info));
+ fd_->Write(ifaces.data(), ifaces.size() * sizeof(InterfaceInfo));
+ } else {
+ // No devices.
+ uint32_t cnt = 0;
+ fd_->Write(&cnt, sizeof(cnt));
+ }
+}
+
+void USBServer::HandleAttach(uint32_t tag) {
+ // We read the request, but it no longer plays any significant role here.
+ AttachRequest req;
+ if (fd_->Read(&req, sizeof(req)) != sizeof(req)) return;
+
+ cvd::LockGuard<cvd::Mutex> lock(write_mutex_);
+ ResponseHeader rsp{handle_ ? StatusSuccess : StatusFailure, tag};
+ fd_->Write(&rsp, sizeof(rsp));
+}
+
+void USBServer::HandleHeartbeat(uint32_t tag) {
+ cvd::LockGuard<cvd::Mutex> lock(write_mutex_);
+ ResponseHeader rsp{handle_ ? StatusSuccess : StatusFailure, tag};
+ fd_->Write(&rsp, sizeof(rsp));
+}
+
+void USBServer::HandleControlTransfer(uint32_t tag) {
+ ControlTransfer req;
+ // If disconnected prematurely, don't send response.
+ if (fd_->Read(&req, sizeof(req)) != sizeof(req)) return;
+
+ // Technically speaking this isn't endpoint, but names, masks, values and
+ // meaning here is exactly same.
+ bool is_data_in =
+ ((req.type & LIBUSB_ENDPOINT_DIR_MASK) == LIBUSB_ENDPOINT_IN);
+
+ std::unique_ptr<TransportRequest> treq(new TransportRequest(
+ handle_,
+ [this, is_data_in, tag](bool is_success, const uint8_t* data,
+ int32_t length) {
+ OnTransferComplete(tag, is_data_in, is_success, data, length);
+ },
+ req));
+
+ if (!is_data_in && req.length) {
+ // If disconnected prematurely, don't send response.
+ int32_t got = 0;
+ while (got < req.length) {
+ auto read = fd_->Read(&treq->Buffer()[got], req.length - got);
+ if (fd_->GetErrno() != 0) {
+ ALOGE("Failed to read from client: %s", fd_->StrError());
+ return;
+ } else if (read == 0) {
+ ALOGE("Failed to read from client: short read");
+ return;
+ }
+ got += read;
+ }
+ }
+
+ // At this point we store transport request internally until it completes.
+ TransportRequest* treq_ptr = treq.get();
+ {
+ cvd::LockGuard<cvd::Mutex> lock(requests_mutex_);
+ requests_in_flight_[tag] = std::move(treq);
+ }
+
+ if (!treq_ptr->Submit()) {
+ OnTransferComplete(tag, is_data_in, false, nullptr, 0);
+ }
+}
+
+void USBServer::HandleDataTransfer(uint32_t tag) {
+ DataTransfer req;
+ // If disconnected prematurely, don't send response.
+ if (fd_->Read(&req, sizeof(req)) != sizeof(req)) return;
+
+ bool is_data_in = !req.is_host_to_device;
+
+ std::unique_ptr<TransportRequest> treq(new TransportRequest(
+ handle_,
+ [this, is_data_in, tag](bool is_success, const uint8_t* data,
+ int32_t length) {
+ OnTransferComplete(tag, is_data_in, is_success, data, length);
+ },
+ req));
+
+ if (!is_data_in && req.length) {
+ // If disconnected prematurely, don't send response.
+ int32_t got = 0;
+ while (got < req.length) {
+ auto read = fd_->Read(&treq->Buffer()[got], req.length - got);
+ if (fd_->GetErrno() != 0) {
+ ALOGE("Failed to read from client: %s", fd_->StrError());
+ return;
+ } else if (read == 0) {
+ ALOGE("Failed to read from client: short read");
+ return;
+ }
+ got += read;
+ }
+ }
+
+ // At this point we store transport request internally until it completes.
+ TransportRequest* treq_ptr = treq.get();
+ {
+ cvd::LockGuard<cvd::Mutex> lock(requests_mutex_);
+ requests_in_flight_[tag] = std::move(treq);
+ }
+
+ if (!treq_ptr->Submit()) {
+ OnTransferComplete(tag, is_data_in, false, nullptr, 0);
+ }
+}
+
+void USBServer::OnTransferComplete(uint32_t tag, bool is_data_in,
+ bool is_success, const uint8_t* buffer,
+ int32_t actual_length) {
+ ResponseHeader rsp{is_success ? StatusSuccess : StatusFailure, tag};
+
+ cvd::LockGuard<cvd::Mutex> lock(write_mutex_);
+ fd_->Write(&rsp, sizeof(rsp));
+ if (is_success && is_data_in) {
+ fd_->Write(&actual_length, sizeof(actual_length));
+ if (actual_length > 0) {
+ // NOTE: don't use buffer_ here directly, as libusb uses first few bytes
+ // to store control data there.
+ int32_t sent = 0;
+ while (sent < actual_length) {
+ int packet_size = fd_->Write(&buffer[sent], actual_length - sent);
+ sent += packet_size;
+ ALOGV("Sending response, %d / %d bytes sent", sent, actual_length);
+ if (fd_->GetErrno() != 0) {
+ ALOGE("Send failed: %s", fd_->StrError());
+ return;
+ }
+ }
+ }
+ }
+
+ {
+ cvd::LockGuard<cvd::Mutex> lock(requests_mutex_);
+ requests_in_flight_.erase(tag);
+ }
+}
+
+int USBServer::HandleDeviceEvent(libusb_context*, libusb_device*,
+ libusb_hotplug_event event, void* self_raw) {
+ auto self = reinterpret_cast<USBServer*>(self_raw);
+ int64_t dummy = 1;
+ self->device_event_fd_->Write(&dummy, sizeof(dummy));
+ return 0;
+}
+
+void* USBServer::ProcessLibUSBRequests(void* self_raw) {
+ USBServer* self = reinterpret_cast<USBServer*>(self_raw);
+ ALOGI("Starting hotplug thread.");
+
+ cvd::SharedFDSet rset;
+ while (true) {
+ // Do not wait if there's no event.
+ timeval select_timeout{0, 0};
+ rset.Zero();
+ rset.Set(self->thread_event_fd_);
+ int ret = cvd::Select(&rset, nullptr, nullptr, &select_timeout);
+ if (ret > 0) break;
+
+ timeval libusb_timeout{1, 0};
+ libusb_handle_events_timeout_completed(nullptr, &libusb_timeout, nullptr);
+ }
+
+ int64_t dummy;
+ self->thread_event_fd_->Read(&dummy, sizeof(dummy));
+ ALOGI("Shutting down hotplug thread.");
+ return nullptr;
+}
+
+void USBServer::InitLibUSB() {
+ if (libusb_init(nullptr) != 0) return;
+ libusb_hotplug_register_callback(
+ nullptr,
+ libusb_hotplug_event(LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT),
+ libusb_hotplug_flag(0), kExportedVendorID, kExportedProductID,
+ LIBUSB_HOTPLUG_MATCH_ANY, &USBServer::HandleDeviceEvent, this,
+ &hotplug_handle_);
+ handle_ = GetDevice();
+ libusb_thread_.reset(new cvd::ScopedThread(&ProcessLibUSBRequests, this));
+}
+
+void USBServer::ExitLibUSB() {
+ if (!libusb_thread_) return;
+ libusb_hotplug_deregister_callback(nullptr, hotplug_handle_);
+ int64_t dummy = 1;
+ thread_event_fd_->Write(&dummy, sizeof(dummy));
+ libusb_thread_.reset();
+ handle_.reset();
+ libusb_exit(nullptr);
+}
+
+void USBServer::Serve() {
+ cvd::SharedFDSet rset;
+ while (true) {
+ timeval retry_timeout{1, 0};
+ timeval* select_timeout = nullptr;
+ if (!handle_) {
+ select_timeout = &retry_timeout;
+ }
+
+ rset.Zero();
+ rset.Set(fd_);
+ rset.Set(device_event_fd_);
+ int ret = cvd::Select(&rset, nullptr, nullptr, select_timeout);
+
+ // device_event_fd_ is reset each time libusb notices device has re-appeared
+ // or is gone. In both cases, the existing handle is no longer valid.
+ if (rset.IsSet(device_event_fd_)) {
+ int64_t dummy;
+ device_event_fd_->Read(&dummy, sizeof(dummy));
+ handle_.reset();
+ }
+
+ if (!handle_) {
+ ExitLibUSB();
+ InitLibUSB();
+ if (handle_) {
+ ALOGI("Device present.");
+ }
+ }
+
+ if (ret < 0) continue;
+
+ if (rset.IsSet(fd_)) {
+ RequestHeader req;
+ if (fd_->Read(&req, sizeof(req)) != sizeof(req)) {
+ // There's nobody on the other side.
+ sleep(3);
+ continue;
+ }
+
+ switch (req.command) {
+ case CmdDeviceList:
+ ALOGV("Processing DeviceList command, tag=%d", req.tag);
+ HandleDeviceList(req.tag);
+ break;
+
+ case CmdAttach:
+ ALOGV("Processing Attach command, tag=%d", req.tag);
+ HandleAttach(req.tag);
+ break;
+
+ case CmdControlTransfer:
+ ALOGV("Processing ControlTransfer command, tag=%d", req.tag);
+ HandleControlTransfer(req.tag);
+ break;
+
+ case CmdDataTransfer:
+ ALOGV("Processing DataTransfer command, tag=%d", req.tag);
+ HandleDataTransfer(req.tag);
+ break;
+
+ case CmdHeartbeat:
+ ALOGV("Processing Heartbeat command, tag=%d", req.tag);
+ HandleHeartbeat(req.tag);
+ break;
+
+ default:
+ ALOGE("Discarding unknown command %08x, tag=%d", req.command,
+ req.tag);
+ }
+ }
+ }
+}
+
+} // namespace usb_forward
diff --git a/guest/commands/usbforward/usb_server.h b/guest/commands/usbforward/usb_server.h
new file mode 100644
index 0000000..77449b9
--- /dev/null
+++ b/guest/commands/usbforward/usb_server.h
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#pragma once
+
+#include <map>
+#include <memory>
+#include <string>
+#include <libusb/libusb.h>
+
+#include "common/libs/fs/shared_fd.h"
+#include "common/libs/threads/cuttlefish_thread.h"
+#include "guest/commands/usbforward/transport_request.h"
+
+namespace usb_forward {
+
+// USBServer exposes access to USB devices over pipe (virtio channel etc).
+// Usage:
+//
+// cvd::SharedFD pipe = cvd::SharedFD::Open(pipe_path, O_RDWR);
+// USBServer server(pipe);
+// CHECK(server.Init());
+// server.Serve();
+class USBServer final {
+ public:
+ USBServer(const cvd::SharedFD& fd);
+ ~USBServer() = default;
+
+ // Serve incoming USB requests.
+ void Serve();
+
+ private:
+ // HandleDeviceEvent opens and closes Android Gadget device, whenever it
+ // appears / disappears.
+ static int HandleDeviceEvent(libusb_context*, libusb_device*,
+ libusb_hotplug_event event, void* self_raw);
+
+ // Handle CmdDeviceList request.
+ void HandleDeviceList(uint32_t tag);
+
+ // Handle CmdAttach request.
+ void HandleAttach(uint32_t tag);
+
+ // Handle CmdControlTransfer request.
+ void HandleControlTransfer(uint32_t tag);
+
+ // Handle CmdDataTransfer request.
+ void HandleDataTransfer(uint32_t tag);
+
+ // Handle CmdHeartbeat request.
+ void HandleHeartbeat(uint32_t tag);
+
+ // OnAsyncDataTransferComplete handles end of asynchronous data transfer cycle
+ // and sends response back to caller.
+ void OnTransferComplete(uint32_t tag, bool is_data_in, bool is_success,
+ const uint8_t* buffer, int32_t actual_length);
+
+ // Initialize, Configure and start libusb.
+ void InitLibUSB();
+
+ // Stop, Deconfigure and Clean up libusb.
+ void ExitLibUSB();
+
+ // Extract device info, if device is available.
+ bool GetDeviceInfo(DeviceInfo* info, std::vector<InterfaceInfo>* ifaces);
+
+ // Handle asynchronous libusb events.
+ static void* ProcessLibUSBRequests(void* self_ptr);
+
+ std::shared_ptr<libusb_device_handle> handle_;
+ libusb_hotplug_callback_handle hotplug_handle_;
+
+ std::unique_ptr<cvd::ScopedThread> libusb_thread_;
+ cvd::Mutex write_mutex_;
+ cvd::SharedFD fd_;
+ cvd::SharedFD device_event_fd_;
+ cvd::SharedFD thread_event_fd_;
+
+ cvd::Mutex requests_mutex_;
+ std::map<uint32_t, std::unique_ptr<TransportRequest>> requests_in_flight_;
+
+ USBServer(const USBServer& other) = delete;
+ USBServer& operator=(const USBServer& other) = delete;
+};
+
+} // namespace usb_forward
diff --git a/guest/commands/wifirouter/Android.bp b/guest/commands/wifirouter/Android.bp
new file mode 100644
index 0000000..1bb89af
--- /dev/null
+++ b/guest/commands/wifirouter/Android.bp
@@ -0,0 +1,31 @@
+//
+// Copyright (C) 2017 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.
+//
+cc_binary {
+ name: "wifirouter",
+ vendor: true,
+ srcs: ["router.cc"],
+ static_libs: [
+ "libbase",
+ "libgflags",
+ "liblog",
+ "libnl",
+ ],
+ header_libs: [
+ "cuttlefish_common_headers",
+ "cuttlefish_glog",
+ ],
+}
+
diff --git a/guest/commands/wifirouter/router.cc b/guest/commands/wifirouter/router.cc
new file mode 100644
index 0000000..d01c3eb
--- /dev/null
+++ b/guest/commands/wifirouter/router.cc
@@ -0,0 +1,302 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#include "common/libs/wifi/router.h"
+
+#include <cerrno>
+#include <cstddef>
+#include <map>
+#include <memory>
+#include <set>
+
+#include <gflags/gflags.h>
+#include <glog/logging.h>
+#include <netlink/genl/ctrl.h>
+#include <netlink/genl/family.h>
+#include <netlink/genl/genl.h>
+#include <netlink/netlink.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <unistd.h>
+
+DEFINE_string(socket_name, "cvd-wifirouter",
+ "Name of the unix-domain socket providing access for routing. "
+ "Socket will be created in abstract namespace.");
+
+namespace cvd {
+namespace {
+using MacHash = uint64_t;
+using MacToClientsTable = std::multimap<MacHash, int>;
+using ClientsTable = std::set<int>;
+
+// Copied out of mac80211_hwsim.h header.
+constexpr int HWSIM_CMD_REGISTER = 1;
+constexpr int HWSIM_ATTR_ADDR_TRANSMITTER = 2;
+constexpr int HWSIM_ATTR_MAX = 19;
+
+// Name of the WIFI SIM Netlink Family.
+constexpr char kWifiSimFamilyName[] = "MAC80211_HWSIM";
+const int kMaxSupportedPacketSize = getpagesize();
+
+// Get hash for mac address serialized to 6 bytes of data starting at specified
+// location.
+// We don't care about byte ordering as much as we do about having all bytes
+// there. Byte order does not matter, we want to use it as a key in our map.
+uint64_t GetMacHash(const void* macaddr) {
+ auto typed = reinterpret_cast<const uint16_t*>(macaddr);
+ return (1ull * typed[0] << 32) | (typed[1] << 16) | typed[2];
+}
+
+// Enable asynchronous notifications from MAC80211_HWSIM.
+// - `sock` is a valid netlink socket connected to NETLINK_GENERIC,
+// - `family` is MAC80211_HWSIM genl family number.
+//
+// Upon failure, this function will terminate execution of the program.
+void RegisterForHWSimNotifications(nl_sock* sock, int family) {
+ std::unique_ptr<nl_msg, void (*)(nl_msg*)> msg(
+ nlmsg_alloc(), [](nl_msg* m) { nlmsg_free(m); });
+ genlmsg_put(msg.get(), NL_AUTO_PID, NL_AUTO_SEQ, family, 0, NLM_F_REQUEST,
+ HWSIM_CMD_REGISTER, 0);
+ nl_send_auto(sock, msg.get());
+ auto res = nl_wait_for_ack(sock);
+ if (res < 0) {
+ LOG(ERROR) << "Could not register for notifications: " << nl_geterror(res);
+ exit(1);
+ }
+}
+
+// Create and configure WIFI Router server socket.
+// This function is guaranteed to success. If at any point an error is detected,
+// the function will terminate execution of the program.
+int CreateWifiRouterServerSocket() {
+ auto fd = socket(AF_UNIX, SOCK_SEQPACKET, 0);
+ if (fd <= 0) {
+ LOG(ERROR) << "Could not create unix socket: " << strerror(-fd);
+ exit(1);
+ }
+
+ sockaddr_un addr{};
+ addr.sun_family = AF_UNIX;
+ auto len = std::min(sizeof(addr.sun_path) - 2, FLAGS_socket_name.size());
+ strncpy(&addr.sun_path[1], FLAGS_socket_name.c_str(), len);
+ len += offsetof(sockaddr_un, sun_path) + 1; // include heading \0 byte.
+ auto res = bind(fd, reinterpret_cast<sockaddr*>(&addr), len);
+
+ if (res < 0) {
+ LOG(ERROR) << "Could not bind unix socket: " << strerror(-res);
+ exit(1);
+ }
+
+ listen(fd, 4);
+ return fd;
+}
+
+// Accept new WIFI Router client. When successful, client will be placed in
+// clients table.
+void AcceptNewClient(int server_fd, ClientsTable* clients) {
+ auto client = accept(server_fd, nullptr, nullptr);
+ if (client < 0) {
+ LOG(ERROR) << "Could not accept client: " << strerror(errno);
+ return;
+ }
+
+ clients->insert(client);
+ LOG(INFO) << "Client " << client << " added.";
+}
+
+// Disconnect and remove client from list of registered clients and recipients
+// of WLAN traffic.
+void RemoveClient(int client, ClientsTable* clients,
+ MacToClientsTable* targets) {
+ close(client);
+ clients->erase(client);
+ for (auto iter = targets->begin(); iter != targets->end();) {
+ if (iter->second == client) {
+ iter = targets->erase(iter);
+ } else {
+ ++iter;
+ }
+ }
+ LOG(INFO) << "Client " << client << " removed.";
+}
+
+// Read MAC80211HWSIM packet, find the originating MAC address and redirect it
+// to proper sink.
+void RouteWIFIPacket(nl_sock* nl, int simfamily, ClientsTable* clients,
+ MacToClientsTable* targets) {
+ sockaddr_nl tmp;
+ uint8_t* buf;
+
+ const auto len = nl_recv(nl, &tmp, &buf, nullptr);
+ if (len < 0) {
+ LOG(ERROR) << "Could not read from netlink: " << nl_geterror(len);
+ return;
+ }
+
+ std::unique_ptr<nlmsghdr, void (*)(nlmsghdr*)> msg(
+ reinterpret_cast<nlmsghdr*>(buf), [](nlmsghdr* m) { free(m); });
+
+ // Discard messages that originate from anything else than MAC80211_HWSIM.
+ if (msg->nlmsg_type != simfamily) return;
+
+ std::unique_ptr<nl_msg, void (*)(nl_msg*)> rep(
+ nlmsg_alloc(), [](nl_msg* m) { nlmsg_free(m); });
+ genlmsg_put(rep.get(), 0, 0, 0, 0, 0, WIFIROUTER_CMD_NOTIFY, 0);
+
+ // Note, this is generic netlink message, and uses different parsing
+ // technique.
+ nlattr* attrs[HWSIM_ATTR_MAX + 1];
+ if (genlmsg_parse(msg.get(), 0, attrs, HWSIM_ATTR_MAX, nullptr)) return;
+
+ std::set<int> pending_removals;
+ auto addr = attrs[HWSIM_ATTR_ADDR_TRANSMITTER];
+ if (addr != nullptr) {
+ nla_put(rep.get(), WIFIROUTER_ATTR_MAC, nla_len(addr), nla_data(addr));
+ nla_put(rep.get(), WIFIROUTER_ATTR_PACKET, len, buf);
+ auto hdr = nlmsg_hdr(rep.get());
+
+ auto key = GetMacHash(nla_data(attrs[HWSIM_ATTR_ADDR_TRANSMITTER]));
+ LOG(INFO) << "Received netlink packet from " << std::hex << key;
+ for (auto it = targets->find(key); it != targets->end() && it->first == key;
+ ++it) {
+ auto num_written =
+ send(it->second, hdr, hdr->nlmsg_len, MSG_NOSIGNAL);
+ if (num_written != static_cast<int64_t>(hdr->nlmsg_len)) {
+ pending_removals.insert(it->second);
+ }
+ }
+
+ for (auto client : pending_removals) {
+ RemoveClient(client, clients, targets);
+ }
+ }
+}
+
+bool HandleClientMessage(int client, MacToClientsTable* targets) {
+ std::unique_ptr<nlmsghdr, void (*)(nlmsghdr*)> msg(
+ reinterpret_cast<nlmsghdr*>(malloc(kMaxSupportedPacketSize)),
+ [](nlmsghdr* h) { free(h); });
+ int64_t size = recv(client, msg.get(), kMaxSupportedPacketSize, 0);
+
+ // Invalid message or no data -> client invalid or disconnected.
+ if (size == 0 || size != msg->nlmsg_len || size < sizeof(nlmsghdr)) {
+ return false;
+ }
+
+ int result = -EINVAL;
+ genlmsghdr* ghdr = reinterpret_cast<genlmsghdr*>(nlmsg_data(msg.get()));
+
+ switch (ghdr->cmd) {
+ case WIFIROUTER_CMD_REGISTER:
+ // Register client to receive notifications for specified MAC address.
+ nlattr* attrs[WIFIROUTER_ATTR_MAX];
+ if (!nlmsg_parse(msg.get(), sizeof(genlmsghdr), attrs,
+ WIFIROUTER_ATTR_MAX - 1, nullptr)) {
+ if (attrs[WIFIROUTER_ATTR_MAC] != nullptr) {
+ targets->emplace(GetMacHash(nla_data(attrs[WIFIROUTER_ATTR_MAC])),
+ client);
+ result = 0;
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ nlmsgerr err{.error = result};
+ std::unique_ptr<nl_msg, void (*)(nl_msg*)> rsp(nlmsg_alloc(), nlmsg_free);
+ nlmsg_put(rsp.get(), msg->nlmsg_pid, msg->nlmsg_seq, NLMSG_ERROR, 0, 0);
+ nlmsg_append(rsp.get(), &err, sizeof(err), 0);
+ auto hdr = nlmsg_hdr(rsp.get());
+ if (send(client, hdr, hdr->nlmsg_len, MSG_NOSIGNAL) !=
+ static_cast<int64_t>(hdr->nlmsg_len)) {
+ return false;
+ }
+ return true;
+}
+
+// Process incoming requests from netlink, server or clients.
+void ServerLoop(int server_fd, nl_sock* netlink_sock, int family) {
+ ClientsTable clients;
+ MacToClientsTable targets;
+ int netlink_fd = nl_socket_get_fd(netlink_sock);
+
+ while (true) {
+ auto max_fd = server_fd;
+ fd_set reads{};
+
+ auto fdset = [&max_fd, &reads](int fd) {
+ FD_SET(fd, &reads);
+ max_fd = std::max(max_fd, fd);
+ };
+
+ fdset(server_fd);
+ fdset(netlink_fd);
+ for (int client : clients) fdset(client);
+
+ if (select(max_fd + 1, &reads, nullptr, nullptr, nullptr) <= 0) continue;
+
+ if (FD_ISSET(server_fd, &reads)) AcceptNewClient(server_fd, &clients);
+ if (FD_ISSET(netlink_fd, &reads))
+ RouteWIFIPacket(netlink_sock, family, &clients, &targets);
+
+ // Process any client messages left. Drop any client that is no longer
+ // talking with us.
+ for (auto client = clients.begin(); client != clients.end();) {
+ auto cfd = *client++;
+ // Is our client sending us data?
+ if (FD_ISSET(cfd, &reads)) {
+ if (!HandleClientMessage(cfd, &targets)) {
+ // Client should be disconnected.
+ RemoveClient(cfd, &clients, &targets);
+ }
+ }
+ }
+ }
+}
+
+} // namespace
+} // namespace cvd
+
+int main(int argc, char* argv[]) {
+ using namespace cvd;
+ google::ParseCommandLineFlags(&argc, &argv, true);
+#if !defined(ANDROID)
+ // We should check for legitimate google logging here.
+ google::InitGoogleLogging(argv[0]);
+ google::InstallFailureSignalHandler();
+#endif
+
+ std::unique_ptr<nl_sock, void (*)(nl_sock*)> sock(nl_socket_alloc(),
+ nl_socket_free);
+
+ auto res = nl_connect(sock.get(), NETLINK_GENERIC);
+ if (res < 0) {
+ LOG(ERROR) << "Could not connect to netlink generic: " << nl_geterror(res);
+ exit(1);
+ }
+
+ auto mac80211_family = genl_ctrl_resolve(sock.get(), kWifiSimFamilyName);
+ if (mac80211_family <= 0) {
+ LOG(ERROR) << "Could not find MAC80211 HWSIM. Please make sure module "
+ << "'mac80211_hwsim' is loaded on your system.";
+ exit(1);
+ }
+
+ RegisterForHWSimNotifications(sock.get(), mac80211_family);
+ auto server_fd = CreateWifiRouterServerSocket();
+ ServerLoop(server_fd, sock.get(), mac80211_family);
+}
diff --git a/guest/frontend/vnc_server/Android.mk b/guest/frontend/vnc_server/Android.mk
new file mode 100644
index 0000000..7a01360
--- /dev/null
+++ b/guest/frontend/vnc_server/Android.mk
@@ -0,0 +1,74 @@
+# Copyright (C) 2016 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.
+#
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+
+ifneq (,$(filter $(PLATFORM_SDK_VERSION),16 17 18 19 21 22 23)) # J K L M
+ # prior to (not including) nyc, libjpeg was used instead of libjpeg turbo
+ # the libjpeg turbo in external/ is a backport with the shared library name
+ # changed to libjpeg_turbo to avoid conflict with the system's libjpeg
+ LIBJPEG_TURBO_NAME := libjpeg_turbo
+else
+ # nyc and later use libjpeg turbo under its usual name
+ LIBJPEG_TURBO_NAME := libjpeg
+endif
+
+LOCAL_C_INCLUDES := \
+ device/google/cuttlefish_common \
+ external/libjpeg-turbo \
+ external/jsoncpp/include
+
+LOCAL_MODULE := vnc_server
+LOCAL_VENDOR_MODULE := true
+LOCAL_MODULE_TAGS := optional
+LOCAL_SRC_FILES := \
+ blackboard.cpp \
+ frame_buffer_watcher.cpp \
+ jpeg_compressor.cpp \
+ main.cpp \
+ simulated_hw_composer.cpp \
+ tcp_socket.cpp \
+ VirtualInputDevice.cpp \
+ virtual_inputs.cpp \
+ vnc_client_connection.cpp \
+ vnc_server.cpp \
+
+LOCAL_CFLAGS := \
+ $(VSOC_VERSION_CFLAGS) \
+ -std=gnu++11 \
+ -Wall -Werror \
+ -Wno-error-unused -Wno-error=unused-parameter \
+ -Wno-attributes
+
+LOCAL_CFLAGS += -Wno-error=implicit-exception-spec-mismatch
+
+LOCAL_SHARED_LIBRARIES := \
+ $(LIBJPEG_TURBO_NAME) \
+ libbase \
+ liblog \
+ libutils \
+ libcutils \
+ cuttlefish_auto_resources \
+ libcuttlefish_fs \
+ vsoc_lib \
+ libvsocframebuffer
+
+LOCAL_STATIC_LIBRARIES := \
+ liblog \
+ libjsoncpp
+
+include $(BUILD_EXECUTABLE)
diff --git a/guest/frontend/vnc_server/VirtualInputDevice.cpp b/guest/frontend/vnc_server/VirtualInputDevice.cpp
new file mode 100644
index 0000000..c757532
--- /dev/null
+++ b/guest/frontend/vnc_server/VirtualInputDevice.cpp
@@ -0,0 +1,428 @@
+/*
+ * Copyright (C) 2014 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.
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/input.h>
+#include <linux/uinput.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <vector>
+
+#include "keysyms.h"
+
+#define LOG_TAG "RemoterVirtualInput"
+#include <cutils/log.h>
+#include <cutils/properties.h>
+
+#include "VirtualInputDevice.h"
+
+#define ARRAY_SIZE(a) \
+ ((sizeof(a) / sizeof(*(a))) / \
+ static_cast<size_t>(!(sizeof(a) % sizeof(*(a)))))
+
+//////////////////////////
+// VirtualButton Support
+//////////////////////////
+
+namespace cvd {
+uint32_t VirtualButton::Senabled_events_[] = {EV_KEY};
+
+VirtualButton::VirtualButton(const char* name, uint32_t input_keycode)
+ : VirtualInputDevice(name, BUS_USB, 0x6006, 0x6007, 1),
+ input_keycode_(input_keycode) {
+ if (!VirtualInputDevice::Init(Senabled_events_, ARRAY_SIZE(Senabled_events_),
+ &input_keycode_, 1, NULL, 0, NULL, 0)) {
+ LOG_FATAL("VirtualInputDevice Init() failed");
+ }
+}
+
+void VirtualButton::HandleButtonPressEvent(bool button_down) {
+ EmitEvent(EV_KEY, input_keycode_, button_down);
+ EmitEvent(EV_SYN, 0, 0);
+}
+
+//////////////////////////
+// VirtualKeyboard Support
+//////////////////////////
+
+uint32_t VirtualKeyboard::Senabled_events_[] = {EV_KEY};
+
+struct KeyEventToInput {
+ uint32_t xk;
+ uint32_t input_code;
+};
+
+static const KeyEventToInput key_table[] = {
+ {xk::AltLeft, KEY_LEFTALT},
+ {xk::ControlLeft, KEY_LEFTCTRL},
+ {xk::ShiftLeft, KEY_LEFTSHIFT},
+ {xk::AltRight, KEY_RIGHTALT},
+ {xk::ControlRight, KEY_RIGHTCTRL},
+ {xk::ShiftRight, KEY_RIGHTSHIFT},
+ {xk::MetaLeft, KEY_LEFTMETA},
+ {xk::MetaRight, KEY_RIGHTMETA},
+ {xk::MultiKey, KEY_COMPOSE},
+
+ {xk::CapsLock, KEY_CAPSLOCK},
+ {xk::NumLock, KEY_NUMLOCK},
+ {xk::ScrollLock, KEY_SCROLLLOCK},
+
+ {xk::BackSpace, KEY_BACKSPACE},
+ {xk::Tab, KEY_TAB},
+ {xk::Return, KEY_ENTER},
+ {xk::Escape, KEY_ESC},
+
+ {' ', KEY_SPACE},
+ {'!', KEY_1},
+ {'"', KEY_APOSTROPHE},
+ {'#', KEY_3},
+ {'$', KEY_4},
+ {'%', KEY_5},
+ {'^', KEY_6},
+ {'&', KEY_7},
+ {'\'', KEY_APOSTROPHE},
+ {'(', KEY_9},
+ {')', KEY_0},
+ {'*', KEY_8},
+ {'+', KEY_EQUAL},
+ {',', KEY_COMMA},
+ {'-', KEY_MINUS},
+ {'.', KEY_DOT},
+ {'/', KEY_SLASH},
+ {'0', KEY_0},
+ {'1', KEY_1},
+ {'2', KEY_2},
+ {'3', KEY_3},
+ {'4', KEY_4},
+ {'5', KEY_5},
+ {'6', KEY_6},
+ {'7', KEY_7},
+ {'8', KEY_8},
+ {'9', KEY_9},
+ {':', KEY_SEMICOLON},
+ {';', KEY_SEMICOLON},
+ {'<', KEY_COMMA},
+ {'=', KEY_EQUAL},
+ {'>', KEY_DOT},
+ {'?', KEY_SLASH},
+ {'@', KEY_2},
+ {'A', KEY_A},
+ {'B', KEY_B},
+ {'C', KEY_C},
+ {'D', KEY_D},
+ {'E', KEY_E},
+ {'F', KEY_F},
+ {'G', KEY_G},
+ {'H', KEY_H},
+ {'I', KEY_I},
+ {'J', KEY_J},
+ {'K', KEY_K},
+ {'L', KEY_L},
+ {'M', KEY_M},
+ {'N', KEY_N},
+ {'O', KEY_O},
+ {'P', KEY_P},
+ {'Q', KEY_Q},
+ {'R', KEY_R},
+ {'S', KEY_S},
+ {'T', KEY_T},
+ {'U', KEY_U},
+ {'V', KEY_V},
+ {'W', KEY_W},
+ {'X', KEY_X},
+ {'Y', KEY_Y},
+ {'Z', KEY_Z},
+ {'[', KEY_LEFTBRACE},
+ {'\\', KEY_BACKSLASH},
+ {']', KEY_RIGHTBRACE},
+ {'-', KEY_MINUS},
+ {'_', KEY_MINUS},
+ {'`', KEY_GRAVE},
+ {'a', KEY_A},
+ {'b', KEY_B},
+ {'c', KEY_C},
+ {'d', KEY_D},
+ {'e', KEY_E},
+ {'f', KEY_F},
+ {'g', KEY_G},
+ {'h', KEY_H},
+ {'i', KEY_I},
+ {'j', KEY_J},
+ {'k', KEY_K},
+ {'l', KEY_L},
+ {'m', KEY_M},
+ {'n', KEY_N},
+ {'o', KEY_O},
+ {'p', KEY_P},
+ {'q', KEY_Q},
+ {'r', KEY_R},
+ {'s', KEY_S},
+ {'t', KEY_T},
+ {'u', KEY_U},
+ {'v', KEY_V},
+ {'w', KEY_W},
+ {'x', KEY_X},
+ {'y', KEY_Y},
+ {'z', KEY_Z},
+ {'{', KEY_LEFTBRACE},
+ {'\\', KEY_BACKSLASH},
+ {'|', KEY_BACKSLASH},
+ {'}', KEY_RIGHTBRACE},
+ {'~', KEY_GRAVE},
+
+ {xk::F1, KEY_F1},
+ {xk::F2, KEY_F2},
+ {xk::F3, KEY_F3},
+ {xk::F4, KEY_F4},
+ {xk::F5, KEY_F5},
+ {xk::F6, KEY_F6},
+ {xk::F7, KEY_F7},
+ {xk::F8, KEY_F8},
+ {xk::F9, KEY_F9},
+ {xk::F10, KEY_F10},
+ {xk::F11, KEY_F11},
+ {xk::F12, KEY_F12},
+ {xk::F13, KEY_F13},
+ {xk::F14, KEY_F14},
+ {xk::F15, KEY_F15},
+ {xk::F16, KEY_F16},
+ {xk::F17, KEY_F17},
+ {xk::F18, KEY_F18},
+ {xk::F19, KEY_F19},
+ {xk::F20, KEY_F20},
+ {xk::F21, KEY_F21},
+ {xk::F22, KEY_F22},
+ {xk::F23, KEY_F23},
+ {xk::F24, KEY_F24},
+
+ {xk::Keypad0, KEY_KP0},
+ {xk::Keypad1, KEY_KP1},
+ {xk::Keypad2, KEY_KP2},
+ {xk::Keypad3, KEY_KP3},
+ {xk::Keypad4, KEY_KP4},
+ {xk::Keypad5, KEY_KP5},
+ {xk::Keypad6, KEY_KP6},
+ {xk::Keypad7, KEY_KP7},
+ {xk::Keypad8, KEY_KP8},
+ {xk::Keypad9, KEY_KP9},
+ {xk::KeypadMultiply, KEY_KPASTERISK},
+ {xk::KeypadSubtract, KEY_KPMINUS},
+ {xk::KeypadAdd, KEY_KPPLUS},
+ {xk::KeypadDecimal, KEY_KPDOT},
+ {xk::KeypadEnter, KEY_KPENTER},
+ {xk::KeypadDivide, KEY_KPSLASH},
+ {xk::KeypadEqual, KEY_KPEQUAL},
+ {xk::PlusMinus, KEY_KPPLUSMINUS},
+
+ {xk::SysReq, KEY_SYSRQ},
+ {xk::LineFeed, KEY_LINEFEED},
+ {xk::Home, KEY_HOME},
+ {xk::Up, KEY_UP},
+ {xk::PageUp, KEY_PAGEUP},
+ {xk::Left, KEY_LEFT},
+ {xk::Right, KEY_RIGHT},
+ {xk::End, KEY_END},
+ {xk::Down, KEY_DOWN},
+ {xk::PageDown, KEY_PAGEDOWN},
+ {xk::Insert, KEY_INSERT},
+ {xk::Delete, KEY_DELETE},
+ {xk::Pause, KEY_PAUSE},
+ {xk::KeypadSeparator, KEY_KPCOMMA},
+ {xk::Yen, KEY_YEN},
+ {xk::Cancel, KEY_STOP},
+ {xk::Redo, KEY_AGAIN},
+ {xk::Undo, KEY_UNDO},
+ {xk::Find, KEY_FIND},
+ {xk::Print, KEY_PRINT},
+ {xk::VolumeDown, KEY_VOLUMEDOWN},
+ {xk::Mute, KEY_MUTE},
+ {xk::VolumeUp, KEY_VOLUMEUP},
+ {xk::Menu, KEY_MENU},
+ {xk::VNCMenu, KEY_MENU},
+};
+
+VirtualKeyboard::VirtualKeyboard(const char* name)
+ : VirtualInputDevice(name, BUS_USB, 0x6006, 0x6008, 1) {
+ std::vector<uint32_t> keycodes(ARRAY_SIZE(key_table));
+ for (size_t i = 0; i < keycodes.size(); ++i) {
+ keymapping_[key_table[i].xk] = key_table[i].input_code;
+ keycodes[i] = key_table[i].input_code;
+ }
+
+ if (!VirtualInputDevice::Init(Senabled_events_, ARRAY_SIZE(Senabled_events_),
+ &keycodes[0], keycodes.size(), NULL, 0, NULL,
+ 0)) {
+ LOG_FATAL("VirtualInputDevice Init() failed");
+ }
+}
+
+void VirtualKeyboard::GenerateKeyPressEvent(int keycode, bool button_down) {
+ if (keymapping_.count(keycode)) {
+ EmitEvent(EV_KEY, keymapping_[keycode], button_down);
+ EmitEvent(EV_SYN, 0, 0);
+ }
+ ALOGI("Unknown keycode %d", keycode);
+}
+
+//////////////////////////
+// VirtualTouchPad Support
+//////////////////////////
+
+uint32_t VirtualTouchPad::Senabled_events_[] = {EV_ABS, EV_KEY, EV_SYN};
+uint32_t VirtualTouchPad::Senabled_keys_[] = {BTN_TOUCH};
+uint32_t VirtualTouchPad::Senabled_abs_[] = {ABS_X, ABS_Y};
+uint32_t VirtualTouchPad::Senabled_props_[] = {INPUT_PROP_DIRECT};
+
+VirtualTouchPad::VirtualTouchPad(const char* name, int x_res, int y_res)
+ : VirtualInputDevice(name, BUS_USB, 0x6006, 0x6006, 1),
+ x_res_(x_res),
+ y_res_(y_res) {
+ // Customization of uinput_user_dev() must happen before calling our base
+ // Init().
+ uinput_user_dev()->absmin[ABS_X] = 0;
+ uinput_user_dev()->absmax[ABS_X] = x_res_;
+ uinput_user_dev()->absmin[ABS_Y] = 0;
+ uinput_user_dev()->absmax[ABS_Y] = y_res_;
+
+ if (!VirtualInputDevice::Init(Senabled_events_, ARRAY_SIZE(Senabled_events_),
+ Senabled_keys_, ARRAY_SIZE(Senabled_keys_),
+ Senabled_abs_, ARRAY_SIZE(Senabled_abs_),
+ Senabled_props_, ARRAY_SIZE(Senabled_props_))) {
+ LOG_FATAL("VirtualInputDevice Init() failed");
+ }
+}
+
+void VirtualTouchPad::HandlePointerEvent(bool touch_down, int x, int y) {
+ EmitEvent(EV_ABS, ABS_X, x);
+ EmitEvent(EV_ABS, ABS_Y, y);
+ EmitEvent(EV_KEY, BTN_TOUCH, touch_down);
+ EmitEvent(EV_SYN, 0, 0);
+}
+
+//////////////////////////////////
+// Base VirtualInputDevice Support
+//////////////////////////////////
+VirtualInputDevice::VirtualInputDevice(const char* name, uint16_t bus_type,
+ uint16_t vendor, uint16_t product,
+ uint16_t version)
+ : fd_(-1) {
+ memset(&uinput_user_dev_, 0, sizeof(uinput_user_dev_));
+ strncpy(uinput_user_dev_.name, name, sizeof(uinput_user_dev_.name));
+ uinput_user_dev_.id.bustype = bus_type;
+ uinput_user_dev_.id.vendor = vendor;
+ uinput_user_dev_.id.product = product;
+ uinput_user_dev_.id.version = version;
+}
+
+VirtualInputDevice::~VirtualInputDevice() {
+ if (fd_ != -1) {
+ close(fd_);
+ fd_ = -1;
+ }
+}
+
+bool VirtualInputDevice::Init(uint32_t* events, int num_events, uint32_t* keys,
+ int num_keys, uint32_t* abs, int num_abs,
+ uint32_t* props, int num_props) {
+ if ((fd_ = open("/dev/uinput", O_WRONLY | O_NONBLOCK)) < 0) {
+ SLOGE("Failed to open /dev/uinput (%s)", strerror(errno));
+ return false;
+ }
+ if (events && !EnableEventBits(events, num_events)) {
+ SLOGE("Failed to set event bits (%s)", strerror(errno));
+ return false;
+ }
+ if (keys && !EnableKeyBits(keys, num_keys)) {
+ SLOGE("Failed to set key bits (%s)", strerror(errno));
+ return false;
+ }
+ if (abs && !EnableAbsBits(abs, num_abs)) {
+ SLOGE("Failed to set abs bits (%s)", strerror(errno));
+ return false;
+ }
+ if (props && !EnablePropBits(props, num_props)) {
+ SLOGE("Failed to set prop bits (%s)", strerror(errno));
+ return false;
+ }
+ if (!FinalizeDeviceCreation()) {
+ SLOGE("Failed to finalize device creation (%s)", strerror(errno));
+ return false;
+ }
+ return true;
+}
+
+bool VirtualInputDevice::EmitEvent(uint16_t type, uint16_t code,
+ uint32_t value) {
+ struct input_event ev;
+ ev.type = type;
+ ev.code = code;
+ ev.value = value;
+ if (write(fd_, &ev, sizeof(ev)) < 0) {
+ SLOGE("write() failed (%s)", strerror(errno));
+ return false;
+ }
+ return true;
+}
+
+bool VirtualInputDevice::DoIoctls(int request, uint32_t* list,
+ int num_elements) {
+ for (int i = 0; i < num_elements; i++) {
+ int rc = ioctl(fd_, request, *list++);
+ if (rc < 0) {
+ SLOGE("ioctl failed (%s)", strerror(errno));
+ return false;
+ }
+ }
+ return true;
+}
+
+bool VirtualInputDevice::EnableEventBits(uint32_t* events, int num_elements) {
+ return DoIoctls(UI_SET_EVBIT, events, num_elements);
+}
+
+bool VirtualInputDevice::EnableKeyBits(uint32_t* keys, int num_elements) {
+ return DoIoctls(UI_SET_KEYBIT, keys, num_elements);
+}
+
+bool VirtualInputDevice::EnableAbsBits(uint32_t* abs, int num_elements) {
+ return DoIoctls(UI_SET_ABSBIT, abs, num_elements);
+}
+
+bool VirtualInputDevice::EnablePropBits(uint32_t* props, int num_elements) {
+// JB and ICE do not have the latest uinput headers.
+#ifndef UI_SET_PROPBIT
+#define UI_SET_PROPBIT _IOW(UINPUT_IOCTL_BASE, 110, int)
+#endif // #ifndef UI_SET_PROPBIT
+ return DoIoctls(UI_SET_PROPBIT, props, num_elements);
+}
+
+bool VirtualInputDevice::FinalizeDeviceCreation() {
+ if (write(fd_, &uinput_user_dev_, sizeof(uinput_user_dev_)) < 0) {
+ SLOGE("Unable to set input device info (%s)", strerror(errno));
+ return false;
+ }
+ if (ioctl(fd_, UI_DEV_CREATE) < 0) {
+ SLOGE("Unable to create input device (%s)", strerror(errno));
+ return false;
+ }
+ return true;
+}
+
+} // namespace cvd
diff --git a/guest/frontend/vnc_server/VirtualInputDevice.h b/guest/frontend/vnc_server/VirtualInputDevice.h
new file mode 100644
index 0000000..d25f45c
--- /dev/null
+++ b/guest/frontend/vnc_server/VirtualInputDevice.h
@@ -0,0 +1,93 @@
+#pragma once
+/*
+ * Copyright (C) 2014 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.
+ */
+
+#include <linux/uinput.h>
+#include <map>
+
+namespace cvd {
+// Base virtual input device class which contains a bunch of boiler-plate code.
+class VirtualInputDevice {
+public:
+ VirtualInputDevice(const char* name, uint16_t bus_type, uint16_t vendor,
+ uint16_t product, uint16_t version);
+ virtual ~VirtualInputDevice();
+
+protected:
+ bool Init(uint32_t* events, int num_events, uint32_t* keys, int num_keys,
+ uint32_t* abs, int num_abs, uint32_t* props, int num_props);
+
+ bool EmitEvent(uint16_t type, uint16_t code, uint32_t value);
+
+ struct uinput_user_dev* uinput_user_dev() { return &uinput_user_dev_; }
+
+private:
+ bool EnableEventBits(uint32_t* events, int num_elements);
+ bool EnableKeyBits(uint32_t* keys, int num_elements);
+ bool EnableAbsBits(uint32_t* abs, int num_elements);
+ bool EnablePropBits(uint32_t* props, int num_elements);
+ bool DoIoctls(int request, uint32_t* list, int num_elements);
+ bool FinalizeDeviceCreation();
+
+ int fd_;
+ struct uinput_user_dev uinput_user_dev_;
+};
+
+// Virtual touch-pad.
+class VirtualTouchPad : public VirtualInputDevice {
+public:
+ VirtualTouchPad(const char* name, int x_res, int y_res);
+ virtual ~VirtualTouchPad() {}
+
+ void HandlePointerEvent(bool touch_down, int x, int y);
+
+private:
+ static uint32_t Senabled_events_[];
+ static uint32_t Senabled_keys_[];
+ static uint32_t Senabled_abs_[];
+ static uint32_t Senabled_props_[];
+
+ int x_res_;
+ int y_res_;
+};
+
+// Virtual button.
+class VirtualButton : public VirtualInputDevice {
+public:
+ VirtualButton(const char* name, uint32_t input_keycode);
+ virtual ~VirtualButton() {}
+
+ void HandleButtonPressEvent(bool button_down);
+
+private:
+ static uint32_t Senabled_events_[];
+ uint32_t input_keycode_;
+};
+
+// Virtual keyboard.
+class VirtualKeyboard : public VirtualInputDevice {
+public:
+ VirtualKeyboard(const char* name);
+ virtual ~VirtualKeyboard() {}
+
+ void GenerateKeyPressEvent(int code, bool down);
+
+private:
+ static uint32_t Senabled_events_[];
+ std::map<uint32_t, uint32_t> keymapping_;
+};
+
+} // namespace cvd
diff --git a/guest/frontend/vnc_server/blackboard.cpp b/guest/frontend/vnc_server/blackboard.cpp
new file mode 100644
index 0000000..b28e399
--- /dev/null
+++ b/guest/frontend/vnc_server/blackboard.cpp
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include "blackboard.h"
+#include "frame_buffer_watcher.h"
+#include <utility>
+#include <algorithm>
+
+#define LOG_TAG ""
+#include <cutils/log.h>
+
+using cvd::vnc::BlackBoard;
+using cvd::vnc::Stripe;
+
+cvd::vnc::SeqNumberVec cvd::vnc::MakeSeqNumberVec() {
+ return SeqNumberVec(FrameBufferWatcher::StripesPerFrame());
+}
+
+void BlackBoard::NewStripeReady(int index, StripeSeqNumber seq_num) {
+ std::lock_guard<std::mutex> guard(m_);
+ D("new stripe arrived from frame watcher");
+ auto& current_seq_num = most_recent_stripe_seq_nums_[index];
+ current_seq_num = std::max(current_seq_num, seq_num);
+ for (auto& client : clients_) {
+ if (client.second.ready_to_receive) {
+ client.second.new_frame_cv.notify_one();
+ }
+ }
+}
+
+void BlackBoard::Register(const VncClientConnection* conn) {
+ {
+ std::lock_guard<std::mutex> guard(m_);
+ ALOG_ASSERT(!clients_.count(conn));
+ clients_[conn]; // constructs new state in place
+ }
+ new_client_cv_.notify_one();
+}
+
+void BlackBoard::Unregister(const VncClientConnection* conn) {
+ std::lock_guard<std::mutex> guard(m_);
+ ALOG_ASSERT(clients_.count(conn));
+ clients_.erase(clients_.find(conn));
+}
+
+bool BlackBoard::NoNewStripesFor(const SeqNumberVec& seq_nums) const {
+ ALOG_ASSERT(seq_nums.size() == most_recent_stripe_seq_nums.size());
+ for (auto state_seq_num = seq_nums.begin(),
+ held_seq_num = most_recent_stripe_seq_nums_.begin();
+ state_seq_num != seq_nums.end(); ++state_seq_num, ++held_seq_num) {
+ if (*state_seq_num < *held_seq_num) {
+ return false;
+ }
+ }
+ return true;
+}
+
+cvd::vnc::StripePtrVec BlackBoard::WaitForSenderWork(
+ const VncClientConnection* conn) {
+ std::unique_lock<std::mutex> guard(m_);
+ auto& state = GetStateForClient(conn);
+ D("Waiting for stripe...");
+ while (!state.closed &&
+ (!state.ready_to_receive || NoNewStripesFor(state.stripe_seq_nums))) {
+ state.new_frame_cv.wait(guard);
+ }
+ D("At least one new stripe is available, should unblock %p", conn);
+ state.ready_to_receive = false;
+ auto new_stripes = frame_buffer_watcher_->StripesNewerThan(
+ state.orientation, state.stripe_seq_nums);
+ for (auto& s : new_stripes) {
+ state.stripe_seq_nums[s->index] = s->seq_number;
+ }
+ return new_stripes;
+}
+
+void BlackBoard::WaitForAtLeastOneClientConnection() {
+ std::unique_lock<std::mutex> guard(m_);
+ while (clients_.empty()) {
+ new_client_cv_.wait(guard);
+ }
+}
+
+void BlackBoard::SetOrientation(const VncClientConnection* conn,
+ ScreenOrientation orientation) {
+ std::lock_guard<std::mutex> guard(m_);
+ auto& state = GetStateForClient(conn);
+ state.orientation = orientation;
+ // After an orientation change the vnc client will need all stripes from
+ // the new orientation, regardless of age.
+ ResetToZero(&state.stripe_seq_nums);
+}
+
+void BlackBoard::SignalClientNeedsEntireScreen(
+ const VncClientConnection* conn) {
+ std::lock_guard<std::mutex> guard(m_);
+ ResetToZero(&GetStateForClient(conn).stripe_seq_nums);
+}
+
+void BlackBoard::ResetToZero(SeqNumberVec* seq_nums) {
+ seq_nums->assign(FrameBufferWatcher::StripesPerFrame(), StripeSeqNumber{});
+}
+
+void BlackBoard::FrameBufferUpdateRequestReceived(
+ const VncClientConnection* conn) {
+ std::lock_guard<std::mutex> guard(m_);
+ D("Received frame buffer update request");
+ auto& state = GetStateForClient(conn);
+ state.ready_to_receive = true;
+ state.new_frame_cv.notify_one();
+}
+
+void BlackBoard::StopWaiting(const VncClientConnection* conn) {
+ std::lock_guard<std::mutex> guard(m_);
+ auto& state = GetStateForClient(conn);
+ state.closed = true;
+ // Wake up the thread that might be in WaitForSenderWork()
+ state.new_frame_cv.notify_one();
+}
+
+void BlackBoard::set_frame_buffer_watcher(
+ cvd::vnc::FrameBufferWatcher* frame_buffer_watcher) {
+ std::lock_guard<std::mutex> guard(m_);
+ frame_buffer_watcher_ = frame_buffer_watcher;
+}
+
+void BlackBoard::set_jpeg_quality_level(int quality_level) {
+ // NOTE all vnc clients share a common jpeg quality level because the
+ // server doesn't compress per-client. The quality level for all clients
+ // will be whatever the most recent set was by any client.
+ std::lock_guard<std::mutex> guard(m_);
+ if (quality_level < kJpegMinQualityEncoding ||
+ quality_level > kJpegMaxQualityEncoding) {
+ ALOGW("Bogus jpeg quality level: %d. Quality must be in range [%d, %d]",
+ quality_level, kJpegMinQualityEncoding, kJpegMaxQualityEncoding);
+ return;
+ }
+ jpeg_quality_level_ = 55 + (5 * (quality_level + 32));
+ D("jpeg quality level set to %d%%:", jpeg_quality_level_);
+}
+
+BlackBoard::ClientFBUState& BlackBoard::GetStateForClient(
+ const VncClientConnection* conn) {
+ ALOG_ASSERT(clients_.count(conn));
+ return clients_[conn];
+}
diff --git a/guest/frontend/vnc_server/blackboard.h b/guest/frontend/vnc_server/blackboard.h
new file mode 100644
index 0000000..dfd9257
--- /dev/null
+++ b/guest/frontend/vnc_server/blackboard.h
@@ -0,0 +1,114 @@
+#pragma once
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include "vnc_utils.h"
+
+#include <android-base/thread_annotations.h>
+
+#include <unordered_map>
+#include <mutex>
+#include <condition_variable>
+#include <memory>
+
+namespace cvd {
+namespace vnc {
+
+class VncClientConnection;
+class FrameBufferWatcher;
+using StripePtrVec = std::vector<std::shared_ptr<const Stripe>>;
+using SeqNumberVec = std::vector<StripeSeqNumber>;
+
+SeqNumberVec MakeSeqNumberVec();
+
+class BlackBoard {
+ private:
+ struct ClientFBUState {
+ bool ready_to_receive{};
+ ScreenOrientation orientation{};
+ std::condition_variable new_frame_cv;
+ SeqNumberVec stripe_seq_nums = MakeSeqNumberVec();
+ bool closed{};
+ };
+
+ public:
+ class Registerer {
+ public:
+ Registerer(BlackBoard* bb, const VncClientConnection* conn)
+ : bb_{bb}, conn_{conn} {
+ bb->Register(conn);
+ }
+ ~Registerer() {
+ bb_->Unregister(conn_);
+ }
+ Registerer(const Registerer&) = delete;
+ Registerer& operator=(const Registerer&) = delete;
+
+ private:
+ BlackBoard* bb_{};
+ const VncClientConnection* conn_{};
+ };
+
+ BlackBoard() = default;
+ BlackBoard(const BlackBoard&) = delete;
+ BlackBoard& operator=(const BlackBoard&) = delete;
+
+ bool NoNewStripesFor(const SeqNumberVec& seq_nums) const REQUIRES(m_);
+ void NewStripeReady(int index, StripeSeqNumber seq_num);
+ void Register(const VncClientConnection* conn);
+ void Unregister(const VncClientConnection* conn);
+
+ StripePtrVec WaitForSenderWork(const VncClientConnection* conn);
+
+ void WaitForAtLeastOneClientConnection();
+
+ void FrameBufferUpdateRequestReceived(const VncClientConnection* conn);
+ // Setting orientation implies needing the entire screen
+ void SetOrientation(const VncClientConnection* conn,
+ ScreenOrientation orientation);
+ void SignalClientNeedsEntireScreen(const VncClientConnection* conn);
+
+ void StopWaiting(const VncClientConnection* conn);
+
+ void set_frame_buffer_watcher(FrameBufferWatcher* frame_buffer_watcher);
+
+ // quality_level must be the value received from the client, in the range
+ // [kJpegMinQualityEncoding, kJpegMaxQualityEncoding], else it is ignored.
+ void set_jpeg_quality_level(int quality_level);
+
+ int jpeg_quality_level() const {
+ std::lock_guard<std::mutex> guard(m_);
+ return jpeg_quality_level_;
+ }
+
+ private:
+ ClientFBUState& GetStateForClient(const VncClientConnection* conn)
+ REQUIRES(m_);
+ static void ResetToZero(SeqNumberVec* seq_nums);
+
+ mutable std::mutex m_;
+ SeqNumberVec most_recent_stripe_seq_nums_ GUARDED_BY(m_) = MakeSeqNumberVec();
+ std::unordered_map<const VncClientConnection*, ClientFBUState> clients_
+ GUARDED_BY(m_);
+ int jpeg_quality_level_ GUARDED_BY(m_) = 100;
+ std::condition_variable new_client_cv_;
+ // NOTE the FrameBufferWatcher pointer itself should be
+ // guarded, but not the pointee.
+ FrameBufferWatcher* frame_buffer_watcher_ GUARDED_BY(m_){};
+};
+
+} // namespace vnc
+} // namespace cvd
diff --git a/guest/frontend/vnc_server/frame_buffer_watcher.cpp b/guest/frontend/vnc_server/frame_buffer_watcher.cpp
new file mode 100644
index 0000000..5f68748
--- /dev/null
+++ b/guest/frontend/vnc_server/frame_buffer_watcher.cpp
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include "vnc_utils.h"
+#include "frame_buffer_watcher.h"
+#include <common/libs/thread_safe_queue/thread_safe_queue.h>
+
+#include <algorithm>
+#include <cstdint>
+#include <cstring>
+#include <iterator>
+#include <memory>
+#include <mutex>
+#include <thread>
+#include <utility>
+
+#define LOG_TAG ""
+#include <cutils/log.h>
+
+using cvd::vnc::FrameBufferWatcher;
+
+FrameBufferWatcher::FrameBufferWatcher(BlackBoard* bb)
+ : bb_{bb}, hwcomposer{bb_} {
+ for (auto& stripes_vec : stripes_) {
+ std::generate_n(std::back_inserter(stripes_vec),
+ SimulatedHWComposer::NumberOfStripes(),
+ std::make_shared<Stripe>);
+ }
+ bb_->set_frame_buffer_watcher(this);
+ auto num_workers = std::max(std::thread::hardware_concurrency(), 1u);
+ std::generate_n(std::back_inserter(workers_), num_workers, [this] {
+ return std::thread{&FrameBufferWatcher::Worker, this};
+ });
+}
+
+FrameBufferWatcher::~FrameBufferWatcher() {
+ {
+ std::lock_guard<std::mutex> guard(m_);
+ closed_ = true;
+ }
+ for (auto& tid : workers_) {
+ tid.join();
+ }
+}
+
+bool FrameBufferWatcher::closed() const {
+ std::lock_guard<std::mutex> guard(m_);
+ return closed_;
+}
+
+cvd::vnc::Stripe FrameBufferWatcher::Rotated(Stripe stripe) {
+ LOG_ALWAYS_FATAL_IF(stripe.orientation == ScreenOrientation::Landscape,
+ "Rotating a landscape stripe, this is a mistake");
+ auto w = stripe.width;
+ auto h = stripe.height;
+ const auto& raw = stripe.raw_data;
+ Message rotated(raw.size(), 0xAA);
+ for (std::uint16_t i = 0; i < w; ++i) {
+ for (std::uint16_t j = 0; j < h; ++j) {
+ auto to = (i * h + j) * BytesPerPixel();
+ auto from = (w - (i + 1) + w * j) * BytesPerPixel();
+ ALOG_ASSERT(from < raw.size());
+ ALOG_ASSERT(to < rotated.size());
+ std::memcpy(&rotated[to], &raw[from], BytesPerPixel());
+ }
+ }
+ std::swap(stripe.x, stripe.y);
+ std::swap(stripe.width, stripe.height);
+ stripe.raw_data = std::move(rotated);
+ stripe.orientation = ScreenOrientation::Landscape;
+ return stripe;
+}
+
+bool FrameBufferWatcher::StripeIsDifferentFromPrevious(
+ const Stripe& stripe) const {
+ return Stripes(stripe.orientation)[stripe.index]->raw_data != stripe.raw_data;
+}
+
+cvd::vnc::StripePtrVec FrameBufferWatcher::StripesNewerThan(
+ ScreenOrientation orientation, const SeqNumberVec& seq_numbers) const {
+ std::lock_guard<std::mutex> guard(stripes_lock_);
+ const auto& stripes = Stripes(orientation);
+ ALOG_ASSERT(seq_numbers.size() == stripes.size());
+ StripePtrVec new_stripes;
+ auto seq_number_it = seq_numbers.begin();
+ std::copy_if(stripes.begin(), stripes.end(), std::back_inserter(new_stripes),
+ [seq_number_it](const StripePtrVec::value_type& s) mutable {
+ return *(seq_number_it++) < s->seq_number;
+ });
+ return new_stripes;
+}
+
+cvd::vnc::StripePtrVec& FrameBufferWatcher::Stripes(
+ ScreenOrientation orientation) {
+ return stripes_[static_cast<int>(orientation)];
+}
+
+const cvd::vnc::StripePtrVec& FrameBufferWatcher::Stripes(
+ ScreenOrientation orientation) const {
+ return stripes_[static_cast<int>(orientation)];
+}
+
+bool FrameBufferWatcher::UpdateMostRecentSeqNumIfStripeIsNew(
+ const Stripe& stripe) {
+ if (most_recent_identical_stripe_seq_nums_[stripe.index] <=
+ stripe.seq_number) {
+ most_recent_identical_stripe_seq_nums_[stripe.index] = stripe.seq_number;
+ return true;
+ }
+ return false;
+}
+
+bool FrameBufferWatcher::UpdateStripeIfStripeIsNew(
+ const std::shared_ptr<const Stripe>& stripe) {
+ std::lock_guard<std::mutex> guard(stripes_lock_);
+ if (UpdateMostRecentSeqNumIfStripeIsNew(*stripe)) {
+ Stripes(stripe->orientation)[stripe->index] = stripe;
+ return true;
+ }
+ return false;
+}
+
+void FrameBufferWatcher::CompressStripe(JpegCompressor* jpeg_compressor,
+ Stripe* stripe) {
+ stripe->jpeg_data = jpeg_compressor->Compress(
+ stripe->raw_data, bb_->jpeg_quality_level(), 0, 0, stripe->width,
+ stripe->height, stripe->width);
+}
+
+void FrameBufferWatcher::Worker() {
+ JpegCompressor jpeg_compressor;
+#ifdef FUZZ_TEST_VNC
+ std::default_random_engine e{std::random_device{}()};
+ std::uniform_int_distribution<int> random{0, 2};
+#endif
+ while (!closed()) {
+ auto portrait_stripe = hwcomposer.GetNewStripe();
+ if (closed()) {
+ break;
+ }
+ {
+ // TODO(haining) use if (with init) and else for c++17 instead of extra
+ // scope and continue
+ // if (std::lock_guard guard(stripes_lock_); /*condition*/) { }
+ std::lock_guard<std::mutex> guard(stripes_lock_);
+ if (!StripeIsDifferentFromPrevious(portrait_stripe)) {
+ UpdateMostRecentSeqNumIfStripeIsNew(portrait_stripe);
+ continue;
+ }
+ }
+ auto seq_num = portrait_stripe.seq_number;
+ auto index = portrait_stripe.index;
+ auto landscape_stripe = Rotated(portrait_stripe);
+ auto stripes = {std::make_shared<Stripe>(std::move(portrait_stripe)),
+ std::make_shared<Stripe>(std::move(landscape_stripe))};
+ for (auto& stripe : stripes) {
+#ifdef FUZZ_TEST_VNC
+ if (random(e)) {
+ usleep(10000);
+ }
+#endif
+ CompressStripe(&jpeg_compressor, stripe.get());
+ }
+ bool any_new_stripes = false;
+ for (auto& stripe : stripes) {
+ any_new_stripes = UpdateStripeIfStripeIsNew(stripe) || any_new_stripes;
+ }
+ if (any_new_stripes) {
+ bb_->NewStripeReady(index, seq_num);
+ }
+ }
+}
+
+int FrameBufferWatcher::StripesPerFrame() {
+ return SimulatedHWComposer::NumberOfStripes();
+}
diff --git a/guest/frontend/vnc_server/frame_buffer_watcher.h b/guest/frontend/vnc_server/frame_buffer_watcher.h
new file mode 100644
index 0000000..b678b8f
--- /dev/null
+++ b/guest/frontend/vnc_server/frame_buffer_watcher.h
@@ -0,0 +1,74 @@
+#pragma once
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include "blackboard.h"
+#include "jpeg_compressor.h"
+#include "simulated_hw_composer.h"
+
+#include <vector>
+#include <memory>
+#include <mutex>
+#include <thread>
+#include <utility>
+
+namespace cvd {
+namespace vnc {
+class FrameBufferWatcher {
+ public:
+ explicit FrameBufferWatcher(BlackBoard* bb);
+ FrameBufferWatcher(const FrameBufferWatcher&) = delete;
+ FrameBufferWatcher& operator=(const FrameBufferWatcher&) = delete;
+ ~FrameBufferWatcher();
+
+ StripePtrVec StripesNewerThan(ScreenOrientation orientation,
+ const SeqNumberVec& seq_num) const;
+ static int StripesPerFrame();
+
+ private:
+ static Stripe Rotated(Stripe stripe);
+
+ bool closed() const;
+ bool StripeIsDifferentFromPrevious(const Stripe& stripe) const
+ REQUIRES(stripes_lock_);
+ // returns true if stripe is still considered new and seq number was updated
+ bool UpdateMostRecentSeqNumIfStripeIsNew(const Stripe& stripe)
+ REQUIRES(stripes_lock_);
+ // returns true if stripe is still considered new and was updated
+ bool UpdateStripeIfStripeIsNew(const std::shared_ptr<const Stripe>& stripe)
+ EXCLUDES(stripes_lock_);
+ // Compresses stripe->raw_data to stripe->jpeg_data
+ void CompressStripe(JpegCompressor* jpeg_compressor, Stripe* stripe);
+ void Worker();
+ void Updater();
+
+ StripePtrVec& Stripes(ScreenOrientation orientation) REQUIRES(stripes_lock_);
+ const StripePtrVec& Stripes(ScreenOrientation orientation) const
+ REQUIRES(stripes_lock_);
+
+ std::vector<std::thread> workers_;
+ mutable std::mutex stripes_lock_;
+ std::array<StripePtrVec, kNumOrientations> stripes_ GUARDED_BY(stripes_lock_);
+ SeqNumberVec most_recent_identical_stripe_seq_nums_
+ GUARDED_BY(stripes_lock_) = MakeSeqNumberVec();
+ mutable std::mutex m_;
+ bool closed_ GUARDED_BY(m_){};
+ BlackBoard* bb_{};
+ SimulatedHWComposer hwcomposer{bb_};
+};
+
+} // namespace vnc
+} // namespace cvd
diff --git a/guest/frontend/vnc_server/jpeg_compressor.cpp b/guest/frontend/vnc_server/jpeg_compressor.cpp
new file mode 100644
index 0000000..560f9d8
--- /dev/null
+++ b/guest/frontend/vnc_server/jpeg_compressor.cpp
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include "jpeg_compressor.h"
+#include "vnc_utils.h"
+
+#include <stdio.h> // stdio.h must appear before jpeglib.h
+#include <jpeglib.h>
+
+#define LOG_TAG ""
+#include <cutils/log.h>
+
+using cvd::vnc::JpegCompressor;
+
+namespace {
+void InitCinfo(jpeg_compress_struct* cinfo, jpeg_error_mgr* err,
+ std::uint16_t width, std::uint16_t height, int jpeg_quality) {
+ cinfo->err = jpeg_std_error(err);
+ jpeg_create_compress(cinfo);
+
+ cinfo->image_width = width;
+ cinfo->image_height = height;
+ cinfo->input_components = cvd::vnc::BytesPerPixel();
+ cinfo->in_color_space = JCS_EXT_RGBX;
+
+ jpeg_set_defaults(cinfo);
+ jpeg_set_quality(cinfo, jpeg_quality, true);
+}
+} // namespace
+
+cvd::vnc::Message JpegCompressor::Compress(const Message& frame,
+ int jpeg_quality, std::uint16_t x,
+ std::uint16_t y, std::uint16_t width,
+ std::uint16_t height,
+ int screen_width) {
+ jpeg_compress_struct cinfo{};
+ jpeg_error_mgr err{};
+ InitCinfo(&cinfo, &err, width, height, jpeg_quality);
+
+ auto* compression_buffer = buffer_.get();
+ auto compression_buffer_size = buffer_capacity_;
+ jpeg_mem_dest(&cinfo, &compression_buffer, &compression_buffer_size);
+ jpeg_start_compress(&cinfo, true);
+
+ while (cinfo.next_scanline < cinfo.image_height) {
+ auto row = static_cast<JSAMPROW>(const_cast<std::uint8_t*>(
+ &frame[(y * screen_width * BytesPerPixel()) +
+ (cinfo.next_scanline * BytesPerPixel() * screen_width) +
+ (x * BytesPerPixel())]));
+ jpeg_write_scanlines(&cinfo, &row, 1);
+ }
+ jpeg_finish_compress(&cinfo);
+ jpeg_destroy_compress(&cinfo);
+
+ UpdateBuffer(compression_buffer, compression_buffer_size);
+ return {compression_buffer, compression_buffer + compression_buffer_size};
+}
+
+void JpegCompressor::UpdateBuffer(std::uint8_t* compression_buffer,
+ unsigned long compression_buffer_size) {
+ if (buffer_capacity_ < compression_buffer_size) {
+ ALOG_ASSERT(buffer_ != compression_buffer);
+ buffer_capacity_ = compression_buffer_size;
+ buffer_.reset(compression_buffer);
+ } else {
+ ALOG_ASSERT(buffer_ == compression_buffer);
+ }
+}
diff --git a/guest/frontend/vnc_server/jpeg_compressor.h b/guest/frontend/vnc_server/jpeg_compressor.h
new file mode 100644
index 0000000..d9c40ee
--- /dev/null
+++ b/guest/frontend/vnc_server/jpeg_compressor.h
@@ -0,0 +1,53 @@
+#pragma once
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include "vnc_utils.h"
+
+#include <memory>
+#include <cstdlib>
+#include <cstdint>
+
+namespace cvd {
+namespace vnc {
+
+// libjpeg-turbo with jpeg_mem_dest (using memory as a destination) is funky.
+// If you give it a buffer that is big enough it will use it.
+// If you give it a buffer that is too small, it will allocate a new buffer
+// but will NOT free the buffer you gave it.
+// This class keeps track of the capacity of the working buffer, and frees the
+// old buffer if libjpeg-turbo silently discards it.
+class JpegCompressor {
+ public:
+ Message Compress(const Message& frame, int jpeg_quality, std::uint16_t x,
+ std::uint16_t y, std::uint16_t width, std::uint16_t height,
+ int screen_width);
+
+ private:
+ void UpdateBuffer(std::uint8_t* compression_buffer,
+ unsigned long compression_buffer_size);
+ struct Freer {
+ void operator()(void* p) const {
+ std::free(p);
+ }
+ };
+
+ std::unique_ptr<std::uint8_t, Freer> buffer_;
+ unsigned long buffer_capacity_{};
+};
+
+} // namespace vnc
+} // namespace cvd
diff --git a/guest/frontend/vnc_server/keysyms.h b/guest/frontend/vnc_server/keysyms.h
new file mode 100644
index 0000000..8da0117
--- /dev/null
+++ b/guest/frontend/vnc_server/keysyms.h
@@ -0,0 +1,107 @@
+#pragma once
+/*
+ * Copyright (C) 2017 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.
+ */
+
+namespace cvd {
+namespace xk {
+
+constexpr uint32_t BackSpace = 0xff08,
+ Tab = 0xff09,
+ Return = 0xff0d,
+ Enter = Return,
+ Escape = 0xff1b,
+ MultiKey = 0xff20,
+ Insert = 0xff63,
+ Delete = 0xffff,
+ Pause = 0xff13,
+ Home = 0xff50,
+ End = 0xff57,
+ PageUp = 0xff55,
+ PageDown = 0xff56,
+ Left = 0xff51,
+ Up = 0xff52,
+ Right = 0xff53,
+ Down = 0xff54,
+ F1 = 0xffbe,
+ F2 = 0xffbf,
+ F3 = 0xffc0,
+ F4 = 0xffc1,
+ F5 = 0xffc2,
+ F6 = 0xffc3,
+ F7 = 0xffc4,
+ F8 = 0xffc5,
+ F9 = 0xffc6,
+ F10 = 0xffc7,
+ F11 = 0xffc8,
+ F12 = 0xffc9,
+ F13 = 0xffca,
+ F14 = 0xffcb,
+ F15 = 0xffcc,
+ F16 = 0xffcd,
+ F17 = 0xffce,
+ F18 = 0xffcf,
+ F19 = 0xffd0,
+ F20 = 0xffd1,
+ F21 = 0xffd2,
+ F22 = 0xffd3,
+ F23 = 0xffd4,
+ F24 = 0xffd5,
+ ShiftLeft = 0xffe1,
+ ShiftRight = 0xffe2,
+ ControlLeft = 0xffe3,
+ ControlRight = 0xffe4,
+ MetaLeft = 0xffe7,
+ MetaRight = 0xffe8,
+ AltLeft = 0xffe9,
+ AltRight = 0xffea,
+ CapsLock = 0xffe5,
+ NumLock = 0xff7f,
+ ScrollLock = 0xff14,
+ Keypad0 = 0xffb0,
+ Keypad1 = 0xffb1,
+ Keypad2 = 0xffb2,
+ Keypad3 = 0xffb3,
+ Keypad4 = 0xffb4,
+ Keypad5 = 0xffb5,
+ Keypad6 = 0xffb6,
+ Keypad7 = 0xffb7,
+ Keypad8 = 0xffb8,
+ Keypad9 = 0xffb9,
+ KeypadMultiply = 0xffaa,
+ KeypadSubtract = 0xffad,
+ KeypadAdd = 0xffab,
+ KeypadDecimal = 0xffae,
+ KeypadEnter = 0xff8d,
+ KeypadDivide = 0xffaf,
+ KeypadEqual = 0xffbd,
+ PlusMinus = 0xb1,
+ SysReq = 0xff15,
+ LineFeed = 0xff0a,
+ KeypadSeparator = 0xffac,
+ Yen = 0xa5,
+ Cancel = 0xff69,
+ Undo = 0xff65,
+ Redo = 0xff66,
+ Find = 0xff68,
+ Print = 0xff61,
+ VolumeDown = 0x1008ff11,
+ Mute = 0x1008ff12,
+ VolumeUp = 0x1008ff13,
+ Menu = 0xff67,
+ VNCMenu = 0xffed; // VNC seems to translate MENU to this
+
+} // namespace xk
+} // namespace cvd
diff --git a/guest/frontend/vnc_server/main.cpp b/guest/frontend/vnc_server/main.cpp
new file mode 100644
index 0000000..f03fdcb
--- /dev/null
+++ b/guest/frontend/vnc_server/main.cpp
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include "vnc_server.h"
+
+#include <signal.h>
+#include <algorithm>
+#include <string>
+
+namespace {
+constexpr int kVncServerPort = 6444;
+
+// TODO(haining) use gflags when available
+bool HasAggressiveFlag(int argc, char* argv[]) {
+ const std::string kAggressive = "--aggressive";
+ auto end = argv + argc;
+ return std::find(argv, end, kAggressive) != end;
+}
+} // namespace
+
+int main(int argc, char* argv[]) {
+ struct sigaction new_action, old_action;
+ memset(&new_action, 0, sizeof(new_action));
+ new_action.sa_handler = SIG_IGN;
+ sigaction(SIGPIPE, &new_action, &old_action);
+ cvd::vnc::VncServer vnc_server(kVncServerPort, HasAggressiveFlag(argc, argv));
+ vnc_server.MainLoop();
+}
diff --git a/guest/frontend/vnc_server/simulated_hw_composer.cpp b/guest/frontend/vnc_server/simulated_hw_composer.cpp
new file mode 100644
index 0000000..473a6f9
--- /dev/null
+++ b/guest/frontend/vnc_server/simulated_hw_composer.cpp
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include "simulated_hw_composer.h"
+
+using cvd::vnc::SimulatedHWComposer;
+
+SimulatedHWComposer::SimulatedHWComposer(BlackBoard* bb)
+ :
+#ifdef FUZZ_TEST_VNC
+ engine_{std::random_device{}()},
+#endif
+ control_{VSoCFrameBufferControl::getInstance()},
+ bb_{bb},
+ stripes_(kMaxQueueElements, &SimulatedHWComposer::EraseHalfOfElements) {
+ void* p{};
+ VSoCFrameBuffer::OpenAndMapFrameBuffer(&p, &frame_buffer_fd_);
+ frame_buffer_memory_ = static_cast<char*>(p);
+ stripe_maker_ = std::thread(&SimulatedHWComposer::MakeStripes, this);
+}
+
+SimulatedHWComposer::~SimulatedHWComposer() {
+ close();
+ stripe_maker_.join();
+ VSoCFrameBuffer::UnmapAndCloseFrameBuffer(frame_buffer_memory_,
+ frame_buffer_fd_);
+}
+
+cvd::vnc::Stripe SimulatedHWComposer::GetNewStripe() {
+ auto s = stripes_.Pop();
+#ifdef FUZZ_TEST_VNC
+ if (random_(engine_)) {
+ usleep(7000);
+ stripes_.Push(std::move(s));
+ s = stripes_.Pop();
+ }
+#endif
+ return s;
+}
+
+bool SimulatedHWComposer::closed() {
+ std::lock_guard<std::mutex> guard(m_);
+ return closed_;
+}
+
+void SimulatedHWComposer::close() {
+ std::lock_guard<std::mutex> guard(m_);
+ closed_ = true;
+}
+
+// Assuming the number of stripes is less than half the size of the queue
+// this will be safe as the newest stripes won't be lost. In the real
+// hwcomposer, where stripes are coming in a different order, the full
+// queue case would probably need a different approach to be safe.
+void SimulatedHWComposer::EraseHalfOfElements(
+ ThreadSafeQueue<Stripe>::QueueImpl* q) {
+ q->erase(q->begin(), std::next(q->begin(), kMaxQueueElements / 2));
+}
+
+void SimulatedHWComposer::MakeStripes() {
+ std::uint32_t previous_seq_num{};
+ auto screen_height = ActualScreenHeight();
+ Message raw_screen;
+ std::uint64_t stripe_seq_num = 1;
+ while (!closed()) {
+ bb_->WaitForAtLeastOneClientConnection();
+ int y_offset{};
+ control_.WaitForFrameBufferChangeSince(previous_seq_num, &y_offset,
+ &previous_seq_num, nullptr);
+
+ const auto* frame_start =
+ frame_buffer_memory_ + y_offset * ActualScreenWidth() * BytesPerPixel();
+ raw_screen.assign(frame_start, frame_start + ScreenSizeInBytes());
+
+ for (int i = 0; i < kNumStripes; ++i) {
+ ++stripe_seq_num;
+ std::uint16_t y = (screen_height / kNumStripes) * i;
+
+ // Last frames on the right and/or bottom handle extra pixels
+ // when a screen dimension is not evenly divisible by Frame::kNumSlots.
+ std::uint16_t height =
+ screen_height / kNumStripes +
+ (i + 1 == kNumStripes ? screen_height % kNumStripes : 0);
+ const auto* raw_start =
+ &raw_screen[y * ActualScreenWidth() * BytesPerPixel()];
+ const auto* raw_end =
+ raw_start + (height * ActualScreenWidth() * BytesPerPixel());
+ // creating a named object and setting individual data members in order
+ // to make klp happy
+ // TODO (haining) construct this inside the call when not compiling
+ // on klp
+ Stripe s{};
+ s.index = i;
+ s.frame_id = previous_seq_num;
+ s.x = 0;
+ s.y = y;
+ s.width = ActualScreenWidth();
+ s.height = height;
+ s.raw_data.assign(raw_start, raw_end);
+ s.seq_number = StripeSeqNumber{stripe_seq_num};
+ s.orientation = ScreenOrientation::Portrait;
+ stripes_.Push(std::move(s));
+ }
+ }
+}
+
+int SimulatedHWComposer::NumberOfStripes() {
+ return kNumStripes;
+}
diff --git a/guest/frontend/vnc_server/simulated_hw_composer.h b/guest/frontend/vnc_server/simulated_hw_composer.h
new file mode 100644
index 0000000..c8a8320
--- /dev/null
+++ b/guest/frontend/vnc_server/simulated_hw_composer.h
@@ -0,0 +1,69 @@
+#pragma once
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include "blackboard.h"
+
+#include <guest/libs/legacy_framebuffer/vsoc_framebuffer.h>
+#include <guest/libs/legacy_framebuffer/vsoc_framebuffer_control.h>
+#include <common/libs/thread_safe_queue/thread_safe_queue.h>
+
+#include <mutex>
+#include <thread>
+
+#include <condition_variable>
+#ifdef FUZZ_TEST_VNC
+#include <random>
+#endif
+
+namespace cvd {
+namespace vnc {
+class SimulatedHWComposer {
+ public:
+ SimulatedHWComposer(BlackBoard* bb);
+ SimulatedHWComposer(const SimulatedHWComposer&) = delete;
+ SimulatedHWComposer& operator=(const SimulatedHWComposer&) = delete;
+ ~SimulatedHWComposer();
+
+ Stripe GetNewStripe();
+
+ // NOTE not constexpr on purpose
+ static int NumberOfStripes();
+
+ private:
+ bool closed();
+ void close();
+ static void EraseHalfOfElements(ThreadSafeQueue<Stripe>::QueueImpl* q);
+ void MakeStripes();
+
+#ifdef FUZZ_TEST_VNC
+ std::default_random_engine engine_;
+ std::uniform_int_distribution<int> random_ =
+ std::uniform_int_distribution<int>{0, 2};
+#endif
+ static constexpr int kNumStripes = 8;
+ constexpr static std::size_t kMaxQueueElements = 64;
+ bool closed_ GUARDED_BY(m_){};
+ std::mutex m_;
+ VSoCFrameBufferControl& control_;
+ BlackBoard* bb_{};
+ ThreadSafeQueue<Stripe> stripes_;
+ std::thread stripe_maker_;
+ char* frame_buffer_memory_{};
+ int frame_buffer_fd_{};
+};
+} // namespace vnc
+} // namespace cvd
diff --git a/guest/frontend/vnc_server/tcp_socket.cpp b/guest/frontend/vnc_server/tcp_socket.cpp
new file mode 100644
index 0000000..c83559b
--- /dev/null
+++ b/guest/frontend/vnc_server/tcp_socket.cpp
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include "tcp_socket.h"
+#include <cerrno>
+
+#include <cutils/sockets.h>
+#define LOG_TAG ""
+#include <cutils/log.h>
+
+using cvd::vnc::ClientSocket;
+using cvd::vnc::ServerSocket;
+using cvd::vnc::Message;
+
+Message ClientSocket::Recv(size_t length) {
+ Message buf(length);
+ ssize_t total_read = 0;
+ while (total_read < static_cast<ssize_t>(length)) {
+ auto just_read = read(fd_, &buf[total_read], buf.size() - total_read);
+ if (just_read <= 0) {
+ if (just_read < 0) {
+ ALOGE("read() error: %s", strerror(errno));
+ }
+ other_side_closed_ = true;
+ return Message{};
+ }
+ total_read += just_read;
+ }
+ ALOG_ASSERT(total_read == static_cast<ssize_t>(length));
+ return buf;
+}
+
+ssize_t ClientSocket::Send(const uint8_t* data, std::size_t size) {
+ std::lock_guard<std::mutex> lock(send_lock_);
+ ssize_t written{};
+ while (written < static_cast<ssize_t>(size)) {
+ auto just_written = write(fd_, data + written, size - written);
+ if (just_written <= 0) {
+ ALOGI("Couldn't write to vnc client: %s", strerror(errno));
+ return just_written;
+ }
+ written += just_written;
+ }
+ return written;
+}
+
+ssize_t ClientSocket::Send(const Message& message) {
+ return Send(&message[0], message.size());
+}
+
+ServerSocket::ServerSocket(int port)
+ : fd_{socket_inaddr_any_server(port, SOCK_STREAM)} {
+ if (fd_ < 0) {
+ LOG_FATAL("Couldn't open streaming server on port %d", port);
+ }
+}
+
+ClientSocket ServerSocket::Accept() {
+ int client = accept(fd_, nullptr, nullptr);
+ if (client < 0) {
+ LOG_FATAL("Error attemping to accept: %s", strerror(errno));
+ }
+ return ClientSocket{client};
+}
diff --git a/guest/frontend/vnc_server/tcp_socket.h b/guest/frontend/vnc_server/tcp_socket.h
new file mode 100644
index 0000000..c8c1b32
--- /dev/null
+++ b/guest/frontend/vnc_server/tcp_socket.h
@@ -0,0 +1,99 @@
+#pragma once
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include "vnc_utils.h"
+
+#include <mutex>
+#include <cstdint>
+#include <cstddef>
+
+#include <unistd.h>
+
+namespace cvd {
+namespace vnc {
+
+class ServerSocket;
+
+// Recv and Send wait until all data has been received or sent.
+// Send is thread safe in this regard, Recv is not.
+class ClientSocket {
+ public:
+ ClientSocket(ClientSocket&& other) : fd_{other.fd_} {
+ other.fd_ = -1;
+ }
+
+ ClientSocket& operator=(ClientSocket&& other) {
+ if (fd_ >= 0) {
+ close(fd_);
+ }
+ fd_ = other.fd_;
+ other.fd_ = -1;
+ return *this;
+ }
+
+ ClientSocket(const ClientSocket&) = delete;
+ ClientSocket& operator=(const ClientSocket&) = delete;
+
+ ~ClientSocket() {
+ if (fd_ >= 0) {
+ close(fd_);
+ }
+ }
+
+ Message Recv(std::size_t length);
+ ssize_t Send(const std::uint8_t* data, std::size_t size);
+ ssize_t Send(const Message& message);
+
+ template <std::size_t N>
+ ssize_t Send(const std::uint8_t (&data)[N]) {
+ return Send(data, N);
+ }
+
+ bool closed() const {
+ return other_side_closed_;
+ }
+
+ private:
+ friend ServerSocket;
+ explicit ClientSocket(int fd) : fd_(fd) {}
+
+ int fd_ = -1;
+ bool other_side_closed_{};
+ std::mutex send_lock_;
+};
+
+class ServerSocket {
+ public:
+ explicit ServerSocket(int port);
+
+ ServerSocket(const ServerSocket&) = delete;
+ ServerSocket& operator=(const ServerSocket&) = delete;
+
+ ~ServerSocket() {
+ if (fd_ >= 0) {
+ close(fd_);
+ }
+ }
+
+ ClientSocket Accept();
+
+ private:
+ int fd_ = -1;
+};
+
+} // namespace vnc
+} // namespace cvd
diff --git a/guest/frontend/vnc_server/virtual_inputs.cpp b/guest/frontend/vnc_server/virtual_inputs.cpp
new file mode 100644
index 0000000..ab4bb7a
--- /dev/null
+++ b/guest/frontend/vnc_server/virtual_inputs.cpp
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include "virtual_inputs.h"
+#include <mutex>
+
+using cvd::vnc::VirtualInputs;
+
+void VirtualInputs::GenerateKeyPressEvent(int code, bool down) {
+ std::lock_guard<std::mutex> guard(m_);
+ virtual_keyboard_.GenerateKeyPressEvent(code, down);
+}
+
+void VirtualInputs::PressPowerButton(bool down) {
+ std::lock_guard<std::mutex> guard(m_);
+ virtual_power_button_.HandleButtonPressEvent(down);
+}
+
+void VirtualInputs::HandlePointerEvent(bool touch_down, int x, int y) {
+ std::lock_guard<std::mutex> guard(m_);
+ virtual_touch_pad_.HandlePointerEvent(touch_down, x, y);
+}
diff --git a/guest/frontend/vnc_server/virtual_inputs.h b/guest/frontend/vnc_server/virtual_inputs.h
new file mode 100644
index 0000000..4b24d39
--- /dev/null
+++ b/guest/frontend/vnc_server/virtual_inputs.h
@@ -0,0 +1,44 @@
+#pragma once
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include "VirtualInputDevice.h"
+#include "vnc_utils.h"
+
+#include <linux/input.h>
+#include <android-base/thread_annotations.h>
+
+#include <mutex>
+
+namespace cvd {
+namespace vnc {
+
+class VirtualInputs {
+ public:
+ void GenerateKeyPressEvent(int code, bool down);
+ void PressPowerButton(bool down);
+ void HandlePointerEvent(bool touch_down, int x, int y);
+
+ private:
+ std::mutex m_;
+ VirtualKeyboard virtual_keyboard_ GUARDED_BY(m_){"remote-keyboard"};
+ VirtualTouchPad virtual_touch_pad_ GUARDED_BY(m_){
+ "remote-touchpad", ActualScreenWidth(), ActualScreenHeight()};
+ VirtualButton virtual_power_button_ GUARDED_BY(m_){"remote-power", KEY_POWER};
+};
+
+} // namespace vnc
+} // namespace cvd
diff --git a/guest/frontend/vnc_server/vnc_client_connection.cpp b/guest/frontend/vnc_server/vnc_client_connection.cpp
new file mode 100644
index 0000000..f6b4669
--- /dev/null
+++ b/guest/frontend/vnc_server/vnc_client_connection.cpp
@@ -0,0 +1,676 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include "vnc_client_connection.h"
+#include "vnc_utils.h"
+#include "tcp_socket.h"
+#include "keysyms.h"
+
+#include <guest/libs/legacy_framebuffer/vsoc_framebuffer.h>
+
+#include <netinet/in.h>
+#include <sys/time.h>
+
+#include <algorithm>
+#include <cmath>
+#include <cstdint>
+#include <cstring>
+#include <memory>
+#include <mutex>
+#include <string>
+#include <thread>
+#include <utility>
+#include <vector>
+
+#ifdef LOG_TAG
+#undef LOG_TAG
+#endif
+#define LOG_TAG ""
+#include <cutils/log.h>
+#include <cutils/sockets.h>
+
+using cvd::vnc::Message;
+using cvd::vnc::VncClientConnection;
+using cvd::vnc::Stripe;
+using cvd::vnc::StripePtrVec;
+
+namespace {
+class BigEndianChecker {
+ public:
+ BigEndianChecker() {
+ uint32_t u = 1;
+ is_big_endian_ = *reinterpret_cast<const char*>(&u) == 0;
+ }
+ bool operator()() const {
+ return is_big_endian_;
+ }
+
+ private:
+ bool is_big_endian_{};
+};
+
+const BigEndianChecker ImBigEndian;
+
+constexpr int32_t kDesktopSizeEncoding = -223;
+constexpr int32_t kTightEncoding = 7;
+
+// These are the lengths not counting the first byte. The first byte
+// indicates the message type.
+constexpr size_t kSetPixelFormatLength = 19;
+constexpr size_t kFramebufferUpdateRequestLength = 9;
+constexpr size_t kSetEncodingsLength = 3; // more bytes follow
+constexpr size_t kKeyEventLength = 7;
+constexpr size_t kPointerEventLength = 5;
+constexpr size_t kClientCutTextLength = 7; // more bytes follow
+
+void AppendInNetworkByteOrder(Message* msg, const std::uint8_t b) {
+ msg->push_back(b);
+}
+
+void AppendInNetworkByteOrder(Message* msg, const std::uint16_t s) {
+ const std::uint16_t n = htons(s);
+ auto p = reinterpret_cast<const std::uint8_t*>(&n);
+ msg->insert(msg->end(), p, p + sizeof n);
+}
+
+void AppendInNetworkByteOrder(Message* msg, const std::uint32_t w) {
+ const std::uint32_t n = htonl(w);
+ auto p = reinterpret_cast<const std::uint8_t*>(&n);
+ msg->insert(msg->end(), p, p + sizeof n);
+}
+
+void AppendInNetworkByteOrder(Message* msg, const int32_t w) {
+ std::uint32_t u{};
+ std::memcpy(&u, &w, sizeof u);
+ AppendInNetworkByteOrder(msg, u);
+}
+
+void AppendInNetworkByteOrder(Message* msg, const std::string& str) {
+ msg->insert(msg->end(), str.begin(), str.end());
+}
+
+void AppendToMessage(Message*) {}
+
+template <typename T, typename... Ts>
+void AppendToMessage(Message* msg, T v, Ts... vals) {
+ AppendInNetworkByteOrder(msg, v);
+ AppendToMessage(msg, vals...);
+}
+
+template <typename... Ts>
+Message CreateMessage(Ts... vals) {
+ Message m;
+ AppendToMessage(&m, vals...);
+ return m;
+}
+
+std::string HostName() {
+ return "Cuttlefish";
+}
+
+std::uint16_t uint16_tAt(const void* p) {
+ std::uint16_t u{};
+ std::memcpy(&u, p, sizeof u);
+ return ntohs(u);
+}
+
+std::uint32_t uint32_tAt(const void* p) {
+ std::uint32_t u{};
+ std::memcpy(&u, p, sizeof u);
+ return ntohl(u);
+}
+
+std::int32_t int32_tAt(const void* p) {
+ std::uint32_t u{};
+ std::memcpy(&u, p, sizeof u);
+ u = ntohl(u);
+ std::int32_t s{};
+ std::memcpy(&s, &u, sizeof s);
+ return s;
+}
+
+std::uint32_t RedVal(std::uint32_t pixel) {
+ return (pixel >> VSoCFrameBuffer::kRedShift) &
+ ((0x1 << VSoCFrameBuffer::kRedBits) - 1);
+}
+
+std::uint32_t BlueVal(std::uint32_t pixel) {
+ return (pixel >> VSoCFrameBuffer::kBlueShift) &
+ ((0x1 << VSoCFrameBuffer::kBlueBits) - 1);
+}
+
+std::uint32_t GreenVal(std::uint32_t pixel) {
+ return (pixel >> VSoCFrameBuffer::kGreenShift) &
+ ((0x1 << VSoCFrameBuffer::kGreenBits) - 1);
+}
+}
+namespace cvd {
+namespace vnc {
+bool operator==(const VncClientConnection::FrameBufferUpdateRequest& lhs,
+ const VncClientConnection::FrameBufferUpdateRequest& rhs) {
+ return lhs.x_pos == rhs.x_pos && lhs.y_pos == rhs.y_pos &&
+ lhs.width == rhs.width && lhs.height == rhs.height;
+}
+
+bool operator!=(const VncClientConnection::FrameBufferUpdateRequest& lhs,
+ const VncClientConnection::FrameBufferUpdateRequest& rhs) {
+ return !(lhs == rhs);
+}
+} // namespace vnc
+} // namespace cvd // namespace
+
+VncClientConnection::VncClientConnection(ClientSocket client,
+ VirtualInputs* virtual_inputs,
+ BlackBoard* bb, bool aggressive)
+ : client_{std::move(client)},
+ virtual_inputs_{virtual_inputs},
+ bb_{bb} {
+ frame_buffer_request_handler_tid_ = std::thread(
+ &VncClientConnection::FrameBufferUpdateRequestHandler, this, aggressive);
+}
+
+VncClientConnection::~VncClientConnection() {
+ {
+ std::lock_guard<std::mutex> guard(m_);
+ closed_ = true;
+ }
+ bb_->StopWaiting(this);
+ frame_buffer_request_handler_tid_.join();
+}
+
+void VncClientConnection::StartSession() {
+ SetupProtocol();
+ if (client_.closed()) {
+ return;
+ }
+ SetupSecurityType();
+ if (client_.closed()) {
+ return;
+ }
+ GetClientInit();
+ if (client_.closed()) {
+ return;
+ }
+ SendServerInit();
+ if (client_.closed()) {
+ return;
+ }
+ NormalSession();
+ ALOGI("vnc session terminated");
+}
+
+bool VncClientConnection::closed() {
+ std::lock_guard<std::mutex> guard(m_);
+ return closed_;
+}
+
+void VncClientConnection::SetupProtocol() {
+ static constexpr char kRFBVersion[] = "RFB 003.008\n";
+ static constexpr auto kVersionLen = (sizeof kRFBVersion) - 1;
+ client_.Send(reinterpret_cast<const std::uint8_t*>(kRFBVersion), kVersionLen);
+ auto client_protocol = client_.Recv(kVersionLen);
+ if (std::memcmp(&client_protocol[0], kRFBVersion,
+ std::min(kVersionLen, client_protocol.size())) != 0) {
+ client_protocol.push_back('\0');
+ ALOGE("vnc client wants a different protocol: %s",
+ reinterpret_cast<const char*>(&client_protocol[0]));
+ }
+}
+
+void VncClientConnection::SetupSecurityType() {
+ static constexpr std::uint8_t kNoneSecurity = 0x1;
+ // The first '0x1' indicates the number of items that follow
+ static constexpr std::uint8_t kOnlyNoneSecurity[] = {0x01, kNoneSecurity};
+ client_.Send(kOnlyNoneSecurity);
+ auto client_security = client_.Recv(1);
+ if (client_.closed()) {
+ return;
+ }
+ if (client_security.front() != kNoneSecurity) {
+ ALOGE("vnc client is asking for security type %d",
+ static_cast<int>(client_security.front()));
+ }
+ static constexpr std::uint8_t kZero[4] = {};
+ client_.Send(kZero);
+}
+
+void VncClientConnection::GetClientInit() {
+ auto client_shared = client_.Recv(1);
+}
+
+void VncClientConnection::SendServerInit() {
+ const std::string server_name = HostName();
+ std::lock_guard<std::mutex> guard(m_);
+ auto server_init = CreateMessage(
+ static_cast<std::uint16_t>(ScreenWidth()),
+ static_cast<std::uint16_t>(ScreenHeight()), pixel_format_.bits_per_pixel,
+ pixel_format_.depth, pixel_format_.big_endian, pixel_format_.true_color,
+ pixel_format_.red_max, pixel_format_.green_max, pixel_format_.blue_max,
+ pixel_format_.red_shift, pixel_format_.green_shift,
+ pixel_format_.blue_shift, std::uint16_t{}, // padding
+ std::uint8_t{}, // padding
+ static_cast<std::uint32_t>(server_name.size()), server_name);
+ client_.Send(server_init);
+}
+
+Message VncClientConnection::MakeFrameBufferUpdateHeader(
+ std::uint16_t num_stripes) {
+ return CreateMessage(std::uint8_t{0}, // message-type
+ std::uint8_t{}, // padding
+ std::uint16_t{num_stripes});
+}
+
+void VncClientConnection::AppendRawStripeHeader(Message* frame_buffer_update,
+ const Stripe& stripe) {
+ static constexpr int32_t kRawEncoding = 0;
+ AppendToMessage(frame_buffer_update, std::uint16_t{stripe.x},
+ std::uint16_t{stripe.y}, std::uint16_t{stripe.width},
+ std::uint16_t{stripe.height}, kRawEncoding);
+}
+
+void VncClientConnection::AppendJpegSize(Message* frame_buffer_update,
+ size_t jpeg_size) {
+ constexpr size_t kJpegSizeOneByteMax = 127;
+ constexpr size_t kJpegSizeTwoByteMax = 16383;
+ constexpr size_t kJpegSizeThreeByteMax = 4194303;
+
+ if (jpeg_size <= kJpegSizeOneByteMax) {
+ AppendToMessage(frame_buffer_update, static_cast<std::uint8_t>(jpeg_size));
+ } else if (jpeg_size <= kJpegSizeTwoByteMax) {
+ auto sz = static_cast<std::uint32_t>(jpeg_size);
+ AppendToMessage(frame_buffer_update,
+ static_cast<std::uint8_t>((sz & 0x7F) | 0x80),
+ static_cast<std::uint8_t>((sz >> 7) & 0xFF));
+ } else {
+ if (jpeg_size > kJpegSizeThreeByteMax) {
+ LOG_FATAL("jpeg size is too big: %d must be under %zu", jpeg_size,
+ kJpegSizeThreeByteMax);
+ }
+ const auto sz = static_cast<std::uint32_t>(jpeg_size);
+ AppendToMessage(frame_buffer_update,
+ static_cast<std::uint8_t>((sz & 0x7F) | 0x80),
+ static_cast<std::uint8_t>(((sz >> 7) & 0x7F) | 0x80),
+ static_cast<std::uint8_t>((sz >> 14) & 0xFF));
+ }
+}
+
+void VncClientConnection::AppendRawStripe(Message* frame_buffer_update,
+ const Stripe& stripe) const {
+ using Pixel = VSoCFrameBuffer::Pixel;
+ auto& fbu = *frame_buffer_update;
+ AppendRawStripeHeader(&fbu, stripe);
+ auto init_size = fbu.size();
+ fbu.insert(fbu.end(), stripe.raw_data.begin(), stripe.raw_data.end());
+ for (size_t i = init_size; i < fbu.size(); i += sizeof(Pixel)) {
+ ALOG_ASSERT((i + sizeof(Pixel)) < fbu.size());
+ Pixel raw_pixel{};
+ std::memcpy(&raw_pixel, &fbu[i], sizeof raw_pixel);
+ auto red = RedVal(raw_pixel);
+ auto green = GreenVal(raw_pixel);
+ auto blue = BlueVal(raw_pixel);
+ Pixel pixel = Pixel{red} << pixel_format_.red_shift |
+ Pixel{blue} << pixel_format_.blue_shift |
+ Pixel{green} << pixel_format_.green_shift;
+
+ if (bool(pixel_format_.big_endian) != ImBigEndian()) {
+ // flip them bits (refactor into function)
+ auto p = reinterpret_cast<char*>(&pixel);
+ std::swap(p[0], p[3]);
+ std::swap(p[1], p[2]);
+ }
+ ALOG_ASSERT(i + sizeof pixel <= fbu.size());
+ std::memcpy(&fbu[i], &pixel, sizeof pixel);
+ }
+}
+
+Message VncClientConnection::MakeRawFrameBufferUpdate(
+ const StripePtrVec& stripes) const {
+ auto fbu =
+ MakeFrameBufferUpdateHeader(static_cast<std::uint16_t>(stripes.size()));
+ for (auto& stripe : stripes) {
+ AppendRawStripe(&fbu, *stripe);
+ }
+ return fbu;
+}
+
+void VncClientConnection::AppendJpegStripeHeader(Message* frame_buffer_update,
+ const Stripe& stripe) {
+ static constexpr std::uint8_t kJpegEncoding = 0x90;
+ AppendToMessage(frame_buffer_update, stripe.x, stripe.y, stripe.width,
+ stripe.height, kTightEncoding, kJpegEncoding);
+ AppendJpegSize(frame_buffer_update, stripe.jpeg_data.size());
+}
+
+void VncClientConnection::AppendJpegStripe(Message* frame_buffer_update,
+ const Stripe& stripe) {
+ AppendJpegStripeHeader(frame_buffer_update, stripe);
+ frame_buffer_update->insert(frame_buffer_update->end(),
+ stripe.jpeg_data.begin(), stripe.jpeg_data.end());
+}
+
+Message VncClientConnection::MakeJpegFrameBufferUpdate(
+ const StripePtrVec& stripes) {
+ auto fbu =
+ MakeFrameBufferUpdateHeader(static_cast<std::uint16_t>(stripes.size()));
+ for (auto& stripe : stripes) {
+ AppendJpegStripe(&fbu, *stripe);
+ }
+ return fbu;
+}
+
+Message VncClientConnection::MakeFrameBufferUpdate(
+ const StripePtrVec& stripes) {
+ return use_jpeg_compression_ ? MakeJpegFrameBufferUpdate(stripes)
+ : MakeRawFrameBufferUpdate(stripes);
+}
+
+void VncClientConnection::FrameBufferUpdateRequestHandler(bool aggressive) {
+ BlackBoard::Registerer reg(bb_, this);
+ const StripeSeqNumber kBeginningOfTime{};
+
+ while (!closed()) {
+ auto stripes = bb_->WaitForSenderWork(this);
+ if (closed()) {
+ break;
+ }
+ LOG_ALWAYS_FATAL_IF(stripes.empty(), "Got 0 stripes");
+ {
+ // lock here so a portrait frame can't be sent after a landscape
+ // DesktopSize update, or vice versa.
+ std::lock_guard<std::mutex> guard(m_);
+ D("Sending update in %s mode",
+ current_orientation_ == ScreenOrientation::Portrait ? "portrait"
+ : "landscape");
+ client_.Send(MakeFrameBufferUpdate(stripes));
+ }
+ if (aggressive) {
+ bb_->FrameBufferUpdateRequestReceived(this);
+ }
+ }
+}
+
+void VncClientConnection::SendDesktopSizeUpdate() {
+ static constexpr int32_t kDesktopSizeEncoding = -223;
+ client_.Send(CreateMessage(std::uint8_t{0}, // message-type,
+ std::uint8_t{}, // padding
+ std::uint16_t{1}, // one pseudo rectangle
+ std::uint16_t{0}, std::uint16_t{0},
+ static_cast<std::uint16_t>(ScreenWidth()),
+ static_cast<std::uint16_t>(ScreenHeight()),
+ kDesktopSizeEncoding));
+}
+
+bool VncClientConnection::IsUrgent(
+ const FrameBufferUpdateRequest& update_request) const {
+ return !update_request.incremental ||
+ update_request != previous_update_request_;
+}
+
+void VncClientConnection::HandleFramebufferUpdateRequest() {
+ auto msg = client_.Recv(kFramebufferUpdateRequestLength);
+ if (msg.size() != kFramebufferUpdateRequestLength) {
+ return;
+ }
+ FrameBufferUpdateRequest fbur{msg[1] == 0, uint16_tAt(&msg[1]),
+ uint16_tAt(&msg[3]), uint16_tAt(&msg[5]),
+ uint16_tAt(&msg[7])};
+ if (IsUrgent(fbur)) {
+ bb_->SignalClientNeedsEntireScreen(this);
+ }
+ bb_->FrameBufferUpdateRequestReceived(this);
+ previous_update_request_ = fbur;
+}
+
+void VncClientConnection::HandleSetEncodings() {
+ auto msg = client_.Recv(kSetEncodingsLength);
+ if (msg.size() != kSetEncodingsLength) {
+ return;
+ }
+ auto count = uint16_tAt(&msg[1]);
+ auto encodings = client_.Recv(count * sizeof(int32_t));
+ if (encodings.size() % sizeof(int32_t) != 0) {
+ return;
+ }
+ {
+ std::lock_guard<std::mutex> guard(m_);
+ use_jpeg_compression_ = false;
+ }
+ for (size_t i = 0; i < encodings.size(); i += sizeof(int32_t)) {
+ auto enc = int32_tAt(&encodings[i]);
+ D("client requesting encoding: %d\n", enc);
+ if (enc == kTightEncoding) {
+ // This is a deviation from the spec which says that if a jpeg quality
+ // level is not specified, tight encoding won't use jpeg.
+ std::lock_guard<std::mutex> guard(m_);
+ use_jpeg_compression_ = true;
+ }
+ if (kJpegMinQualityEncoding <= enc && enc <= kJpegMaxQualityEncoding) {
+ D("jpeg compression level: %d", enc);
+ bb_->set_jpeg_quality_level(enc);
+ }
+ if (enc == kDesktopSizeEncoding) {
+ supports_desktop_size_encoding_ = true;
+ }
+ }
+}
+
+void VncClientConnection::HandleSetPixelFormat() {
+ std::lock_guard<std::mutex> guard(m_);
+ auto msg = client_.Recv(kSetPixelFormatLength);
+ if (msg.size() != kSetPixelFormatLength) {
+ return;
+ }
+ pixel_format_.bits_per_pixel = msg[3];
+ pixel_format_.depth = msg[4];
+ pixel_format_.big_endian = msg[5];
+ pixel_format_.true_color = msg[7];
+ pixel_format_.red_max = uint16_tAt(&msg[8]);
+ pixel_format_.green_max = uint16_tAt(&msg[10]);
+ pixel_format_.blue_max = uint16_tAt(&msg[12]);
+ pixel_format_.red_shift = msg[13];
+ pixel_format_.green_shift = msg[14];
+ pixel_format_.blue_shift = msg[15];
+}
+
+void VncClientConnection::HandlePointerEvent() {
+ auto msg = client_.Recv(kPointerEventLength);
+ if (msg.size() != kPointerEventLength) {
+ return;
+ }
+ std::uint8_t button_mask = msg[0];
+ auto x_pos = uint16_tAt(&msg[1]);
+ auto y_pos = uint16_tAt(&msg[3]);
+ {
+ std::lock_guard<std::mutex> guard(m_);
+ if (current_orientation_ == ScreenOrientation::Landscape) {
+ std::tie(x_pos, y_pos) =
+ std::make_pair(ActualScreenWidth() - y_pos, x_pos);
+ }
+ }
+ virtual_inputs_->HandlePointerEvent(button_mask, x_pos, y_pos);
+}
+
+void VncClientConnection::UpdateAccelerometer(float x, float y, float z) {
+ // TODO(jemoreira): Implement when vsoc sensor hal is updated
+}
+
+VncClientConnection::Coordinates VncClientConnection::CoordinatesForOrientation(
+ ScreenOrientation orientation) const {
+ // Compute the acceleration vector that we need to send to mimic
+ // this change.
+ constexpr float g = 9.81;
+ constexpr float angle = 20.0;
+ const float cos_angle = std::cos(angle / M_PI);
+ const float sin_angle = std::sin(angle / M_PI);
+ const float z = g * sin_angle;
+ switch (orientation) {
+ case ScreenOrientation::Portrait:
+ return {0, g * cos_angle, z};
+ case ScreenOrientation::Landscape:
+ return {g * cos_angle, 0, z};
+ }
+}
+
+int VncClientConnection::ScreenWidth() const {
+ return current_orientation_ == ScreenOrientation::Portrait
+ ? ActualScreenWidth()
+ : ActualScreenHeight();
+}
+
+int VncClientConnection::ScreenHeight() const {
+ return current_orientation_ == ScreenOrientation::Portrait
+ ? ActualScreenHeight()
+ : ActualScreenWidth();
+}
+
+void VncClientConnection::SetScreenOrientation(ScreenOrientation orientation) {
+ std::lock_guard<std::mutex> guard(m_);
+ auto coords = CoordinatesForOrientation(orientation);
+ UpdateAccelerometer(coords.x, coords.y, coords.z);
+ if (supports_desktop_size_encoding_) {
+ auto previous_orientation = current_orientation_;
+ current_orientation_ = orientation;
+ if (current_orientation_ != previous_orientation &&
+ supports_desktop_size_encoding_) {
+ SendDesktopSizeUpdate();
+ bb_->SetOrientation(this, current_orientation_);
+ // TODO not sure if I should be sending a frame update along with this,
+ // or just letting the next FBUR handle it. This seems to me like it's
+ // sending one more frame buffer update than was requested, which is
+ // maybe a violation of the spec?
+ }
+ }
+}
+
+bool VncClientConnection::RotateIfIsRotationCommand(std::uint32_t key) {
+ // Due to different configurations on different platforms we're supporting
+ // a set of options for rotating the screen. These are similar to what
+ // the emulator supports and has supported.
+ // ctrl+left and ctrl+right work on windows and linux
+ // command+left and command+right work on Mac
+ // ctrl+fn+F11 and ctrl+fn+F12 work when chromoting to ubuntu from a Mac
+ if (!control_key_down_ && !meta_key_down_) {
+ return false;
+ }
+ switch (key) {
+ case cvd::xk::Right:
+ case cvd::xk::F12:
+ D("switching to portrait");
+ SetScreenOrientation(ScreenOrientation::Portrait);
+ break;
+ case cvd::xk::Left:
+ case cvd::xk::F11:
+ D("switching to landscape");
+ SetScreenOrientation(ScreenOrientation::Landscape);
+ break;
+ default:
+ return false;
+ }
+ return true;
+}
+
+void VncClientConnection::HandleKeyEvent() {
+ auto msg = client_.Recv(kKeyEventLength);
+ if (msg.size() != kKeyEventLength) {
+ return;
+ }
+
+ auto key = uint32_tAt(&msg[3]);
+ bool key_down = msg[0];
+ switch (key) {
+ case cvd::xk::ControlLeft:
+ case cvd::xk::ControlRight:
+ control_key_down_ = key_down;
+ break;
+ case cvd::xk::MetaLeft:
+ case cvd::xk::MetaRight:
+ meta_key_down_ = key_down;
+ break;
+ case cvd::xk::F5:
+ key = cvd::xk::Menu;
+ break;
+ case cvd::xk::F7:
+ virtual_inputs_->PressPowerButton(key_down);
+ return;
+ default:
+ break;
+ }
+
+ if (RotateIfIsRotationCommand(key)) {
+ return;
+ }
+
+ virtual_inputs_->GenerateKeyPressEvent(key, key_down);
+}
+
+void VncClientConnection::HandleClientCutText() {
+ auto msg = client_.Recv(kClientCutTextLength);
+ if (msg.size() != kClientCutTextLength) {
+ return;
+ }
+ auto len = uint32_tAt(&msg[3]);
+ client_.Recv(len);
+}
+
+void VncClientConnection::NormalSession() {
+ static constexpr std::uint8_t kSetPixelFormatMessage{0};
+ static constexpr std::uint8_t kSetEncodingsMessage{2};
+ static constexpr std::uint8_t kFramebufferUpdateRequestMessage{3};
+ static constexpr std::uint8_t kKeyEventMessage{4};
+ static constexpr std::uint8_t kPointerEventMessage{5};
+ static constexpr std::uint8_t kClientCutTextMessage{6};
+ while (true) {
+ if (client_.closed()) {
+ return;
+ }
+ auto msg = client_.Recv(1);
+ if (client_.closed()) {
+ return;
+ }
+ auto msg_type = msg.front();
+ D("Received message type %d\n", static_cast<int>(msg_type));
+
+ switch (msg_type) {
+ case kSetPixelFormatMessage:
+ HandleSetPixelFormat();
+ break;
+
+ case kSetEncodingsMessage:
+ HandleSetEncodings();
+ break;
+
+ case kFramebufferUpdateRequestMessage:
+ HandleFramebufferUpdateRequest();
+ break;
+
+ case kKeyEventMessage:
+ HandleKeyEvent();
+ break;
+
+ case kPointerEventMessage:
+ HandlePointerEvent();
+ break;
+
+ case kClientCutTextMessage:
+ HandleClientCutText();
+ break;
+
+ default:
+ ALOGW("message type not handled: %d", static_cast<int>(msg_type));
+ break;
+ }
+ }
+}
diff --git a/guest/frontend/vnc_server/vnc_client_connection.h b/guest/frontend/vnc_server/vnc_client_connection.h
new file mode 100644
index 0000000..b2fe6e7
--- /dev/null
+++ b/guest/frontend/vnc_server/vnc_client_connection.h
@@ -0,0 +1,169 @@
+#pragma once
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include "blackboard.h"
+#include "virtual_inputs.h"
+#include "vnc_utils.h"
+#include "tcp_socket.h"
+
+#include <android-base/thread_annotations.h>
+#include <common/libs/fs/shared_fd.h>
+
+#include <cstdint>
+#include <memory>
+#include <mutex>
+#include <thread>
+#include <vector>
+
+namespace cvd {
+namespace vnc {
+
+class VncClientConnection {
+ public:
+ VncClientConnection(ClientSocket client, VirtualInputs* virtual_inputs,
+ BlackBoard* bb, bool aggressive);
+ VncClientConnection(const VncClientConnection&) = delete;
+ VncClientConnection& operator=(const VncClientConnection&) = delete;
+ ~VncClientConnection();
+
+ void StartSession();
+
+ private:
+ struct PixelFormat {
+ std::uint8_t bits_per_pixel;
+ std::uint8_t depth;
+ std::uint8_t big_endian;
+ std::uint8_t true_color;
+ std::uint16_t red_max;
+ std::uint16_t green_max;
+ std::uint16_t blue_max;
+ std::uint8_t red_shift;
+ std::uint8_t green_shift;
+ std::uint8_t blue_shift;
+ };
+
+ struct FrameBufferUpdateRequest {
+ bool incremental;
+ std::uint16_t x_pos;
+ std::uint16_t y_pos;
+ std::uint16_t width;
+ std::uint16_t height;
+ };
+
+ friend bool operator==(const FrameBufferUpdateRequest&,
+ const FrameBufferUpdateRequest&);
+ friend bool operator!=(const FrameBufferUpdateRequest&,
+ const FrameBufferUpdateRequest&);
+
+ bool closed();
+ void SetupProtocol();
+ void SetupSecurityType();
+
+ void GetClientInit();
+
+ void SendServerInit() EXCLUDES(m_);
+ static Message MakeFrameBufferUpdateHeader(std::uint16_t num_stripes);
+
+ static void AppendRawStripeHeader(Message* frame_buffer_update,
+ const Stripe& stripe);
+ void AppendRawStripe(Message* frame_buffer_update, const Stripe& stripe) const
+ REQUIRES(m_);
+ Message MakeRawFrameBufferUpdate(const StripePtrVec& stripes) const
+ REQUIRES(m_);
+
+ static void AppendJpegSize(Message* frame_buffer_update, size_t jpeg_size);
+ static void AppendJpegStripeHeader(Message* frame_buffer_update,
+ const Stripe& stripe);
+ static void AppendJpegStripe(Message* frame_buffer_update,
+ const Stripe& stripe);
+ static Message MakeJpegFrameBufferUpdate(const StripePtrVec& stripes);
+
+ Message MakeFrameBufferUpdate(const StripePtrVec& frame) REQUIRES(m_);
+
+ void FrameBufferUpdateRequestHandler(bool aggressive) EXCLUDES(m_);
+
+ void SendDesktopSizeUpdate() REQUIRES(m_);
+
+ bool IsUrgent(const FrameBufferUpdateRequest& update_request) const;
+ static StripeSeqNumber MostRecentStripeSeqNumber(const StripePtrVec& stripes);
+
+ void HandleFramebufferUpdateRequest() EXCLUDES(m_);
+
+ void HandleSetEncodings();
+
+ void HandleSetPixelFormat();
+
+ void HandlePointerEvent() EXCLUDES(m_);
+
+ void UpdateAccelerometer(float x, float y, float z);
+
+ struct Coordinates {
+ float x;
+ float y;
+ float z;
+ };
+
+ Coordinates CoordinatesForOrientation(ScreenOrientation orientation) const;
+
+ int ScreenWidth() const REQUIRES(m_);
+
+ int ScreenHeight() const REQUIRES(m_);
+
+ void SetScreenOrientation(ScreenOrientation orientation) EXCLUDES(m_);
+
+ // Returns true if key is special and the screen was rotated.
+ bool RotateIfIsRotationCommand(std::uint32_t key);
+
+ void HandleKeyEvent();
+
+ void HandleClientCutText();
+
+ void NormalSession();
+
+ mutable std::mutex m_;
+ ClientSocket client_;
+ bool control_key_down_ = false;
+ bool meta_key_down_ = false;
+ VirtualInputs* virtual_inputs_{};
+
+ FrameBufferUpdateRequest previous_update_request_{};
+ BlackBoard* bb_;
+ bool use_jpeg_compression_ GUARDED_BY(m_) = false;
+
+ std::thread frame_buffer_request_handler_tid_;
+ bool closed_ GUARDED_BY(m_){};
+
+ PixelFormat pixel_format_ GUARDED_BY(m_) = {
+ std::uint8_t{32}, // bits per pixel
+ std::uint8_t{8}, // depth
+ std::uint8_t{}, // big_endian
+ std::uint8_t{}, // true_color
+ std::uint16_t{}, // red_max, (maxes not used when true color flag is 0)
+ std::uint16_t{}, // green_max
+ std::uint16_t{}, // blue_max
+ std::uint8_t{}, // red_shift (shifts not used when true color flag is 0)
+ std::uint8_t{}, // green_shift
+ std::uint8_t{}, // blue_shift
+ };
+
+ bool supports_desktop_size_encoding_ = false;
+ ScreenOrientation current_orientation_ GUARDED_BY(m_) =
+ ScreenOrientation::Portrait;
+};
+
+} // namespace vnc
+} // namespace cvd
diff --git a/guest/frontend/vnc_server/vnc_server.cpp b/guest/frontend/vnc_server/vnc_server.cpp
new file mode 100644
index 0000000..4b1ce89
--- /dev/null
+++ b/guest/frontend/vnc_server/vnc_server.cpp
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include "blackboard.h"
+#include "frame_buffer_watcher.h"
+#include "jpeg_compressor.h"
+#include "tcp_socket.h"
+#include "virtual_inputs.h"
+#include "vnc_client_connection.h"
+#include "vnc_server.h"
+#include "vnc_utils.h"
+
+using cvd::vnc::VncServer;
+
+VncServer::VncServer(int port, bool aggressive)
+ : server_(port), frame_buffer_watcher_{&bb_}, aggressive_{aggressive} {}
+
+void VncServer::MainLoop() {
+ while (true) {
+ auto connection = server_.Accept();
+ StartClient(std::move(connection));
+ }
+}
+
+void VncServer::StartClient(ClientSocket sock) {
+ std::thread t(&VncServer::StartClientThread, this, std::move(sock));
+ t.detach();
+}
+
+void VncServer::StartClientThread(ClientSocket sock) {
+ // NOTE if VncServer is expected to be destroyed, we have a problem here.
+ // All of the client threads will be pointing to the VncServer's
+ // data members. In the current setup, if the VncServer is destroyed with
+ // clients still running, the clients will all be left with dangling
+ // pointers.
+ VncClientConnection client(std::move(sock), &virtual_inputs_, &bb_,
+ aggressive_);
+ client.StartSession();
+}
diff --git a/guest/frontend/vnc_server/vnc_server.h b/guest/frontend/vnc_server/vnc_server.h
new file mode 100644
index 0000000..f50947e
--- /dev/null
+++ b/guest/frontend/vnc_server/vnc_server.h
@@ -0,0 +1,55 @@
+#pragma once
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include "blackboard.h"
+#include "frame_buffer_watcher.h"
+#include "jpeg_compressor.h"
+#include "tcp_socket.h"
+#include "virtual_inputs.h"
+#include "vnc_client_connection.h"
+#include "vnc_utils.h"
+
+#include <thread>
+#include <string>
+#include <utility>
+
+namespace cvd {
+namespace vnc {
+
+class VncServer {
+ public:
+ explicit VncServer(int port, bool aggressive);
+
+ VncServer(const VncServer&) = delete;
+ VncServer& operator=(const VncServer&) = delete;
+
+ [[noreturn]] void MainLoop();
+
+ private:
+ void StartClient(ClientSocket sock);
+
+ void StartClientThread(ClientSocket sock);
+
+ ServerSocket server_;
+ VirtualInputs virtual_inputs_;
+ BlackBoard bb_;
+ FrameBufferWatcher frame_buffer_watcher_;
+ bool aggressive_{};
+};
+
+} // namespace vnc
+} // namespace cvd
diff --git a/guest/frontend/vnc_server/vnc_utils.h b/guest/frontend/vnc_server/vnc_utils.h
new file mode 100644
index 0000000..5a996a9
--- /dev/null
+++ b/guest/frontend/vnc_server/vnc_utils.h
@@ -0,0 +1,94 @@
+#pragma once
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include <guest/libs/legacy_framebuffer/vsoc_framebuffer.h>
+
+#include <vector>
+#include <array>
+#include <utility>
+#include <cstdint>
+
+#undef D
+#ifdef VSOC_VNC_DEBUG
+#define D(...) ALOGD(__VA_ARGS__)
+#else
+#define D(...) ((void)0)
+#endif
+
+namespace cvd {
+namespace vnc {
+
+// TODO(haining) when the hwcomposer gives a sequence number type, use that
+// instead. It might just work to replace this class with a type alias
+// using StripeSeqNumber = whatever_the_hwcomposer_uses;
+class StripeSeqNumber {
+ public:
+ StripeSeqNumber() = default;
+ explicit StripeSeqNumber(std::uint64_t t) : t_{t} {}
+ bool operator<(const StripeSeqNumber& other) const {
+ return t_ < other.t_;
+ }
+
+ bool operator<=(const StripeSeqNumber& other) const {
+ return t_ <= other.t_;
+ }
+
+ private:
+ std::uint64_t t_{};
+};
+
+using Message = std::vector<std::uint8_t>;
+
+constexpr int32_t kJpegMaxQualityEncoding = -23;
+constexpr int32_t kJpegMinQualityEncoding = -32;
+
+enum class ScreenOrientation { Portrait, Landscape };
+constexpr int kNumOrientations = 2;
+
+struct Stripe {
+ int index = -1;
+ std::uint64_t frame_id{};
+ std::uint16_t x{};
+ std::uint16_t y{};
+ std::uint16_t width{};
+ std::uint16_t height{};
+ Message raw_data{};
+ Message jpeg_data{};
+ StripeSeqNumber seq_number{};
+ ScreenOrientation orientation{};
+};
+
+inline constexpr int BytesPerPixel() {
+ return sizeof(VSoCFrameBuffer::Pixel);
+}
+
+// The width of the screen regardless of orientation. Does not change.
+inline int ActualScreenWidth() {
+ return VSoCFrameBuffer::getInstance().x_res();
+}
+
+// The height of the screen regardless of orientation. Does not change.
+inline int ActualScreenHeight() {
+ return VSoCFrameBuffer::getInstance().y_res();
+}
+
+inline int ScreenSizeInBytes() {
+ return ActualScreenWidth() * ActualScreenHeight() * BytesPerPixel();
+}
+
+} // namespace vnc
+} // namespace cvd
diff --git a/guest/hals/camera/Android.mk b/guest/hals/camera/Android.mk
new file mode 100644
index 0000000..ced3f91
--- /dev/null
+++ b/guest/hals/camera/Android.mk
@@ -0,0 +1,154 @@
+# Copyright (C) 2017 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.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+# Emulator camera module########################################################
+
+emulator_camera_module_relative_path := hw
+emulator_camera_cflags := -fno-short-enums $(VSOC_VERSION_CFLAGS)
+emulator_camera_cflags += -Wno-unused-parameter -Wno-missing-field-initializers
+emulator_camera_clang_flags := -Wno-c++11-narrowing
+emulator_camera_shared_libraries := \
+ libbase \
+ libbinder \
+ liblog \
+ libutils \
+ libcutils \
+ libcamera_client \
+ libui \
+ libdl \
+ libjpeg \
+ libcamera_metadata \
+ libhardware
+
+emulator_camera_static_libraries := \
+ libjsoncpp
+
+emulator_camera_c_includes := \
+ device/google/cuttlefish_common \
+ frameworks/native/include/media/hardware \
+ $(call include-path-for, camera) \
+
+emulator_camera_src := \
+ CameraConfiguration.cpp \
+ EmulatedCameraHal.cpp \
+ EmulatedCameraFactory.cpp \
+ EmulatedBaseCamera.cpp \
+ EmulatedCamera.cpp \
+ EmulatedCameraDevice.cpp \
+ EmulatedFakeCamera.cpp \
+ EmulatedFakeCameraDevice.cpp \
+ Converters.cpp \
+ PreviewWindow.cpp \
+ CallbackNotifier.cpp \
+ JpegCompressor.cpp
+emulated_camera2_src := \
+ VSoCEmulatedCameraHotplugThread.cpp \
+ EmulatedCamera2.cpp \
+ EmulatedFakeCamera2.cpp \
+ fake-pipeline2/Scene.cpp \
+ fake-pipeline2/Sensor.cpp \
+ fake-pipeline2/JpegCompressor.cpp
+emulated_camera3_src := \
+ EmulatedCamera3.cpp \
+ EmulatedFakeCamera3.cpp
+
+
+emulated_camera2_stub_src := \
+ StubEmulatedCamera2.cpp \
+ StubEmulatedFakeCamera2.cpp
+
+enable_emulated_camera3 = $(shell test $(PLATFORM_SDK_VERSION) -ge 23 && echo yes)
+enable_emulated_camera2 = $(shell test $(PLATFORM_SDK_VERSION) -ge 19 && echo yes)
+
+# Emulated camera - goldfish / vbox_x86 build###################################
+#
+ifeq (0, $(shell test $(PLATFORM_SDK_VERSION) -ge 24; echo $$?))
+emulator_camera_c_includes += external/libjpeg-turbo
+else
+emulator_camera_c_includes += external/jpeg
+endif
+
+LOCAL_MODULE_RELATIVE_PATH := ${emulator_camera_module_relative_path}
+LOCAL_MULTILIB := first
+LOCAL_CFLAGS := ${emulator_camera_cflags}
+LOCAL_CLANG_CFLAGS += ${emulator_camera_clang_flags}
+
+LOCAL_SHARED_LIBRARIES := ${emulator_camera_shared_libraries}
+LOCAL_STATIC_LIBRARIES := ${emulator_camera_static_libraries}
+LOCAL_C_INCLUDES += ${emulator_camera_c_includes}
+LOCAL_SRC_FILES := ${emulator_camera_src} ${emulator_camera_ext_src} \
+ $(if $(enable_emulated_camera2),$(emulated_camera2_src),) \
+ $(if $(enable_emulated_camera3),$(emulated_camera3_src),)
+
+LOCAL_MODULE := camera.vsoc
+LOCAL_MODULE_TAGS := optional
+LOCAL_VENDOR_MODULE := true
+
+include $(BUILD_SHARED_LIBRARY)
+
+# JPEG stub#####################################################################
+
+include $(CLEAR_VARS)
+
+jpeg_module_relative_path := hw
+jpeg_cflags := -fno-short-enums
+jpeg_cflags += -Wno-unused-parameter
+jpeg_clang_flags += -Wno-c++11-narrowing
+jpeg_shared_libraries := \
+ libbase \
+ libcutils \
+ liblog \
+ libskia \
+ libjpeg \
+ libandroid_runtime \
+ cuttlefish_auto_resources
+jpeg_static_libraries := libyuv_static
+jpeg_c_includes := \
+ device/google/cuttlefish_common \
+ external/libyuv/files/include \
+ external/skia/include/core/ \
+ frameworks/base/core/jni/android/graphics \
+ frameworks/native/include
+jpeg_src := \
+ JpegStub.cpp \
+ ExifMetadataBuilder.cpp
+
+# JPEG stub - goldfish build####################################################
+
+ifeq (0, $(shell test $(PLATFORM_SDK_VERSION) -ge 24; echo $$?))
+jpeg_c_includes += external/libjpeg-turbo
+else
+jpeg_c_includes += external/jpeg
+endif
+
+LOCAL_MODULE_RELATIVE_PATH := ${emulator_camera_module_relative_path}
+LOCAL_MULTILIB := first
+LOCAL_CFLAGS += ${jpeg_cflags}
+LOCAL_CLANG_CFLAGS += ${jpeg_clangflags}
+
+LOCAL_SHARED_LIBRARIES := ${jpeg_shared_libraries}
+LOCAL_STATIC_LIBRARIES := ${jpeg_static_libraries}
+LOCAL_C_INCLUDES += ${jpeg_c_includes}
+LOCAL_SRC_FILES := ${jpeg_src}
+
+LOCAL_MODULE := camera.vsoc.jpeg
+LOCAL_MODULE_TAGS := optional
+LOCAL_VENDOR_MODULE := true
+
+include $(BUILD_SHARED_LIBRARY)
+
diff --git a/guest/hals/camera/CallbackNotifier.cpp b/guest/hals/camera/CallbackNotifier.cpp
new file mode 100755
index 0000000..5ac5a6b
--- /dev/null
+++ b/guest/hals/camera/CallbackNotifier.cpp
@@ -0,0 +1,311 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+/*
+ * Contains implementation of a class CallbackNotifier that manages callbacks set
+ * via set_callbacks, enable_msg_type, and disable_msg_type camera HAL API.
+ */
+
+#define LOG_NDEBUG 0
+#define LOG_TAG "EmulatedCamera_CallbackNotifier"
+#include <cutils/log.h>
+#include <MetadataBufferType.h>
+#include "EmulatedCameraDevice.h"
+#include "CallbackNotifier.h"
+#include "ImageMetadata.h"
+#include "JpegCompressor.h"
+
+namespace android {
+
+/* String representation of camera messages. */
+static const char* lCameraMessages[] =
+{
+ "CAMERA_MSG_ERROR",
+ "CAMERA_MSG_SHUTTER",
+ "CAMERA_MSG_FOCUS",
+ "CAMERA_MSG_ZOOM",
+ "CAMERA_MSG_PREVIEW_FRAME",
+ "CAMERA_MSG_VIDEO_FRAME",
+ "CAMERA_MSG_POSTVIEW_FRAME",
+ "CAMERA_MSG_RAW_IMAGE",
+ "CAMERA_MSG_COMPRESSED_IMAGE",
+ "CAMERA_MSG_RAW_IMAGE_NOTIFY",
+ "CAMERA_MSG_PREVIEW_METADATA"
+};
+static const int lCameraMessagesNum = sizeof(lCameraMessages) / sizeof(char*);
+
+/* Builds an array of strings for the given set of messages.
+ * Param:
+ * msg - Messages to get strings for,
+ * strings - Array where to save strings
+ * max - Maximum number of entries in the array.
+ * Return:
+ * Number of strings saved into the 'strings' array.
+ */
+static int GetMessageStrings(uint32_t msg, const char** strings, int max)
+{
+ int index = 0;
+ int out = 0;
+ while (msg != 0 && out < max && index < lCameraMessagesNum) {
+ while ((msg & 0x1) == 0 && index < lCameraMessagesNum) {
+ msg >>= 1;
+ index++;
+ }
+ if ((msg & 0x1) != 0 && index < lCameraMessagesNum) {
+ strings[out] = lCameraMessages[index];
+ out++;
+ msg >>= 1;
+ index++;
+ }
+ }
+
+ return out;
+}
+
+/* Logs messages, enabled by the mask. */
+static void PrintMessages(uint32_t msg)
+{
+ const char* strs[lCameraMessagesNum];
+ const int translated = GetMessageStrings(msg, strs, lCameraMessagesNum);
+ for (int n = 0; n < translated; n++) {
+ ALOGV(" %s", strs[n]);
+ }
+}
+
+CallbackNotifier::CallbackNotifier()
+ : mNotifyCB(NULL),
+ mDataCB(NULL),
+ mDataCBTimestamp(NULL),
+ mGetMemoryCB(NULL),
+ mCBOpaque(NULL),
+ mLastFrameTimestamp(0),
+ mFrameRefreshFreq(0),
+ mMessageEnabler(0),
+ mJpegQuality(90),
+ mVideoRecEnabled(false),
+ mTakingPicture(false)
+{
+}
+
+CallbackNotifier::~CallbackNotifier()
+{
+}
+
+/****************************************************************************
+ * Camera API
+ ***************************************************************************/
+
+void CallbackNotifier::setCallbacks(camera_notify_callback notify_cb,
+ camera_data_callback data_cb,
+ camera_data_timestamp_callback data_cb_timestamp,
+ camera_request_memory get_memory,
+ void* user)
+{
+ ALOGV("%s: %p, %p, %p, %p (%p)",
+ __FUNCTION__, notify_cb, data_cb, data_cb_timestamp, get_memory, user);
+
+ Mutex::Autolock locker(&mObjectLock);
+ mNotifyCB = notify_cb;
+ mDataCB = data_cb;
+ mDataCBTimestamp = data_cb_timestamp;
+ mGetMemoryCB = get_memory;
+ mCBOpaque = user;
+}
+
+void CallbackNotifier::enableMessage(uint msg_type)
+{
+ ALOGV("%s: msg_type = 0x%x", __FUNCTION__, msg_type);
+ PrintMessages(msg_type);
+
+ Mutex::Autolock locker(&mObjectLock);
+ mMessageEnabler |= msg_type;
+ ALOGV("**** Currently enabled messages:");
+ PrintMessages(mMessageEnabler);
+}
+
+void CallbackNotifier::disableMessage(uint msg_type)
+{
+ ALOGV("%s: msg_type = 0x%x", __FUNCTION__, msg_type);
+ PrintMessages(msg_type);
+
+ Mutex::Autolock locker(&mObjectLock);
+ mMessageEnabler &= ~msg_type;
+ ALOGV("**** Currently enabled messages:");
+ PrintMessages(mMessageEnabler);
+}
+
+status_t CallbackNotifier::enableVideoRecording(int fps)
+{
+ ALOGV("%s: FPS = %d", __FUNCTION__, fps);
+
+ Mutex::Autolock locker(&mObjectLock);
+ mVideoRecEnabled = true;
+ mLastFrameTimestamp = 0;
+ mFrameRefreshFreq = 1000000000LL / fps;
+
+ return NO_ERROR;
+}
+
+void CallbackNotifier::disableVideoRecording()
+{
+ ALOGV("%s:", __FUNCTION__);
+
+ Mutex::Autolock locker(&mObjectLock);
+ mVideoRecEnabled = false;
+ mLastFrameTimestamp = 0;
+ mFrameRefreshFreq = 0;
+}
+
+void CallbackNotifier::releaseRecordingFrame(const void* opaque)
+{
+ List<camera_memory_t*>::iterator it = mCameraMemoryTs.begin();
+ for( ; it != mCameraMemoryTs.end(); ++it ) {
+ if ( (*it)->data == opaque ) {
+ (*it)->release( *it );
+ mCameraMemoryTs.erase(it);
+ break;
+ }
+ }
+}
+
+status_t CallbackNotifier::storeMetaDataInBuffers(bool enable)
+{
+ // Return error if metadata is request, otherwise silently agree.
+ return enable ? INVALID_OPERATION : NO_ERROR;
+}
+
+/****************************************************************************
+ * Public API
+ ***************************************************************************/
+
+void CallbackNotifier::cleanupCBNotifier()
+{
+ Mutex::Autolock locker(&mObjectLock);
+ mMessageEnabler = 0;
+ mNotifyCB = NULL;
+ mDataCB = NULL;
+ mDataCBTimestamp = NULL;
+ mGetMemoryCB = NULL;
+ mCBOpaque = NULL;
+ mLastFrameTimestamp = 0;
+ mFrameRefreshFreq = 0;
+ mJpegQuality = 90;
+ mVideoRecEnabled = false;
+ mTakingPicture = false;
+}
+
+void CallbackNotifier::onNextFrameAvailable(const void* frame,
+ nsecs_t timestamp,
+ EmulatedCameraDevice* camera_dev)
+{
+ if (isMessageEnabled(CAMERA_MSG_VIDEO_FRAME) && isVideoRecordingEnabled() &&
+ isNewVideoFrameTime(timestamp)) {
+ camera_memory_t* cam_buff =
+ mGetMemoryCB(-1, camera_dev->getFrameBufferSize(), 1, mCBOpaque);
+ if (NULL != cam_buff && NULL != cam_buff->data) {
+ memcpy(cam_buff->data, frame, camera_dev->getFrameBufferSize());
+ mDataCBTimestamp(timestamp, CAMERA_MSG_VIDEO_FRAME,
+ cam_buff, 0, mCBOpaque);
+
+ mCameraMemoryTs.push_back( cam_buff );
+ } else {
+ ALOGE("%s: Memory failure in CAMERA_MSG_VIDEO_FRAME", __FUNCTION__);
+ }
+ }
+
+ if (isMessageEnabled(CAMERA_MSG_PREVIEW_FRAME)) {
+ camera_memory_t* cam_buff =
+ mGetMemoryCB(-1, camera_dev->getFrameBufferSize(), 1, mCBOpaque);
+ if (NULL != cam_buff && NULL != cam_buff->data) {
+ memcpy(cam_buff->data, frame, camera_dev->getFrameBufferSize());
+ mDataCB(CAMERA_MSG_PREVIEW_FRAME, cam_buff, 0, NULL, mCBOpaque);
+ cam_buff->release(cam_buff);
+ } else {
+ ALOGE("%s: Memory failure in CAMERA_MSG_PREVIEW_FRAME", __FUNCTION__);
+ }
+ }
+
+ if (mTakingPicture) {
+ /* This happens just once. */
+ mTakingPicture = false;
+ /* The sequence of callbacks during picture taking is:
+ * - CAMERA_MSG_SHUTTER
+ * - CAMERA_MSG_RAW_IMAGE_NOTIFY
+ * - CAMERA_MSG_COMPRESSED_IMAGE
+ */
+ if (isMessageEnabled(CAMERA_MSG_SHUTTER)) {
+ mNotifyCB(CAMERA_MSG_SHUTTER, 0, 0, mCBOpaque);
+ }
+ if (isMessageEnabled(CAMERA_MSG_RAW_IMAGE_NOTIFY)) {
+ mNotifyCB(CAMERA_MSG_RAW_IMAGE_NOTIFY, 0, 0, mCBOpaque);
+ }
+ if (isMessageEnabled(CAMERA_MSG_COMPRESSED_IMAGE)) {
+ /* Compress the frame to JPEG. Note that when taking pictures, we
+ * have requested camera device to provide us with NV21 frames. */
+ NV21JpegCompressor compressor;
+ struct ::ImageMetadata meta;
+ status_t res =
+ camera_dev->getImageMetadata(&meta);
+ if (res == NO_ERROR) {
+ res = compressor.compressRawImage(frame, &meta, mJpegQuality);
+ if (res == NO_ERROR) {
+ camera_memory_t* jpeg_buff =
+ mGetMemoryCB(-1, compressor.getCompressedSize(), 1, mCBOpaque);
+ if (NULL != jpeg_buff && NULL != jpeg_buff->data) {
+ compressor.getCompressedImage(jpeg_buff->data);
+ mDataCB(CAMERA_MSG_COMPRESSED_IMAGE, jpeg_buff, 0, NULL, mCBOpaque);
+ jpeg_buff->release(jpeg_buff);
+ } else {
+ ALOGE("%s: Memory failure in CAMERA_MSG_VIDEO_FRAME", __FUNCTION__);
+ }
+ } else {
+ ALOGE("%s: Compression failure in CAMERA_MSG_VIDEO_FRAME", __FUNCTION__);
+ }
+ } else {
+ ALOGE("%s: Image Metadata acquisition failure.", __FUNCTION__);
+ }
+ }
+ }
+}
+
+void CallbackNotifier::onCameraDeviceError(int err)
+{
+ if (isMessageEnabled(CAMERA_MSG_ERROR) && mNotifyCB != NULL) {
+ mNotifyCB(CAMERA_MSG_ERROR, err, 0, mCBOpaque);
+ }
+}
+
+void CallbackNotifier::onCameraFocusAcquired() {
+ if (isMessageEnabled(CAMERA_MSG_FOCUS) && mNotifyCB != NULL) {
+ mNotifyCB(CAMERA_MSG_FOCUS, 1, 0, mCBOpaque);
+ }
+}
+
+/****************************************************************************
+ * Private API
+ ***************************************************************************/
+
+bool CallbackNotifier::isNewVideoFrameTime(nsecs_t timestamp)
+{
+ Mutex::Autolock locker(&mObjectLock);
+ if ((timestamp - mLastFrameTimestamp) >= mFrameRefreshFreq) {
+ mLastFrameTimestamp = timestamp;
+ return true;
+ }
+ return false;
+}
+
+}; /* namespace android */
diff --git a/guest/hals/camera/CallbackNotifier.h b/guest/hals/camera/CallbackNotifier.h
new file mode 100755
index 0000000..569ca57
--- /dev/null
+++ b/guest/hals/camera/CallbackNotifier.h
@@ -0,0 +1,242 @@
+/*
+ * Copyright (C) 2011 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 HW_EMULATOR_CAMERA_CALLBACK_NOTIFIER_H
+#define HW_EMULATOR_CAMERA_CALLBACK_NOTIFIER_H
+
+/*
+ * Contains declaration of a class CallbackNotifier that manages callbacks set
+ * via set_callbacks, enable_msg_type, and disable_msg_type camera HAL API.
+ */
+
+#include <utils/List.h>
+
+namespace android {
+
+class EmulatedCameraDevice;
+
+/* Manages callbacks set via set_callbacks, enable_msg_type, and disable_msg_type
+ * camera HAL API.
+ *
+ * Objects of this class are contained in EmulatedCamera objects, and handle
+ * relevant camera API callbacks.
+ * Locking considerations. Apparently, it's not allowed to call callbacks
+ * registered in this class, while holding a lock: recursion is quite possible,
+ * which will cause a deadlock.
+ */
+class CallbackNotifier {
+public:
+ /* Constructs CallbackNotifier instance. */
+ CallbackNotifier();
+
+ /* Destructs CallbackNotifier instance. */
+ ~CallbackNotifier();
+
+ /****************************************************************************
+ * Camera API
+ ***************************************************************************/
+
+public:
+ /* Actual handler for camera_device_ops_t::set_callbacks callback.
+ * This method is called by the containing emulated camera object when it is
+ * handing the camera_device_ops_t::set_callbacks callback.
+ */
+ void setCallbacks(camera_notify_callback notify_cb,
+ camera_data_callback data_cb,
+ camera_data_timestamp_callback data_cb_timestamp,
+ camera_request_memory get_memory,
+ void* user);
+
+ /* Actual handler for camera_device_ops_t::enable_msg_type callback.
+ * This method is called by the containing emulated camera object when it is
+ * handing the camera_device_ops_t::enable_msg_type callback.
+ */
+ void enableMessage(uint msg_type);
+
+ /* Actual handler for camera_device_ops_t::disable_msg_type callback.
+ * This method is called by the containing emulated camera object when it is
+ * handing the camera_device_ops_t::disable_msg_type callback.
+ */
+ void disableMessage(uint msg_type);
+
+ /* Actual handler for camera_device_ops_t::store_meta_data_in_buffers
+ * callback. This method is called by the containing emulated camera object
+ * when it is handing the camera_device_ops_t::store_meta_data_in_buffers
+ * callback.
+ * Return:
+ * NO_ERROR on success, or an appropriate error status.
+ */
+ status_t storeMetaDataInBuffers(bool enable);
+
+ /* Enables video recording.
+ * This method is called by the containing emulated camera object when it is
+ * handing the camera_device_ops_t::start_recording callback.
+ * Param:
+ * fps - Video frame frequency. This parameter determins when a frame
+ * received via onNextFrameAvailable call will be pushed through the
+ * callback.
+ * Return:
+ * NO_ERROR on success, or an appropriate error status.
+ */
+ status_t enableVideoRecording(int fps);
+
+ /* Disables video recording.
+ * This method is called by the containing emulated camera object when it is
+ * handing the camera_device_ops_t::stop_recording callback.
+ */
+ void disableVideoRecording();
+
+ /* Releases video frame, sent to the framework.
+ * This method is called by the containing emulated camera object when it is
+ * handing the camera_device_ops_t::release_recording_frame callback.
+ */
+ void releaseRecordingFrame(const void* opaque);
+
+ /* Actual handler for camera_device_ops_t::msg_type_enabled callback.
+ * This method is called by the containing emulated camera object when it is
+ * handing the camera_device_ops_t::msg_type_enabled callback.
+ * Note: this method doesn't grab a lock while checking message status, since
+ * upon exit the status would be undefined anyway. So, grab a lock before
+ * calling this method if you care about persisting a defined message status.
+ * Return:
+ * 0 if message is disabled, or non-zero value, if message is enabled.
+ */
+ inline int isMessageEnabled(uint msg_type)
+ {
+ return mMessageEnabler & msg_type;
+ }
+
+ /* Checks id video recording is enabled.
+ * This method is called by the containing emulated camera object when it is
+ * handing the camera_device_ops_t::recording_enabled callback.
+ * Note: this method doesn't grab a lock while checking video recordin status,
+ * since upon exit the status would be undefined anyway. So, grab a lock
+ * before calling this method if you care about persisting of a defined video
+ * recording status.
+ * Return:
+ * true if video recording is enabled, or false if it is disabled.
+ */
+ inline bool isVideoRecordingEnabled()
+ {
+ return mVideoRecEnabled;
+ }
+
+ /****************************************************************************
+ * Public API
+ ***************************************************************************/
+
+public:
+ /* Resets the callback notifier. */
+ void cleanupCBNotifier();
+
+ /* Next frame is available in the camera device.
+ * This is a notification callback that is invoked by the camera device when
+ * a new frame is available.
+ * Note that most likely this method is called in context of a worker thread
+ * that camera device has created for frame capturing.
+ * Param:
+ * frame - Captured frame, or NULL if camera device didn't pull the frame
+ * yet. If NULL is passed in this parameter use GetCurrentFrame method
+ * of the camera device class to obtain the next frame. Also note that
+ * the size of the frame that is passed here (as well as the frame
+ * returned from the GetCurrentFrame method) is defined by the current
+ * frame settings (width + height + pixel format) for the camera device.
+ * timestamp - Frame's timestamp.
+ * camera_dev - Camera device instance that delivered the frame.
+ */
+ void onNextFrameAvailable(const void* frame,
+ nsecs_t timestamp,
+ EmulatedCameraDevice* camera_dev);
+
+ /* Entry point for notifications that occur in camera device.
+ * Param:
+ * err - CAMERA_ERROR_XXX error code.
+ */
+ void onCameraDeviceError(int err);
+
+ /* Reports focus operation completion to camera client.
+ */
+ void onCameraFocusAcquired();
+
+ /* Sets, or resets taking picture state.
+ * This state control whether or not to notify the framework about compressed
+ * image, shutter, and other picture related events.
+ */
+ void setTakingPicture(bool taking)
+ {
+ mTakingPicture = taking;
+ }
+
+ /* Sets JPEG quality used to compress frame during picture taking. */
+ void setJpegQuality(int jpeg_quality)
+ {
+ mJpegQuality = jpeg_quality;
+ }
+
+ /****************************************************************************
+ * Private API
+ ***************************************************************************/
+
+protected:
+ /* Checks if it's time to push new video frame.
+ * Note that this method must be called while object is locked.
+ * Param:
+ * timestamp - Timestamp for the new frame. */
+ bool isNewVideoFrameTime(nsecs_t timestamp);
+
+ /****************************************************************************
+ * Data members
+ ***************************************************************************/
+
+protected:
+ /* Locks this instance for data change. */
+ Mutex mObjectLock;
+
+ /*
+ * Callbacks, registered in set_callbacks.
+ */
+
+ camera_notify_callback mNotifyCB;
+ camera_data_callback mDataCB;
+ camera_data_timestamp_callback mDataCBTimestamp;
+ camera_request_memory mGetMemoryCB;
+ void* mCBOpaque;
+
+ /* video frame queue for the CameraHeapMemory destruction */
+ List<camera_memory_t*> mCameraMemoryTs;
+
+ /* Timestamp when last frame has been delivered to the framework. */
+ nsecs_t mLastFrameTimestamp;
+
+ /* Video frequency in nanosec. */
+ nsecs_t mFrameRefreshFreq;
+
+ /* Message enabler. */
+ uint32_t mMessageEnabler;
+
+ /* JPEG quality used to compress frame during picture taking. */
+ int mJpegQuality;
+
+ /* Video recording status. */
+ bool mVideoRecEnabled;
+
+ /* Picture taking status. */
+ bool mTakingPicture;
+};
+
+}; /* namespace android */
+
+#endif /* HW_EMULATOR_CAMERA_CALLBACK_NOTIFIER_H */
diff --git a/guest/hals/camera/CameraConfiguration.cpp b/guest/hals/camera/CameraConfiguration.cpp
new file mode 100644
index 0000000..8023045
--- /dev/null
+++ b/guest/hals/camera/CameraConfiguration.cpp
@@ -0,0 +1,305 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#include "CameraConfiguration.h"
+
+#define LOG_TAG "CameraConfiguration"
+
+#include <android-base/file.h>
+#include <android-base/strings.h>
+#include <cutils/log.h>
+#include <json/json.h>
+#include <json/reader.h>
+#include <stdlib.h>
+
+namespace cvd {
+namespace {
+////////////////////// Device Personality keys //////////////////////
+//
+// **** Camera ****
+//
+// Example segment (transcribed to constants):
+//
+// kCameraDefinitionsKey: [
+// {
+// kCameraDefinitionOrientationKey: "front",
+// kCameraDefinitionHalVersionKey: "1",
+// kCameraDefinitionResolutionsKey: [
+// {
+// kCameraDefinitionResolutionWidthKey: "1600",
+// kCameraDefinitionResolutionHeightKey: "1200",
+// },
+// {
+// kCameraDefinitionResolutionWidthKey: "1280",
+// kCameraDefinitionResolutionHeightKey: "800",
+// }
+// ]
+// },
+// {
+// kCameraDefinitionOrientationKey: "back",
+// kCameraDefinitionHalVersionKey: "1",
+// kCameraDefinitionResolutionsKey: [
+// {
+// kCameraDefinitionResolutionWidthKey: "1024",
+// kCameraDefinitionResolutionHeightKey: "768",
+// },
+// {
+// kCameraDefinitionResolutionWidthKey: "800",
+// kCameraDefinitionResolutionHeightKey: "600",
+// }
+// ]
+// }
+// ]
+//
+
+// Location of the camera configuration files.
+const char* const kConfigurationFileLocation = "/vendor/etc/config/camera.json";
+
+//
+// Array of camera definitions for all cameras available on the device (array).
+// Top Level Key.
+const char* const kCameraDefinitionsKey = "camera_definitions";
+
+// Camera orientation of currently defined camera (string).
+// Currently supported values:
+// - "back",
+// - "front".
+const char* const kCameraDefinitionOrientationKey = "orientation";
+
+// Camera HAL version of currently defined camera (int).
+// Currently supported values:
+// - 1 (Camera HALv1)
+// - 2 (Camera HALv2)
+// - 3 (Camera HALv3)
+const char* const kCameraDefinitionHalVersionKey = "hal_version";
+
+// Array of resolutions supported by camera (array).
+const char* const kCameraDefinitionResolutionsKey = "resolutions";
+
+// Width of currently defined resolution (int).
+// Must be divisible by 8.
+const char* const kCameraDefinitionResolutionWidthKey = "width";
+
+// Height of currently defined resolution (int).
+// Must be divisible by 8.
+const char* const kCameraDefinitionResolutionHeightKey = "height";
+
+// Convert string value to camera orientation.
+bool ValueToCameraOrientation(
+ const std::string& value,
+ CameraDefinition::Orientation* orientation) {
+ if (value == "back") {
+ *orientation = CameraDefinition::kBack;
+ return true;
+ } else if (value == "front") {
+ *orientation = CameraDefinition::kFront;
+ return true;
+ }
+ ALOGE("%s: Invalid camera orientation: %s.",
+ __FUNCTION__, value.c_str());
+ return false;
+}
+
+// Convert string value to camera HAL version.
+bool ValueToCameraHalVersion(
+ const std::string& value,
+ CameraDefinition::HalVersion* hal_version) {
+ int temp;
+ char* endptr;
+
+ temp = strtol(value.c_str(), &endptr, 10);
+ if (endptr != value.c_str() + value.size()) {
+ ALOGE("%s: Invalid camera HAL version. Expected number, got %s.",
+ __FUNCTION__, value.c_str());
+ return false;
+ }
+
+ switch (temp) {
+ case 1:
+ *hal_version = CameraDefinition::kHalV1;
+ break;
+
+ case 2:
+ *hal_version = CameraDefinition::kHalV2;
+ break;
+
+ case 3:
+ *hal_version = CameraDefinition::kHalV3;
+ break;
+
+ default:
+ ALOGE("%s: Invalid camera HAL version. Version %d not supported.",
+ __FUNCTION__, temp);
+ return false;
+ }
+
+ return true;
+}
+
+bool ValueToCameraResolution(
+ const std::string& width, const std::string& height,
+ CameraDefinition::Resolution* resolution) {
+ char* endptr;
+
+ resolution->width = strtol(width.c_str(), &endptr, 10);
+ if (endptr != width.c_str() + width.size()) {
+ ALOGE("%s: Invalid camera resolution width. Expected number, got %s.",
+ __FUNCTION__, width.c_str());
+ return false;
+ }
+
+ resolution->height = strtol(height.c_str(), &endptr, 10);
+ if (endptr != height.c_str() + height.size()) {
+ ALOGE("%s: Invalid camera resolution height. Expected number, got %s.",
+ __FUNCTION__, height.c_str());
+ return false;
+ }
+
+ // Validate width and height parameters are sane.
+ if (resolution->width <= 0 || resolution->height <= 0) {
+ ALOGE("%s: Invalid camera resolution: %dx%d", __FUNCTION__,
+ resolution->width, resolution->height);
+ return false;
+ }
+
+ // Validate width and height divisible by 8.
+ if ((resolution->width & 7) != 0 || (resolution->height & 7) != 0) {
+ ALOGE("%s: Invalid camera resolution: width and height must be "
+ "divisible by 8, got %dx%d (%dx%d).", __FUNCTION__,
+ resolution->width, resolution->height,
+ resolution->width & 7, resolution->height & 7);
+ return false;
+ }
+
+ return true;
+}
+
+// Process camera definitions.
+// Returns true, if definitions were sane.
+bool ConfigureCameras(
+ const Json::Value& value,
+ std::vector<CameraDefinition>* cameras) {
+ if (!value.isObject()) {
+ ALOGE("%s: Configuration root is not an object", __FUNCTION__);
+ return false;
+ }
+
+ if (!value.isMember(kCameraDefinitionsKey)) return true;
+ for (Json::ValueConstIterator iter = value[kCameraDefinitionsKey].begin();
+ iter != value[kCameraDefinitionsKey].end();
+ ++iter) {
+ cameras->push_back(CameraDefinition());
+ CameraDefinition& camera = cameras->back();
+
+ if (!iter->isObject()) {
+ ALOGE("%s: Camera definition is not an object", __FUNCTION__);
+ continue;
+ }
+
+ // Camera without orientation -> invalid setting.
+ if (!iter->isMember(kCameraDefinitionOrientationKey)) {
+ ALOGE("%s: Invalid camera definition: key %s is missing.",
+ __FUNCTION__, kCameraDefinitionOrientationKey);
+ return false;
+ }
+
+ if (!ValueToCameraOrientation(
+ (*iter)[kCameraDefinitionOrientationKey].asString(),
+ &camera.orientation)) return false;
+
+ // Camera without HAL version -> invalid setting.
+ if (!(*iter).isMember(kCameraDefinitionHalVersionKey)) {
+ ALOGE("%s: Invalid camera definition: key %s is missing.",
+ __FUNCTION__, kCameraDefinitionHalVersionKey);
+ return false;
+ }
+
+ if (!ValueToCameraHalVersion(
+ (*iter)[kCameraDefinitionHalVersionKey].asString(),
+ &camera.hal_version)) return false;
+
+ // Camera without resolutions -> invalid setting.
+ if (!iter->isMember(kCameraDefinitionResolutionsKey)) {
+ ALOGE("%s: Invalid camera definition: key %s is missing.",
+ __FUNCTION__, kCameraDefinitionResolutionsKey);
+ return false;
+ }
+
+ const Json::Value& json_resolutions =
+ (*iter)[kCameraDefinitionResolutionsKey];
+
+ // Resolutions not an array, or an empty array -> invalid setting.
+ if (!json_resolutions.isArray() || json_resolutions.empty()) {
+ ALOGE("%s: Invalid camera definition: %s is not an array or is empty.",
+ __FUNCTION__, kCameraDefinitionResolutionsKey);
+ return false;
+ }
+
+ // Process all resolutions.
+ for (Json::ValueConstIterator json_res_iter = json_resolutions.begin();
+ json_res_iter != json_resolutions.end();
+ ++json_res_iter) {
+ // Check presence of width and height keys.
+ if (!json_res_iter->isObject()) {
+ ALOGE("%s: Camera resolution item is not an object", __FUNCTION__);
+ continue;
+ }
+ if (!json_res_iter->isMember(kCameraDefinitionResolutionWidthKey) ||
+ !json_res_iter->isMember(kCameraDefinitionResolutionHeightKey)) {
+ ALOGE("%s: Invalid camera resolution: keys %s and %s are both required.",
+ __FUNCTION__,
+ kCameraDefinitionResolutionWidthKey,
+ kCameraDefinitionResolutionHeightKey);
+ return false;
+ }
+
+ camera.resolutions.push_back(CameraDefinition::Resolution());
+ CameraDefinition::Resolution& resolution = camera.resolutions.back();
+
+ if (!ValueToCameraResolution(
+ (*json_res_iter)[kCameraDefinitionResolutionWidthKey].asString(),
+ (*json_res_iter)[kCameraDefinitionResolutionHeightKey].asString(),
+ &resolution)) return false;
+ }
+ }
+
+ return true;
+}
+} // namespace
+
+bool CameraConfiguration::Init() {
+ cameras_.clear();
+ std::string config;
+ if (!android::base::ReadFileToString(
+ kConfigurationFileLocation, &config)) {
+ ALOGE("%s: Could not open configuration file: %s",
+ __FUNCTION__, kConfigurationFileLocation);
+ return false;
+ }
+
+ Json::Reader config_reader;
+ Json::Value root;
+ if (!config_reader.parse(config, root)) {
+ ALOGE("Could not parse configuration file: %s",
+ config_reader.getFormattedErrorMessages().c_str());
+ return false;
+ }
+
+ return ConfigureCameras(root, &cameras_);
+}
+
+} // namespace cvd
+
+
diff --git a/guest/hals/camera/CameraConfiguration.h b/guest/hals/camera/CameraConfiguration.h
new file mode 100644
index 0000000..90aa087
--- /dev/null
+++ b/guest/hals/camera/CameraConfiguration.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2017 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 GUEST_HALS_CAMERA_CAMERACONFIGURATION_H_
+#define GUEST_HALS_CAMERA_CAMERACONFIGURATION_H_
+
+#include <vector>
+
+namespace cvd {
+
+// Camera properties and features.
+struct CameraDefinition {
+ // Camera facing direction.
+ enum Orientation {
+ kFront,
+ kBack
+ };
+
+ // Camera recognized HAL versions.
+ enum HalVersion {
+ kHalV1,
+ kHalV2,
+ kHalV3
+ };
+
+ struct Resolution {
+ int width;
+ int height;
+ };
+
+ Orientation orientation;
+ HalVersion hal_version;
+ std::vector<Resolution> resolutions;
+};
+
+class CameraConfiguration {
+ public:
+ CameraConfiguration() {}
+ ~CameraConfiguration() {}
+
+ const std::vector<CameraDefinition>& cameras() const {
+ return cameras_;
+ }
+
+ bool Init();
+
+ private:
+ std::vector<CameraDefinition> cameras_;
+};
+
+} // namespace cvd
+
+#endif // GUEST_HALS_CAMERA_CAMERACONFIGURATION_H_
diff --git a/guest/hals/camera/Converters.cpp b/guest/hals/camera/Converters.cpp
new file mode 100755
index 0000000..f63f67f
--- /dev/null
+++ b/guest/hals/camera/Converters.cpp
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+/*
+ * Contains implemenation of framebuffer conversion routines.
+ */
+
+#define LOG_NDEBUG 0
+#define LOG_TAG "EmulatedCamera_Converter"
+#include <cutils/log.h>
+#include "Converters.h"
+
+namespace android {
+
+static void _YUV420SToRGB565(const uint8_t* Y,
+ const uint8_t* U,
+ const uint8_t* V,
+ int dUV,
+ uint16_t* rgb,
+ int width,
+ int height)
+{
+ const uint8_t* U_pos = U;
+ const uint8_t* V_pos = V;
+
+ for (int y = 0; y < height; y++) {
+ for (int x = 0; x < width; x += 2, U += dUV, V += dUV) {
+ const uint8_t nU = *U;
+ const uint8_t nV = *V;
+ *rgb = YUVToRGB565(*Y, nU, nV);
+ Y++; rgb++;
+ *rgb = YUVToRGB565(*Y, nU, nV);
+ Y++; rgb++;
+ }
+ if (y & 0x1) {
+ U_pos = U;
+ V_pos = V;
+ } else {
+ U = U_pos;
+ V = V_pos;
+ }
+ }
+}
+
+static void _YUV420SToRGB32(const uint8_t* Y,
+ const uint8_t* U,
+ const uint8_t* V,
+ int dUV,
+ uint32_t* rgb,
+ int width,
+ int height)
+{
+ const uint8_t* U_pos = U;
+ const uint8_t* V_pos = V;
+
+ for (int y = 0; y < height; y++) {
+ for (int x = 0; x < width; x += 2, U += dUV, V += dUV) {
+ const uint8_t nU = *U;
+ const uint8_t nV = *V;
+ *rgb = YUVToRGB32(*Y, nU, nV);
+ Y++; rgb++;
+ *rgb = YUVToRGB32(*Y, nU, nV);
+ Y++; rgb++;
+ }
+ if (y & 0x1) {
+ U_pos = U;
+ V_pos = V;
+ } else {
+ U = U_pos;
+ V = V_pos;
+ }
+ }
+}
+
+void YV12ToRGB565(const void* yv12, void* rgb, int width, int height)
+{
+ const int pix_total = width * height;
+ const uint8_t* Y = reinterpret_cast<const uint8_t*>(yv12);
+ const uint8_t* U = Y + pix_total;
+ const uint8_t* V = U + pix_total / 4;
+ _YUV420SToRGB565(Y, U, V, 1, reinterpret_cast<uint16_t*>(rgb), width, height);
+}
+
+void YV12ToRGB32(const void* yv12, void* rgb, int width, int height)
+{
+ const int pix_total = width * height;
+ const uint8_t* Y = reinterpret_cast<const uint8_t*>(yv12);
+ const uint8_t* V = Y + pix_total;
+ const uint8_t* U = V + pix_total / 4;
+ _YUV420SToRGB32(Y, U, V, 1, reinterpret_cast<uint32_t*>(rgb), width, height);
+}
+
+void YU12ToRGB32(const void* yu12, void* rgb, int width, int height)
+{
+ const int pix_total = width * height;
+ const uint8_t* Y = reinterpret_cast<const uint8_t*>(yu12);
+ const uint8_t* U = Y + pix_total;
+ const uint8_t* V = U + pix_total / 4;
+ _YUV420SToRGB32(Y, U, V, 1, reinterpret_cast<uint32_t*>(rgb), width, height);
+}
+
+/* Common converter for YUV 4:2:0 interleaved to RGB565.
+ * y, u, and v point to Y,U, and V panes, where U and V values are interleaved.
+ */
+static void _NVXXToRGB565(const uint8_t* Y,
+ const uint8_t* U,
+ const uint8_t* V,
+ uint16_t* rgb,
+ int width,
+ int height)
+{
+ _YUV420SToRGB565(Y, U, V, 2, rgb, width, height);
+}
+
+/* Common converter for YUV 4:2:0 interleaved to RGB32.
+ * y, u, and v point to Y,U, and V panes, where U and V values are interleaved.
+ */
+static void _NVXXToRGB32(const uint8_t* Y,
+ const uint8_t* U,
+ const uint8_t* V,
+ uint32_t* rgb,
+ int width,
+ int height)
+{
+ _YUV420SToRGB32(Y, U, V, 2, rgb, width, height);
+}
+
+void NV12ToRGB565(const void* nv12, void* rgb, int width, int height)
+{
+ const int pix_total = width * height;
+ const uint8_t* y = reinterpret_cast<const uint8_t*>(nv12);
+ _NVXXToRGB565(y, y + pix_total, y + pix_total + 1,
+ reinterpret_cast<uint16_t*>(rgb), width, height);
+}
+
+void NV12ToRGB32(const void* nv12, void* rgb, int width, int height)
+{
+ const int pix_total = width * height;
+ const uint8_t* y = reinterpret_cast<const uint8_t*>(nv12);
+ _NVXXToRGB32(y, y + pix_total, y + pix_total + 1,
+ reinterpret_cast<uint32_t*>(rgb), width, height);
+}
+
+void NV21ToRGB565(const void* nv21, void* rgb, int width, int height)
+{
+ const int pix_total = width * height;
+ const uint8_t* y = reinterpret_cast<const uint8_t*>(nv21);
+ _NVXXToRGB565(y, y + pix_total + 1, y + pix_total,
+ reinterpret_cast<uint16_t*>(rgb), width, height);
+}
+
+void NV21ToRGB32(const void* nv21, void* rgb, int width, int height)
+{
+ const int pix_total = width * height;
+ const uint8_t* y = reinterpret_cast<const uint8_t*>(nv21);
+ _NVXXToRGB32(y, y + pix_total + 1, y + pix_total,
+ reinterpret_cast<uint32_t*>(rgb), width, height);
+}
+
+}; /* namespace android */
diff --git a/guest/hals/camera/Converters.h b/guest/hals/camera/Converters.h
new file mode 100755
index 0000000..13e2a85
--- /dev/null
+++ b/guest/hals/camera/Converters.h
@@ -0,0 +1,314 @@
+/*
+ * Copyright (C) 2011 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 HW_EMULATOR_CAMERA_CONVERTERS_H
+#define HW_EMULATOR_CAMERA_CONVERTERS_H
+
+#include <endian.h>
+
+#ifndef __BYTE_ORDER
+#error "could not determine byte order"
+#endif
+
+/*
+ * Contains declaration of framebuffer conversion routines.
+ *
+ * NOTE: RGB and big/little endian considerations. Wherewer in this code RGB
+ * pixels are represented as WORD, or DWORD, the color order inside the
+ * WORD / DWORD matches the one that would occur if that WORD / DWORD would have
+ * been read from the typecasted framebuffer:
+ *
+ * const uint32_t rgb = *reinterpret_cast<const uint32_t*>(framebuffer);
+ *
+ * So, if this code runs on the little endian CPU, red color in 'rgb' would be
+ * masked as 0x000000ff, and blue color would be masked as 0x00ff0000, while if
+ * the code runs on a big endian CPU, the red color in 'rgb' would be masked as
+ * 0xff000000, and blue color would be masked as 0x0000ff00,
+ */
+
+namespace android {
+
+/*
+ * RGB565 color masks
+ */
+
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+static const uint16_t kRed5 = 0x001f;
+static const uint16_t kGreen6 = 0x07e0;
+static const uint16_t kBlue5 = 0xf800;
+#else // __BYTE_ORDER
+static const uint16_t kRed5 = 0xf800;
+static const uint16_t kGreen6 = 0x07e0;
+static const uint16_t kBlue5 = 0x001f;
+#endif // __BYTE_ORDER
+static const uint32_t kBlack16 = 0x0000;
+static const uint32_t kWhite16 = kRed5 | kGreen6 | kBlue5;
+
+/*
+ * RGB32 color masks
+ */
+
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+static const uint32_t kRed8 = 0x000000ff;
+static const uint32_t kGreen8 = 0x0000ff00;
+static const uint32_t kBlue8 = 0x00ff0000;
+#else // __BYTE_ORDER
+static const uint32_t kRed8 = 0x00ff0000;
+static const uint32_t kGreen8 = 0x0000ff00;
+static const uint32_t kBlue8 = 0x000000ff;
+#endif // __BYTE_ORDER
+static const uint32_t kBlack32 = 0x00000000;
+static const uint32_t kWhite32 = kRed8 | kGreen8 | kBlue8;
+
+/*
+ * Extracting, and saving color bytes from / to WORD / DWORD RGB.
+ */
+
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+/* Extract red, green, and blue bytes from RGB565 word. */
+#define R16(rgb) static_cast<uint8_t>(rgb & kRed5)
+#define G16(rgb) static_cast<uint8_t>((rgb & kGreen6) >> 5)
+#define B16(rgb) static_cast<uint8_t>((rgb & kBlue5) >> 11)
+/* Make 8 bits red, green, and blue, extracted from RGB565 word. */
+#define R16_32(rgb) static_cast<uint8_t>(((rgb & kRed5) << 3) | ((rgb & kRed5) >> 2))
+#define G16_32(rgb) static_cast<uint8_t>(((rgb & kGreen6) >> 3) | ((rgb & kGreen6) >> 9))
+#define B16_32(rgb) static_cast<uint8_t>(((rgb & kBlue5) >> 8) | ((rgb & kBlue5) >> 14))
+/* Extract red, green, and blue bytes from RGB32 dword. */
+#define R32(rgb) static_cast<uint8_t>(rgb & kRed8)
+#define G32(rgb) static_cast<uint8_t>(((rgb & kGreen8) >> 8) & 0xff)
+#define B32(rgb) static_cast<uint8_t>(((rgb & kBlue8) >> 16) & 0xff)
+/* Build RGB565 word from red, green, and blue bytes. */
+#define RGB565(r, g, b) static_cast<uint16_t>((((static_cast<uint16_t>(b) << 6) | g) << 5) | r)
+/* Build RGB32 dword from red, green, and blue bytes. */
+#define RGB32(r, g, b) static_cast<uint32_t>((((static_cast<uint32_t>(b) << 8) | g) << 8) | r)
+#else // __BYTE_ORDER
+/* Extract red, green, and blue bytes from RGB565 word. */
+#define R16(rgb) static_cast<uint8_t>((rgb & kRed5) >> 11)
+#define G16(rgb) static_cast<uint8_t>((rgb & kGreen6) >> 5)
+#define B16(rgb) static_cast<uint8_t>(rgb & kBlue5)
+/* Make 8 bits red, green, and blue, extracted from RGB565 word. */
+#define R16_32(rgb) static_cast<uint8_t>(((rgb & kRed5) >> 8) | ((rgb & kRed5) >> 14))
+#define G16_32(rgb) static_cast<uint8_t>(((rgb & kGreen6) >> 3) | ((rgb & kGreen6) >> 9))
+#define B16_32(rgb) static_cast<uint8_t>(((rgb & kBlue5) << 3) | ((rgb & kBlue5) >> 2))
+/* Extract red, green, and blue bytes from RGB32 dword. */
+#define R32(rgb) static_cast<uint8_t>((rgb & kRed8) >> 16)
+#define G32(rgb) static_cast<uint8_t>((rgb & kGreen8) >> 8)
+#define B32(rgb) static_cast<uint8_t>(rgb & kBlue8)
+/* Build RGB565 word from red, green, and blue bytes. */
+#define RGB565(r, g, b) static_cast<uint16_t>((((static_cast<uint16_t>(r) << 6) | g) << 5) | b)
+/* Build RGB32 dword from red, green, and blue bytes. */
+#define RGB32(r, g, b) static_cast<uint32_t>((((static_cast<uint32_t>(r) << 8) | g) << 8) | b)
+#endif // __BYTE_ORDER
+
+/* An union that simplifies breaking 32 bit RGB into separate R, G, and B colors.
+ */
+typedef union RGB32_t {
+ uint32_t color;
+ struct {
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+ uint8_t r; uint8_t g; uint8_t b; uint8_t a;
+#else // __BYTE_ORDER
+ uint8_t a; uint8_t b; uint8_t g; uint8_t r;
+#endif // __BYTE_ORDER
+ };
+} RGB32_t;
+
+
+/* Clips a value to the unsigned 0-255 range, treating negative values as zero.
+ */
+static __inline__ int
+clamp(int x)
+{
+ if (x > 255) return 255;
+ if (x < 0) return 0;
+ return x;
+}
+
+/********************************************************************************
+ * Basics of RGB -> YUV conversion
+ *******************************************************************************/
+
+/*
+ * RGB -> YUV conversion macros
+ */
+#define RGB2Y(r, g, b) (uint8_t)(((66 * (r) + 129 * (g) + 25 * (b) + 128) >> 8) + 16)
+#define RGB2U(r, g, b) (uint8_t)(((-38 * (r) - 74 * (g) + 112 * (b) + 128) >> 8) + 128)
+#define RGB2V(r, g, b) (uint8_t)(((112 * (r) - 94 * (g) - 18 * (b) + 128) >> 8) + 128)
+
+/* Converts R8 G8 B8 color to YUV. */
+static __inline__ void
+R8G8B8ToYUV(uint8_t r, uint8_t g, uint8_t b, uint8_t* y, uint8_t* u, uint8_t* v)
+{
+ *y = RGB2Y((int)r, (int)g, (int)b);
+ *u = RGB2U((int)r, (int)g, (int)b);
+ *v = RGB2V((int)r, (int)g, (int)b);
+}
+
+/* Converts RGB565 color to YUV. */
+static __inline__ void
+RGB565ToYUV(uint16_t rgb, uint8_t* y, uint8_t* u, uint8_t* v)
+{
+ R8G8B8ToYUV(R16_32(rgb), G16_32(rgb), B16_32(rgb), y, u, v);
+}
+
+/* Converts RGB32 color to YUV. */
+static __inline__ void
+RGB32ToYUV(uint32_t rgb, uint8_t* y, uint8_t* u, uint8_t* v)
+{
+ RGB32_t rgb_c;
+ rgb_c.color = rgb;
+ R8G8B8ToYUV(rgb_c.r, rgb_c.g, rgb_c.b, y, u, v);
+}
+
+/********************************************************************************
+ * Basics of YUV -> RGB conversion.
+ * Note that due to the fact that guest uses RGB only on preview window, and the
+ * RGB format that is used is RGB565, we can limit YUV -> RGB conversions to
+ * RGB565 only.
+ *******************************************************************************/
+
+/*
+ * YUV -> RGB conversion macros
+ */
+
+/* "Optimized" macros that take specialy prepared Y, U, and V values:
+ * C = Y - 16
+ * D = U - 128
+ * E = V - 128
+ */
+#define YUV2RO(C, D, E) clamp((298 * (C) + 409 * (E) + 128) >> 8)
+#define YUV2GO(C, D, E) clamp((298 * (C) - 100 * (D) - 208 * (E) + 128) >> 8)
+#define YUV2BO(C, D, E) clamp((298 * (C) + 516 * (D) + 128) >> 8)
+
+/*
+ * Main macros that take the original Y, U, and V values
+ */
+#define YUV2R(y, u, v) clamp((298 * ((y)-16) + 409 * ((v)-128) + 128) >> 8)
+#define YUV2G(y, u, v) clamp((298 * ((y)-16) - 100 * ((u)-128) - 208 * ((v)-128) + 128) >> 8)
+#define YUV2B(y, u, v) clamp((298 * ((y)-16) + 516 * ((u)-128) + 128) >> 8)
+
+
+/* Converts YUV color to RGB565. */
+static __inline__ uint16_t
+YUVToRGB565(int y, int u, int v)
+{
+ /* Calculate C, D, and E values for the optimized macro. */
+ y -= 16; u -= 128; v -= 128;
+ const uint16_t r = (YUV2RO(y,u,v) >> 3) & 0x1f;
+ const uint16_t g = (YUV2GO(y,u,v) >> 2) & 0x3f;
+ const uint16_t b = (YUV2BO(y,u,v) >> 3) & 0x1f;
+ return RGB565(r, g, b);
+}
+
+/* Converts YUV color to RGB32. */
+static __inline__ uint32_t
+YUVToRGB32(int y, int u, int v)
+{
+ /* Calculate C, D, and E values for the optimized macro. */
+ y -= 16; u -= 128; v -= 128;
+ RGB32_t rgb;
+ rgb.r = YUV2RO(y,u,v) & 0xff;
+ rgb.g = YUV2GO(y,u,v) & 0xff;
+ rgb.b = YUV2BO(y,u,v) & 0xff;
+ return rgb.color;
+}
+
+/* YUV pixel descriptor. */
+struct YUVPixel {
+ uint8_t Y;
+ uint8_t U;
+ uint8_t V;
+
+ inline YUVPixel()
+ : Y(0), U(0), V(0)
+ {
+ }
+
+ inline explicit YUVPixel(uint16_t rgb565)
+ {
+ RGB565ToYUV(rgb565, &Y, &U, &V);
+ }
+
+ inline explicit YUVPixel(uint32_t rgb32)
+ {
+ RGB32ToYUV(rgb32, &Y, &U, &V);
+ }
+
+ inline void get(uint8_t* pY, uint8_t* pU, uint8_t* pV) const
+ {
+ *pY = Y; *pU = U; *pV = V;
+ }
+};
+
+/* Converts an YV12 framebuffer to RGB565 framebuffer.
+ * Param:
+ * yv12 - YV12 framebuffer.
+ * rgb - RGB565 framebuffer.
+ * width, height - Dimensions for both framebuffers.
+ */
+void YV12ToRGB565(const void* yv12, void* rgb, int width, int height);
+
+/* Converts an YV12 framebuffer to RGB32 framebuffer.
+ * Param:
+ * yv12 - YV12 framebuffer.
+ * rgb - RGB32 framebuffer.
+ * width, height - Dimensions for both framebuffers.
+ */
+void YV12ToRGB32(const void* yv12, void* rgb, int width, int height);
+
+/* Converts an YU12 framebuffer to RGB32 framebuffer.
+ * Param:
+ * yu12 - YU12 framebuffer.
+ * rgb - RGB32 framebuffer.
+ * width, height - Dimensions for both framebuffers.
+ */
+void YU12ToRGB32(const void* yu12, void* rgb, int width, int height);
+
+/* Converts an NV12 framebuffer to RGB565 framebuffer.
+ * Param:
+ * nv12 - NV12 framebuffer.
+ * rgb - RGB565 framebuffer.
+ * width, height - Dimensions for both framebuffers.
+ */
+void NV12ToRGB565(const void* nv12, void* rgb, int width, int height);
+
+/* Converts an NV12 framebuffer to RGB32 framebuffer.
+ * Param:
+ * nv12 - NV12 framebuffer.
+ * rgb - RGB32 framebuffer.
+ * width, height - Dimensions for both framebuffers.
+ */
+void NV12ToRGB32(const void* nv12, void* rgb, int width, int height);
+
+/* Converts an NV21 framebuffer to RGB565 framebuffer.
+ * Param:
+ * nv21 - NV21 framebuffer.
+ * rgb - RGB565 framebuffer.
+ * width, height - Dimensions for both framebuffers.
+ */
+void NV21ToRGB565(const void* nv21, void* rgb, int width, int height);
+
+/* Converts an NV21 framebuffer to RGB32 framebuffer.
+ * Param:
+ * nv21 - NV21 framebuffer.
+ * rgb - RGB32 framebuffer.
+ * width, height - Dimensions for both framebuffers.
+ */
+void NV21ToRGB32(const void* nv21, void* rgb, int width, int height);
+
+}; /* namespace android */
+
+#endif /* HW_EMULATOR_CAMERA_CONVERTERS_H */
diff --git a/guest/hals/camera/EmulatedBaseCamera.cpp b/guest/hals/camera/EmulatedBaseCamera.cpp
new file mode 100644
index 0000000..2fbbc0b
--- /dev/null
+++ b/guest/hals/camera/EmulatedBaseCamera.cpp
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+/*
+ * Contains implementation of a class EmulatedBaseCamera that encapsulates
+ * functionality common to all emulated camera device versions ("fake",
+ * "webcam", "video file", "cam2.0" etc.). Instances of this class (for each
+ * emulated camera) are created during the construction of the
+ * EmulatedCameraFactory instance. This class serves as an entry point for all
+ * camera API calls that are common across all versions of the
+ * camera_device_t/camera_module_t structures.
+ */
+
+#define LOG_NDEBUG 0
+#define LOG_TAG "EmulatedCamera_BaseCamera"
+#include <cutils/log.h>
+
+#include "EmulatedBaseCamera.h"
+
+namespace android {
+
+EmulatedBaseCamera::EmulatedBaseCamera(int cameraId,
+ uint32_t cameraVersion,
+ struct hw_device_t* device,
+ struct hw_module_t* module)
+ : mCameraInfo(NULL),
+ mCameraID(cameraId),
+ mCameraDeviceVersion(cameraVersion)
+{
+ /*
+ * Initialize camera_device descriptor for this object.
+ */
+
+ /* Common header */
+ device->tag = HARDWARE_DEVICE_TAG;
+ device->version = cameraVersion;
+ device->module = module;
+ device->close = NULL; // Must be filled in by child implementation
+}
+
+EmulatedBaseCamera::~EmulatedBaseCamera()
+{
+}
+
+status_t EmulatedBaseCamera::getCameraInfo(struct camera_info* info)
+{
+ ALOGV("%s", __FUNCTION__);
+
+ info->device_version = mCameraDeviceVersion;
+#if VSOC_PLATFORM_SDK_BEFORE(O)
+ // static_camera_characteristics should be initialized if and only if two
+ // conditions hold:
+ // CAMERA_MODULE_API_VERSION_2_0 or higher
+ // CAMERA_DEVICE_API_VERSION_2_0 or higher
+ // See android/hardware/libhardware/include/hardware/camera_common.h
+ //
+ // The CAMERA_MODULE_API_VERSION is above 2 on all of the supported
+ // branches.
+ //
+ // The CVD supports both CAMERA_DEVICE_API_VERSION_1_0 and
+ // CAMERA_DEVICE_API_VERSION_3_0.
+ //
+ // By the spec, the framework should not look at this field on
+ // CAMERA_DEVICE_API_VERSION_1_0. However, the framework
+ // referenced them unconditionally in the M, N, and N-MR1 branches.
+ // See b/67841929 for evidence.
+ //
+ // We have to support those branches, so make initialization uconditional.
+ // However, keep the 0xcafef00d fake initiziation on O and later to ensure
+ // that we'll catch future framework changes that violate the spec.
+ info->static_camera_characteristics = mCameraInfo;
+#else
+ if (mCameraDeviceVersion >= HARDWARE_DEVICE_API_VERSION(2, 0)) {
+ info->static_camera_characteristics = mCameraInfo;
+ } else {
+ info->static_camera_characteristics = (camera_metadata_t*)0xcafef00d;
+ }
+#endif
+
+ return NO_ERROR;
+}
+
+status_t EmulatedBaseCamera::plugCamera() {
+ ALOGE("%s: not supported", __FUNCTION__);
+ return INVALID_OPERATION;
+}
+
+status_t EmulatedBaseCamera::unplugCamera() {
+ ALOGE("%s: not supported", __FUNCTION__);
+ return INVALID_OPERATION;
+}
+
+#if VSOC_PLATFORM_SDK_AFTER(J_MR2)
+camera_device_status_t EmulatedBaseCamera::getHotplugStatus() {
+ return CAMERA_DEVICE_STATUS_PRESENT;
+}
+#endif
+
+status_t EmulatedBaseCamera::setTorchMode(bool /* enabled */) {
+ ALOGE("%s: not supported", __FUNCTION__);
+ return INVALID_OPERATION;
+}
+
+
+} /* namespace android */
diff --git a/guest/hals/camera/EmulatedBaseCamera.h b/guest/hals/camera/EmulatedBaseCamera.h
new file mode 100644
index 0000000..ab64b45
--- /dev/null
+++ b/guest/hals/camera/EmulatedBaseCamera.h
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2012 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 HW_EMULATOR_CAMERA_EMULATED_BASE_CAMERA_H
+#define HW_EMULATOR_CAMERA_EMULATED_BASE_CAMERA_H
+
+#include <hardware/camera_common.h>
+#include <utils/Errors.h>
+#include "guest/libs/platform_support/api_level_fixes.h"
+#include "CameraConfiguration.h"
+#include "ImageMetadata.h"
+
+namespace android {
+
+/*
+ * Contains declaration of a class EmulatedBaseCamera that encapsulates
+ * functionality common to all emulated camera device versions ("fake",
+ * "webcam", "video file", etc.). Instances of this class (for each emulated
+ * camera) are created during the construction of the EmulatedCameraFactory
+ * instance. This class serves as an entry point for all camera API calls that
+ * are common across all versions of the camera_device_t/camera_module_t
+ * structures.
+ */
+
+class EmulatedBaseCamera {
+ public:
+ EmulatedBaseCamera(int cameraId,
+ uint32_t cameraVersion,
+ struct hw_device_t* device,
+ struct hw_module_t* module);
+
+ virtual ~EmulatedBaseCamera();
+
+ /****************************************************************************
+ * Public API
+ ***************************************************************************/
+
+ public:
+ /* Initializes EmulatedCamera instance.
+ * Return:
+ * NO_ERROR on success, or an appropriate error status on failure.
+ */
+ virtual status_t Initialize(const cvd::CameraDefinition& params) = 0;
+
+ /****************************************************************************
+ * Camera API implementation
+ ***************************************************************************/
+
+ public:
+ /* Creates connection to the emulated camera device.
+ * This method is called in response to hw_module_methods_t::open callback.
+ * NOTE: When this method is called the object is locked.
+ * Note that failures in this method are reported as negative EXXX statuses.
+ */
+ virtual status_t connectCamera(hw_device_t** device) = 0;
+
+
+ /* Plug the connection for the emulated camera. Until it's plugged in
+ * calls to connectCamera should fail with -ENODEV.
+ */
+ virtual status_t plugCamera();
+
+ /* Unplug the connection from underneath the emulated camera.
+ * This is similar to closing the camera, except that
+ * all function calls into the camera device will return
+ * -EPIPE errors until the camera is reopened.
+ */
+ virtual status_t unplugCamera();
+
+#if VSOC_PLATFORM_SDK_AFTER(J_MR2)
+ virtual camera_device_status_t getHotplugStatus();
+#endif
+
+ /* Closes connection to the emulated camera.
+ * This method is called in response to camera_device::close callback.
+ * NOTE: When this method is called the object is locked.
+ * Note that failures in this method are reported as negative EXXX statuses.
+ */
+ virtual status_t closeCamera() = 0;
+
+ /* Gets camera information.
+ * This method is called in response to camera_module_t::get_camera_info
+ * callback.
+ * NOTE: When this method is called the object is locked.
+ * Note that failures in this method are reported as negative EXXX statuses.
+ */
+ virtual status_t getCameraInfo(struct camera_info* info) = 0;
+
+ /* Gets image metadata.
+ * This method is called to collect metadata for (currently) taken picture.
+ */
+ virtual status_t getImageMetadata(struct ImageMetadata* meta) = 0;
+
+ /* Set torch mode.
+ * This method is called in response to camera_module_t::set_torch_mode
+ * callback.
+ */
+ virtual status_t setTorchMode(bool enabled);
+
+ /****************************************************************************
+ * Data members
+ ***************************************************************************/
+
+ protected:
+ /* Fixed camera information for camera2 devices. Must be valid to access if
+ * mCameraDeviceVersion is >= HARDWARE_DEVICE_API_VERSION(2,0) */
+ camera_metadata_t *mCameraInfo;
+
+ /* Zero-based ID assigned to this camera. */
+ int mCameraID;
+
+ private:
+
+ /* Version of the camera device HAL implemented by this camera */
+ int mCameraDeviceVersion;
+};
+
+} /* namespace android */
+
+#endif /* HW_EMULATOR_CAMERA_EMULATED_BASE_CAMERA_H */
diff --git a/guest/hals/camera/EmulatedCamera.cpp b/guest/hals/camera/EmulatedCamera.cpp
new file mode 100755
index 0000000..dd0a4cb
--- /dev/null
+++ b/guest/hals/camera/EmulatedCamera.cpp
@@ -0,0 +1,1162 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+/*
+ * Contains implementation of a class EmulatedCamera that encapsulates
+ * functionality common to all emulated cameras ("fake", "webcam", "video file",
+ * etc.). Instances of this class (for each emulated camera) are created during
+ * the construction of the EmulatedCameraFactory instance. This class serves as
+ * an entry point for all camera API calls that defined by camera_device_ops_t
+ * API.
+ */
+
+#include "guest/libs/platform_support/api_level_fixes.h"
+
+#define LOG_NDEBUG 0
+#define LOG_TAG "EmulatedCamera_Camera"
+#include <cutils/log.h>
+#include "EmulatedCamera.h"
+//#include "EmulatedFakeCameraDevice.h"
+#include "Converters.h"
+
+/* Defines whether we should trace parameter changes. */
+#define DEBUG_PARAM 1
+
+namespace android {
+namespace {
+const char* kSupportedFlashModes[] = {
+ CameraParameters::FLASH_MODE_OFF,
+ CameraParameters::FLASH_MODE_AUTO,
+ CameraParameters::FLASH_MODE_ON,
+ CameraParameters::FLASH_MODE_RED_EYE,
+ CameraParameters::FLASH_MODE_TORCH,
+ NULL
+};
+
+std::string BuildParameterValue(const char** value_array) {
+ std::string result;
+
+ for (int index = 0; value_array[index] != NULL; ++index) {
+ if (index) result.append(",");
+ result.append(value_array[index]);
+ }
+ return result;
+}
+
+bool CheckParameterValue(const char* value, const char** supported_values) {
+ for (int index = 0; supported_values[index] != NULL; ++index) {
+ if (!strcmp(value, supported_values[index])) return true;
+ }
+ return false;
+}
+
+} // namespace
+
+#if DEBUG_PARAM
+/* Calculates and logs parameter changes.
+ * Param:
+ * current - Current set of camera parameters.
+ * new_par - String representation of new parameters.
+ */
+static void PrintParamDiff(const CameraParameters& current, const char* new_par);
+#else
+#define PrintParamDiff(current, new_par) (void(0))
+#endif /* DEBUG_PARAM */
+
+/* A helper routine that adds a value to the camera parameter.
+ * Param:
+ * param - Camera parameter to add a value to.
+ * val - Value to add.
+ * Return:
+ * A new string containing parameter with the added value on success, or NULL on
+ * a failure. If non-NULL string is returned, the caller is responsible for
+ * freeing it with 'free'.
+ */
+static char* AddValue(const char* param, const char* val);
+
+EmulatedCamera::EmulatedCamera(int cameraId,
+ struct hw_module_t* module)
+ : EmulatedBaseCamera(cameraId,
+ HARDWARE_DEVICE_API_VERSION(1, 0),
+ &common,
+ module),
+ mPreviewWindow(),
+ mCallbackNotifier()
+{
+ /* camera_device v1 fields. */
+ common.close = EmulatedCamera::close;
+ ops = &mDeviceOps;
+ priv = this;
+}
+
+EmulatedCamera::~EmulatedCamera()
+{
+}
+
+/****************************************************************************
+ * Public API
+ ***************************************************************************/
+
+status_t EmulatedCamera::Initialize(const cvd::CameraDefinition&)
+{
+ /* Preview formats supported by this HAL. */
+ char preview_formats[1024];
+ snprintf(preview_formats, sizeof(preview_formats), "%s,%s,%s",
+ CameraParameters::PIXEL_FORMAT_YUV420SP,
+ CameraParameters::PIXEL_FORMAT_YUV420P,
+ CameraParameters::PIXEL_FORMAT_RGBA8888);
+
+ /*
+ * Fake required parameters.
+ */
+
+ mParameters.set(CameraParameters::KEY_SUPPORTED_JPEG_THUMBNAIL_SIZES, "320x240,0x0");
+
+ mParameters.set(CameraParameters::KEY_JPEG_THUMBNAIL_WIDTH, "512");
+ mParameters.set(CameraParameters::KEY_JPEG_THUMBNAIL_HEIGHT, "384");
+ mParameters.set(CameraParameters::KEY_JPEG_QUALITY, "90");
+ mParameters.set(CameraParameters::KEY_FOCAL_LENGTH, "4.31");
+ mParameters.set(CameraParameters::KEY_HORIZONTAL_VIEW_ANGLE, "54.8");
+ mParameters.set(CameraParameters::KEY_VERTICAL_VIEW_ANGLE, "42.5");
+ mParameters.set(CameraParameters::KEY_JPEG_THUMBNAIL_QUALITY, "90");
+
+ /* Preview format settings used here are related to panoramic view only. It's
+ * not related to the preview window that works only with RGB frames, which
+ * is explicitly stated when set_buffers_geometry is called on the preview
+ * window object. */
+ mParameters.set(CameraParameters::KEY_SUPPORTED_PREVIEW_FORMATS,
+ preview_formats);
+ mParameters.setPreviewFormat(CameraParameters::PIXEL_FORMAT_YUV420SP);
+
+ /* We don't relay on the actual frame rates supported by the camera device,
+ * since we will emulate them through timeouts in the emulated camera device
+ * worker thread. */
+ mParameters.set(CameraParameters::KEY_SUPPORTED_PREVIEW_FRAME_RATES,
+ "30,24,20,15,10,5");
+ mParameters.set(CameraParameters::KEY_SUPPORTED_PREVIEW_FPS_RANGE,
+ "(5000,30000),(15000,15000),(30000,30000)");
+ mParameters.set(CameraParameters::KEY_PREVIEW_FPS_RANGE,
+ "5000,30000");
+ mParameters.setPreviewFrameRate(30000);
+
+ /* Only PIXEL_FORMAT_YUV420P is accepted by video framework in emulator! */
+ mParameters.set(CameraParameters::KEY_VIDEO_FRAME_FORMAT,
+ CameraParameters::PIXEL_FORMAT_YUV420P);
+ mParameters.set(CameraParameters::KEY_SUPPORTED_PICTURE_FORMATS,
+ CameraParameters::PIXEL_FORMAT_JPEG);
+ mParameters.setPictureFormat(CameraParameters::PIXEL_FORMAT_JPEG);
+
+ /* Set exposure compensation. */
+ mParameters.set(CameraParameters::KEY_MAX_EXPOSURE_COMPENSATION, "6");
+ mParameters.set(CameraParameters::KEY_MIN_EXPOSURE_COMPENSATION, "-6");
+ mParameters.set(CameraParameters::KEY_EXPOSURE_COMPENSATION_STEP, "0.5");
+ mParameters.set(CameraParameters::KEY_EXPOSURE_COMPENSATION, "0");
+
+ /* Sets the white balance modes and the device-dependent scale factors. */
+ char supported_white_balance[1024];
+ snprintf(supported_white_balance, sizeof(supported_white_balance),
+ "%s,%s,%s,%s",
+ CameraParameters::WHITE_BALANCE_AUTO,
+ CameraParameters::WHITE_BALANCE_INCANDESCENT,
+ CameraParameters::WHITE_BALANCE_DAYLIGHT,
+ CameraParameters::WHITE_BALANCE_TWILIGHT);
+ mParameters.set(CameraParameters::KEY_SUPPORTED_WHITE_BALANCE,
+ supported_white_balance);
+ mParameters.set(CameraParameters::KEY_WHITE_BALANCE,
+ CameraParameters::WHITE_BALANCE_AUTO);
+ getCameraDevice()->initializeWhiteBalanceModes(
+ CameraParameters::WHITE_BALANCE_AUTO, 1.0f, 1.0f);
+ getCameraDevice()->initializeWhiteBalanceModes(
+ CameraParameters::WHITE_BALANCE_INCANDESCENT, 1.38f, 0.60f);
+ getCameraDevice()->initializeWhiteBalanceModes(
+ CameraParameters::WHITE_BALANCE_DAYLIGHT, 1.09f, 0.92f);
+ getCameraDevice()->initializeWhiteBalanceModes(
+ CameraParameters::WHITE_BALANCE_TWILIGHT, 0.92f, 1.22f);
+ getCameraDevice()->setWhiteBalanceMode(CameraParameters::WHITE_BALANCE_AUTO);
+
+ /*
+ * Not supported features
+ */
+ mParameters.set(CameraParameters::KEY_SUPPORTED_FOCUS_MODES,
+ CameraParameters::FOCUS_MODE_FIXED);
+ mParameters.set(CameraParameters::KEY_FOCUS_MODE,
+ CameraParameters::FOCUS_MODE_FIXED);
+ mParameters.set(CameraParameters::KEY_SUPPORTED_FLASH_MODES,
+ BuildParameterValue(kSupportedFlashModes).c_str());
+ mParameters.set(CameraParameters::KEY_FLASH_MODE,
+ CameraParameters::FLASH_MODE_OFF);
+ mParameters.set(CameraParameters::KEY_FOCUS_DISTANCES, "0.1,0.1,0.1");
+ mParameters.set(CameraParameters::KEY_MAX_NUM_DETECTED_FACES_HW, "0");
+ mParameters.set(CameraParameters::KEY_MAX_NUM_DETECTED_FACES_SW, "0");
+ mParameters.set(CameraParameters::KEY_ZOOM_RATIOS, "100");
+ mParameters.set(CameraParameters::KEY_ZOOM_SUPPORTED,
+ CameraParameters::FALSE);
+ mParameters.set(CameraParameters::KEY_SMOOTH_ZOOM_SUPPORTED,
+ CameraParameters::FALSE);
+ mParameters.set(CameraParameters::KEY_ZOOM, "0");
+ mParameters.set(CameraParameters::KEY_MAX_ZOOM, "0");
+
+ return NO_ERROR;
+}
+
+void EmulatedCamera::onNextFrameAvailable(const void* frame,
+ nsecs_t timestamp,
+ EmulatedCameraDevice* camera_dev)
+{
+ /* Notify the preview window first. */
+ mPreviewWindow.onNextFrameAvailable(frame, timestamp, camera_dev);
+
+ /* Notify callback notifier next. */
+ mCallbackNotifier.onNextFrameAvailable(frame, timestamp, camera_dev);
+}
+
+void EmulatedCamera::onCameraDeviceError(int err)
+{
+ /* Errors are reported through the callback notifier */
+ mCallbackNotifier.onCameraDeviceError(err);
+}
+
+void EmulatedCamera::onCameraFocusAcquired() {
+ mCallbackNotifier.onCameraFocusAcquired();
+}
+
+/****************************************************************************
+ * Camera API implementation.
+ ***************************************************************************/
+
+status_t EmulatedCamera::connectCamera(hw_device_t** device)
+{
+ ALOGV("%s", __FUNCTION__);
+
+ status_t res = EINVAL;
+ EmulatedCameraDevice* const camera_dev = getCameraDevice();
+ ALOGE_IF(camera_dev == NULL, "%s: No camera device instance.", __FUNCTION__);
+
+ if (camera_dev != NULL) {
+ /* Connect to the camera device. */
+ res = getCameraDevice()->connectDevice();
+ if (res == NO_ERROR) {
+ *device = &common;
+ }
+ }
+
+ return -res;
+}
+
+status_t EmulatedCamera::closeCamera()
+{
+ ALOGV("%s", __FUNCTION__);
+
+ return cleanupCamera();
+}
+
+status_t EmulatedCamera::getCameraInfo(struct camera_info* info)
+{
+ ALOGV("%s", __FUNCTION__);
+
+ const char* valstr = NULL;
+
+ valstr = mParameters.get(EmulatedCamera::FACING_KEY);
+ if (valstr != NULL) {
+ if (strcmp(valstr, EmulatedCamera::FACING_FRONT) == 0) {
+ info->facing = CAMERA_FACING_FRONT;
+ }
+ else if (strcmp(valstr, EmulatedCamera::FACING_BACK) == 0) {
+ info->facing = CAMERA_FACING_BACK;
+ }
+ } else {
+ info->facing = CAMERA_FACING_BACK;
+ }
+
+ valstr = mParameters.get(EmulatedCamera::ORIENTATION_KEY);
+ if (valstr != NULL) {
+ info->orientation = atoi(valstr);
+ } else {
+ info->orientation = 0;
+ }
+
+#if VSOC_PLATFORM_SDK_AFTER(L_MR1)
+ info->resource_cost = 100;
+ info->conflicting_devices = NULL;
+ info->conflicting_devices_length = 0;
+#endif
+
+ return EmulatedBaseCamera::getCameraInfo(info);
+}
+
+status_t EmulatedCamera::getImageMetadata(struct ImageMetadata* meta) {
+ meta->mLensFocalLength =
+ mParameters.getFloat(CameraParameters::KEY_FOCAL_LENGTH);
+ meta->mGpsLatitude =
+ mParameters.getFloat(CameraParameters::KEY_GPS_LATITUDE);
+ meta->mGpsLongitude =
+ mParameters.getFloat(CameraParameters::KEY_GPS_LONGITUDE);
+ meta->mGpsAltitude =
+ mParameters.getFloat(CameraParameters::KEY_GPS_ALTITUDE);
+ meta->mGpsTimestamp =
+ mParameters.getInt(CameraParameters::KEY_GPS_TIMESTAMP);
+ meta->mThumbnailWidth =
+ mParameters.getInt(CameraParameters::KEY_JPEG_THUMBNAIL_WIDTH);
+ meta->mThumbnailHeight =
+ mParameters.getInt(CameraParameters::KEY_JPEG_THUMBNAIL_HEIGHT);
+ const char* temp =
+ mParameters.get(CameraParameters::KEY_GPS_PROCESSING_METHOD);
+ if (temp) {
+ meta->mGpsProcessingMethod = temp;
+ }
+ return NO_ERROR;
+}
+
+status_t EmulatedCamera::setPreviewWindow(struct preview_stream_ops* window)
+{
+ /* Callback should return a negative errno. */
+ return -mPreviewWindow.setPreviewWindow(window,
+ mParameters.getPreviewFrameRate());
+}
+
+void EmulatedCamera::setCallbacks(camera_notify_callback notify_cb,
+ camera_data_callback data_cb,
+ camera_data_timestamp_callback data_cb_timestamp,
+ camera_request_memory get_memory,
+ void* user)
+{
+ mCallbackNotifier.setCallbacks(notify_cb, data_cb, data_cb_timestamp,
+ get_memory, user);
+}
+
+void EmulatedCamera::enableMsgType(int32_t msg_type)
+{
+ mCallbackNotifier.enableMessage(msg_type);
+}
+
+void EmulatedCamera::disableMsgType(int32_t msg_type)
+{
+ mCallbackNotifier.disableMessage(msg_type);
+}
+
+int EmulatedCamera::isMsgTypeEnabled(int32_t msg_type)
+{
+ return mCallbackNotifier.isMessageEnabled(msg_type);
+}
+
+status_t EmulatedCamera::startPreview()
+{
+ /* Callback should return a negative errno. */
+ return -doStartPreview();
+}
+
+void EmulatedCamera::stopPreview()
+{
+ doStopPreview();
+}
+
+int EmulatedCamera::isPreviewEnabled()
+{
+ return mPreviewWindow.isPreviewEnabled();
+}
+
+status_t EmulatedCamera::storeMetaDataInBuffers(int enable)
+{
+ /* Callback should return a negative errno. */
+ return -mCallbackNotifier.storeMetaDataInBuffers(enable);
+}
+
+status_t EmulatedCamera::startRecording()
+{
+ /* Callback should return a negative errno. */
+ return -mCallbackNotifier.enableVideoRecording(mParameters.getPreviewFrameRate());
+}
+
+void EmulatedCamera::stopRecording()
+{
+ mCallbackNotifier.disableVideoRecording();
+}
+
+int EmulatedCamera::isRecordingEnabled()
+{
+ return mCallbackNotifier.isVideoRecordingEnabled();
+}
+
+void EmulatedCamera::releaseRecordingFrame(const void* opaque)
+{
+ mCallbackNotifier.releaseRecordingFrame(opaque);
+}
+
+status_t EmulatedCamera::setAutoFocus()
+{
+ ALOGV("%s", __FUNCTION__);
+
+ /* Trigger auto-focus. Focus response cannot be sent directly from here. */
+ getCameraDevice()->startAutoFocus();
+
+ /* TODO: Future enhancements. */
+ return NO_ERROR;
+}
+
+status_t EmulatedCamera::cancelAutoFocus()
+{
+ ALOGV("%s", __FUNCTION__);
+
+ /* TODO: Future enhancements. */
+ return NO_ERROR;
+}
+
+status_t EmulatedCamera::takePicture()
+{
+ ALOGV("%s", __FUNCTION__);
+
+ status_t res;
+ int width, height;
+ uint32_t org_fmt;
+
+ /* Collect frame info for the picture. */
+ mParameters.getPictureSize(&width, &height);
+ const char* pix_fmt = mParameters.getPictureFormat();
+ if (strcmp(pix_fmt, CameraParameters::PIXEL_FORMAT_YUV420P) == 0) {
+ org_fmt = V4L2_PIX_FMT_YUV420;
+ } else if (strcmp(pix_fmt, CameraParameters::PIXEL_FORMAT_RGBA8888) == 0) {
+ org_fmt = V4L2_PIX_FMT_RGB32;
+ } else if (strcmp(pix_fmt, CameraParameters::PIXEL_FORMAT_YUV420SP) == 0) {
+ org_fmt = V4L2_PIX_FMT_NV21;
+ } else if (strcmp(pix_fmt, CameraParameters::PIXEL_FORMAT_JPEG) == 0) {
+ /* We only have JPEG converted for NV21 format. */
+ org_fmt = V4L2_PIX_FMT_NV21;
+ } else {
+ ALOGE("%s: Unsupported pixel format %s", __FUNCTION__, pix_fmt);
+ return EINVAL;
+ }
+ /* Get JPEG quality. */
+ int jpeg_quality = mParameters.getInt(CameraParameters::KEY_JPEG_QUALITY);
+ if (jpeg_quality <= 0) {
+ jpeg_quality = 90; /* Fall back to default. */
+ }
+
+ /*
+ * Make sure preview is not running, and device is stopped before taking
+ * picture.
+ */
+
+ const bool preview_on = mPreviewWindow.isPreviewEnabled();
+ if (preview_on) {
+ doStopPreview();
+ }
+
+ /* Camera device should have been stopped when the shutter message has been
+ * enabled. */
+ EmulatedCameraDevice* const camera_dev = getCameraDevice();
+ if (camera_dev->isStarted()) {
+ ALOGW("%s: Camera device is started", __FUNCTION__);
+ camera_dev->stopDeliveringFrames();
+ camera_dev->stopDevice();
+ }
+
+ /* Compute target FPS rate.
+ * Pretend to simulate generation of (max_fps_rate) */
+ int min_fps_rate, max_fps_rate;
+ mParameters.getPreviewFpsRange(&min_fps_rate, &max_fps_rate);
+
+ /*
+ * Take the picture now.
+ */
+
+ /* Start camera device for the picture frame. */
+ ALOGD("Starting camera for picture: %.4s(%s)[%dx%d]",
+ reinterpret_cast<const char*>(&org_fmt), pix_fmt, width, height);
+ res = camera_dev->startDevice(width, height, org_fmt, max_fps_rate);
+ if (res != NO_ERROR) {
+ if (preview_on) {
+ doStartPreview();
+ }
+ return res;
+ }
+
+ /* Deliver one frame only. */
+ mCallbackNotifier.setJpegQuality(jpeg_quality);
+ mCallbackNotifier.setTakingPicture(true);
+ res = camera_dev->startDeliveringFrames(true);
+ if (res != NO_ERROR) {
+ mCallbackNotifier.setTakingPicture(false);
+ if (preview_on) {
+ doStartPreview();
+ }
+ }
+ return res;
+}
+
+status_t EmulatedCamera::cancelPicture()
+{
+ ALOGV("%s", __FUNCTION__);
+
+ return NO_ERROR;
+}
+
+status_t EmulatedCamera::setParameters(const char* parms)
+{
+ ALOGV("%s", __FUNCTION__);
+ PrintParamDiff(mParameters, parms);
+
+ CameraParameters new_param;
+ String8 str8_param(parms);
+ new_param.unflatten(str8_param);
+
+ /*
+ * Check if requested dimensions are valid.
+ */
+ if (!CheckParameterValue(new_param.get(CameraParameters::KEY_FLASH_MODE),
+ kSupportedFlashModes)) {
+ ALOGE("%s: Unsupported flash mode: %s",
+ __FUNCTION__,
+ new_param.get(CameraParameters::KEY_FLASH_MODE));
+ return -EINVAL;
+ }
+ if (strcmp(new_param.get(CameraParameters::KEY_FOCUS_MODE),
+ CameraParameters::FOCUS_MODE_FIXED)) {
+ ALOGE("%s: Unsupported flash mode: %s",
+ __FUNCTION__,
+ new_param.get(CameraParameters::KEY_FOCUS_MODE));
+ return -EINVAL;
+ }
+
+ int preview_width, preview_height;
+ new_param.getPreviewSize(&preview_width, &preview_height);
+ if (preview_width <= 0 || preview_height <= 0) return -EINVAL;
+
+
+ /*
+ * Check for new exposure compensation parameter.
+ */
+ int new_exposure_compensation = new_param.getInt(
+ CameraParameters::KEY_EXPOSURE_COMPENSATION);
+ const int min_exposure_compensation = new_param.getInt(
+ CameraParameters::KEY_MIN_EXPOSURE_COMPENSATION);
+ const int max_exposure_compensation = new_param.getInt(
+ CameraParameters::KEY_MAX_EXPOSURE_COMPENSATION);
+
+ // Checks if the exposure compensation change is supported.
+ if ((min_exposure_compensation != 0) || (max_exposure_compensation != 0)) {
+ if (new_exposure_compensation > max_exposure_compensation) {
+ new_exposure_compensation = max_exposure_compensation;
+ }
+ if (new_exposure_compensation < min_exposure_compensation) {
+ new_exposure_compensation = min_exposure_compensation;
+ }
+
+ const int current_exposure_compensation = mParameters.getInt(
+ CameraParameters::KEY_EXPOSURE_COMPENSATION);
+ if (current_exposure_compensation != new_exposure_compensation) {
+ const float exposure_value = new_exposure_compensation *
+ new_param.getFloat(
+ CameraParameters::KEY_EXPOSURE_COMPENSATION_STEP);
+
+ getCameraDevice()->setExposureCompensation(
+ exposure_value);
+ }
+ }
+
+ const char* new_white_balance = new_param.get(
+ CameraParameters::KEY_WHITE_BALANCE);
+ const char* supported_white_balance = new_param.get(
+ CameraParameters::KEY_SUPPORTED_WHITE_BALANCE);
+
+ if ((supported_white_balance != NULL) && (new_white_balance != NULL) &&
+ (strstr(supported_white_balance, new_white_balance) != NULL)) {
+
+ const char* current_white_balance = mParameters.get(
+ CameraParameters::KEY_WHITE_BALANCE);
+ if ((current_white_balance == NULL) ||
+ (strcmp(current_white_balance, new_white_balance) != 0)) {
+ ALOGV("Setting white balance to %s", new_white_balance);
+ getCameraDevice()->setWhiteBalanceMode(new_white_balance);
+ }
+ }
+
+ mParameters = new_param;
+
+ return NO_ERROR;
+}
+
+/* A dumb variable indicating "no params" / error on the exit from
+ * EmulatedCamera::getParameters(). */
+static char lNoParam = '\0';
+char* EmulatedCamera::getParameters()
+{
+ String8 params(mParameters.flatten());
+ char* ret_str =
+ reinterpret_cast<char*>(malloc(sizeof(char) * (params.length()+1)));
+ memset(ret_str, 0, params.length()+1);
+ if (ret_str != NULL) {
+ strncpy(ret_str, params.string(), params.length()+1);
+ return ret_str;
+ } else {
+ ALOGE("%s: Unable to allocate string for %s", __FUNCTION__, params.string());
+ /* Apparently, we can't return NULL fron this routine. */
+ return &lNoParam;
+ }
+}
+
+void EmulatedCamera::putParameters(char* params)
+{
+ /* This method simply frees parameters allocated in getParameters(). */
+ if (params != NULL && params != &lNoParam) {
+ free(params);
+ }
+}
+
+status_t EmulatedCamera::sendCommand(int32_t cmd, int32_t arg1, int32_t arg2)
+{
+ ALOGV("%s: cmd = %d, arg1 = %d, arg2 = %d", __FUNCTION__, cmd, arg1, arg2);
+
+ switch (cmd) {
+ case CAMERA_CMD_START_FACE_DETECTION:
+ case CAMERA_CMD_STOP_FACE_DETECTION:
+ return -EINVAL;
+ }
+
+ /* TODO: Future enhancements. */
+ return 0;
+}
+
+void EmulatedCamera::releaseCamera()
+{
+ ALOGV("%s", __FUNCTION__);
+
+ cleanupCamera();
+}
+
+status_t EmulatedCamera::dumpCamera(int fd)
+{
+ ALOGV("%s", __FUNCTION__);
+
+ /* TODO: Future enhancements. */
+ return -EINVAL;
+}
+
+/****************************************************************************
+ * Preview management.
+ ***************************************************************************/
+
+status_t EmulatedCamera::doStartPreview()
+{
+ ALOGV("%s", __FUNCTION__);
+
+ EmulatedCameraDevice* camera_dev = getCameraDevice();
+ if (camera_dev->isStarted()) {
+ camera_dev->stopDeliveringFrames();
+ camera_dev->stopDevice();
+ }
+
+ status_t res = mPreviewWindow.startPreview();
+ if (res != NO_ERROR) {
+ return res;
+ }
+
+ /* Make sure camera device is connected. */
+ if (!camera_dev->isConnected()) {
+ res = camera_dev->connectDevice();
+ if (res != NO_ERROR) {
+ mPreviewWindow.stopPreview();
+ return res;
+ }
+ }
+
+ int width, height;
+ /* Lets see what should we use for frame width, and height. */
+ if (mParameters.get(CameraParameters::KEY_VIDEO_SIZE) != NULL) {
+ mParameters.getVideoSize(&width, &height);
+ } else {
+ mParameters.getPreviewSize(&width, &height);
+ }
+ /* Lets see what should we use for the frame pixel format. Note that there
+ * are two parameters that define pixel formats for frames sent to the
+ * application via notification callbacks:
+ * - KEY_VIDEO_FRAME_FORMAT, that is used when recording video, and
+ * - KEY_PREVIEW_FORMAT, that is used for preview frame notification.
+ * We choose one or the other, depending on "recording-hint" property set by
+ * the framework that indicating intention: video, or preview. */
+ const char* pix_fmt = NULL;
+ const char* is_video = mParameters.get(EmulatedCamera::RECORDING_HINT_KEY);
+ if (is_video == NULL) {
+ is_video = CameraParameters::FALSE;
+ }
+ if (strcmp(is_video, CameraParameters::TRUE) == 0) {
+ /* Video recording is requested. Lets see if video frame format is set. */
+ pix_fmt = mParameters.get(CameraParameters::KEY_VIDEO_FRAME_FORMAT);
+ }
+ /* If this was not video recording, or video frame format is not set, lets
+ * use preview pixel format for the main framebuffer. */
+ if (pix_fmt == NULL) {
+ pix_fmt = mParameters.getPreviewFormat();
+ }
+ if (pix_fmt == NULL) {
+ ALOGE("%s: Unable to obtain video format", __FUNCTION__);
+ mPreviewWindow.stopPreview();
+ return EINVAL;
+ }
+
+ /* Convert framework's pixel format to the FOURCC one. */
+ uint32_t org_fmt;
+ if (strcmp(pix_fmt, CameraParameters::PIXEL_FORMAT_YUV420P) == 0) {
+ org_fmt = V4L2_PIX_FMT_YUV420;
+ } else if (strcmp(pix_fmt, CameraParameters::PIXEL_FORMAT_RGBA8888) == 0) {
+ org_fmt = V4L2_PIX_FMT_RGB32;
+ } else if (strcmp(pix_fmt, CameraParameters::PIXEL_FORMAT_YUV420SP) == 0) {
+ org_fmt = V4L2_PIX_FMT_NV21;
+ } else {
+ ALOGE("%s: Unsupported pixel format %s", __FUNCTION__, pix_fmt);
+ mPreviewWindow.stopPreview();
+ return EINVAL;
+ }
+
+ /* Fetch the desired frame rate. */
+ int min_fps_rate, max_fps_rate;
+ mParameters.getPreviewFpsRange(&min_fps_rate, &max_fps_rate);
+
+ ALOGD("Starting camera: %dx%d -> %.4s(%s)",
+ width, height, reinterpret_cast<const char*>(&org_fmt), pix_fmt);
+ res = camera_dev->startDevice(width, height, org_fmt, max_fps_rate);
+ if (res != NO_ERROR) {
+ mPreviewWindow.stopPreview();
+ return res;
+ }
+
+ res = camera_dev->startDeliveringFrames(false);
+ if (res != NO_ERROR) {
+ camera_dev->stopDevice();
+ mPreviewWindow.stopPreview();
+ }
+
+ return res;
+}
+
+status_t EmulatedCamera::doStopPreview()
+{
+ ALOGV("%s", __FUNCTION__);
+
+ status_t res = NO_ERROR;
+ if (mPreviewWindow.isPreviewEnabled()) {
+ /* Stop the camera. */
+ if (getCameraDevice()->isStarted()) {
+ getCameraDevice()->stopDeliveringFrames();
+ res = getCameraDevice()->stopDevice();
+ }
+
+ if (res == NO_ERROR) {
+ /* Disable preview as well. */
+ mPreviewWindow.stopPreview();
+ }
+ }
+
+ return NO_ERROR;
+}
+
+/****************************************************************************
+ * Private API.
+ ***************************************************************************/
+
+status_t EmulatedCamera::cleanupCamera()
+{
+ status_t res = NO_ERROR;
+
+ /* If preview is running - stop it. */
+ res = doStopPreview();
+ if (res != NO_ERROR) {
+ return -res;
+ }
+
+ /* Stop and disconnect the camera device. */
+ EmulatedCameraDevice* const camera_dev = getCameraDevice();
+ if (camera_dev != NULL) {
+ if (camera_dev->isStarted()) {
+ camera_dev->stopDeliveringFrames();
+ res = camera_dev->stopDevice();
+ if (res != NO_ERROR) {
+ return -res;
+ }
+ }
+ if (camera_dev->isConnected()) {
+ res = camera_dev->disconnectDevice();
+ if (res != NO_ERROR) {
+ return -res;
+ }
+ }
+ }
+
+ mCallbackNotifier.cleanupCBNotifier();
+
+ return NO_ERROR;
+}
+
+/****************************************************************************
+ * Camera API callbacks as defined by camera_device_ops structure.
+ *
+ * Callbacks here simply dispatch the calls to an appropriate method inside
+ * EmulatedCamera instance, defined by the 'dev' parameter.
+ ***************************************************************************/
+
+int EmulatedCamera::set_preview_window(struct camera_device* dev,
+ struct preview_stream_ops* window)
+{
+ EmulatedCamera* ec = reinterpret_cast<EmulatedCamera*>(dev->priv);
+ if (ec == NULL) {
+ ALOGE("%s: Unexpected NULL camera device", __FUNCTION__);
+ return -EINVAL;
+ }
+ return ec->setPreviewWindow(window);
+}
+
+void EmulatedCamera::set_callbacks(
+ struct camera_device* dev,
+ camera_notify_callback notify_cb,
+ camera_data_callback data_cb,
+ camera_data_timestamp_callback data_cb_timestamp,
+ camera_request_memory get_memory,
+ void* user)
+{
+ EmulatedCamera* ec = reinterpret_cast<EmulatedCamera*>(dev->priv);
+ if (ec == NULL) {
+ ALOGE("%s: Unexpected NULL camera device", __FUNCTION__);
+ return;
+ }
+ ec->setCallbacks(notify_cb, data_cb, data_cb_timestamp, get_memory, user);
+}
+
+void EmulatedCamera::enable_msg_type(struct camera_device* dev, int32_t msg_type)
+{
+ EmulatedCamera* ec = reinterpret_cast<EmulatedCamera*>(dev->priv);
+ if (ec == NULL) {
+ ALOGE("%s: Unexpected NULL camera device", __FUNCTION__);
+ return;
+ }
+ ec->enableMsgType(msg_type);
+}
+
+void EmulatedCamera::disable_msg_type(struct camera_device* dev, int32_t msg_type)
+{
+ EmulatedCamera* ec = reinterpret_cast<EmulatedCamera*>(dev->priv);
+ if (ec == NULL) {
+ ALOGE("%s: Unexpected NULL camera device", __FUNCTION__);
+ return;
+ }
+ ec->disableMsgType(msg_type);
+}
+
+int EmulatedCamera::msg_type_enabled(struct camera_device* dev, int32_t msg_type)
+{
+ EmulatedCamera* ec = reinterpret_cast<EmulatedCamera*>(dev->priv);
+ if (ec == NULL) {
+ ALOGE("%s: Unexpected NULL camera device", __FUNCTION__);
+ return -EINVAL;
+ }
+ return ec->isMsgTypeEnabled(msg_type);
+}
+
+int EmulatedCamera::start_preview(struct camera_device* dev)
+{
+ EmulatedCamera* ec = reinterpret_cast<EmulatedCamera*>(dev->priv);
+ if (ec == NULL) {
+ ALOGE("%s: Unexpected NULL camera device", __FUNCTION__);
+ return -EINVAL;
+ }
+ return ec->startPreview();
+}
+
+void EmulatedCamera::stop_preview(struct camera_device* dev)
+{
+ EmulatedCamera* ec = reinterpret_cast<EmulatedCamera*>(dev->priv);
+ if (ec == NULL) {
+ ALOGE("%s: Unexpected NULL camera device", __FUNCTION__);
+ return;
+ }
+ ec->stopPreview();
+}
+
+int EmulatedCamera::preview_enabled(struct camera_device* dev)
+{
+ EmulatedCamera* ec = reinterpret_cast<EmulatedCamera*>(dev->priv);
+ if (ec == NULL) {
+ ALOGE("%s: Unexpected NULL camera device", __FUNCTION__);
+ return -EINVAL;
+ }
+ return ec->isPreviewEnabled();
+}
+
+int EmulatedCamera::store_meta_data_in_buffers(struct camera_device* dev,
+ int enable)
+{
+ EmulatedCamera* ec = reinterpret_cast<EmulatedCamera*>(dev->priv);
+ if (ec == NULL) {
+ ALOGE("%s: Unexpected NULL camera device", __FUNCTION__);
+ return -EINVAL;
+ }
+ return ec->storeMetaDataInBuffers(enable);
+}
+
+int EmulatedCamera::start_recording(struct camera_device* dev)
+{
+ EmulatedCamera* ec = reinterpret_cast<EmulatedCamera*>(dev->priv);
+ if (ec == NULL) {
+ ALOGE("%s: Unexpected NULL camera device", __FUNCTION__);
+ return -EINVAL;
+ }
+ return ec->startRecording();
+}
+
+void EmulatedCamera::stop_recording(struct camera_device* dev)
+{
+ EmulatedCamera* ec = reinterpret_cast<EmulatedCamera*>(dev->priv);
+ if (ec == NULL) {
+ ALOGE("%s: Unexpected NULL camera device", __FUNCTION__);
+ return;
+ }
+ ec->stopRecording();
+}
+
+int EmulatedCamera::recording_enabled(struct camera_device* dev)
+{
+ EmulatedCamera* ec = reinterpret_cast<EmulatedCamera*>(dev->priv);
+ if (ec == NULL) {
+ ALOGE("%s: Unexpected NULL camera device", __FUNCTION__);
+ return -EINVAL;
+ }
+ return ec->isRecordingEnabled();
+}
+
+void EmulatedCamera::release_recording_frame(struct camera_device* dev,
+ const void* opaque)
+{
+ EmulatedCamera* ec = reinterpret_cast<EmulatedCamera*>(dev->priv);
+ if (ec == NULL) {
+ ALOGE("%s: Unexpected NULL camera device", __FUNCTION__);
+ return;
+ }
+ ec->releaseRecordingFrame(opaque);
+}
+
+int EmulatedCamera::auto_focus(struct camera_device* dev)
+{
+ EmulatedCamera* ec = reinterpret_cast<EmulatedCamera*>(dev->priv);
+ if (ec == NULL) {
+ ALOGE("%s: Unexpected NULL camera device", __FUNCTION__);
+ return -EINVAL;
+ }
+ return ec->setAutoFocus();
+}
+
+int EmulatedCamera::cancel_auto_focus(struct camera_device* dev)
+{
+ EmulatedCamera* ec = reinterpret_cast<EmulatedCamera*>(dev->priv);
+ if (ec == NULL) {
+ ALOGE("%s: Unexpected NULL camera device", __FUNCTION__);
+ return -EINVAL;
+ }
+ return ec->cancelAutoFocus();
+}
+
+int EmulatedCamera::take_picture(struct camera_device* dev)
+{
+ EmulatedCamera* ec = reinterpret_cast<EmulatedCamera*>(dev->priv);
+ if (ec == NULL) {
+ ALOGE("%s: Unexpected NULL camera device", __FUNCTION__);
+ return -EINVAL;
+ }
+ return ec->takePicture();
+}
+
+int EmulatedCamera::cancel_picture(struct camera_device* dev)
+{
+ EmulatedCamera* ec = reinterpret_cast<EmulatedCamera*>(dev->priv);
+ if (ec == NULL) {
+ ALOGE("%s: Unexpected NULL camera device", __FUNCTION__);
+ return -EINVAL;
+ }
+ return ec->cancelPicture();
+}
+
+int EmulatedCamera::set_parameters(struct camera_device* dev, const char* parms)
+{
+ EmulatedCamera* ec = reinterpret_cast<EmulatedCamera*>(dev->priv);
+ if (ec == NULL) {
+ ALOGE("%s: Unexpected NULL camera device", __FUNCTION__);
+ return -EINVAL;
+ }
+ return ec->setParameters(parms);
+}
+
+char* EmulatedCamera::get_parameters(struct camera_device* dev)
+{
+ EmulatedCamera* ec = reinterpret_cast<EmulatedCamera*>(dev->priv);
+ if (ec == NULL) {
+ ALOGE("%s: Unexpected NULL camera device", __FUNCTION__);
+ return NULL;
+ }
+ return ec->getParameters();
+}
+
+void EmulatedCamera::put_parameters(struct camera_device* dev, char* params)
+{
+ EmulatedCamera* ec = reinterpret_cast<EmulatedCamera*>(dev->priv);
+ if (ec == NULL) {
+ ALOGE("%s: Unexpected NULL camera device", __FUNCTION__);
+ return;
+ }
+ ec->putParameters(params);
+}
+
+int EmulatedCamera::send_command(struct camera_device* dev,
+ int32_t cmd,
+ int32_t arg1,
+ int32_t arg2)
+{
+ EmulatedCamera* ec = reinterpret_cast<EmulatedCamera*>(dev->priv);
+ if (ec == NULL) {
+ ALOGE("%s: Unexpected NULL camera device", __FUNCTION__);
+ return -EINVAL;
+ }
+ return ec->sendCommand(cmd, arg1, arg2);
+}
+
+void EmulatedCamera::release(struct camera_device* dev)
+{
+ EmulatedCamera* ec = reinterpret_cast<EmulatedCamera*>(dev->priv);
+ if (ec == NULL) {
+ ALOGE("%s: Unexpected NULL camera device", __FUNCTION__);
+ return;
+ }
+ ec->releaseCamera();
+}
+
+int EmulatedCamera::dump(struct camera_device* dev, int fd)
+{
+ EmulatedCamera* ec = reinterpret_cast<EmulatedCamera*>(dev->priv);
+ if (ec == NULL) {
+ ALOGE("%s: Unexpected NULL camera device", __FUNCTION__);
+ return -EINVAL;
+ }
+ return ec->dumpCamera(fd);
+}
+
+int EmulatedCamera::close(struct hw_device_t* device)
+{
+ EmulatedCamera* ec =
+ reinterpret_cast<EmulatedCamera*>(reinterpret_cast<struct camera_device*>(device)->priv);
+ if (ec == NULL) {
+ ALOGE("%s: Unexpected NULL camera device", __FUNCTION__);
+ return -EINVAL;
+ }
+ return ec->closeCamera();
+}
+
+/****************************************************************************
+ * Static initializer for the camera callback API
+ ****************************************************************************/
+
+camera_device_ops_t EmulatedCamera::mDeviceOps = {
+ EmulatedCamera::set_preview_window,
+ EmulatedCamera::set_callbacks,
+ EmulatedCamera::enable_msg_type,
+ EmulatedCamera::disable_msg_type,
+ EmulatedCamera::msg_type_enabled,
+ EmulatedCamera::start_preview,
+ EmulatedCamera::stop_preview,
+ EmulatedCamera::preview_enabled,
+ EmulatedCamera::store_meta_data_in_buffers,
+ EmulatedCamera::start_recording,
+ EmulatedCamera::stop_recording,
+ EmulatedCamera::recording_enabled,
+ EmulatedCamera::release_recording_frame,
+ EmulatedCamera::auto_focus,
+ EmulatedCamera::cancel_auto_focus,
+ EmulatedCamera::take_picture,
+ EmulatedCamera::cancel_picture,
+ EmulatedCamera::set_parameters,
+ EmulatedCamera::get_parameters,
+ EmulatedCamera::put_parameters,
+ EmulatedCamera::send_command,
+ EmulatedCamera::release,
+ EmulatedCamera::dump
+};
+
+/****************************************************************************
+ * Common keys
+ ***************************************************************************/
+
+const char EmulatedCamera::FACING_KEY[] = "prop-facing";
+const char EmulatedCamera::ORIENTATION_KEY[] = "prop-orientation";
+const char EmulatedCamera::RECORDING_HINT_KEY[] = "recording-hint";
+
+/****************************************************************************
+ * Common string values
+ ***************************************************************************/
+
+const char EmulatedCamera::FACING_BACK[] = "back";
+const char EmulatedCamera::FACING_FRONT[] = "front";
+
+/****************************************************************************
+ * Helper routines
+ ***************************************************************************/
+
+static char* AddValue(const char* param, const char* val)
+{
+ const size_t len1 = strlen(param);
+ const size_t len2 = strlen(val);
+ char* ret = reinterpret_cast<char*>(malloc(len1 + len2 + 2));
+ ALOGE_IF(ret == NULL, "%s: Memory failure", __FUNCTION__);
+ if (ret != NULL) {
+ memcpy(ret, param, len1);
+ ret[len1] = ',';
+ memcpy(ret + len1 + 1, val, len2);
+ ret[len1 + len2 + 1] = '\0';
+ }
+ return ret;
+}
+
+/****************************************************************************
+ * Parameter debugging helpers
+ ***************************************************************************/
+
+#if DEBUG_PARAM
+static void PrintParamDiff(const CameraParameters& current,
+ const char* new_par)
+{
+ char tmp[2048];
+ const char* wrk = new_par;
+
+ /* Divided with ';' */
+ const char* next = strchr(wrk, ';');
+ while (next != NULL) {
+ snprintf(tmp, sizeof(tmp), "%.*s", (int)(intptr_t)(next-wrk), wrk);
+ /* in the form key=value */
+ char* val = strchr(tmp, '=');
+ if (val != NULL) {
+ *val = '\0'; val++;
+ const char* in_current = current.get(tmp);
+ if (in_current != NULL) {
+ if (strcmp(in_current, val)) {
+ ALOGD("=== Value changed: %s: %s -> %s", tmp, in_current, val);
+ }
+ } else {
+ ALOGD("+++ New parameter: %s=%s", tmp, val);
+ }
+ } else {
+ ALOGW("No value separator in %s", tmp);
+ }
+ wrk = next + 1;
+ next = strchr(wrk, ';');
+ }
+}
+#endif /* DEBUG_PARAM */
+
+}; /* namespace android */
diff --git a/guest/hals/camera/EmulatedCamera.h b/guest/hals/camera/EmulatedCamera.h
new file mode 100755
index 0000000..9976250
--- /dev/null
+++ b/guest/hals/camera/EmulatedCamera.h
@@ -0,0 +1,410 @@
+/*
+ * Copyright (C) 2011 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 HW_EMULATOR_CAMERA_EMULATED_CAMERA_H
+#define HW_EMULATOR_CAMERA_EMULATED_CAMERA_H
+
+/*
+ * Contains declaration of a class EmulatedCamera that encapsulates
+ * functionality common to all version 1.0 emulated camera devices ("fake",
+ * "webcam", "video file", etc.). Instances of this class (for each emulated
+ * camera) are created during the construction of the EmulatedCameraFactory
+ * instance. This class serves as an entry point for all camera API calls that
+ * defined by camera_device_ops_t API.
+ */
+
+#include <camera/CameraParameters.h>
+#include "EmulatedBaseCamera.h"
+#include "EmulatedCameraDevice.h"
+#include "PreviewWindow.h"
+#include "CallbackNotifier.h"
+
+namespace android {
+
+/* Encapsulates functionality common to all version 1.0 emulated camera devices
+ * ("fake", "webcam", "file stream", etc.).
+ *
+ * Note that EmulatedCameraFactory instantiates object of this class just once,
+ * when EmulatedCameraFactory instance gets constructed. Connection to /
+ * disconnection from the actual camera device is handled by calls to
+ * connectDevice(), and closeCamera() methods of this class that are ivoked in
+ * response to hw_module_methods_t::open, and camera_device::close callbacks.
+ */
+class EmulatedCamera : public camera_device, public EmulatedBaseCamera {
+public:
+ /* Constructs EmulatedCamera instance.
+ * Param:
+ * cameraId - Zero based camera identifier, which is an index of the camera
+ * instance in camera factory's array.
+ * module - Emulated camera HAL module descriptor.
+ */
+ EmulatedCamera(int cameraId,
+ struct hw_module_t* module);
+
+ /* Destructs EmulatedCamera instance. */
+ virtual ~EmulatedCamera();
+
+ /****************************************************************************
+ * Abstract API
+ ***************************************************************************/
+
+public:
+ /* Gets emulated camera device used by this instance of the emulated camera.
+ */
+ virtual EmulatedCameraDevice* getCameraDevice() = 0;
+
+ /****************************************************************************
+ * Public API
+ ***************************************************************************/
+
+public:
+ /** Override of base class method */
+ virtual status_t Initialize(const cvd::CameraDefinition& properties);
+
+ /* Next frame is available in the camera device.
+ * This is a notification callback that is invoked by the camera device when
+ * a new frame is available.
+ * Note that most likely this method is called in context of a worker thread
+ * that camera device has created for frame capturing.
+ * Param:
+ * frame - Captured frame, or NULL if camera device didn't pull the frame
+ * yet. If NULL is passed in this parameter use GetCurrentFrame method
+ * of the camera device class to obtain the next frame. Also note that
+ * the size of the frame that is passed here (as well as the frame
+ * returned from the GetCurrentFrame method) is defined by the current
+ * frame settings (width + height + pixel format) for the camera device.
+ * timestamp - Frame's timestamp.
+ * camera_dev - Camera device instance that delivered the frame.
+ */
+ virtual void onNextFrameAvailable(const void* frame,
+ nsecs_t timestamp,
+ EmulatedCameraDevice* camera_dev);
+
+ /* Entry point for notifications that occur in camera device.
+ * Param:
+ * err - CAMERA_ERROR_XXX error code.
+ */
+ virtual void onCameraDeviceError(int err);
+
+ /* Device acquired focus.
+ * This is a notification callback that is invoked by the camera device
+ * when focusing operation (requested by client) completes.
+ */
+ virtual void onCameraFocusAcquired();
+
+ /****************************************************************************
+ * Camera API implementation
+ ***************************************************************************/
+
+public:
+ /** Override of base class method */
+ virtual status_t connectCamera(hw_device_t** device);
+
+ /** Override of base class method */
+ virtual status_t closeCamera();
+
+ /** Override of base class method */
+ virtual status_t getCameraInfo(struct camera_info* info);
+
+ /** Override of base class method */
+ virtual status_t getImageMetadata(struct ImageMetadata* meta);
+
+ /****************************************************************************
+ * Camera API implementation.
+ * These methods are called from the camera API callback routines.
+ ***************************************************************************/
+
+protected:
+ /* Actual handler for camera_device_ops_t::set_preview_window callback.
+ * NOTE: When this method is called the object is locked.
+ * Note that failures in this method are reported as negave EXXX statuses.
+ */
+ virtual status_t setPreviewWindow(struct preview_stream_ops *window);
+
+ /* Actual handler for camera_device_ops_t::set_callbacks callback.
+ * NOTE: When this method is called the object is locked.
+ */
+ virtual void setCallbacks(camera_notify_callback notify_cb,
+ camera_data_callback data_cb,
+ camera_data_timestamp_callback data_cb_timestamp,
+ camera_request_memory get_memory,
+ void* user);
+
+ /* Actual handler for camera_device_ops_t::enable_msg_type callback.
+ * NOTE: When this method is called the object is locked.
+ */
+ virtual void enableMsgType(int32_t msg_type);
+
+ /* Actual handler for camera_device_ops_t::disable_msg_type callback.
+ * NOTE: When this method is called the object is locked.
+ */
+ virtual void disableMsgType(int32_t msg_type);
+
+ /* Actual handler for camera_device_ops_t::msg_type_enabled callback.
+ * NOTE: When this method is called the object is locked.
+ * Return:
+ * 0 if message(s) is (are) disabled, != 0 if enabled.
+ */
+ virtual int isMsgTypeEnabled(int32_t msg_type);
+
+ /* Actual handler for camera_device_ops_t::start_preview callback.
+ * NOTE: When this method is called the object is locked.
+ * Note that failures in this method are reported as negave EXXX statuses.
+ */
+ virtual status_t startPreview();
+
+ /* Actual handler for camera_device_ops_t::stop_preview callback.
+ * NOTE: When this method is called the object is locked.
+ */
+ virtual void stopPreview();
+
+ /* Actual handler for camera_device_ops_t::preview_enabled callback.
+ * NOTE: When this method is called the object is locked.
+ * Return:
+ * 0 if preview is disabled, != 0 if enabled.
+ */
+ virtual int isPreviewEnabled();
+
+ /* Actual handler for camera_device_ops_t::store_meta_data_in_buffers callback.
+ * NOTE: When this method is called the object is locked.
+ * Note that failures in this method are reported as negave EXXX statuses.
+ */
+ virtual status_t storeMetaDataInBuffers(int enable);
+
+ /* Actual handler for camera_device_ops_t::start_recording callback.
+ * NOTE: When this method is called the object is locked.
+ * Note that failures in this method are reported as negave EXXX statuses.
+ */
+ virtual status_t startRecording();
+
+ /* Actual handler for camera_device_ops_t::stop_recording callback.
+ * NOTE: When this method is called the object is locked.
+ */
+ virtual void stopRecording();
+
+ /* Actual handler for camera_device_ops_t::recording_enabled callback.
+ * NOTE: When this method is called the object is locked.
+ * Return:
+ * 0 if recording is disabled, != 0 if enabled.
+ */
+ virtual int isRecordingEnabled();
+
+ /* Actual handler for camera_device_ops_t::release_recording_frame callback.
+ * NOTE: When this method is called the object is locked.
+ */
+ virtual void releaseRecordingFrame(const void* opaque);
+
+ /* Actual handler for camera_device_ops_t::auto_focus callback.
+ * NOTE: When this method is called the object is locked.
+ * Note that failures in this method are reported as negave EXXX statuses.
+ */
+ virtual status_t setAutoFocus();
+
+ /* Actual handler for camera_device_ops_t::cancel_auto_focus callback.
+ * NOTE: When this method is called the object is locked.
+ * Note that failures in this method are reported as negave EXXX statuses.
+ */
+ virtual status_t cancelAutoFocus();
+
+ /* Actual handler for camera_device_ops_t::take_picture callback.
+ * NOTE: When this method is called the object is locked.
+ * Note that failures in this method are reported as negave EXXX statuses.
+ */
+ virtual status_t takePicture();
+
+ /* Actual handler for camera_device_ops_t::cancel_picture callback.
+ * NOTE: When this method is called the object is locked.
+ * Note that failures in this method are reported as negave EXXX statuses.
+ */
+ virtual status_t cancelPicture();
+
+ /* Actual handler for camera_device_ops_t::set_parameters callback.
+ * NOTE: When this method is called the object is locked.
+ * Note that failures in this method are reported as negave EXXX statuses.
+ */
+ virtual status_t setParameters(const char* parms);
+
+ /* Actual handler for camera_device_ops_t::get_parameters callback.
+ * NOTE: When this method is called the object is locked.
+ * Return:
+ * Flattened parameters string. The caller will free the buffer allocated
+ * for the string by calling camera_device_ops_t::put_parameters callback.
+ */
+ virtual char* getParameters();
+
+ /* Actual handler for camera_device_ops_t::put_parameters callback.
+ * Called to free the string returned from camera_device_ops_t::get_parameters
+ * callback. There is nothing more to it: the name of the callback is just
+ * misleading.
+ * NOTE: When this method is called the object is locked.
+ */
+ virtual void putParameters(char* params);
+
+ /* Actual handler for camera_device_ops_t::send_command callback.
+ * NOTE: When this method is called the object is locked.
+ * Note that failures in this method are reported as negave EXXX statuses.
+ */
+ virtual status_t sendCommand(int32_t cmd, int32_t arg1, int32_t arg2);
+
+ /* Actual handler for camera_device_ops_t::release callback.
+ * NOTE: When this method is called the object is locked.
+ */
+ virtual void releaseCamera();
+
+ /* Actual handler for camera_device_ops_t::dump callback.
+ * NOTE: When this method is called the object is locked.
+ * Note that failures in this method are reported as negave EXXX statuses.
+ */
+ virtual status_t dumpCamera(int fd);
+
+ /****************************************************************************
+ * Preview management.
+ ***************************************************************************/
+
+protected:
+ /* Starts preview.
+ * Note that when this method is called mPreviewWindow may be NULL,
+ * indicating that framework has an intention to start displaying video
+ * frames, but didn't create the preview window yet.
+ * Return:
+ * NO_ERROR on success, or an appropriate error status on failure.
+ */
+ virtual status_t doStartPreview();
+
+ /* Stops preview.
+ * This method reverts DoStartPreview.
+ * Return:
+ * NO_ERROR on success, or an appropriate error status on failure.
+ */
+ virtual status_t doStopPreview();
+
+ /****************************************************************************
+ * Private API.
+ ***************************************************************************/
+
+protected:
+ /* Cleans up camera when released. */
+ virtual status_t cleanupCamera();
+
+ /****************************************************************************
+ * Camera API callbacks as defined by camera_device_ops structure.
+ * See hardware/libhardware/include/hardware/camera.h for information on
+ * each of these callbacks. Implemented in this class, these callbacks simply
+ * dispatch the call into an instance of EmulatedCamera class defined by the
+ * 'camera_device' parameter.
+ ***************************************************************************/
+
+private:
+ static int set_preview_window(struct camera_device* dev,
+ struct preview_stream_ops* window);
+
+ static void set_callbacks(struct camera_device* dev,
+ camera_notify_callback notify_cb,
+ camera_data_callback data_cb,
+ camera_data_timestamp_callback data_cb_timestamp,
+ camera_request_memory get_memory,
+ void* user);
+
+ static void enable_msg_type(struct camera_device* dev, int32_t msg_type);
+
+ static void disable_msg_type(struct camera_device* dev, int32_t msg_type);
+
+ static int msg_type_enabled(struct camera_device* dev, int32_t msg_type);
+
+ static int start_preview(struct camera_device* dev);
+
+ static void stop_preview(struct camera_device* dev);
+
+ static int preview_enabled(struct camera_device* dev);
+
+ static int store_meta_data_in_buffers(struct camera_device* dev, int enable);
+
+ static int start_recording(struct camera_device* dev);
+
+ static void stop_recording(struct camera_device* dev);
+
+ static int recording_enabled(struct camera_device* dev);
+
+ static void release_recording_frame(struct camera_device* dev,
+ const void* opaque);
+
+ static int auto_focus(struct camera_device* dev);
+
+ static int cancel_auto_focus(struct camera_device* dev);
+
+ static int take_picture(struct camera_device* dev);
+
+ static int cancel_picture(struct camera_device* dev);
+
+ static int set_parameters(struct camera_device* dev, const char* parms);
+
+ static char* get_parameters(struct camera_device* dev);
+
+ static void put_parameters(struct camera_device* dev, char* params);
+
+ static int send_command(struct camera_device* dev,
+ int32_t cmd,
+ int32_t arg1,
+ int32_t arg2);
+
+ static void release(struct camera_device* dev);
+
+ static int dump(struct camera_device* dev, int fd);
+
+ static int close(struct hw_device_t* device);
+
+ /****************************************************************************
+ * Data members
+ ***************************************************************************/
+
+protected:
+ /* Locks this instance for parameters, state, etc. change. */
+ Mutex mObjectLock;
+
+ /* Camera parameters. */
+ CameraParameters mParameters;
+
+ /* Preview window. */
+ PreviewWindow mPreviewWindow;
+
+ /* Callback notifier. */
+ CallbackNotifier mCallbackNotifier;
+
+private:
+ /* Registered callbacks implementing camera API. */
+ static camera_device_ops_t mDeviceOps;
+
+ /****************************************************************************
+ * Common keys
+ ***************************************************************************/
+
+public:
+ static const char FACING_KEY[];
+ static const char ORIENTATION_KEY[];
+ static const char RECORDING_HINT_KEY[];
+
+ /****************************************************************************
+ * Common string values
+ ***************************************************************************/
+
+ /* Possible values for FACING_KEY */
+ static const char FACING_BACK[];
+ static const char FACING_FRONT[];
+};
+
+}; /* namespace android */
+
+#endif /* HW_EMULATOR_CAMERA_EMULATED_CAMERA_H */
diff --git a/guest/hals/camera/EmulatedCamera2.cpp b/guest/hals/camera/EmulatedCamera2.cpp
new file mode 100644
index 0000000..bc5e391
--- /dev/null
+++ b/guest/hals/camera/EmulatedCamera2.cpp
@@ -0,0 +1,410 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+/*
+ * Contains implementation of a class EmulatedCamera that encapsulates
+ * functionality common to all version 2.0 emulated camera devices. Instances
+ * of this class (for each emulated camera) are created during the construction
+ * of the EmulatedCameraFactory instance. This class serves as an entry point
+ * for all camera API calls that defined by camera2_device_ops_t API.
+ */
+
+#define LOG_NDEBUG 0
+#define LOG_TAG "EmulatedCamera2_Camera"
+#include <cutils/log.h>
+
+#include "EmulatedCamera2.h"
+#include "system/camera_metadata.h"
+
+namespace android {
+
+/* Constructs EmulatedCamera2 instance.
+ * Param:
+ * cameraId - Zero based camera identifier, which is an index of the camera
+ * instance in camera factory's array.
+ * module - Emulated camera HAL module descriptor.
+ */
+EmulatedCamera2::EmulatedCamera2(int cameraId,
+ struct hw_module_t* module):
+ EmulatedBaseCamera(cameraId,
+ CAMERA_DEVICE_API_VERSION_2_0,
+ &common,
+ module)
+{
+ common.close = EmulatedCamera2::close;
+ ops = &sDeviceOps;
+ priv = this;
+
+ mNotifyCb = NULL;
+
+ mRequestQueueSrc = NULL;
+ mFrameQueueDst = NULL;
+
+ mVendorTagOps.get_camera_vendor_section_name =
+ EmulatedCamera2::get_camera_vendor_section_name;
+ mVendorTagOps.get_camera_vendor_tag_name =
+ EmulatedCamera2::get_camera_vendor_tag_name;
+ mVendorTagOps.get_camera_vendor_tag_type =
+ EmulatedCamera2::get_camera_vendor_tag_type;
+ mVendorTagOps.parent = this;
+
+ mStatusPresent = true;
+}
+
+/* Destructs EmulatedCamera2 instance. */
+EmulatedCamera2::~EmulatedCamera2() {
+}
+
+/****************************************************************************
+ * Abstract API
+ ***************************************************************************/
+
+/****************************************************************************
+ * Public API
+ ***************************************************************************/
+
+status_t EmulatedCamera2::Initialize(const cvd::CameraDefinition& props) {
+ return NO_ERROR;
+}
+
+/****************************************************************************
+ * Camera API implementation
+ ***************************************************************************/
+
+status_t EmulatedCamera2::connectCamera(hw_device_t** device) {
+ *device = &common;
+ return NO_ERROR;
+}
+
+status_t EmulatedCamera2::closeCamera() {
+ return NO_ERROR;
+}
+
+status_t EmulatedCamera2::getCameraInfo(struct camera_info* info) {
+ return EmulatedBaseCamera::getCameraInfo(info);
+}
+
+/****************************************************************************
+ * Camera Device API implementation.
+ * These methods are called from the camera API callback routines.
+ ***************************************************************************/
+
+/** Request input queue */
+
+int EmulatedCamera2::requestQueueNotify() {
+ return INVALID_OPERATION;
+}
+
+/** Count of requests in flight */
+int EmulatedCamera2::getInProgressCount() {
+ return INVALID_OPERATION;
+}
+
+/** Cancel all captures in flight */
+int EmulatedCamera2::flushCapturesInProgress() {
+ return INVALID_OPERATION;
+}
+
+/** Construct a default request for a given use case */
+int EmulatedCamera2::constructDefaultRequest(
+ int request_template,
+ camera_metadata_t **request) {
+ return INVALID_OPERATION;
+}
+
+/** Output stream creation and management */
+
+int EmulatedCamera2::allocateStream(
+ uint32_t width,
+ uint32_t height,
+ int format,
+ const camera2_stream_ops_t *stream_ops,
+ uint32_t *stream_id,
+ uint32_t *format_actual,
+ uint32_t *usage,
+ uint32_t *max_buffers) {
+ return INVALID_OPERATION;
+}
+
+int EmulatedCamera2::registerStreamBuffers(
+ uint32_t stream_id,
+ int num_buffers,
+ buffer_handle_t *buffers) {
+ return INVALID_OPERATION;
+}
+
+
+int EmulatedCamera2::releaseStream(uint32_t stream_id) {
+ return INVALID_OPERATION;
+}
+
+/** Reprocessing input stream management */
+
+int EmulatedCamera2::allocateReprocessStream(
+ uint32_t width,
+ uint32_t height,
+ uint32_t format,
+ const camera2_stream_in_ops_t *reprocess_stream_ops,
+ uint32_t *stream_id,
+ uint32_t *consumer_usage,
+ uint32_t *max_buffers) {
+ return INVALID_OPERATION;
+}
+
+int EmulatedCamera2::allocateReprocessStreamFromStream(
+ uint32_t output_stream_id,
+ const camera2_stream_in_ops_t *reprocess_stream_ops,
+ uint32_t *stream_id) {
+ return INVALID_OPERATION;
+}
+
+int EmulatedCamera2::releaseReprocessStream(uint32_t stream_id) {
+ return INVALID_OPERATION;
+}
+
+/** 3A triggering */
+
+int EmulatedCamera2::triggerAction(uint32_t trigger_id,
+ int ext1, int ext2) {
+ return INVALID_OPERATION;
+}
+
+/** Custom tag query methods */
+
+const char* EmulatedCamera2::getVendorSectionName(uint32_t tag) {
+ return NULL;
+}
+
+const char* EmulatedCamera2::getVendorTagName(uint32_t tag) {
+ return NULL;
+}
+
+int EmulatedCamera2::getVendorTagType(uint32_t tag) {
+ return -1;
+}
+
+/** Debug methods */
+
+int EmulatedCamera2::dump(int fd) {
+ return INVALID_OPERATION;
+}
+
+/****************************************************************************
+ * Private API.
+ ***************************************************************************/
+
+/****************************************************************************
+ * Camera API callbacks as defined by camera2_device_ops structure. See
+ * hardware/libhardware/include/hardware/camera2.h for information on each
+ * of these callbacks. Implemented in this class, these callbacks simply
+ * dispatch the call into an instance of EmulatedCamera2 class defined by the
+ * 'camera_device2' parameter, or set a member value in the same.
+ ***************************************************************************/
+
+EmulatedCamera2* getInstance(const camera2_device_t *d) {
+ const EmulatedCamera2* cec = static_cast<const EmulatedCamera2*>(d);
+ return const_cast<EmulatedCamera2*>(cec);
+}
+
+int EmulatedCamera2::set_request_queue_src_ops(const camera2_device_t *d,
+ const camera2_request_queue_src_ops *queue_src_ops) {
+ EmulatedCamera2* ec = getInstance(d);
+ ec->mRequestQueueSrc = queue_src_ops;
+ return NO_ERROR;
+}
+
+int EmulatedCamera2::notify_request_queue_not_empty(const camera2_device_t *d) {
+ EmulatedCamera2* ec = getInstance(d);
+ return ec->requestQueueNotify();
+}
+
+int EmulatedCamera2::set_frame_queue_dst_ops(const camera2_device_t *d,
+ const camera2_frame_queue_dst_ops *queue_dst_ops) {
+ EmulatedCamera2* ec = getInstance(d);
+ ec->mFrameQueueDst = queue_dst_ops;
+ return NO_ERROR;
+}
+
+int EmulatedCamera2::get_in_progress_count(const camera2_device_t *d) {
+ EmulatedCamera2* ec = getInstance(d);
+ return ec->getInProgressCount();
+}
+
+int EmulatedCamera2::flush_captures_in_progress(const camera2_device_t *d) {
+ EmulatedCamera2* ec = getInstance(d);
+ return ec->flushCapturesInProgress();
+}
+
+int EmulatedCamera2::construct_default_request(const camera2_device_t *d,
+ int request_template,
+ camera_metadata_t **request) {
+ EmulatedCamera2* ec = getInstance(d);
+ return ec->constructDefaultRequest(request_template, request);
+}
+
+int EmulatedCamera2::allocate_stream(const camera2_device_t *d,
+ uint32_t width,
+ uint32_t height,
+ int format,
+ const camera2_stream_ops_t *stream_ops,
+ uint32_t *stream_id,
+ uint32_t *format_actual,
+ uint32_t *usage,
+ uint32_t *max_buffers) {
+ EmulatedCamera2* ec = getInstance(d);
+ return ec->allocateStream(width, height, format, stream_ops,
+ stream_id, format_actual, usage, max_buffers);
+}
+
+int EmulatedCamera2::register_stream_buffers(const camera2_device_t *d,
+ uint32_t stream_id,
+ int num_buffers,
+ buffer_handle_t *buffers) {
+ EmulatedCamera2* ec = getInstance(d);
+ return ec->registerStreamBuffers(stream_id,
+ num_buffers,
+ buffers);
+}
+int EmulatedCamera2::release_stream(const camera2_device_t *d,
+ uint32_t stream_id) {
+ EmulatedCamera2* ec = getInstance(d);
+ return ec->releaseStream(stream_id);
+}
+
+int EmulatedCamera2::allocate_reprocess_stream(const camera2_device_t *d,
+ uint32_t width,
+ uint32_t height,
+ uint32_t format,
+ const camera2_stream_in_ops_t *reprocess_stream_ops,
+ uint32_t *stream_id,
+ uint32_t *consumer_usage,
+ uint32_t *max_buffers) {
+ EmulatedCamera2* ec = getInstance(d);
+ return ec->allocateReprocessStream(width, height, format,
+ reprocess_stream_ops, stream_id, consumer_usage, max_buffers);
+}
+
+int EmulatedCamera2::allocate_reprocess_stream_from_stream(
+ const camera2_device_t *d,
+ uint32_t output_stream_id,
+ const camera2_stream_in_ops_t *reprocess_stream_ops,
+ uint32_t *stream_id) {
+ EmulatedCamera2* ec = getInstance(d);
+ return ec->allocateReprocessStreamFromStream(output_stream_id,
+ reprocess_stream_ops, stream_id);
+}
+
+
+int EmulatedCamera2::release_reprocess_stream(const camera2_device_t *d,
+ uint32_t stream_id) {
+ EmulatedCamera2* ec = getInstance(d);
+ return ec->releaseReprocessStream(stream_id);
+}
+
+int EmulatedCamera2::trigger_action(const camera2_device_t *d,
+ uint32_t trigger_id,
+ int ext1,
+ int ext2) {
+ EmulatedCamera2* ec = getInstance(d);
+ return ec->triggerAction(trigger_id, ext1, ext2);
+}
+
+int EmulatedCamera2::set_notify_callback(const camera2_device_t *d,
+ camera2_notify_callback notify_cb, void* user) {
+ EmulatedCamera2* ec = getInstance(d);
+ Mutex::Autolock l(ec->mMutex);
+ ec->mNotifyCb = notify_cb;
+ ec->mNotifyUserPtr = user;
+ return NO_ERROR;
+}
+
+int EmulatedCamera2::get_metadata_vendor_tag_ops(const camera2_device_t *d,
+ vendor_tag_query_ops_t **ops) {
+ EmulatedCamera2* ec = getInstance(d);
+ *ops = static_cast<vendor_tag_query_ops_t*>(
+ &ec->mVendorTagOps);
+ return NO_ERROR;
+}
+
+const char* EmulatedCamera2::get_camera_vendor_section_name(
+ const vendor_tag_query_ops_t *v,
+ uint32_t tag) {
+ EmulatedCamera2* ec = static_cast<const TagOps*>(v)->parent;
+ return ec->getVendorSectionName(tag);
+}
+
+const char* EmulatedCamera2::get_camera_vendor_tag_name(
+ const vendor_tag_query_ops_t *v,
+ uint32_t tag) {
+ EmulatedCamera2* ec = static_cast<const TagOps*>(v)->parent;
+ return ec->getVendorTagName(tag);
+}
+
+int EmulatedCamera2::get_camera_vendor_tag_type(
+ const vendor_tag_query_ops_t *v,
+ uint32_t tag) {
+ EmulatedCamera2* ec = static_cast<const TagOps*>(v)->parent;
+ return ec->getVendorTagType(tag);
+}
+
+int EmulatedCamera2::dump(const camera2_device_t *d, int fd) {
+ EmulatedCamera2* ec = getInstance(d);
+ return ec->dump(fd);
+}
+
+int EmulatedCamera2::close(struct hw_device_t* device) {
+ EmulatedCamera2* ec =
+ static_cast<EmulatedCamera2*>(
+ reinterpret_cast<camera2_device_t*>(device) );
+ if (ec == NULL) {
+ ALOGE("%s: Unexpected NULL camera2 device", __FUNCTION__);
+ return -EINVAL;
+ }
+ return ec->closeCamera();
+}
+
+void EmulatedCamera2::sendNotification(int32_t msgType,
+ int32_t ext1, int32_t ext2, int32_t ext3) {
+ camera2_notify_callback notifyCb;
+ {
+ Mutex::Autolock l(mMutex);
+ notifyCb = mNotifyCb;
+ }
+ if (notifyCb != NULL) {
+ notifyCb(msgType, ext1, ext2, ext3, mNotifyUserPtr);
+ }
+}
+
+camera2_device_ops_t EmulatedCamera2::sDeviceOps = {
+ EmulatedCamera2::set_request_queue_src_ops,
+ EmulatedCamera2::notify_request_queue_not_empty,
+ EmulatedCamera2::set_frame_queue_dst_ops,
+ EmulatedCamera2::get_in_progress_count,
+ EmulatedCamera2::flush_captures_in_progress,
+ EmulatedCamera2::construct_default_request,
+ EmulatedCamera2::allocate_stream,
+ EmulatedCamera2::register_stream_buffers,
+ EmulatedCamera2::release_stream,
+ EmulatedCamera2::allocate_reprocess_stream,
+ EmulatedCamera2::allocate_reprocess_stream_from_stream,
+ EmulatedCamera2::release_reprocess_stream,
+ EmulatedCamera2::trigger_action,
+ EmulatedCamera2::set_notify_callback,
+ EmulatedCamera2::get_metadata_vendor_tag_ops,
+ EmulatedCamera2::dump
+};
+
+}; /* namespace android */
diff --git a/guest/hals/camera/EmulatedCamera2.h b/guest/hals/camera/EmulatedCamera2.h
new file mode 100644
index 0000000..baf5a18
--- /dev/null
+++ b/guest/hals/camera/EmulatedCamera2.h
@@ -0,0 +1,279 @@
+/*
+ * Copyright (C) 2012 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 HW_EMULATOR_CAMERA_EMULATED_CAMERA2_H
+#define HW_EMULATOR_CAMERA_EMULATED_CAMERA2_H
+
+/*
+ * Contains declaration of a class EmulatedCamera that encapsulates
+ * functionality common to all version 2.0 emulated camera devices. Instances
+ * of this class (for each emulated camera) are created during the construction
+ * of the EmulatedCameraFactory instance. This class serves as an entry point
+ * for all camera API calls that defined by camera2_device_ops_t API.
+ */
+
+#include "hardware/camera2.h"
+#include "system/camera_metadata.h"
+#include "EmulatedBaseCamera.h"
+#include <utils/Thread.h>
+#include <utils/Mutex.h>
+
+namespace android {
+
+/* Encapsulates functionality common to all version 2.0 emulated camera devices
+ *
+ * Note that EmulatedCameraFactory instantiates object of this class just once,
+ * when EmulatedCameraFactory instance gets constructed. Connection to /
+ * disconnection from the actual camera device is handled by calls to
+ * connectDevice(), and closeCamera() methods of this class that are invoked in
+ * response to hw_module_methods_t::open, and camera_device::close callbacks.
+ */
+class EmulatedCamera2 : public camera2_device, public EmulatedBaseCamera {
+public:
+ /* Constructs EmulatedCamera2 instance.
+ * Param:
+ * cameraId - Zero based camera identifier, which is an index of the camera
+ * instance in camera factory's array.
+ * module - Emulated camera HAL module descriptor.
+ */
+ EmulatedCamera2(int cameraId,
+ struct hw_module_t* module);
+
+ /* Destructs EmulatedCamera2 instance. */
+ virtual ~EmulatedCamera2();
+
+ /****************************************************************************
+ * Abstract API
+ ***************************************************************************/
+
+public:
+
+ /****************************************************************************
+ * Public API
+ ***************************************************************************/
+
+public:
+ virtual status_t Initialize(const cvd::CameraDefinition& props);
+
+ /****************************************************************************
+ * Camera module API and generic hardware device API implementation
+ ***************************************************************************/
+
+public:
+ virtual status_t connectCamera(hw_device_t** device);
+
+ virtual status_t closeCamera();
+
+ virtual status_t getCameraInfo(struct camera_info* info) = 0;
+
+ virtual status_t getImageMetadata(struct ImageMetadata* meta) {
+ // TODO(ender): fill in Image metadata structure.
+ return ENOSYS;
+ }
+
+ /****************************************************************************
+ * Camera API implementation.
+ * These methods are called from the camera API callback routines.
+ ***************************************************************************/
+
+protected:
+ /** Request input queue notification */
+ virtual int requestQueueNotify();
+
+ /** Count of requests in flight */
+ virtual int getInProgressCount();
+
+ /** Cancel all captures in flight */
+ virtual int flushCapturesInProgress();
+
+ virtual int constructDefaultRequest(
+ int request_template,
+ camera_metadata_t **request);
+
+ /** Output stream creation and management */
+ virtual int allocateStream(
+ uint32_t width,
+ uint32_t height,
+ int format,
+ const camera2_stream_ops_t *stream_ops,
+ uint32_t *stream_id,
+ uint32_t *format_actual,
+ uint32_t *usage,
+ uint32_t *max_buffers);
+
+ virtual int registerStreamBuffers(
+ uint32_t stream_id,
+ int num_buffers,
+ buffer_handle_t *buffers);
+
+ virtual int releaseStream(uint32_t stream_id);
+
+ /** Input stream creation and management */
+ virtual int allocateReprocessStream(
+ uint32_t width,
+ uint32_t height,
+ uint32_t format,
+ const camera2_stream_in_ops_t *reprocess_stream_ops,
+ uint32_t *stream_id,
+ uint32_t *consumer_usage,
+ uint32_t *max_buffers);
+
+ virtual int allocateReprocessStreamFromStream(
+ uint32_t output_stream_id,
+ const camera2_stream_in_ops_t *reprocess_stream_ops,
+ uint32_t *stream_id);
+
+ virtual int releaseReprocessStream(uint32_t stream_id);
+
+ /** 3A action triggering */
+ virtual int triggerAction(uint32_t trigger_id,
+ int32_t ext1, int32_t ext2);
+
+ /** Custom tag definitions */
+ virtual const char* getVendorSectionName(uint32_t tag);
+ virtual const char* getVendorTagName(uint32_t tag);
+ virtual int getVendorTagType(uint32_t tag);
+
+ /** Debug methods */
+
+ virtual int dump(int fd);
+
+ /****************************************************************************
+ * Camera API callbacks as defined by camera2_device_ops structure. See
+ * hardware/libhardware/include/hardware/camera2.h for information on each
+ * of these callbacks. Implemented in this class, these callbacks simply
+ * dispatch the call into an instance of EmulatedCamera2 class defined in
+ * the 'camera_device2' parameter.
+ ***************************************************************************/
+
+private:
+ /** Input request queue */
+ static int set_request_queue_src_ops(const camera2_device_t *,
+ const camera2_request_queue_src_ops *queue_src_ops);
+ static int notify_request_queue_not_empty(const camera2_device_t *);
+
+ /** Output frame queue */
+ static int set_frame_queue_dst_ops(const camera2_device_t *,
+ const camera2_frame_queue_dst_ops *queue_dst_ops);
+
+ /** In-progress request management */
+ static int get_in_progress_count(const camera2_device_t *);
+
+ static int flush_captures_in_progress(const camera2_device_t *);
+
+ /** Request template creation */
+ static int construct_default_request(const camera2_device_t *,
+ int request_template,
+ camera_metadata_t **request);
+
+ /** Stream management */
+ static int allocate_stream(const camera2_device_t *,
+ uint32_t width,
+ uint32_t height,
+ int format,
+ const camera2_stream_ops_t *stream_ops,
+ uint32_t *stream_id,
+ uint32_t *format_actual,
+ uint32_t *usage,
+ uint32_t *max_buffers);
+
+ static int register_stream_buffers(const camera2_device_t *,
+ uint32_t stream_id,
+ int num_buffers,
+ buffer_handle_t *buffers);
+
+ static int release_stream(const camera2_device_t *,
+ uint32_t stream_id);
+
+ static int allocate_reprocess_stream(const camera2_device_t *,
+ uint32_t width,
+ uint32_t height,
+ uint32_t format,
+ const camera2_stream_in_ops_t *reprocess_stream_ops,
+ uint32_t *stream_id,
+ uint32_t *consumer_usage,
+ uint32_t *max_buffers);
+
+ static int allocate_reprocess_stream_from_stream(const camera2_device_t *,
+ uint32_t output_stream_id,
+ const camera2_stream_in_ops_t *reprocess_stream_ops,
+ uint32_t *stream_id);
+
+ static int release_reprocess_stream(const camera2_device_t *,
+ uint32_t stream_id);
+
+ /** 3A triggers*/
+ static int trigger_action(const camera2_device_t *,
+ uint32_t trigger_id,
+ int ext1,
+ int ext2);
+
+ /** Notifications to application */
+ static int set_notify_callback(const camera2_device_t *,
+ camera2_notify_callback notify_cb,
+ void *user);
+
+ /** Vendor metadata registration */
+ static int get_metadata_vendor_tag_ops(const camera2_device_t *,
+ vendor_tag_query_ops_t **ops);
+ // for get_metadata_vendor_tag_ops
+ static const char* get_camera_vendor_section_name(
+ const vendor_tag_query_ops_t *,
+ uint32_t tag);
+ static const char* get_camera_vendor_tag_name(
+ const vendor_tag_query_ops_t *,
+ uint32_t tag);
+ static int get_camera_vendor_tag_type(
+ const vendor_tag_query_ops_t *,
+ uint32_t tag);
+
+ static int dump(const camera2_device_t *, int fd);
+
+ /** For hw_device_t ops */
+ static int close(struct hw_device_t* device);
+
+ /****************************************************************************
+ * Data members shared with implementations
+ ***************************************************************************/
+ protected:
+ /** Mutex for calls through camera2 device interface */
+ Mutex mMutex;
+
+ bool mStatusPresent;
+
+ const camera2_request_queue_src_ops *mRequestQueueSrc;
+ const camera2_frame_queue_dst_ops *mFrameQueueDst;
+
+ struct TagOps : public vendor_tag_query_ops {
+ EmulatedCamera2 *parent;
+ };
+ TagOps mVendorTagOps;
+
+ void sendNotification(int32_t msgType,
+ int32_t ext1, int32_t ext2, int32_t ext3);
+
+ /****************************************************************************
+ * Data members
+ ***************************************************************************/
+ private:
+ static camera2_device_ops_t sDeviceOps;
+ camera2_notify_callback mNotifyCb;
+ void* mNotifyUserPtr;
+};
+
+}; /* namespace android */
+
+#endif /* HW_EMULATOR_CAMERA_EMULATED_CAMERA2_H */
diff --git a/guest/hals/camera/EmulatedCamera3.cpp b/guest/hals/camera/EmulatedCamera3.cpp
new file mode 100644
index 0000000..c9e48a5
--- /dev/null
+++ b/guest/hals/camera/EmulatedCamera3.cpp
@@ -0,0 +1,271 @@
+/*
+ * Copyright (C) 2013 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.
+ */
+
+/**
+ * Contains implementation of a class EmulatedCamera that encapsulates
+ * functionality common to all version 3.0 emulated camera devices. Instances
+ * of this class (for each emulated camera) are created during the construction
+ * of the EmulatedCameraFactory instance. This class serves as an entry point
+ * for all camera API calls that defined by camera3_device_ops_t API.
+ */
+
+#define LOG_NDEBUG 0
+#define LOG_TAG "EmulatedCamera3_Camera"
+#include <cutils/log.h>
+
+#include "EmulatedCamera3.h"
+#include "system/camera_metadata.h"
+
+namespace android {
+
+/**
+ * Constructs EmulatedCamera3 instance.
+ * Param:
+ * cameraId - Zero based camera identifier, which is an index of the camera
+ * instance in camera factory's array.
+ * module - Emulated camera HAL module descriptor.
+ */
+EmulatedCamera3::EmulatedCamera3(int cameraId,
+ struct hw_module_t* module):
+ EmulatedBaseCamera(cameraId,
+ CAMERA_DEVICE_API_VERSION_3_3,
+ &common,
+ module),
+ mStatus(STATUS_ERROR)
+{
+ common.close = EmulatedCamera3::close;
+ ops = &sDeviceOps;
+
+ mCallbackOps = NULL;
+
+}
+
+/* Destructs EmulatedCamera3 instance. */
+EmulatedCamera3::~EmulatedCamera3() {
+}
+
+/****************************************************************************
+ * Abstract API
+ ***************************************************************************/
+
+/****************************************************************************
+ * Public API
+ ***************************************************************************/
+
+status_t EmulatedCamera3::Initialize(const cvd::CameraDefinition& params) {
+ ALOGV("%s", __FUNCTION__);
+
+ mStatus = STATUS_CLOSED;
+ return NO_ERROR;
+}
+
+/****************************************************************************
+ * Camera API implementation
+ ***************************************************************************/
+
+status_t EmulatedCamera3::connectCamera(hw_device_t** device) {
+ ALOGV("%s", __FUNCTION__);
+ if (device == NULL) return BAD_VALUE;
+
+ if (mStatus != STATUS_CLOSED) {
+ ALOGE("%s: Trying to open a camera in state %d!",
+ __FUNCTION__, mStatus);
+ return INVALID_OPERATION;
+ }
+
+ *device = &common;
+ mStatus = STATUS_OPEN;
+ return NO_ERROR;
+}
+
+status_t EmulatedCamera3::closeCamera() {
+ mStatus = STATUS_CLOSED;
+ return NO_ERROR;
+}
+
+status_t EmulatedCamera3::getCameraInfo(struct camera_info* info) {
+ return EmulatedBaseCamera::getCameraInfo(info);
+}
+
+/****************************************************************************
+ * Camera Device API implementation.
+ * These methods are called from the camera API callback routines.
+ ***************************************************************************/
+
+status_t EmulatedCamera3::initializeDevice(
+ const camera3_callback_ops *callbackOps) {
+ if (callbackOps == NULL) {
+ ALOGE("%s: NULL callback ops provided to HAL!",
+ __FUNCTION__);
+ return BAD_VALUE;
+ }
+
+ if (mStatus != STATUS_OPEN) {
+ ALOGE("%s: Trying to initialize a camera in state %d!",
+ __FUNCTION__, mStatus);
+ return INVALID_OPERATION;
+ }
+
+ mCallbackOps = callbackOps;
+ mStatus = STATUS_READY;
+
+ return NO_ERROR;
+}
+
+status_t EmulatedCamera3::configureStreams(
+ camera3_stream_configuration *streamList) {
+ ALOGE("%s: Not implemented", __FUNCTION__);
+ return INVALID_OPERATION;
+}
+
+status_t EmulatedCamera3::registerStreamBuffers(
+ const camera3_stream_buffer_set *bufferSet) {
+ ALOGE("%s: Not implemented", __FUNCTION__);
+ return INVALID_OPERATION;
+}
+
+const camera_metadata_t* EmulatedCamera3::constructDefaultRequestSettings(
+ int type) {
+ ALOGE("%s: Not implemented", __FUNCTION__);
+ return NULL;
+}
+
+status_t EmulatedCamera3::processCaptureRequest(
+ camera3_capture_request *request) {
+ ALOGE("%s: Not implemented", __FUNCTION__);
+ return INVALID_OPERATION;
+}
+
+status_t EmulatedCamera3::flush() {
+ ALOGE("%s: Not implemented", __FUNCTION__);
+ return INVALID_OPERATION;
+}
+
+/** Debug methods */
+
+void EmulatedCamera3::dump(int fd) {
+ ALOGE("%s: Not implemented", __FUNCTION__);
+ return;
+}
+
+/****************************************************************************
+ * Protected API. Callbacks to the framework.
+ ***************************************************************************/
+
+void EmulatedCamera3::sendCaptureResult(camera3_capture_result_t *result) {
+ mCallbackOps->process_capture_result(mCallbackOps, result);
+}
+
+void EmulatedCamera3::sendNotify(camera3_notify_msg_t *msg) {
+ mCallbackOps->notify(mCallbackOps, msg);
+}
+
+/****************************************************************************
+ * Private API.
+ ***************************************************************************/
+
+/****************************************************************************
+ * Camera API callbacks as defined by camera3_device_ops structure. See
+ * hardware/libhardware/include/hardware/camera3.h for information on each
+ * of these callbacks. Implemented in this class, these callbacks simply
+ * dispatch the call into an instance of EmulatedCamera3 class defined by the
+ * 'camera_device3' parameter, or set a member value in the same.
+ ***************************************************************************/
+
+EmulatedCamera3* getInstance(const camera3_device_t *d) {
+ const EmulatedCamera3* cec = static_cast<const EmulatedCamera3*>(d);
+ return const_cast<EmulatedCamera3*>(cec);
+}
+
+int EmulatedCamera3::initialize(const struct camera3_device *d,
+ const camera3_callback_ops_t *callback_ops) {
+ EmulatedCamera3* ec = getInstance(d);
+ return ec->initializeDevice(callback_ops);
+}
+
+int EmulatedCamera3::configure_streams(const struct camera3_device *d,
+ camera3_stream_configuration_t *stream_list) {
+ EmulatedCamera3* ec = getInstance(d);
+ return ec->configureStreams(stream_list);
+}
+
+int EmulatedCamera3::register_stream_buffers(
+ const struct camera3_device *d,
+ const camera3_stream_buffer_set_t *buffer_set) {
+ EmulatedCamera3* ec = getInstance(d);
+ return ec->registerStreamBuffers(buffer_set);
+}
+
+int EmulatedCamera3::process_capture_request(
+ const struct camera3_device *d,
+ camera3_capture_request_t *request) {
+ EmulatedCamera3* ec = getInstance(d);
+ return ec->processCaptureRequest(request);
+}
+
+const camera_metadata_t* EmulatedCamera3::construct_default_request_settings(
+ const camera3_device_t *d, int type) {
+ EmulatedCamera3* ec = getInstance(d);
+ return ec->constructDefaultRequestSettings(type);
+}
+
+void EmulatedCamera3::dump(const camera3_device_t *d, int fd) {
+ EmulatedCamera3* ec = getInstance(d);
+ ec->dump(fd);
+}
+
+int EmulatedCamera3::flush(const camera3_device_t *d) {
+ EmulatedCamera3* ec = getInstance(d);
+ return ec->flush();
+}
+
+int EmulatedCamera3::close(struct hw_device_t* device) {
+ EmulatedCamera3* ec =
+ static_cast<EmulatedCamera3*>(
+ reinterpret_cast<camera3_device_t*>(device) );
+ if (ec == NULL) {
+ ALOGE("%s: Unexpected NULL camera3 device", __FUNCTION__);
+ return BAD_VALUE;
+ }
+ return ec->closeCamera();
+}
+
+camera3_device_ops_t EmulatedCamera3::sDeviceOps = {
+ EmulatedCamera3::initialize,
+ EmulatedCamera3::configure_streams,
+ /* DEPRECATED: register_stream_buffers */ nullptr,
+ EmulatedCamera3::construct_default_request_settings,
+ EmulatedCamera3::process_capture_request,
+ /* DEPRECATED: get_metadata_vendor_tag_ops */ nullptr,
+ EmulatedCamera3::dump,
+ EmulatedCamera3::flush
+};
+
+const char* EmulatedCamera3::sAvailableCapabilitiesStrings[NUM_CAPABILITIES] = {
+ "BACKWARD_COMPATIBLE",
+ "MANUAL_SENSOR",
+ "MANUAL_POST_PROCESSING",
+ "RAW",
+ "PRIVATE_REPROCESSING",
+ "READ_SENSOR_SETTINGS",
+ "BURST_CAPTURE",
+ "YUV_REPROCESSING",
+ "DEPTH_OUTPUT",
+ "CONSTRAINED_HIGH_SPEED_VIDEO",
+ "FULL_LEVEL"
+};
+
+}; /* namespace android */
diff --git a/guest/hals/camera/EmulatedCamera3.h b/guest/hals/camera/EmulatedCamera3.h
new file mode 100644
index 0000000..e7a2ac1
--- /dev/null
+++ b/guest/hals/camera/EmulatedCamera3.h
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2013 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 HW_EMULATOR_CAMERA_EMULATED_CAMERA3_H
+#define HW_EMULATOR_CAMERA_EMULATED_CAMERA3_H
+
+/**
+ * Contains declaration of a class EmulatedCamera that encapsulates
+ * functionality common to all version 3.0 emulated camera devices. Instances
+ * of this class (for each emulated camera) are created during the construction
+ * of the EmulatedCameraFactory instance. This class serves as an entry point
+ * for all camera API calls that defined by camera3_device_ops_t API.
+ */
+
+#include "hardware/camera3.h"
+#include "system/camera_metadata.h"
+#include "EmulatedBaseCamera.h"
+
+namespace android {
+
+/**
+ * Encapsulates functionality common to all version 3.0 emulated camera devices
+ *
+ * Note that EmulatedCameraFactory instantiates an object of this class just
+ * once, when EmulatedCameraFactory instance gets constructed. Connection to /
+ * disconnection from the actual camera device is handled by calls to
+ * connectDevice(), and closeCamera() methods of this class that are invoked in
+ * response to hw_module_methods_t::open, and camera_device::close callbacks.
+ */
+class EmulatedCamera3 : public camera3_device, public EmulatedBaseCamera {
+public:
+ /* Constructs EmulatedCamera3 instance.
+ * Param:
+ * cameraId - Zero based camera identifier, which is an index of the camera
+ * instance in camera factory's array.
+ * module - Emulated camera HAL module descriptor.
+ */
+ EmulatedCamera3(int cameraId,
+ struct hw_module_t* module);
+
+ /* Destructs EmulatedCamera2 instance. */
+ virtual ~EmulatedCamera3();
+
+ /* List of all defined capabilities plus useful HW levels */
+ enum AvailableCapabilities {
+ BACKWARD_COMPATIBLE,
+ MANUAL_SENSOR,
+ MANUAL_POST_PROCESSING,
+ RAW,
+ PRIVATE_REPROCESSING,
+ READ_SENSOR_SETTINGS,
+ BURST_CAPTURE,
+ YUV_REPROCESSING,
+ DEPTH_OUTPUT,
+ CONSTRAINED_HIGH_SPEED_VIDEO,
+ // Levels
+ FULL_LEVEL,
+
+ NUM_CAPABILITIES
+ };
+
+ // Char strings for above enum, with size NUM_CAPABILITIES
+ static const char *sAvailableCapabilitiesStrings[];
+
+ /****************************************************************************
+ * Abstract API
+ ***************************************************************************/
+
+public:
+
+ /****************************************************************************
+ * Public API
+ ***************************************************************************/
+
+public:
+ virtual status_t Initialize(const cvd::CameraDefinition& params);
+
+ /****************************************************************************
+ * Camera module API and generic hardware device API implementation
+ ***************************************************************************/
+
+public:
+ virtual status_t connectCamera(hw_device_t** device);
+
+ virtual status_t closeCamera();
+
+ virtual status_t getCameraInfo(struct camera_info* info);
+
+ virtual status_t getImageMetadata(struct ImageMetadata* meta) {
+ // TODO(ender): fill in Image metadata structure.
+ return ENOSYS;
+ }
+
+ /****************************************************************************
+ * Camera API implementation.
+ * These methods are called from the camera API callback routines.
+ ***************************************************************************/
+
+protected:
+
+ virtual status_t initializeDevice(
+ const camera3_callback_ops *callbackOps);
+
+ virtual status_t configureStreams(
+ camera3_stream_configuration *streamList);
+
+ virtual status_t registerStreamBuffers(
+ const camera3_stream_buffer_set *bufferSet) ;
+
+ virtual const camera_metadata_t* constructDefaultRequestSettings(
+ int type);
+
+ virtual status_t processCaptureRequest(camera3_capture_request *request);
+
+ virtual status_t flush();
+
+ /** Debug methods */
+
+ virtual void dump(int fd);
+
+ /****************************************************************************
+ * Camera API callbacks as defined by camera3_device_ops structure. See
+ * hardware/libhardware/include/hardware/camera3.h for information on each
+ * of these callbacks. Implemented in this class, these callbacks simply
+ * dispatch the call into an instance of EmulatedCamera3 class defined in
+ * the 'camera_device3' parameter.
+ ***************************************************************************/
+
+private:
+
+ /** Startup */
+ static int initialize(const struct camera3_device *,
+ const camera3_callback_ops_t *callback_ops);
+
+ /** Stream configuration and buffer registration */
+
+ static int configure_streams(const struct camera3_device *,
+ camera3_stream_configuration_t *stream_list);
+
+ static int register_stream_buffers(const struct camera3_device *,
+ const camera3_stream_buffer_set_t *buffer_set);
+
+ /** Template request settings provision */
+
+ static const camera_metadata_t* construct_default_request_settings(
+ const struct camera3_device *, int type);
+
+ /** Submission of capture requests to HAL */
+
+ static int process_capture_request(const struct camera3_device *,
+ camera3_capture_request_t *request);
+
+ static void dump(const camera3_device_t *, int fd);
+
+ static int flush(const camera3_device_t *);
+
+ /** For hw_device_t ops */
+ static int close(struct hw_device_t* device);
+
+ /****************************************************************************
+ * Data members shared with implementations
+ ***************************************************************************/
+ protected:
+
+ enum {
+ // State at construction time, and after a device operation error
+ STATUS_ERROR = 0,
+ // State after startup-time init and after device instance close
+ STATUS_CLOSED,
+ // State after being opened, before device instance init
+ STATUS_OPEN,
+ // State after device instance initialization
+ STATUS_READY,
+ // State while actively capturing data
+ STATUS_ACTIVE
+ } mStatus;
+
+ /**
+ * Callbacks back to the framework
+ */
+
+ void sendCaptureResult(camera3_capture_result_t *result);
+ void sendNotify(camera3_notify_msg_t *msg);
+
+ /****************************************************************************
+ * Data members
+ ***************************************************************************/
+ private:
+ static camera3_device_ops_t sDeviceOps;
+ const camera3_callback_ops_t *mCallbackOps;
+};
+
+}; /* namespace android */
+
+#endif /* HW_EMULATOR_CAMERA_EMULATED_CAMERA3_H */
diff --git a/guest/hals/camera/EmulatedCameraCommon.h b/guest/hals/camera/EmulatedCameraCommon.h
new file mode 100755
index 0000000..c1d575c
--- /dev/null
+++ b/guest/hals/camera/EmulatedCameraCommon.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2011 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 HW_EMULATOR_CAMERA_EMULATED_CAMERA_COMMON_H
+#define HW_EMULATOR_CAMERA_EMULATED_CAMERA_COMMON_H
+
+/*
+ * Contains common declarations that are used across the camera emulation.
+ */
+
+#include <linux/videodev2.h>
+#include <hardware/camera.h>
+
+/* A helper class that tracks a routine execution.
+ * Basically, it dumps an enry message in its constructor, and an exit message
+ * in its destructor. Use LOGRE() macro (declared bellow) to create instances
+ * of this class at the beginning of the tracked routines / methods.
+ */
+class HWERoutineTracker {
+public:
+ /* Constructor that prints an "entry" trace message. */
+ explicit HWERoutineTracker(const char* name)
+ : mName(name) {
+ ALOGV("Entering %s", mName);
+ }
+
+ /* Destructor that prints a "leave" trace message. */
+ ~HWERoutineTracker() {
+ ALOGV("Leaving %s", mName);
+ }
+
+private:
+ /* Stores the routine name. */
+ const char* mName;
+};
+
+/* Logs an execution of a routine / method. */
+#define LOGRE() HWERoutineTracker hwertracker_##__LINE__(__FUNCTION__)
+
+/*
+ * min / max macros
+ */
+
+#define min(a,b) (((a) < (b)) ? (a) : (b))
+#define max(a,b) (((a) > (b)) ? (a) : (b))
+
+#endif /* HW_EMULATOR_CAMERA_EMULATED_CAMERA_COMMON_H */
diff --git a/guest/hals/camera/EmulatedCameraDevice.cpp b/guest/hals/camera/EmulatedCameraDevice.cpp
new file mode 100755
index 0000000..36de83a
--- /dev/null
+++ b/guest/hals/camera/EmulatedCameraDevice.cpp
@@ -0,0 +1,442 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+/*
+ * Contains implementation of an abstract class EmulatedCameraDevice that defines
+ * functionality expected from an emulated physical camera device:
+ * - Obtaining and setting camera parameters
+ * - Capturing frames
+ * - Streaming video
+ * - etc.
+ */
+
+#define LOG_NDEBUG 0
+#define LOG_TAG "EmulatedCamera_Device"
+#include <cutils/log.h>
+#include <sys/select.h>
+#include <cmath>
+#include "EmulatedCamera.h"
+#include "EmulatedCameraDevice.h"
+
+namespace android {
+
+const float GAMMA_CORRECTION = 2.2f;
+EmulatedCameraDevice::EmulatedCameraDevice(EmulatedCamera* camera_hal)
+ : mObjectLock(),
+ mCurFrameTimestamp(0),
+ mCameraHAL(camera_hal),
+ mCurrentFrame(NULL),
+ mExposureCompensation(1.0f),
+ mWhiteBalanceScale(NULL),
+ mIsFocusing(false),
+ mSupportedWhiteBalanceScale(),
+ mState(ECDS_CONSTRUCTED)
+{
+}
+
+EmulatedCameraDevice::~EmulatedCameraDevice()
+{
+ ALOGV("EmulatedCameraDevice destructor");
+ if (mCurrentFrame != NULL) {
+ delete[] mCurrentFrame;
+ }
+ for (size_t i = 0; i < mSupportedWhiteBalanceScale.size(); ++i) {
+ if (mSupportedWhiteBalanceScale.valueAt(i) != NULL) {
+ delete[] mSupportedWhiteBalanceScale.valueAt(i);
+ }
+ }
+}
+
+/****************************************************************************
+ * Emulated camera device public API
+ ***************************************************************************/
+
+status_t EmulatedCameraDevice::Initialize()
+{
+ if (isInitialized()) {
+ ALOGW("%s: Emulated camera device is already initialized: mState = %d",
+ __FUNCTION__, mState);
+ return NO_ERROR;
+ }
+
+ /* Instantiate worker thread object. */
+ mWorkerThread = new WorkerThread(this);
+ if (getWorkerThread() == NULL) {
+ ALOGE("%s: Unable to instantiate worker thread object", __FUNCTION__);
+ return ENOMEM;
+ }
+
+ mState = ECDS_INITIALIZED;
+
+ return NO_ERROR;
+}
+
+status_t EmulatedCameraDevice::startDeliveringFrames(bool one_burst)
+{
+ ALOGV("%s", __FUNCTION__);
+
+ if (!isStarted()) {
+ ALOGE("%s: Device is not started", __FUNCTION__);
+ return EINVAL;
+ }
+
+ /* Frames will be delivered from the thread routine. */
+ const status_t res = startWorkerThread(one_burst);
+ ALOGE_IF(res != NO_ERROR, "%s: startWorkerThread failed", __FUNCTION__);
+ return res;
+}
+
+status_t EmulatedCameraDevice::stopDeliveringFrames()
+{
+ ALOGV("%s", __FUNCTION__);
+
+ if (!isStarted()) {
+ ALOGW("%s: Device is not started", __FUNCTION__);
+ return NO_ERROR;
+ }
+
+ const status_t res = stopWorkerThread();
+ ALOGE_IF(res != NO_ERROR, "%s: startWorkerThread failed", __FUNCTION__);
+ return res;
+}
+
+void EmulatedCameraDevice::setExposureCompensation(const float ev) {
+ ALOGV("%s", __FUNCTION__);
+
+ if (!isStarted()) {
+ ALOGW("%s: Fake camera device is not started.", __FUNCTION__);
+ }
+
+ mExposureCompensation = std::pow(2.0f, ev / GAMMA_CORRECTION);
+ ALOGV("New exposure compensation is %f", mExposureCompensation);
+}
+
+void EmulatedCameraDevice::initializeWhiteBalanceModes(const char* mode,
+ const float r_scale,
+ const float b_scale) {
+ ALOGV("%s with %s, %f, %f", __FUNCTION__, mode, r_scale, b_scale);
+ float* value = new float[3];
+ value[0] = r_scale; value[1] = 1.0f; value[2] = b_scale;
+ mSupportedWhiteBalanceScale.add(String8(mode), value);
+}
+
+void EmulatedCameraDevice::setWhiteBalanceMode(const char* mode) {
+ ALOGV("%s with white balance %s", __FUNCTION__, mode);
+ mWhiteBalanceScale =
+ mSupportedWhiteBalanceScale.valueFor(String8(mode));
+}
+
+void EmulatedCameraDevice::startAutoFocus() {
+ mIsFocusing = true;
+}
+
+/* Computes the pixel value after adjusting the white balance to the current
+ * one. The input the y, u, v channel of the pixel and the adjusted value will
+ * be stored in place. The adjustment is done in RGB space.
+ */
+void EmulatedCameraDevice::changeWhiteBalance(uint8_t& y,
+ uint8_t& u,
+ uint8_t& v) const {
+ float r_scale = mWhiteBalanceScale[0];
+ float b_scale = mWhiteBalanceScale[2];
+ int r = static_cast<float>(YUV2R(y, u, v)) / r_scale;
+ int g = YUV2G(y, u, v);
+ int b = static_cast<float>(YUV2B(y, u, v)) / b_scale;
+
+ y = RGB2Y(r, g, b);
+ u = RGB2U(r, g, b);
+ v = RGB2V(r, g, b);
+}
+
+void EmulatedCameraDevice::simulateAutoFocus() {
+ if (mIsFocusing) {
+ ALOGV("%s: Simulating auto-focus", __FUNCTION__);
+ mCameraHAL->onCameraFocusAcquired();
+ mIsFocusing = false;
+ }
+}
+
+status_t EmulatedCameraDevice::getCurrentPreviewFrame(void* buffer)
+{
+ if (!isStarted()) {
+ ALOGE("%s: Device is not started", __FUNCTION__);
+ return EINVAL;
+ }
+ if (mCurrentFrame == NULL || buffer == NULL) {
+ ALOGE("%s: No framebuffer", __FUNCTION__);
+ return EINVAL;
+ }
+
+ /* In emulation the framebuffer is never RGB. */
+ switch (mPixelFormat) {
+ case V4L2_PIX_FMT_YVU420:
+ YV12ToRGB32(mCurrentFrame, buffer, mFrameWidth, mFrameHeight);
+ return NO_ERROR;
+ case V4L2_PIX_FMT_YUV420:
+ YU12ToRGB32(mCurrentFrame, buffer, mFrameWidth, mFrameHeight);
+ return NO_ERROR;
+ case V4L2_PIX_FMT_NV21:
+ NV21ToRGB32(mCurrentFrame, buffer, mFrameWidth, mFrameHeight);
+ return NO_ERROR;
+ case V4L2_PIX_FMT_NV12:
+ NV12ToRGB32(mCurrentFrame, buffer, mFrameWidth, mFrameHeight);
+ return NO_ERROR;
+
+ default:
+ ALOGE("%s: Unknown pixel format %.4s",
+ __FUNCTION__, reinterpret_cast<const char*>(&mPixelFormat));
+ return EINVAL;
+ }
+}
+
+/****************************************************************************
+ * Emulated camera device private API
+ ***************************************************************************/
+
+status_t EmulatedCameraDevice::commonStartDevice(int width,
+ int height,
+ uint32_t pix_fmt,
+ int fps)
+{
+ /* Validate pixel format, and calculate framebuffer size at the same time. */
+ switch (pix_fmt) {
+ case V4L2_PIX_FMT_YVU420:
+ case V4L2_PIX_FMT_YUV420:
+ case V4L2_PIX_FMT_NV21:
+ case V4L2_PIX_FMT_NV12:
+ mFrameBufferSize = (width * height * 12) / 8;
+ break;
+
+ default:
+ ALOGE("%s: Unknown pixel format %.4s",
+ __FUNCTION__, reinterpret_cast<const char*>(&pix_fmt));
+ return EINVAL;
+ }
+
+ /* Cache framebuffer info. */
+ mFrameWidth = width;
+ mFrameHeight = height;
+ mPixelFormat = pix_fmt;
+ mTotalPixels = width * height;
+ mTargetFps = fps;
+
+ /* Allocate framebuffer. */
+ mCurrentFrame = new uint8_t[mFrameBufferSize];
+ if (mCurrentFrame == NULL) {
+ ALOGE("%s: Unable to allocate framebuffer", __FUNCTION__);
+ return ENOMEM;
+ }
+ ALOGV("%s: Allocated %p %zu bytes for %d pixels in %.4s[%dx%d] frame",
+ __FUNCTION__, mCurrentFrame, mFrameBufferSize, mTotalPixels,
+ reinterpret_cast<const char*>(&mPixelFormat), mFrameWidth, mFrameHeight);
+ return NO_ERROR;
+}
+
+void EmulatedCameraDevice::commonStopDevice()
+{
+ mFrameWidth = mFrameHeight = mTotalPixels = 0;
+ mPixelFormat = 0;
+ mTargetFps = 0;
+
+ if (mCurrentFrame != NULL) {
+ delete[] mCurrentFrame;
+ mCurrentFrame = NULL;
+ }
+}
+
+status_t EmulatedCameraDevice::getImageMetadata(struct ImageMetadata* meta) {
+ meta->mWidth = mFrameWidth;
+ meta->mHeight = mFrameHeight;
+ return mCameraHAL->getImageMetadata(meta);
+}
+
+/****************************************************************************
+ * Worker thread management.
+ ***************************************************************************/
+
+status_t EmulatedCameraDevice::startWorkerThread(bool one_burst)
+{
+ ALOGV("%s", __FUNCTION__);
+
+ if (!isInitialized()) {
+ ALOGE("%s: Emulated camera device is not initialized", __FUNCTION__);
+ return EINVAL;
+ }
+
+ const status_t res = getWorkerThread()->startThread(one_burst);
+ ALOGE_IF(res != NO_ERROR, "%s: Unable to start worker thread", __FUNCTION__);
+ return res;
+}
+
+status_t EmulatedCameraDevice::stopWorkerThread()
+{
+ ALOGV("%s", __FUNCTION__);
+
+ if (!isInitialized()) {
+ ALOGE("%s: Emulated camera device is not initialized", __FUNCTION__);
+ return EINVAL;
+ }
+
+ const status_t res = getWorkerThread()->stopThread();
+ ALOGE_IF(res != NO_ERROR, "%s: Unable to stop worker thread", __FUNCTION__);
+ return res;
+}
+
+bool EmulatedCameraDevice::inWorkerThread()
+{
+ /* This will end the thread loop, and will terminate the thread. Derived
+ * classes must override this method. */
+ return false;
+}
+
+/****************************************************************************
+ * Worker thread implementation.
+ ***************************************************************************/
+
+status_t EmulatedCameraDevice::WorkerThread::readyToRun()
+{
+ ALOGV("Starting emulated camera device worker thread...");
+
+ ALOGW_IF(mThreadControl >= 0 || mControlFD >= 0,
+ "%s: Thread control FDs are opened", __FUNCTION__);
+ /* Create a pair of FDs that would be used to control the thread. */
+ int thread_fds[2];
+ status_t ret;
+ Mutex::Autolock lock(mCameraDevice->mObjectLock);
+ if (pipe(thread_fds) == 0) {
+ mThreadControl = thread_fds[1];
+ mControlFD = thread_fds[0];
+ ALOGV("Emulated device's worker thread has been started.");
+ ret = NO_ERROR;
+ } else {
+ ALOGE("%s: Unable to create thread control FDs: %d -> %s",
+ __FUNCTION__, errno, strerror(errno));
+ ret = errno;
+ }
+
+ mSetup.signal();
+ return ret;
+}
+
+status_t EmulatedCameraDevice::WorkerThread::stopThread()
+{
+ ALOGV("Stopping emulated camera device's worker thread...");
+
+ status_t res = EINVAL;
+
+ // Limit the scope of the Autolock
+ {
+ // If thread is running and readyToRun() has not finished running,
+ // then wait until it is done.
+ Mutex::Autolock lock(mCameraDevice->mObjectLock);
+#if VSOC_PLATFORM_SDK_AFTER(J_MR2)
+ if (isRunning() && (mThreadControl < 0 || mControlFD < 0)) {
+#else
+ if (getTid() != -1 && (mThreadControl < 0 || mControlFD < 0)) {
+#endif
+ mSetup.wait(mCameraDevice->mObjectLock);
+ }
+ }
+
+ if (mThreadControl >= 0) {
+ /* Send "stop" message to the thread loop. */
+ const ControlMessage msg = THREAD_STOP;
+ const int wres =
+ TEMP_FAILURE_RETRY(write(mThreadControl, &msg, sizeof(msg)));
+ if (wres == sizeof(msg)) {
+ /* Stop the thread, and wait till it's terminated. */
+ res = requestExitAndWait();
+ if (res == NO_ERROR) {
+ /* Close control FDs. */
+ if (mThreadControl >= 0) {
+ close(mThreadControl);
+ mThreadControl = -1;
+ }
+ if (mControlFD >= 0) {
+ close(mControlFD);
+ mControlFD = -1;
+ }
+ ALOGV("Emulated camera device's worker thread has been stopped.");
+ } else {
+ ALOGE("%s: requestExitAndWait failed: %d -> %s",
+ __FUNCTION__, res, strerror(-res));
+ }
+ } else {
+ ALOGE("%s: Unable to send THREAD_STOP message: %d -> %s",
+ __FUNCTION__, errno, strerror(errno));
+ res = errno ? errno : EINVAL;
+ }
+ } else {
+ ALOGE("%s: Thread control FDs are not opened", __FUNCTION__);
+ }
+
+ return res;
+}
+
+EmulatedCameraDevice::WorkerThread::SelectRes
+EmulatedCameraDevice::WorkerThread::Select(int fd, int timeout)
+{
+ fd_set fds[1];
+ struct timeval tv, *tvp = NULL;
+
+ mCameraDevice->simulateAutoFocus();
+
+ const int fd_num = (fd >= 0) ? max(fd, mControlFD) + 1 :
+ mControlFD + 1;
+ FD_ZERO(fds);
+ FD_SET(mControlFD, fds);
+ if (fd >= 0) {
+ FD_SET(fd, fds);
+ }
+ if (timeout) {
+ tv.tv_sec = timeout / 1000000;
+ tv.tv_usec = timeout % 1000000;
+ tvp = &tv;
+ }
+ int res = TEMP_FAILURE_RETRY(select(fd_num, fds, NULL, NULL, tvp));
+ if (res < 0) {
+ ALOGE("%s: select returned %d and failed: %d -> %s",
+ __FUNCTION__, res, errno, strerror(errno));
+ return ERROR;
+ } else if (res == 0) {
+ /* Timeout. */
+ return TIMEOUT;
+ } else if (FD_ISSET(mControlFD, fds)) {
+ /* A control event. Lets read the message. */
+ ControlMessage msg;
+ res = TEMP_FAILURE_RETRY(read(mControlFD, &msg, sizeof(msg)));
+ if (res != sizeof(msg)) {
+ ALOGE("%s: Unexpected message size %d, or an error %d -> %s",
+ __FUNCTION__, res, errno, strerror(errno));
+ return ERROR;
+ }
+ /* THREAD_STOP is the only message expected here. */
+ if (msg == THREAD_STOP) {
+ ALOGV("%s: THREAD_STOP message is received", __FUNCTION__);
+ return EXIT_THREAD;
+ } else {
+ ALOGE("Unknown worker thread message %d", msg);
+ return ERROR;
+ }
+ } else {
+ /* Must be an FD. */
+ ALOGW_IF(fd < 0 || !FD_ISSET(fd, fds), "%s: Undefined 'select' result",
+ __FUNCTION__);
+ return READY;
+ }
+}
+
+}; /* namespace android */
diff --git a/guest/hals/camera/EmulatedCameraDevice.h b/guest/hals/camera/EmulatedCameraDevice.h
new file mode 100755
index 0000000..7d0a749
--- /dev/null
+++ b/guest/hals/camera/EmulatedCameraDevice.h
@@ -0,0 +1,573 @@
+/*
+ * Copyright (C) 2011 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 HW_EMULATOR_CAMERA_EMULATED_CAMERA_DEVICE_H
+#define HW_EMULATOR_CAMERA_EMULATED_CAMERA_DEVICE_H
+
+/*
+ * Contains declaration of an abstract class EmulatedCameraDevice that defines
+ * functionality expected from an emulated physical camera device:
+ * - Obtaining and setting camera device parameters
+ * - Capturing frames
+ * - Streaming video
+ * - etc.
+ */
+
+#include <utils/threads.h>
+#include <utils/KeyedVector.h>
+#include <utils/String8.h>
+#include "EmulatedCameraCommon.h"
+#include "Converters.h"
+#include "ImageMetadata.h"
+
+namespace android {
+
+class EmulatedCamera;
+
+/* Encapsulates an abstract class EmulatedCameraDevice that defines
+ * functionality expected from an emulated physical camera device:
+ * - Obtaining and setting camera device parameters
+ * - Capturing frames
+ * - Streaming video
+ * - etc.
+ */
+class EmulatedCameraDevice {
+public:
+ /* Constructs EmulatedCameraDevice instance.
+ * Param:
+ * camera_hal - Emulated camera that implements the camera HAL API, and
+ * manages (contains) this object.
+ */
+ explicit EmulatedCameraDevice(EmulatedCamera* camera_hal);
+
+ /* Destructs EmulatedCameraDevice instance. */
+ virtual ~EmulatedCameraDevice();
+
+ /***************************************************************************
+ * Emulated camera device abstract interface
+ **************************************************************************/
+
+public:
+ /* Connects to the camera device.
+ * This method must be called on an initialized instance of this class.
+ * Return:
+ * NO_ERROR on success, or an appropriate error status.
+ */
+ virtual status_t connectDevice() = 0;
+
+ /* Disconnects from the camera device.
+ * Return:
+ * NO_ERROR on success, or an appropriate error status. If this method is
+ * called for already disconnected, or uninitialized instance of this class,
+ * a successful status must be returned from this method. If this method is
+ * called for an instance that is in the "started" state, this method must
+ * return a failure.
+ */
+ virtual status_t disconnectDevice() = 0;
+
+ /* Starts the camera device.
+ * This method tells the camera device to start capturing frames of the given
+ * dimensions for the given pixel format. Note that this method doesn't start
+ * the delivery of the captured frames to the emulated camera. Call
+ * startDeliveringFrames method to start delivering frames. This method must
+ * be called on a connected instance of this class. If it is called on a
+ * disconnected instance, this method must return a failure.
+ * Param:
+ * width, height - Frame dimensions to use when capturing video frames.
+ * pix_fmt - Pixel format to use when capturing video frames.
+ * fps - Target rate of frames per second.
+ * Return:
+ * NO_ERROR on success, or an appropriate error status.
+ */
+ virtual status_t startDevice(int width,
+ int height,
+ uint32_t pix_fmt,
+ int fps) = 0;
+
+ /* Stops the camera device.
+ * This method tells the camera device to stop capturing frames. Note that
+ * this method doesn't stop delivering frames to the emulated camera. Always
+ * call stopDeliveringFrames prior to calling this method.
+ * Return:
+ * NO_ERROR on success, or an appropriate error status. If this method is
+ * called for an object that is not capturing frames, or is disconnected,
+ * or is uninitialized, a successful status must be returned from this
+ * method.
+ */
+ virtual status_t stopDevice() = 0;
+
+ /***************************************************************************
+ * Emulated camera device public API
+ **************************************************************************/
+
+public:
+ /* Initializes EmulatedCameraDevice instance.
+ * Derived classes should override this method in order to cache static
+ * properties of the physical device (list of supported pixel formats, frame
+ * sizes, etc.) If this method is called on an already initialized instance,
+ * it must return a successful status.
+ * Return:
+ * NO_ERROR on success, or an appropriate error status.
+ */
+ virtual status_t Initialize();
+
+ /* Initializes the white balance modes parameters.
+ * The parameters are passed by each individual derived camera API to
+ * represent that different camera manufacturers may have different
+ * preferences on the white balance parameters. Green channel in the RGB
+ * color space is fixed to keep the luminance to be reasonably constant.
+ *
+ * Param:
+ * mode the text describing the current white balance mode
+ * r_scale the scale factor for the R channel in RGB space
+ * b_scale the scale factor for the B channel in RGB space.
+ */
+ void initializeWhiteBalanceModes(const char* mode,
+ const float r_scale,
+ const float b_scale);
+
+ /* Starts delivering frames captured from the camera device.
+ * This method will start the worker thread that would be pulling frames from
+ * the camera device, and will deliver the pulled frames back to the emulated
+ * camera via onNextFrameAvailable callback. This method must be called on a
+ * connected instance of this class with a started camera device. If it is
+ * called on a disconnected instance, or camera device has not been started,
+ * this method must return a failure.
+ * Param:
+ * one_burst - Controls how many frames should be delivered. If this
+ * parameter is 'true', only one captured frame will be delivered to the
+ * emulated camera. If this parameter is 'false', frames will keep
+ * coming until stopDeliveringFrames method is called. Typically, this
+ * parameter is set to 'true' only in order to obtain a single frame
+ * that will be used as a "picture" in takePicture method of the
+ * emulated camera.
+ * Return:
+ * NO_ERROR on success, or an appropriate error status.
+ */
+ virtual status_t startDeliveringFrames(bool one_burst);
+
+ /* Stops delivering frames captured from the camera device.
+ * This method will stop the worker thread started by startDeliveringFrames.
+ * Return:
+ * NO_ERROR on success, or an appropriate error status.
+ */
+ virtual status_t stopDeliveringFrames();
+
+ /* Sets the exposure compensation for the camera device.
+ */
+ void setExposureCompensation(const float ev);
+
+ /* Sets the white balance mode for the device.
+ */
+ void setWhiteBalanceMode(const char* mode);
+
+ /* Initiates focus operation.
+ */
+ virtual void startAutoFocus();
+
+ /* Gets current framebuffer, converted into preview frame format.
+ * This method must be called on a connected instance of this class with a
+ * started camera device. If it is called on a disconnected instance, or
+ * camera device has not been started, this method must return a failure.
+ * Note that this method should be called only after at least one frame has
+ * been captured and delivered. Otherwise it will return garbage in the
+ * preview frame buffer. Typically, this method shuld be called from
+ * onNextFrameAvailable callback.
+ * Param:
+ * buffer - Buffer, large enough to contain the entire preview frame.
+ * Return:
+ * NO_ERROR on success, or an appropriate error status.
+ */
+ virtual status_t getCurrentPreviewFrame(void* buffer);
+
+ /* Gets width of the frame obtained from the physical device.
+ * Return:
+ * Width of the frame obtained from the physical device. Note that value
+ * returned from this method is valid only in case if camera device has been
+ * started.
+ */
+ inline int getFrameWidth() const
+ {
+ ALOGE_IF(!isStarted(), "%s: Device is not started", __FUNCTION__);
+ return mFrameWidth;
+ }
+
+ /* Gets height of the frame obtained from the physical device.
+ * Return:
+ * Height of the frame obtained from the physical device. Note that value
+ * returned from this method is valid only in case if camera device has been
+ * started.
+ */
+ inline int getFrameHeight() const
+ {
+ ALOGE_IF(!isStarted(), "%s: Device is not started", __FUNCTION__);
+ return mFrameHeight;
+ }
+
+ /* Gets byte size of the current frame buffer.
+ * Return:
+ * Byte size of the frame buffer. Note that value returned from this method
+ * is valid only in case if camera device has been started.
+ */
+ inline size_t getFrameBufferSize() const
+ {
+ ALOGE_IF(!isStarted(), "%s: Device is not started", __FUNCTION__);
+ return mFrameBufferSize;
+ }
+
+ /* Gets number of pixels in the current frame buffer.
+ * Return:
+ * Number of pixels in the frame buffer. Note that value returned from this
+ * method is valid only in case if camera device has been started.
+ */
+ inline int getPixelNum() const
+ {
+ ALOGE_IF(!isStarted(), "%s: Device is not started", __FUNCTION__);
+ return mTotalPixels;
+ }
+
+ /* Gets pixel format of the frame that camera device streams to this class.
+ * Throughout camera framework, there are three different forms of pixel
+ * format representation:
+ * - Original format, as reported by the actual camera device. Values for
+ * this format are declared in bionic/libc/kernel/common/linux/videodev2.h
+ * - String representation as defined in CameraParameters::PIXEL_FORMAT_XXX
+ * strings in frameworks/base/include/camera/CameraParameters.h
+ * - HAL_PIXEL_FORMAT_XXX format, as defined in system/core/include/system/graphics.h
+ * Since emulated camera device gets its data from the actual device, it gets
+ * pixel format in the original form. And that's the pixel format
+ * representation that will be returned from this method. HAL components will
+ * need to translate value returned from this method to the appropriate form.
+ * This method must be called only on started instance of this class, since
+ * it's applicable only when camera device is ready to stream frames.
+ * Param:
+ * pix_fmt - Upon success contains the original pixel format.
+ * Return:
+ * Current framebuffer's pixel format. Note that value returned from this
+ * method is valid only in case if camera device has been started.
+ */
+ inline uint32_t getOriginalPixelFormat() const
+ {
+ ALOGE_IF(!isStarted(), "%s: Device is not started", __FUNCTION__);
+ return mPixelFormat;
+ }
+
+ /* Gets image metadata (from HAL).
+ * Return:
+ * Filled in ImageMetadata structure (in/out parameter).
+ */
+ status_t getImageMetadata(struct ::ImageMetadata* meta);
+
+ /*
+ * State checkers.
+ */
+
+ inline bool isInitialized() const {
+ /* Instance is initialized when the worker thread has been successfuly
+ * created (but not necessarily started). */
+ return mWorkerThread.get() != NULL && mState != ECDS_CONSTRUCTED;
+ }
+ inline bool isConnected() const {
+ /* Instance is connected when its status is either"connected", or
+ * "started". */
+ return mState == ECDS_CONNECTED || mState == ECDS_STARTED;
+ }
+ inline bool isStarted() const {
+ return mState == ECDS_STARTED;
+ }
+
+ /****************************************************************************
+ * Emulated camera device private API
+ ***************************************************************************/
+protected:
+ /* Performs common validation and calculation of startDevice parameters.
+ * Param:
+ * width, height, pix_fmt, fps - Parameters passed to startDevice method.
+ * Return:
+ * NO_ERROR on success, or an appropriate error status.
+ */
+ virtual status_t commonStartDevice(int width,
+ int height,
+ uint32_t pix_fmt,
+ int fps);
+
+ /* Performs common cleanup on stopDevice.
+ * This method will undo what commonStartDevice had done.
+ */
+ virtual void commonStopDevice();
+
+ /** Computes a luminance value after taking the exposure compensation.
+ * value into account.
+ *
+ * Param:
+ * inputY - The input luminance value.
+ * Return:
+ * The luminance value after adjusting the exposure compensation.
+ */
+ inline uint8_t changeExposure(const uint8_t& inputY) const {
+ return static_cast<uint8_t>(clamp(static_cast<float>(inputY) *
+ mExposureCompensation));
+ }
+
+ /** Simulates focusing and reports completion to the client.
+ */
+ void simulateAutoFocus();
+
+ /** Computes the pixel value in YUV space after adjusting to the current
+ * white balance mode.
+ */
+ void changeWhiteBalance(uint8_t& y, uint8_t& u, uint8_t& v) const;
+
+ /****************************************************************************
+ * Worker thread management.
+ * Typicaly when emulated camera device starts capturing frames from the
+ * actual device, it does that in a worker thread created in StartCapturing,
+ * and terminated in StopCapturing. Since this is such a typical scenario,
+ * it makes sence to encapsulate worker thread management in the base class
+ * for all emulated camera devices.
+ ***************************************************************************/
+
+protected:
+ /* Starts the worker thread.
+ * Typically, worker thread is started from startDeliveringFrames method of
+ * this class.
+ * Param:
+ * one_burst - Controls how many times thread loop should run. If this
+ * parameter is 'true', thread routine will run only once If this
+ * parameter is 'false', thread routine will run until stopWorkerThread
+ * method is called. See startDeliveringFrames for more info.
+ * Return:
+ * NO_ERROR on success, or an appropriate error status.
+ */
+ virtual status_t startWorkerThread(bool one_burst);
+
+ /* Stops the worker thread.
+ * Note that this method will always wait for the worker thread to terminate.
+ * Typically, worker thread is started from stopDeliveringFrames method of
+ * this class.
+ * Return:
+ * NO_ERROR on success, or an appropriate error status.
+ */
+ virtual status_t stopWorkerThread();
+
+ /* Implementation of the worker thread routine.
+ * In the default implementation of the worker thread routine we simply
+ * return 'false' forcing the thread loop to exit, and the thread to
+ * terminate. Derived class should override that method to provide there the
+ * actual frame delivery.
+ * Return:
+ * true To continue thread loop (this method will be called again), or false
+ * to exit the thread loop and to terminate the thread.
+ */
+ virtual bool inWorkerThread();
+
+ /* Encapsulates a worker thread used by the emulated camera device.
+ */
+ friend class WorkerThread;
+ class WorkerThread : public Thread {
+
+ /****************************************************************************
+ * Public API
+ ***************************************************************************/
+
+ public:
+ inline explicit WorkerThread(EmulatedCameraDevice* camera_dev)
+ : Thread(true), // Callbacks may involve Java calls.
+ mCameraDevice(camera_dev),
+ mThreadControl(-1),
+ mControlFD(-1)
+ {
+ }
+
+ inline ~WorkerThread()
+ {
+ ALOGW_IF(mThreadControl >= 0 || mControlFD >= 0,
+ "%s: Control FDs are opened in the destructor",
+ __FUNCTION__);
+ if (mThreadControl >= 0) {
+ close(mThreadControl);
+ }
+ if (mControlFD >= 0) {
+ close(mControlFD);
+ }
+ }
+
+ /* Starts the thread
+ * Param:
+ * one_burst - Controls how many times thread loop should run. If
+ * this parameter is 'true', thread routine will run only once
+ * If this parameter is 'false', thread routine will run until
+ * stopThread method is called. See startWorkerThread for more
+ * info.
+ * Return:
+ * NO_ERROR on success, or an appropriate error status.
+ */
+ inline status_t startThread(bool one_burst)
+ {
+ mOneBurst = one_burst;
+ return run("Camera_startThread", ANDROID_PRIORITY_URGENT_DISPLAY, 0);
+ }
+
+ /* Overriden base class method.
+ * It is overriden in order to provide one-time initialization just
+ * prior to starting the thread routine.
+ */
+ status_t readyToRun();
+
+ /* Stops the thread. */
+ status_t stopThread();
+
+ /* Values returned from the Select method of this class. */
+ enum SelectRes {
+ /* A timeout has occurred. */
+ TIMEOUT,
+ /* Data are available for read on the provided FD. */
+ READY,
+ /* Thread exit request has been received. */
+ EXIT_THREAD,
+ /* An error has occurred. */
+ ERROR
+ };
+
+ /* Select on an FD event, keeping in mind thread exit message.
+ * Param:
+ * fd - File descriptor on which to wait for an event. This
+ * parameter may be negative. If it is negative this method will
+ * only wait on a control message to the thread.
+ * timeout - Timeout in microseconds. 0 indicates no timeout (wait
+ * forever).
+ * Return:
+ * See SelectRes enum comments.
+ */
+ SelectRes Select(int fd, int timeout);
+
+ /****************************************************************************
+ * Private API
+ ***************************************************************************/
+
+ private:
+ /* Implements abstract method of the base Thread class. */
+ bool threadLoop()
+ {
+ /* Simply dispatch the call to the containing camera device. */
+ if (mCameraDevice->inWorkerThread()) {
+ /* Respect "one burst" parameter (see startThread). */
+ return !mOneBurst;
+ } else {
+ return false;
+ }
+ }
+
+ /* Containing camera device object. */
+ EmulatedCameraDevice* mCameraDevice;
+
+ /* FD that is used to send control messages into the thread. */
+ int mThreadControl;
+
+ /* FD that thread uses to receive control messages. */
+ int mControlFD;
+
+ /* Controls number of times the thread loop runs.
+ * See startThread for more information. */
+ bool mOneBurst;
+
+ /* Enumerates control messages that can be sent into the thread. */
+ enum ControlMessage {
+ /* Stop the thread. */
+ THREAD_STOP
+ };
+
+ Condition mSetup;
+ };
+
+ /* Worker thread accessor. */
+ inline WorkerThread* getWorkerThread() const
+ {
+ return mWorkerThread.get();
+ }
+
+ /****************************************************************************
+ * Data members
+ ***************************************************************************/
+
+protected:
+ /* Locks this instance for parameters, state, etc. change. */
+ Mutex mObjectLock;
+
+ /* Worker thread that is used in frame capturing. */
+ sp<WorkerThread> mWorkerThread;
+
+ /* Timestamp of the current frame. */
+ nsecs_t mCurFrameTimestamp;
+
+ /* Emulated camera object containing this instance. */
+ EmulatedCamera* mCameraHAL;
+
+ /* Framebuffer containing the current frame. */
+ uint8_t* mCurrentFrame;
+
+ /*
+ * Framebuffer properties.
+ */
+
+ /* Byte size of the framebuffer. */
+ size_t mFrameBufferSize;
+
+ /* Original pixel format (one of the V4L2_PIX_FMT_XXX values, as defined in
+ * bionic/libc/kernel/common/linux/videodev2.h */
+ uint32_t mPixelFormat;
+
+ /* Frame width */
+ int mFrameWidth;
+
+ /* Frame height */
+ int mFrameHeight;
+
+ /* Total number of pixels */
+ int mTotalPixels;
+
+ /* Requested FPS rate */
+ int mTargetFps;
+
+ /* Exposure compensation value */
+ float mExposureCompensation;
+
+ float* mWhiteBalanceScale;
+
+ bool mIsFocusing;
+
+ DefaultKeyedVector<String8, float*> mSupportedWhiteBalanceScale;
+
+ /* Defines possible states of the emulated camera device object.
+ */
+ enum EmulatedCameraDeviceState {
+ /* Object has been constructed. */
+ ECDS_CONSTRUCTED,
+ /* Object has been initialized. */
+ ECDS_INITIALIZED,
+ /* Object has been connected to the physical device. */
+ ECDS_CONNECTED,
+ /* Camera device has been started. */
+ ECDS_STARTED,
+ };
+
+ /* Object state. */
+ EmulatedCameraDeviceState mState;
+};
+
+}; /* namespace android */
+
+#endif /* HW_EMULATOR_CAMERA_EMULATED_CAMERA_DEVICE_H */
diff --git a/guest/hals/camera/EmulatedCameraFactory.cpp b/guest/hals/camera/EmulatedCameraFactory.cpp
new file mode 100755
index 0000000..00a3c99
--- /dev/null
+++ b/guest/hals/camera/EmulatedCameraFactory.cpp
@@ -0,0 +1,330 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+/*
+ * Contains implementation of a class EmulatedCameraFactory that manages cameras
+ * available for emulation.
+ */
+
+#define LOG_NDEBUG 0
+#define LOG_TAG "EmulatedCamera_Factory"
+#include <cutils/log.h>
+#include <cutils/properties.h>
+#include "guest/libs/platform_support/api_level_fixes.h"
+#include "EmulatedFakeCamera.h"
+
+#if VSOC_PLATFORM_SDK_AFTER(J_MR2)
+#include "EmulatedFakeCamera2.h"
+#include "EmulatedCameraHotplugThread.h"
+#endif
+
+#if VSOC_PLATFORM_SDK_AFTER(L_MR1)
+#include "EmulatedFakeCamera3.h"
+#endif
+
+#include "EmulatedCameraFactory.h"
+
+extern camera_module_t HAL_MODULE_INFO_SYM;
+
+namespace android {
+EmulatedCameraFactory& EmulatedCameraFactory::Instance() {
+ static EmulatedCameraFactory* factory = new EmulatedCameraFactory;
+ return *factory;
+}
+
+EmulatedCameraFactory::EmulatedCameraFactory()
+#if VSOC_PLATFORM_SDK_AFTER(J_MR2)
+ : mCallbacks(NULL)
+#endif
+{
+ mCameraConfiguration.Init();
+ const std::vector<cvd::CameraDefinition>& cameras =
+ mCameraConfiguration.cameras();
+ for (size_t camera_index = 0;
+ camera_index < cameras.size();
+ ++camera_index) {
+ mCameraDefinitions.push(cameras[camera_index]);
+ /* Reserve a spot for camera, but don't create just yet. */
+ mEmulatedCameras.push(NULL);
+ }
+
+ ALOGV("%d cameras are being emulated.", getEmulatedCameraNum());
+
+#if VSOC_PLATFORM_SDK_AFTER(J_MR2)
+ /* Create hotplug thread */
+ {
+ mHotplugThread = new EmulatedCameraHotplugThread(getEmulatedCameraNum());
+ mHotplugThread->run("EmulatedCameraHotplugThread");
+ }
+#endif
+}
+
+EmulatedBaseCamera* EmulatedCameraFactory::getOrCreateFakeCamera(size_t cameraId) {
+ ::cvd::LockGuard< ::cvd::Mutex > lock(mEmulatedCamerasMutex);
+
+ if (cameraId >= getEmulatedCameraNum()) {
+ ALOGE("%s: Invalid camera ID: %d", __FUNCTION__, cameraId);
+ return NULL;
+ }
+
+ if (mEmulatedCameras[cameraId] != NULL) {
+ return mEmulatedCameras[cameraId];
+ }
+
+ const cvd::CameraDefinition& definition = mCameraDefinitions[cameraId];
+ bool is_back_facing =
+ (definition.orientation == cvd::CameraDefinition::kBack);
+
+ EmulatedBaseCamera* camera;
+ /* Create, and initialize the fake camera */
+ switch (definition.hal_version) {
+ case cvd::CameraDefinition::kHalV1:
+ camera = new EmulatedFakeCamera(cameraId, is_back_facing,
+ &HAL_MODULE_INFO_SYM.common);
+ break;
+#if VSOC_PLATFORM_SDK_AFTER(J_MR2)
+ case cvd::CameraDefinition::kHalV2:
+ camera = new EmulatedFakeCamera2(cameraId, is_back_facing,
+ &HAL_MODULE_INFO_SYM.common);
+ break;
+#endif
+#if VSOC_PLATFORM_SDK_AFTER(L_MR1)
+ case cvd::CameraDefinition::kHalV3:
+ camera = new EmulatedFakeCamera3(cameraId, is_back_facing,
+ &HAL_MODULE_INFO_SYM.common);
+ break;
+#endif
+ default:
+ ALOGE("%s: Unsupported camera hal version requested: %d",
+ __FUNCTION__, definition.hal_version);
+ return NULL;
+ }
+
+ ALOGI("%s: Camera device %d hal version is %d", __FUNCTION__,
+ cameraId, definition.hal_version);
+ int res = camera->Initialize(definition);
+
+ if (res != NO_ERROR) {
+ ALOGE("%s: Unable to intialize camera %d: %s (%d)",
+ __FUNCTION__, cameraId, strerror(-res), res);
+ delete camera;
+ return NULL;
+ }
+
+ ALOGI("%s: Inserting camera", __FUNCTION__);
+ mEmulatedCameras.replaceAt(camera, cameraId);
+ ALOGI("%s: Done", __FUNCTION__);
+ return camera;
+}
+
+EmulatedCameraFactory::~EmulatedCameraFactory()
+{
+ for (size_t n = 0; n < mEmulatedCameras.size(); n++) {
+ if (mEmulatedCameras[n] != NULL) {
+ delete mEmulatedCameras[n];
+ }
+ }
+
+#if VSOC_PLATFORM_SDK_AFTER(J_MR2)
+ if (mHotplugThread != NULL) {
+ mHotplugThread->requestExit();
+ mHotplugThread->join();
+ }
+#endif
+}
+
+/****************************************************************************
+ * Camera HAL API handlers.
+ *
+ * Each handler simply verifies existence of an appropriate EmulatedBaseCamera
+ * instance, and dispatches the call to that instance.
+ *
+ ***************************************************************************/
+
+int EmulatedCameraFactory::cameraDeviceOpen(int camera_id, hw_device_t** device)
+{
+ ALOGV("%s: id = %d", __FUNCTION__, camera_id);
+
+ *device = NULL;
+
+ EmulatedBaseCamera* camera = getOrCreateFakeCamera(camera_id);
+ if (camera == NULL) return -EINVAL;
+
+ return camera->connectCamera(device);
+}
+
+int EmulatedCameraFactory::getCameraInfo(int camera_id, struct camera_info* info)
+{
+ ALOGV("%s: id = %d", __FUNCTION__, camera_id);
+
+ EmulatedBaseCamera* camera = getOrCreateFakeCamera(camera_id);
+ if (camera == NULL) return -EINVAL;
+
+ return camera->getCameraInfo(info);
+}
+
+#if VSOC_PLATFORM_SDK_AFTER(J_MR2)
+int EmulatedCameraFactory::setCallbacks(
+ const camera_module_callbacks_t *callbacks)
+{
+ ALOGV("%s: callbacks = %p", __FUNCTION__, callbacks);
+
+ mCallbacks = callbacks;
+
+ return OK;
+}
+
+void EmulatedCameraFactory::getVendorTagOps(vendor_tag_ops_t* ops) {
+ ALOGV("%s: ops = %p", __FUNCTION__, ops);
+
+ // No vendor tags defined for emulator yet, so not touching ops
+}
+#endif
+
+int EmulatedCameraFactory::setTorchMode(const char* camera_id, bool enabled) {
+ ALOGV("%s: camera_id = %s, enabled =%d", __FUNCTION__, camera_id, enabled);
+
+ EmulatedBaseCamera* camera = getOrCreateFakeCamera(atoi(camera_id));
+ if (camera == NULL) return -EINVAL;
+
+ return camera->setTorchMode(enabled);
+}
+
+/****************************************************************************
+ * Camera HAL API callbacks.
+ ***************************************************************************/
+
+int EmulatedCameraFactory::device_open(const hw_module_t* module,
+ const char* name,
+ hw_device_t** device)
+{
+ /*
+ * Simply verify the parameters, and dispatch the call inside the
+ * EmulatedCameraFactory instance.
+ */
+
+ if (module != &HAL_MODULE_INFO_SYM.common) {
+ ALOGE("%s: Invalid module %p expected %p",
+ __FUNCTION__, module, &HAL_MODULE_INFO_SYM.common);
+ return -EINVAL;
+ }
+ if (name == NULL) {
+ ALOGE("%s: NULL name is not expected here", __FUNCTION__);
+ return -EINVAL;
+ }
+
+ return EmulatedCameraFactory::Instance().cameraDeviceOpen(atoi(name), device);
+}
+
+int EmulatedCameraFactory::get_number_of_cameras(void)
+{
+ return EmulatedCameraFactory::Instance().getEmulatedCameraNum();
+}
+
+int EmulatedCameraFactory::get_camera_info(int camera_id,
+ struct camera_info* info)
+{
+ return EmulatedCameraFactory::Instance().getCameraInfo(camera_id, info);
+}
+
+#if VSOC_PLATFORM_SDK_AFTER(J_MR2)
+int EmulatedCameraFactory::set_callbacks(
+ const camera_module_callbacks_t *callbacks)
+{
+ return EmulatedCameraFactory::Instance().setCallbacks(callbacks);
+}
+
+void EmulatedCameraFactory::get_vendor_tag_ops(vendor_tag_ops_t* ops)
+{
+ EmulatedCameraFactory::Instance().getVendorTagOps(ops);
+}
+#endif
+
+int EmulatedCameraFactory::open_legacy(const struct hw_module_t* module,
+ const char* id, uint32_t halVersion, struct hw_device_t** device) {
+ // Not supporting legacy open
+ return -ENOSYS;
+}
+
+int EmulatedCameraFactory::set_torch_mode(const char* camera_id, bool enabled) {
+ return EmulatedCameraFactory::Instance().setTorchMode(camera_id, enabled);
+}
+
+/********************************************************************************
+ * Internal API
+ *******************************************************************************/
+
+void EmulatedCameraFactory::onStatusChanged(int cameraId, int newStatus) {
+
+ EmulatedBaseCamera *cam = getOrCreateFakeCamera(cameraId);
+ if (!cam) {
+ ALOGE("%s: Invalid camera ID %d", __FUNCTION__, cameraId);
+ return;
+ }
+
+ /**
+ * (Order is important)
+ * Send the callback first to framework, THEN close the camera.
+ */
+
+#if VSOC_PLATFORM_SDK_AFTER(J_MR2)
+ if (newStatus == cam->getHotplugStatus()) {
+ ALOGW("%s: Ignoring transition to the same status", __FUNCTION__);
+ return;
+ }
+
+ const camera_module_callbacks_t* cb = mCallbacks;
+ if (cb != NULL && cb->camera_device_status_change != NULL) {
+ cb->camera_device_status_change(cb, cameraId, newStatus);
+ }
+
+ if (newStatus == CAMERA_DEVICE_STATUS_NOT_PRESENT) {
+ cam->unplugCamera();
+ } else if (newStatus == CAMERA_DEVICE_STATUS_PRESENT) {
+ cam->plugCamera();
+ }
+#endif
+
+}
+
+void EmulatedCameraFactory::onTorchModeStatusChanged(int cameraId, int newStatus) {
+ EmulatedBaseCamera *cam = getOrCreateFakeCamera(cameraId);
+ if (!cam) {
+ ALOGE("%s: Invalid camera ID %d", __FUNCTION__, cameraId);
+ return;
+ }
+
+#if VSOC_PLATFORM_SDK_AFTER(L_MR1)
+ const camera_module_callbacks_t* cb = mCallbacks;
+ if (cb != NULL && cb->torch_mode_status_change != NULL) {
+ char id[10];
+ sprintf(id, "%d", cameraId);
+ cb->torch_mode_status_change(cb, id, newStatus);
+ }
+#endif
+
+}
+
+/********************************************************************************
+ * Initializer for the static member structure.
+ *******************************************************************************/
+
+/* Entry point for camera HAL API. */
+struct hw_module_methods_t EmulatedCameraFactory::mCameraModuleMethods = {
+ VSOC_STATIC_INITIALIZER(open) EmulatedCameraFactory::device_open
+};
+
+}; /* namespace android */
diff --git a/guest/hals/camera/EmulatedCameraFactory.h b/guest/hals/camera/EmulatedCameraFactory.h
new file mode 100755
index 0000000..64588fd
--- /dev/null
+++ b/guest/hals/camera/EmulatedCameraFactory.h
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2011 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 HW_EMULATOR_CAMERA_EMULATED_CAMERA_FACTORY_H
+#define HW_EMULATOR_CAMERA_EMULATED_CAMERA_FACTORY_H
+
+#include <utils/RefBase.h>
+
+#include <utils/Vector.h>
+#include "common/libs/threads/cuttlefish_thread.h"
+#include "guest/libs/platform_support/api_level_fixes.h"
+#include "CameraConfiguration.h"
+#include "EmulatedBaseCamera.h"
+
+namespace android {
+
+class EmulatedCameraHotplugThread;
+
+/*
+ * Contains declaration of a class EmulatedCameraFactory that manages cameras
+ * available for the emulation. A global instance of this class is statically
+ * instantiated and initialized when camera emulation HAL is loaded.
+ */
+
+/* Class EmulatedCameraFactoryManages cameras available for the emulation.
+ *
+ * When the global static instance of this class is created on the module load,
+ * it enumerates cameras available for the emulation by connecting to the
+ * emulator's 'camera' service. For every camera found out there it creates an
+ * instance of an appropriate class, and stores it an in array of emulated
+ * cameras. In addition to the cameras reported by the emulator, a fake camera
+ * emulator is always created, so there is always at least one camera that is
+ * available.
+ *
+ * Instance of this class is also used as the entry point for the camera HAL API,
+ * including:
+ * - hw_module_methods_t::open entry point
+ * - camera_module_t::get_number_of_cameras entry point
+ * - camera_module_t::get_camera_info entry point
+ *
+ */
+class EmulatedCameraFactory {
+public:
+ /* Constructs EmulatedCameraFactory instance.
+ * In this constructor the factory will create and initialize a list of
+ * emulated cameras. All errors that occur on this constructor are reported
+ * via mConstructedOK data member of this class.
+ */
+ EmulatedCameraFactory();
+
+ /* Destructs EmulatedCameraFactory instance. */
+ ~EmulatedCameraFactory();
+
+ /****************************************************************************
+ * Camera HAL API handlers.
+ ***************************************************************************/
+
+public:
+ /* Returns a (singleton) instance of the EmulatedCameraFactory.
+ */
+ static EmulatedCameraFactory& Instance();
+
+ /* Opens (connects to) a camera device.
+ * This method is called in response to hw_module_methods_t::open callback.
+ */
+ int cameraDeviceOpen(int camera_id, hw_device_t** device);
+
+ /* Gets emulated camera information.
+ * This method is called in response to camera_module_t::get_camera_info callback.
+ */
+ int getCameraInfo(int camera_id, struct camera_info *info);
+
+#if VSOC_PLATFORM_SDK_AFTER(J_MR2)
+ /* Sets emulated camera callbacks.
+ * This method is called in response to camera_module_t::set_callbacks callback.
+ */
+ int setCallbacks(const camera_module_callbacks_t *callbacks);
+
+ /* Fill in vendor tags for the module
+ * This method is called in response to camera_module_t::get_vendor_tag_ops callback.
+ */
+ void getVendorTagOps(vendor_tag_ops_t* ops);
+#endif
+
+ int setTorchMode(const char* camera_id, bool enabled);
+
+ /****************************************************************************
+ * Camera HAL API callbacks.
+ ***************************************************************************/
+
+public:
+ /* camera_module_t::get_number_of_cameras callback entry point. */
+ static int get_number_of_cameras(void);
+
+ /* camera_module_t::get_camera_info callback entry point. */
+ static int get_camera_info(int camera_id, struct camera_info *info);
+
+#if VSOC_PLATFORM_SDK_AFTER(J_MR2)
+ /* camera_module_t::set_callbacks callback entry point. */
+ static int set_callbacks(const camera_module_callbacks_t *callbacks);
+
+ /* camera_module_t::get_vendor_tag_ops callback entry point */
+ static void get_vendor_tag_ops(vendor_tag_ops_t* ops);
+#endif
+
+ /* camera_module_t::open_legacy callback entry point */
+ static int open_legacy(const struct hw_module_t* module, const char* id,
+ uint32_t halVersion, struct hw_device_t** device);
+
+ static int set_torch_mode(const char* camera_id, bool enabled);
+
+private:
+ /* hw_module_methods_t::open callback entry point. */
+ static int device_open(const hw_module_t* module,
+ const char* name,
+ hw_device_t** device);
+
+ /****************************************************************************
+ * Public API.
+ ***************************************************************************/
+
+public:
+
+ /* Gets fake camera orientation. */
+ int getFakeCameraOrientation() {
+ /* TODO: Have a boot property that controls that. */
+ return 90;
+ }
+
+ /* Gets number of emulated cameras.
+ */
+ inline size_t getEmulatedCameraNum() const {
+ return mCameraDefinitions.size();
+ }
+
+ void onStatusChanged(int cameraId, int newStatus);
+
+ void onTorchModeStatusChanged(int cameraId, int newStatus);
+
+ /****************************************************************************
+ * Private API
+ ***************************************************************************/
+
+private:
+ /* Create new or return existing fake camera based on camera definition
+ * found in mCameraDefinitions.
+ * Returns NULL if cameraId is not valid (= not a valid index of
+ * mCameraDefinitions)
+ */
+ EmulatedBaseCamera* getOrCreateFakeCamera(size_t cameraId);
+
+ /****************************************************************************
+ * Data members.
+ ***************************************************************************/
+
+private:
+ /* Array of cameras available for the emulation. */
+ Vector<EmulatedBaseCamera*> mEmulatedCameras;
+
+ /* Guards access to mEmulatedCameras. */
+ cvd::Mutex mEmulatedCamerasMutex;
+
+#if VSOC_PLATFORM_SDK_AFTER(J_MR2)
+ /* Camera callbacks (for status changing) */
+ const camera_module_callbacks_t* mCallbacks;
+
+ /* Hotplug thread (to call onStatusChanged) */
+ sp<EmulatedCameraHotplugThread> mHotplugThread;
+#endif
+
+ /* Back- and front camera properties accessed from the vsoc device. */
+ cvd::CameraConfiguration mCameraConfiguration;
+ Vector<cvd::CameraDefinition> mCameraDefinitions;
+
+public:
+ /* Contains device open entry point, as required by HAL API. */
+ static struct hw_module_methods_t mCameraModuleMethods;
+};
+
+}; /* namespace android */
+
+#endif /* HW_EMULATOR_CAMERA_EMULATED_CAMERA_FACTORY_H */
diff --git a/guest/hals/camera/EmulatedCameraHal.cpp b/guest/hals/camera/EmulatedCameraHal.cpp
new file mode 100755
index 0000000..24c7c6d
--- /dev/null
+++ b/guest/hals/camera/EmulatedCameraHal.cpp
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+/*
+ * Contains implementation of the camera HAL layer in the system running
+ * under the emulator.
+ *
+ * This file contains only required HAL header, which directs all the API calls
+ * to the EmulatedCameraFactory class implementation, wich is responsible for
+ * managing emulated cameras.
+ */
+
+#include "EmulatedCameraFactory.h"
+#include "guest/libs/platform_support/api_level_fixes.h"
+
+/*
+ * Required HAL header.
+ */
+camera_module_t HAL_MODULE_INFO_SYM = {
+ VSOC_STATIC_INITIALIZER(common) {
+ VSOC_STATIC_INITIALIZER(tag) HARDWARE_MODULE_TAG,
+#if VSOC_PLATFORM_SDK_AFTER(L_MR1)
+ VSOC_STATIC_INITIALIZER(module_api_version) CAMERA_MODULE_API_VERSION_2_4,
+#elif VSOC_PLATFORM_SDK_AFTER(K)
+ VSOC_STATIC_INITIALIZER(module_api_version) CAMERA_MODULE_API_VERSION_2_3,
+#elif VSOC_PLATFORM_SDK_AFTER(J_MR2)
+ VSOC_STATIC_INITIALIZER(module_api_version) CAMERA_MODULE_API_VERSION_2_2,
+#else
+ VSOC_STATIC_INITIALIZER(module_api_version) CAMERA_MODULE_API_VERSION_2_0,
+#endif
+ VSOC_STATIC_INITIALIZER(hal_api_version) HARDWARE_HAL_API_VERSION,
+ VSOC_STATIC_INITIALIZER(id) CAMERA_HARDWARE_MODULE_ID,
+ VSOC_STATIC_INITIALIZER(name) "Emulated Camera Module",
+ VSOC_STATIC_INITIALIZER(author) "The Android Open Source Project",
+ VSOC_STATIC_INITIALIZER(methods) &android::EmulatedCameraFactory::mCameraModuleMethods,
+ VSOC_STATIC_INITIALIZER(dso) NULL,
+ VSOC_STATIC_INITIALIZER(reserved) {0},
+ },
+ VSOC_STATIC_INITIALIZER(get_number_of_cameras) android::EmulatedCameraFactory::get_number_of_cameras,
+ VSOC_STATIC_INITIALIZER(get_camera_info) android::EmulatedCameraFactory::get_camera_info,
+#if VSOC_PLATFORM_SDK_AFTER(J_MR2)
+ VSOC_STATIC_INITIALIZER(set_callbacks) android::EmulatedCameraFactory::set_callbacks,
+ VSOC_STATIC_INITIALIZER(get_vendor_tag_ops) android::EmulatedCameraFactory::get_vendor_tag_ops,
+#endif
+#if VSOC_PLATFORM_SDK_AFTER(K)
+ VSOC_STATIC_INITIALIZER(open_legacy) android::EmulatedCameraFactory::open_legacy,
+#endif
+#if VSOC_PLATFORM_SDK_AFTER(L_MR1)
+ set_torch_mode: android::EmulatedCameraFactory::set_torch_mode,
+#endif
+};
diff --git a/guest/hals/camera/EmulatedCameraHotplugThread.cpp b/guest/hals/camera/EmulatedCameraHotplugThread.cpp
new file mode 100644
index 0000000..459f037
--- /dev/null
+++ b/guest/hals/camera/EmulatedCameraHotplugThread.cpp
@@ -0,0 +1,369 @@
+/*
+ * Copyright (C) 2013 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_NDEBUG 0
+#define LOG_TAG "EmulatedCamera_HotplugThread"
+#include <cutils/log.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <sys/inotify.h>
+
+#include "EmulatedCameraHotplugThread.h"
+#include "EmulatedCameraFactory.h"
+
+#define FAKE_HOTPLUG_FILE "/data/misc/media/emulator.camera.hotplug"
+
+#define EVENT_SIZE (sizeof(struct inotify_event))
+#define EVENT_BUF_LEN (1024*(EVENT_SIZE+16))
+
+#define SubscriberInfo EmulatedCameraHotplugThread::SubscriberInfo
+
+namespace android {
+
+EmulatedCameraHotplugThread::EmulatedCameraHotplugThread(
+ size_t totalCameraCount) :
+ Thread(/*canCallJava*/false) {
+
+ mRunning = true;
+ mInotifyFd = 0;
+
+ for (size_t id = 0; id < totalCameraCount; ++id) {
+ if (createFileIfNotExists(id)) {
+ mSubscribedCameraIds.push_back(id);
+ }
+ }
+}
+
+EmulatedCameraHotplugThread::~EmulatedCameraHotplugThread() {
+}
+
+status_t EmulatedCameraHotplugThread::requestExitAndWait() {
+ ALOGE("%s: Not implemented. Use requestExit + join instead",
+ __FUNCTION__);
+ return INVALID_OPERATION;
+}
+
+void EmulatedCameraHotplugThread::requestExit() {
+ Mutex::Autolock al(mMutex);
+
+ ALOGV("%s: Requesting thread exit", __FUNCTION__);
+ mRunning = false;
+
+ bool rmWatchFailed = false;
+ Vector<SubscriberInfo>::iterator it;
+ for (it = mSubscribers.begin(); it != mSubscribers.end(); ++it) {
+
+ if (inotify_rm_watch(mInotifyFd, it->WatchID) == -1) {
+
+ ALOGE("%s: Could not remove watch for camID '%d',"
+ " error: '%s' (%d)",
+ __FUNCTION__, it->CameraID, strerror(errno),
+ errno);
+
+ rmWatchFailed = true ;
+ } else {
+ ALOGV("%s: Removed watch for camID '%d'",
+ __FUNCTION__, it->CameraID);
+ }
+ }
+
+ if (rmWatchFailed) { // unlikely
+ // Give the thread a fighting chance to error out on the next
+ // read
+ if (close(mInotifyFd) == -1) {
+ ALOGE("%s: close failure error: '%s' (%d)",
+ __FUNCTION__, strerror(errno), errno);
+ }
+ }
+
+ ALOGV("%s: Request exit complete.", __FUNCTION__);
+}
+
+status_t EmulatedCameraHotplugThread::readyToRun() {
+ Mutex::Autolock al(mMutex);
+
+ mInotifyFd = -1;
+
+ do {
+ ALOGV("%s: Initializing inotify", __FUNCTION__);
+
+ mInotifyFd = inotify_init();
+ if (mInotifyFd == -1) {
+ ALOGE("%s: inotify_init failure error: '%s' (%d)",
+ __FUNCTION__, strerror(errno), errno);
+ mRunning = false;
+ break;
+ }
+
+ /**
+ * For each fake camera file, add a watch for when
+ * the file is closed (if it was written to)
+ */
+ Vector<int>::const_iterator it, end;
+ it = mSubscribedCameraIds.begin();
+ end = mSubscribedCameraIds.end();
+ for (; it != end; ++it) {
+ int cameraId = *it;
+ if (!addWatch(cameraId)) {
+ mRunning = false;
+ break;
+ }
+ }
+ } while(false);
+
+ if (!mRunning) {
+ status_t err = -errno;
+
+ if (mInotifyFd != -1) {
+ close(mInotifyFd);
+ }
+
+ return err;
+ }
+
+ return OK;
+}
+
+bool EmulatedCameraHotplugThread::threadLoop() {
+
+ // If requestExit was already called, mRunning will be false
+ while (mRunning) {
+ char buffer[EVENT_BUF_LEN];
+ int length = TEMP_FAILURE_RETRY(
+ read(mInotifyFd, buffer, EVENT_BUF_LEN));
+
+ if (length < 0) {
+ ALOGE("%s: Error reading from inotify FD, error: '%s' (%d)",
+ __FUNCTION__, strerror(errno),
+ errno);
+ mRunning = false;
+ break;
+ }
+
+ ALOGV("%s: Read %d bytes from inotify FD", __FUNCTION__, length);
+
+ int i = 0;
+ while (i < length) {
+ inotify_event* event = (inotify_event*) &buffer[i];
+
+ if (event->mask & IN_IGNORED) {
+ Mutex::Autolock al(mMutex);
+ if (!mRunning) {
+ ALOGV("%s: Shutting down thread", __FUNCTION__);
+ break;
+ } else {
+ ALOGE("%s: File was deleted, aborting",
+ __FUNCTION__);
+ mRunning = false;
+ break;
+ }
+ } else if (event->mask & IN_CLOSE_WRITE) {
+ int cameraId = getCameraId(event->wd);
+
+ if (cameraId < 0) {
+ ALOGE("%s: Got bad camera ID from WD '%d",
+ __FUNCTION__, event->wd);
+ } else {
+ // Check the file for the new hotplug event
+ String8 filePath = getFilePath(cameraId);
+ /**
+ * NOTE: we carefully avoid getting an inotify
+ * for the same exact file because it's opened for
+ * read-only, but our inotify is for write-only
+ */
+ int newStatus = readFile(filePath);
+
+ if (newStatus < 0) {
+ mRunning = false;
+ break;
+ }
+
+ int halStatus = newStatus ?
+ CAMERA_DEVICE_STATUS_PRESENT :
+ CAMERA_DEVICE_STATUS_NOT_PRESENT;
+ EmulatedCameraFactory::Instance().onStatusChanged(cameraId,
+ halStatus);
+ }
+
+ } else {
+ ALOGW("%s: Unknown mask 0x%x",
+ __FUNCTION__, event->mask);
+ }
+
+ i += EVENT_SIZE + event->len;
+ }
+ }
+
+ if (!mRunning) {
+ close(mInotifyFd);
+ return false;
+ }
+
+ return true;
+}
+
+String8 EmulatedCameraHotplugThread::getFilePath(int cameraId) const {
+ return String8::format(FAKE_HOTPLUG_FILE ".%d", cameraId);
+}
+
+bool EmulatedCameraHotplugThread::createFileIfNotExists(int cameraId) const
+{
+ String8 filePath = getFilePath(cameraId);
+ // make sure this file exists and we have access to it
+ int fd = TEMP_FAILURE_RETRY(
+ open(filePath.string(), O_WRONLY | O_CREAT | O_TRUNC,
+ /* mode = ug+rwx */ S_IRWXU | S_IRWXG ));
+ if (fd == -1) {
+ ALOGE("%s: Could not create file '%s', error: '%s' (%d)",
+ __FUNCTION__, filePath.string(), strerror(errno), errno);
+ return false;
+ }
+
+ // File has '1' by default since we are plugged in by default
+ if (TEMP_FAILURE_RETRY(write(fd, "1\n", /*count*/2)) == -1) {
+ ALOGE("%s: Could not write '1' to file '%s', error: '%s' (%d)",
+ __FUNCTION__, filePath.string(), strerror(errno), errno);
+ return false;
+ }
+
+ close(fd);
+ return true;
+}
+
+int EmulatedCameraHotplugThread::getCameraId(String8 filePath) const {
+ Vector<int>::const_iterator it, end;
+ it = mSubscribedCameraIds.begin();
+ end = mSubscribedCameraIds.end();
+ for (; it != end; ++it) {
+ String8 camPath = getFilePath(*it);
+
+ if (camPath == filePath) {
+ return *it;
+ }
+ }
+
+ return NAME_NOT_FOUND;
+}
+
+int EmulatedCameraHotplugThread::getCameraId(int wd) const {
+ for (size_t i = 0; i < mSubscribers.size(); ++i) {
+ if (mSubscribers[i].WatchID == wd) {
+ return mSubscribers[i].CameraID;
+ }
+ }
+
+ return NAME_NOT_FOUND;
+}
+
+SubscriberInfo* EmulatedCameraHotplugThread::getSubscriberInfo(int cameraId)
+{
+ for (size_t i = 0; i < mSubscribers.size(); ++i) {
+ if (mSubscribers[i].CameraID == cameraId) {
+ return (SubscriberInfo*)&mSubscribers[i];
+ }
+ }
+
+ return NULL;
+}
+
+bool EmulatedCameraHotplugThread::addWatch(int cameraId) {
+ String8 camPath = getFilePath(cameraId);
+ int wd = inotify_add_watch(mInotifyFd,
+ camPath.string(),
+ IN_CLOSE_WRITE);
+
+ if (wd == -1) {
+ ALOGE("%s: Could not add watch for '%s', error: '%s' (%d)",
+ __FUNCTION__, camPath.string(), strerror(errno),
+ errno);
+
+ mRunning = false;
+ return false;
+ }
+
+ ALOGV("%s: Watch added for camID='%d', wd='%d'",
+ __FUNCTION__, cameraId, wd);
+
+ SubscriberInfo si = { cameraId, wd };
+ mSubscribers.push_back(si);
+
+ return true;
+}
+
+bool EmulatedCameraHotplugThread::removeWatch(int cameraId) {
+ SubscriberInfo* si = getSubscriberInfo(cameraId);
+
+ if (!si) return false;
+
+ if (inotify_rm_watch(mInotifyFd, si->WatchID) == -1) {
+
+ ALOGE("%s: Could not remove watch for camID '%d', error: '%s' (%d)",
+ __FUNCTION__, cameraId, strerror(errno),
+ errno);
+
+ return false;
+ }
+
+ Vector<SubscriberInfo>::iterator it;
+ for (it = mSubscribers.begin(); it != mSubscribers.end(); ++it) {
+ if (it->CameraID == cameraId) {
+ break;
+ }
+ }
+
+ if (it != mSubscribers.end()) {
+ mSubscribers.erase(it);
+ }
+
+ return true;
+}
+
+int EmulatedCameraHotplugThread::readFile(String8 filePath) const {
+
+ int fd = TEMP_FAILURE_RETRY(
+ open(filePath.string(), O_RDONLY, /*mode*/0));
+ if (fd == -1) {
+ ALOGE("%s: Could not open file '%s', error: '%s' (%d)",
+ __FUNCTION__, filePath.string(), strerror(errno), errno);
+ return -1;
+ }
+
+ char buffer[1];
+ int length;
+
+ length = TEMP_FAILURE_RETRY(
+ read(fd, buffer, sizeof(buffer)));
+
+ int retval;
+
+ ALOGV("%s: Read file '%s', length='%d', buffer='%c'",
+ __FUNCTION__, filePath.string(), length, buffer[0]);
+
+ if (length == 0) { // EOF
+ retval = 0; // empty file is the same thing as 0
+ } else if (buffer[0] == '0') {
+ retval = 0;
+ } else { // anything non-empty that's not beginning with '0'
+ retval = 1;
+ }
+
+ close(fd);
+
+ return retval;
+}
+
+} //namespace android
diff --git a/guest/hals/camera/EmulatedCameraHotplugThread.h b/guest/hals/camera/EmulatedCameraHotplugThread.h
new file mode 100644
index 0000000..145322e
--- /dev/null
+++ b/guest/hals/camera/EmulatedCameraHotplugThread.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2013 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 HW_EMULATOR_CAMERA_EMULATED_CAMERA_HOTPLUG_H
+#define HW_EMULATOR_CAMERA_EMULATED_CAMERA_HOTPLUG_H
+
+/**
+ * This class emulates hotplug events by inotifying on a file, specific
+ * to a camera ID. When the file changes between 1/0 the hotplug
+ * status goes between PRESENT and NOT_PRESENT.
+ *
+ * Refer to FAKE_HOTPLUG_FILE in EmulatedCameraHotplugThread.cpp
+ */
+
+#include "EmulatedCamera2.h"
+#include <utils/String8.h>
+#include <utils/Vector.h>
+
+namespace android {
+class EmulatedCameraHotplugThread : public Thread {
+ public:
+ EmulatedCameraHotplugThread(size_t totalCameraCount);
+ ~EmulatedCameraHotplugThread();
+
+ virtual void requestExit();
+ virtual status_t requestExitAndWait();
+
+ private:
+
+
+ virtual status_t readyToRun();
+ virtual bool threadLoop();
+
+ struct SubscriberInfo {
+ int CameraID;
+ int WatchID;
+ };
+
+ bool addWatch(int cameraId);
+ bool removeWatch(int cameraId);
+ SubscriberInfo* getSubscriberInfo(int cameraId);
+
+ int getCameraId(String8 filePath) const;
+ int getCameraId(int wd) const;
+
+ String8 getFilePath(int cameraId) const;
+ int readFile(String8 filePath) const;
+
+ bool createFileIfNotExists(int cameraId) const;
+
+ int mInotifyFd;
+ Vector<int> mSubscribedCameraIds;
+ Vector<SubscriberInfo> mSubscribers;
+
+ // variables above are unguarded:
+ // -- accessed in thread loop or in constructor only
+
+ Mutex mMutex;
+
+ bool mRunning; // guarding only when it's important
+};
+} // namespace android
+
+#endif
diff --git a/guest/hals/camera/EmulatedFakeCamera.cpp b/guest/hals/camera/EmulatedFakeCamera.cpp
new file mode 100755
index 0000000..55dbc6c
--- /dev/null
+++ b/guest/hals/camera/EmulatedFakeCamera.cpp
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+/*
+ * Contains implementation of a class EmulatedFakeCamera that encapsulates
+ * functionality of a fake camera.
+ */
+
+#define LOG_NDEBUG 0
+#define LOG_TAG "EmulatedCamera_FakeCamera"
+#include <sstream>
+#include <string>
+#include <cutils/log.h>
+#include <cutils/properties.h>
+#include "EmulatedFakeCamera.h"
+#include "EmulatedCameraFactory.h"
+
+namespace android {
+
+EmulatedFakeCamera::EmulatedFakeCamera(int cameraId,
+ bool facingBack,
+ struct hw_module_t* module)
+ : EmulatedCamera(cameraId, module),
+ mFacingBack(facingBack),
+ mFakeCameraDevice(this)
+{
+}
+
+EmulatedFakeCamera::~EmulatedFakeCamera()
+{
+}
+
+/****************************************************************************
+ * Public API overrides
+ ***************************************************************************/
+
+status_t EmulatedFakeCamera::Initialize(const cvd::CameraDefinition& params)
+{
+ status_t res = mFakeCameraDevice.Initialize();
+ if (res != NO_ERROR) {
+ return res;
+ }
+
+ const char* facing = mFacingBack ? EmulatedCamera::FACING_BACK :
+ EmulatedCamera::FACING_FRONT;
+
+ mParameters.set(EmulatedCamera::FACING_KEY, facing);
+ ALOGD("%s: Fake camera is facing %s", __FUNCTION__, facing);
+
+ mParameters.set(EmulatedCamera::ORIENTATION_KEY,
+ EmulatedCameraFactory::Instance().getFakeCameraOrientation());
+
+ res = EmulatedCamera::Initialize(params);
+ if (res != NO_ERROR) {
+ return res;
+ }
+
+ /*
+ * Parameters provided by the camera device.
+ */
+
+ /* 352x288 and 320x240 frame dimensions are required by the framework for
+ * video mode preview and video recording. */
+ std::ostringstream resolutions;
+ for (size_t index = 0; index < params.resolutions.size(); ++index) {
+ if (resolutions.str().size()) {
+ resolutions << ",";
+ }
+ resolutions << params.resolutions[index].width << "x"
+ << params.resolutions[index].height;
+ }
+
+ mParameters.set(CameraParameters::KEY_SUPPORTED_PICTURE_SIZES,
+ resolutions.str().c_str());
+ mParameters.set(CameraParameters::KEY_SUPPORTED_PREVIEW_SIZES,
+ resolutions.str().c_str());
+ mParameters.setPreviewSize(640, 480);
+ mParameters.setPictureSize(640, 480);
+
+ mParameters.set(CameraParameters::KEY_SUPPORTED_ANTIBANDING,
+ CameraParameters::ANTIBANDING_AUTO);
+ mParameters.set(CameraParameters::KEY_ANTIBANDING,
+ CameraParameters::ANTIBANDING_AUTO);
+ mParameters.set(CameraParameters::KEY_SUPPORTED_EFFECTS,
+ CameraParameters::EFFECT_NONE);
+ mParameters.set(CameraParameters::KEY_EFFECT,
+ CameraParameters::EFFECT_NONE);
+
+
+ return NO_ERROR;
+}
+
+EmulatedCameraDevice* EmulatedFakeCamera::getCameraDevice()
+{
+ return &mFakeCameraDevice;
+}
+
+}; /* namespace android */
diff --git a/guest/hals/camera/EmulatedFakeCamera.h b/guest/hals/camera/EmulatedFakeCamera.h
new file mode 100755
index 0000000..8657df9
--- /dev/null
+++ b/guest/hals/camera/EmulatedFakeCamera.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2011 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 HW_EMULATOR_CAMERA_EMULATED_FAKE_CAMERA_H
+#define HW_EMULATOR_CAMERA_EMULATED_FAKE_CAMERA_H
+
+/*
+ * Contains declaration of a class EmulatedFakeCamera that encapsulates
+ * functionality of a fake camera. This class is nothing more than a placeholder
+ * for EmulatedFakeCameraDevice instance.
+ */
+
+#include "EmulatedCamera.h"
+#include "EmulatedFakeCameraDevice.h"
+
+namespace android {
+
+/* Encapsulates functionality of a fake camera.
+ * This class is nothing more than a placeholder for EmulatedFakeCameraDevice
+ * instance that emulates a fake camera device.
+ */
+class EmulatedFakeCamera : public EmulatedCamera {
+public:
+ /* Constructs EmulatedFakeCamera instance. */
+ EmulatedFakeCamera(int cameraId, bool facingBack, struct hw_module_t* module);
+
+ /* Destructs EmulatedFakeCamera instance. */
+ ~EmulatedFakeCamera();
+
+ /****************************************************************************
+ * EmulatedCamera virtual overrides.
+ ***************************************************************************/
+
+public:
+ /* Initializes EmulatedFakeCamera instance. */
+ status_t Initialize(const cvd::CameraDefinition& params);
+
+ /****************************************************************************
+ * EmulatedCamera abstract API implementation.
+ ***************************************************************************/
+
+protected:
+ /* Gets emulated camera device ised by this instance of the emulated camera.
+ */
+ EmulatedCameraDevice* getCameraDevice();
+
+ /****************************************************************************
+ * Data memebers.
+ ***************************************************************************/
+
+protected:
+ /* Facing back (true) or front (false) switch. */
+ bool mFacingBack;
+
+ /* Contained fake camera device object. */
+ EmulatedFakeCameraDevice mFakeCameraDevice;
+};
+
+}; /* namespace android */
+
+#endif /* HW_EMULATOR_CAMERA_EMULATED_FAKE_CAMERA_H */
diff --git a/guest/hals/camera/EmulatedFakeCamera2.cpp b/guest/hals/camera/EmulatedFakeCamera2.cpp
new file mode 100644
index 0000000..e5f2387
--- /dev/null
+++ b/guest/hals/camera/EmulatedFakeCamera2.cpp
@@ -0,0 +1,2765 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+/*
+ * Contains implementation of a class EmulatedFakeCamera2 that encapsulates
+ * functionality of an advanced fake camera.
+ */
+
+#include <algorithm>
+#include <cstdint>
+#include <iterator>
+
+#define LOG_NDEBUG 0
+#define LOG_TAG "EmulatedCamera_FakeCamera2"
+#include <utils/Log.h>
+
+#include "common/libs/auto_resources/auto_resources.h"
+#include "guest/libs/platform_support/api_level_fixes.h"
+#include "EmulatedFakeCamera2.h"
+#include "EmulatedCameraFactory.h"
+#include "GrallocModule.h"
+
+#define ERROR_CAMERA_NOT_PRESENT -EPIPE
+
+#define CAMERA2_EXT_TRIGGER_TESTING_DISCONNECT 0xFFFFFFFF
+
+namespace android {
+
+const int64_t USEC = 1000LL;
+const int64_t MSEC = USEC * 1000LL;
+const int64_t SEC = MSEC * 1000LL;
+
+const uint32_t EmulatedFakeCamera2::kAvailableFormats[] = {
+#if VSOC_PLATFORM_SDK_AFTER(K)
+ HAL_PIXEL_FORMAT_RAW16,
+#endif
+ HAL_PIXEL_FORMAT_BLOB,
+ HAL_PIXEL_FORMAT_RGBA_8888,
+ // HAL_PIXEL_FORMAT_YV12,
+ HAL_PIXEL_FORMAT_YCrCb_420_SP
+};
+
+const uint32_t EmulatedFakeCamera2::kAvailableRawSizes[2] = {
+ 640, 480
+ // mSensorWidth, mSensorHeight
+};
+
+const uint64_t EmulatedFakeCamera2::kAvailableRawMinDurations[1] = {
+ static_cast<uint64_t>(Sensor::kFrameDurationRange[0])
+};
+
+const uint32_t EmulatedFakeCamera2::kAvailableProcessedSizesBack[4] = {
+ 640, 480, 320, 240
+ // mSensorWidth, mSensorHeight
+};
+
+const uint32_t EmulatedFakeCamera2::kAvailableProcessedSizesFront[4] = {
+ 320, 240, 160, 120
+ // mSensorWidth, mSensorHeight
+};
+
+const uint64_t EmulatedFakeCamera2::kAvailableProcessedMinDurations[1] = {
+ static_cast<uint64_t>(Sensor::kFrameDurationRange[0])
+};
+
+const uint32_t EmulatedFakeCamera2::kAvailableJpegSizesBack[2] = {
+ 640, 480
+ // mSensorWidth, mSensorHeight
+};
+
+const uint32_t EmulatedFakeCamera2::kAvailableJpegSizesFront[2] = {
+ 320, 240
+ // mSensorWidth, mSensorHeight
+};
+
+
+const uint64_t EmulatedFakeCamera2::kAvailableJpegMinDurations[1] = {
+ static_cast<uint64_t>(Sensor::kFrameDurationRange[0])
+};
+
+
+EmulatedFakeCamera2::EmulatedFakeCamera2(int cameraId,
+ bool facingBack,
+ struct hw_module_t* module)
+ : EmulatedCamera2(cameraId,module),
+ mFacingBack(facingBack),
+ mIsConnected(false)
+{
+ ALOGD("Constructing emulated fake camera 2 facing %s",
+ facingBack ? "back" : "front");
+}
+
+EmulatedFakeCamera2::~EmulatedFakeCamera2() {
+ if (mCameraInfo != NULL) {
+ free_camera_metadata(mCameraInfo);
+ }
+}
+
+/****************************************************************************
+ * Public API overrides
+ ***************************************************************************/
+
+status_t EmulatedFakeCamera2::Initialize(const cvd::CameraDefinition& params) {
+ status_t res;
+
+ for (size_t index = 0; index < params.resolutions.size(); ++index) {
+ mAvailableRawSizes.push_back(params.resolutions[index].width);
+ mAvailableRawSizes.push_back(params.resolutions[index].height);
+ mAvailableProcessedSizes.push_back(params.resolutions[index].width);
+ mAvailableProcessedSizes.push_back(params.resolutions[index].height);
+ mAvailableJpegSizes.push_back(params.resolutions[index].width);
+ mAvailableJpegSizes.push_back(params.resolutions[index].height);
+ }
+
+ // Find max width/height
+ int32_t width = 0, height = 0;
+ for (size_t index = 0; index < params.resolutions.size(); ++index) {
+ if (width <= params.resolutions[index].width &&
+ height <= params.resolutions[index].height) {
+ width = params.resolutions[index].width;
+ height = params.resolutions[index].height;
+ }
+ }
+ if (width < 640 || height < 480) {
+ width = 640;
+ height = 480;
+ }
+ mSensorWidth = width;
+ mSensorHeight = height;
+
+ /* TODO(ender): probably should drop this. */
+ std::copy(kAvailableRawSizes,
+ kAvailableRawSizes + arraysize(kAvailableRawSizes),
+ std::back_inserter(mAvailableRawSizes));
+
+ if (params.orientation == cvd::CameraDefinition::kFront) {
+ std::copy(kAvailableProcessedSizesFront,
+ kAvailableProcessedSizesFront +
+ arraysize(kAvailableProcessedSizesFront),
+ std::back_inserter(mAvailableProcessedSizes));
+ std::copy(kAvailableJpegSizesFront,
+ kAvailableJpegSizesFront + arraysize(kAvailableJpegSizesFront),
+ std::back_inserter(mAvailableJpegSizes));
+ } else {
+ std::copy(kAvailableProcessedSizesBack,
+ kAvailableProcessedSizesBack +
+ arraysize(kAvailableProcessedSizesBack),
+ mAvailableProcessedSizes.begin());
+ std::copy(kAvailableJpegSizesBack,
+ kAvailableJpegSizesBack + arraysize(kAvailableJpegSizesBack),
+ mAvailableJpegSizes.begin());
+ }
+
+ res = constructStaticInfo(&mCameraInfo, true);
+ if (res != OK) {
+ ALOGE("%s: Unable to allocate static info: %s (%d)",
+ __FUNCTION__, strerror(-res), res);
+ return res;
+ }
+ res = constructStaticInfo(&mCameraInfo, false);
+ if (res != OK) {
+ ALOGE("%s: Unable to fill in static info: %s (%d)",
+ __FUNCTION__, strerror(-res), res);
+ return res;
+ }
+ if (res != OK) return res;
+
+ mNextStreamId = 1;
+ mNextReprocessStreamId = 1;
+ mRawStreamCount = 0;
+ mProcessedStreamCount = 0;
+ mJpegStreamCount = 0;
+ mReprocessStreamCount = 0;
+
+ return NO_ERROR;
+}
+
+/****************************************************************************
+ * Camera module API overrides
+ ***************************************************************************/
+
+status_t EmulatedFakeCamera2::connectCamera(hw_device_t** device) {
+ status_t res;
+ ALOGV("%s", __FUNCTION__);
+
+ {
+ Mutex::Autolock l(mMutex);
+ if (!mStatusPresent) {
+ ALOGE("%s: Camera ID %d is unplugged", __FUNCTION__,
+ mCameraID);
+ return -ENODEV;
+ }
+ }
+
+ mConfigureThread = new ConfigureThread(this);
+ mReadoutThread = new ReadoutThread(this);
+ mControlThread = new ControlThread(this);
+ mSensor = new Sensor(mSensorWidth, mSensorHeight);
+ mJpegCompressor = new JpegCompressor();
+
+ mNextStreamId = 1;
+ mNextReprocessStreamId = 1;
+
+ res = mSensor->startUp();
+ if (res != NO_ERROR) return res;
+
+ res = mConfigureThread->run("EmulatedFakeCamera2::configureThread");
+ if (res != NO_ERROR) return res;
+
+ res = mReadoutThread->run("EmulatedFakeCamera2::readoutThread");
+ if (res != NO_ERROR) return res;
+
+ res = mControlThread->run("EmulatedFakeCamera2::controlThread");
+ if (res != NO_ERROR) return res;
+
+ status_t ret = EmulatedCamera2::connectCamera(device);
+
+ if (ret >= 0) {
+ mIsConnected = true;
+ }
+
+ return ret;
+}
+
+status_t EmulatedFakeCamera2::plugCamera() {
+ {
+ Mutex::Autolock l(mMutex);
+
+ if (!mStatusPresent) {
+ ALOGI("%s: Plugged back in", __FUNCTION__);
+ mStatusPresent = true;
+ }
+ }
+
+ return NO_ERROR;
+}
+
+status_t EmulatedFakeCamera2::unplugCamera() {
+ {
+ Mutex::Autolock l(mMutex);
+
+ if (mStatusPresent) {
+ ALOGI("%s: Unplugged camera", __FUNCTION__);
+ mStatusPresent = false;
+ }
+ }
+
+ return closeCamera();
+}
+
+camera_device_status_t EmulatedFakeCamera2::getHotplugStatus() {
+ Mutex::Autolock l(mMutex);
+ return mStatusPresent ?
+ CAMERA_DEVICE_STATUS_PRESENT :
+ CAMERA_DEVICE_STATUS_NOT_PRESENT;
+}
+
+
+
+status_t EmulatedFakeCamera2::closeCamera() {
+ {
+ Mutex::Autolock l(mMutex);
+
+ status_t res;
+ ALOGV("%s", __FUNCTION__);
+
+ if (!mIsConnected) {
+ return NO_ERROR;
+ }
+
+ res = mSensor->shutDown();
+ if (res != NO_ERROR) {
+ ALOGE("%s: Unable to shut down sensor: %d", __FUNCTION__, res);
+ return res;
+ }
+
+ mConfigureThread->requestExit();
+ mReadoutThread->requestExit();
+ mControlThread->requestExit();
+ mJpegCompressor->cancel();
+ }
+
+ // give up the lock since we will now block and the threads
+ // can call back into this object
+ mConfigureThread->join();
+ mReadoutThread->join();
+ mControlThread->join();
+
+ ALOGV("%s exit", __FUNCTION__);
+
+ {
+ Mutex::Autolock l(mMutex);
+ mIsConnected = false;
+ }
+
+ return NO_ERROR;
+}
+
+status_t EmulatedFakeCamera2::getCameraInfo(struct camera_info *info) {
+ info->facing = mFacingBack ? CAMERA_FACING_BACK : CAMERA_FACING_FRONT;
+ info->orientation = EmulatedCameraFactory::Instance().getFakeCameraOrientation();
+ return EmulatedCamera2::getCameraInfo(info);
+}
+
+/****************************************************************************
+ * Camera device API overrides
+ ***************************************************************************/
+
+/** Request input queue */
+
+int EmulatedFakeCamera2::requestQueueNotify() {
+ ALOGV("Request queue notification received");
+
+ ALOG_ASSERT(mRequestQueueSrc != NULL,
+ "%s: Request queue src not set, but received queue notification!",
+ __FUNCTION__);
+ ALOG_ASSERT(mFrameQueueDst != NULL,
+ "%s: Request queue src not set, but received queue notification!",
+ __FUNCTION__);
+ ALOG_ASSERT(mStreams.size() != 0,
+ "%s: No streams allocated, but received queue notification!",
+ __FUNCTION__);
+ return mConfigureThread->newRequestAvailable();
+}
+
+int EmulatedFakeCamera2::getInProgressCount() {
+ Mutex::Autolock l(mMutex);
+
+ if (!mStatusPresent) {
+ ALOGW("%s: Camera was physically disconnected", __FUNCTION__);
+ return ERROR_CAMERA_NOT_PRESENT;
+ }
+
+ int requestCount = 0;
+ requestCount += mConfigureThread->getInProgressCount();
+ requestCount += mReadoutThread->getInProgressCount();
+ requestCount += mJpegCompressor->isBusy() ? 1 : 0;
+
+ return requestCount;
+}
+
+int EmulatedFakeCamera2::constructDefaultRequest(
+ int request_template,
+ camera_metadata_t **request) {
+
+ if (request == NULL) return BAD_VALUE;
+ if (request_template < 0 || request_template >= CAMERA2_TEMPLATE_COUNT) {
+ return BAD_VALUE;
+ }
+
+ {
+ Mutex::Autolock l(mMutex);
+ if (!mStatusPresent) {
+ ALOGW("%s: Camera was physically disconnected", __FUNCTION__);
+ return ERROR_CAMERA_NOT_PRESENT;
+ }
+ }
+
+ status_t res;
+ // Pass 1, calculate size and allocate
+ res = constructDefaultRequest(request_template,
+ request,
+ true);
+ if (res != OK) {
+ return res;
+ }
+ // Pass 2, build request
+ res = constructDefaultRequest(request_template,
+ request,
+ false);
+ if (res != OK) {
+ ALOGE("Unable to populate new request for template %d",
+ request_template);
+ }
+
+ return res;
+}
+
+int EmulatedFakeCamera2::allocateStream(
+ uint32_t width,
+ uint32_t height,
+ int format,
+ const camera2_stream_ops_t *stream_ops,
+ uint32_t *stream_id,
+ uint32_t *format_actual,
+ uint32_t *usage,
+ uint32_t *max_buffers) {
+ Mutex::Autolock l(mMutex);
+
+ if (!mStatusPresent) {
+ ALOGW("%s: Camera was physically disconnected", __FUNCTION__);
+ return ERROR_CAMERA_NOT_PRESENT;
+ }
+
+ // Temporary shim until FORMAT_ZSL is removed
+ if (format == CAMERA2_HAL_PIXEL_FORMAT_ZSL) {
+ format = HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED;
+ }
+
+ if (format != HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED) {
+ unsigned int numFormats = sizeof(kAvailableFormats) / sizeof(uint32_t);
+ unsigned int formatIdx = 0;
+ unsigned int sizeOffsetIdx = 0;
+ for (; formatIdx < numFormats; formatIdx++) {
+ if (format == (int)kAvailableFormats[formatIdx]) break;
+ }
+ if (formatIdx == numFormats) {
+ ALOGE("%s: Format 0x%x is not supported", __FUNCTION__, format);
+ return BAD_VALUE;
+ }
+ }
+
+ const uint32_t *availableSizes;
+ size_t availableSizeCount;
+ switch (format) {
+#if VSOC_PLATFORM_SDK_AFTER(K)
+ case HAL_PIXEL_FORMAT_RAW16:
+ availableSizes = &mAvailableRawSizes.front();
+ availableSizeCount = mAvailableRawSizes.size();
+ break;
+#endif
+ case HAL_PIXEL_FORMAT_BLOB:
+ availableSizes = &mAvailableJpegSizes.front();
+ availableSizeCount = mAvailableJpegSizes.size();
+ break;
+ case HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED:
+ case HAL_PIXEL_FORMAT_RGBA_8888:
+ case HAL_PIXEL_FORMAT_YV12:
+ case HAL_PIXEL_FORMAT_YCrCb_420_SP:
+ availableSizes = &mAvailableProcessedSizes.front();
+ availableSizeCount = mAvailableProcessedSizes.size();
+ break;
+ default:
+ ALOGE("%s: Unknown format 0x%x", __FUNCTION__, format);
+ return BAD_VALUE;
+ }
+
+ unsigned int resIdx = 0;
+ for (; resIdx < availableSizeCount; resIdx++) {
+ if (availableSizes[resIdx * 2] == width &&
+ availableSizes[resIdx * 2 + 1] == height) break;
+ }
+ if (resIdx == availableSizeCount) {
+ ALOGE("%s: Format 0x%x does not support resolution %d, %d", __FUNCTION__,
+ format, width, height);
+ return BAD_VALUE;
+ }
+
+ switch (format) {
+#if VSOC_PLATFORM_SDK_AFTER(K)
+ case HAL_PIXEL_FORMAT_RAW16:
+ if (mRawStreamCount >= kMaxRawStreamCount) {
+ ALOGE("%s: Cannot allocate another raw stream (%d already allocated)",
+ __FUNCTION__, mRawStreamCount);
+ return INVALID_OPERATION;
+ }
+ mRawStreamCount++;
+ break;
+#endif
+ case HAL_PIXEL_FORMAT_BLOB:
+ if (mJpegStreamCount >= kMaxJpegStreamCount) {
+ ALOGE("%s: Cannot allocate another JPEG stream (%d already allocated)",
+ __FUNCTION__, mJpegStreamCount);
+ return INVALID_OPERATION;
+ }
+ mJpegStreamCount++;
+ break;
+ default:
+ if (mProcessedStreamCount >= kMaxProcessedStreamCount) {
+ ALOGE("%s: Cannot allocate another processed stream (%d already allocated)",
+ __FUNCTION__, mProcessedStreamCount);
+ return INVALID_OPERATION;
+ }
+ mProcessedStreamCount++;
+ }
+
+ Stream newStream;
+ newStream.ops = stream_ops;
+ newStream.width = width;
+ newStream.height = height;
+ newStream.format = format;
+ // TODO: Query stride from gralloc
+ newStream.stride = width;
+
+ mStreams.add(mNextStreamId, newStream);
+
+ *stream_id = mNextStreamId;
+ if (format_actual) *format_actual = format;
+ *usage = GRALLOC_USAGE_HW_CAMERA_WRITE;
+ *max_buffers = kMaxBufferCount;
+
+ ALOGV("Stream allocated: %d, %d x %d, 0x%x. U: %x, B: %d",
+ *stream_id, width, height, format, *usage, *max_buffers);
+
+ mNextStreamId++;
+ return NO_ERROR;
+}
+
+int EmulatedFakeCamera2::registerStreamBuffers(
+ uint32_t stream_id,
+ int num_buffers,
+ buffer_handle_t *buffers) {
+ Mutex::Autolock l(mMutex);
+
+ if (!mStatusPresent) {
+ ALOGW("%s: Camera was physically disconnected", __FUNCTION__);
+ return ERROR_CAMERA_NOT_PRESENT;
+ }
+
+ ALOGV("%s: Stream %d registering %d buffers", __FUNCTION__,
+ stream_id, num_buffers);
+ // Need to find out what the final concrete pixel format for our stream is
+ // Assumes that all buffers have the same format.
+ if (num_buffers < 1) {
+ ALOGE("%s: Stream %d only has %d buffers!",
+ __FUNCTION__, stream_id, num_buffers);
+ return BAD_VALUE;
+ }
+
+ ssize_t streamIndex = mStreams.indexOfKey(stream_id);
+ if (streamIndex < 0) {
+ ALOGE("%s: Unknown stream id %d!", __FUNCTION__, stream_id);
+ return BAD_VALUE;
+ }
+
+ Stream &stream = mStreams.editValueAt(streamIndex);
+
+ int finalFormat = stream.format;
+
+ if (finalFormat == HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED) {
+ finalFormat = HAL_PIXEL_FORMAT_RGBA_8888;
+ }
+
+ ALOGV("%s: Stream %d format set to %x, previously %x",
+ __FUNCTION__, stream_id, finalFormat, stream.format);
+
+ stream.format = finalFormat;
+
+ return NO_ERROR;
+}
+
+int EmulatedFakeCamera2::releaseStream(uint32_t stream_id) {
+ Mutex::Autolock l(mMutex);
+
+ ssize_t streamIndex = mStreams.indexOfKey(stream_id);
+ if (streamIndex < 0) {
+ ALOGE("%s: Unknown stream id %d!", __FUNCTION__, stream_id);
+ return BAD_VALUE;
+ }
+
+ if (isStreamInUse(stream_id)) {
+ ALOGE("%s: Cannot release stream %d; in use!", __FUNCTION__,
+ stream_id);
+ return BAD_VALUE;
+ }
+
+ switch(mStreams.valueAt(streamIndex).format) {
+#if VSOC_PLATFORM_SDK_AFTER(K)
+ case HAL_PIXEL_FORMAT_RAW16:
+ mRawStreamCount--;
+ break;
+#endif
+ case HAL_PIXEL_FORMAT_BLOB:
+ mJpegStreamCount--;
+ break;
+ default:
+ mProcessedStreamCount--;
+ break;
+ }
+
+ mStreams.removeItemsAt(streamIndex);
+
+ return NO_ERROR;
+}
+
+int EmulatedFakeCamera2::allocateReprocessStreamFromStream(
+ uint32_t output_stream_id,
+ const camera2_stream_in_ops_t *stream_ops,
+ uint32_t *stream_id) {
+ Mutex::Autolock l(mMutex);
+
+ if (!mStatusPresent) {
+ ALOGW("%s: Camera was physically disconnected", __FUNCTION__);
+ return ERROR_CAMERA_NOT_PRESENT;
+ }
+
+ ssize_t baseStreamIndex = mStreams.indexOfKey(output_stream_id);
+ if (baseStreamIndex < 0) {
+ ALOGE("%s: Unknown output stream id %d!", __FUNCTION__, output_stream_id);
+ return BAD_VALUE;
+ }
+
+ const Stream &baseStream = mStreams[baseStreamIndex];
+
+ // We'll reprocess anything we produced
+
+ if (mReprocessStreamCount >= kMaxReprocessStreamCount) {
+ ALOGE("%s: Cannot allocate another reprocess stream (%d already allocated)",
+ __FUNCTION__, mReprocessStreamCount);
+ return INVALID_OPERATION;
+ }
+ mReprocessStreamCount++;
+
+ ReprocessStream newStream;
+ newStream.ops = stream_ops;
+ newStream.width = baseStream.width;
+ newStream.height = baseStream.height;
+ newStream.format = baseStream.format;
+ newStream.stride = baseStream.stride;
+ newStream.sourceStreamId = output_stream_id;
+
+ *stream_id = mNextReprocessStreamId;
+ mReprocessStreams.add(mNextReprocessStreamId, newStream);
+
+ ALOGV("Reprocess stream allocated: %d: %d, %d, 0x%x. Parent stream: %d",
+ *stream_id, newStream.width, newStream.height, newStream.format,
+ output_stream_id);
+
+ mNextReprocessStreamId++;
+ return NO_ERROR;
+}
+
+int EmulatedFakeCamera2::releaseReprocessStream(uint32_t stream_id) {
+ Mutex::Autolock l(mMutex);
+
+ ssize_t streamIndex = mReprocessStreams.indexOfKey(stream_id);
+ if (streamIndex < 0) {
+ ALOGE("%s: Unknown reprocess stream id %d!", __FUNCTION__, stream_id);
+ return BAD_VALUE;
+ }
+
+ if (isReprocessStreamInUse(stream_id)) {
+ ALOGE("%s: Cannot release reprocessing stream %d; in use!", __FUNCTION__,
+ stream_id);
+ return BAD_VALUE;
+ }
+
+ mReprocessStreamCount--;
+ mReprocessStreams.removeItemsAt(streamIndex);
+
+ return NO_ERROR;
+}
+
+int EmulatedFakeCamera2::triggerAction(uint32_t trigger_id,
+ int32_t ext1,
+ int32_t ext2) {
+ Mutex::Autolock l(mMutex);
+
+ if (trigger_id == CAMERA2_EXT_TRIGGER_TESTING_DISCONNECT) {
+ ALOGI("%s: Disconnect trigger - camera must be closed", __FUNCTION__);
+ mStatusPresent = false;
+
+ EmulatedCameraFactory::Instance().onStatusChanged(
+ mCameraID,
+ CAMERA_DEVICE_STATUS_NOT_PRESENT);
+ }
+
+ if (!mStatusPresent) {
+ ALOGW("%s: Camera was physically disconnected", __FUNCTION__);
+ return ERROR_CAMERA_NOT_PRESENT;
+ }
+
+ return mControlThread->triggerAction(trigger_id,
+ ext1, ext2);
+}
+
+/** Shutdown and debug methods */
+
+int EmulatedFakeCamera2::dump(int fd) {
+ String8 result;
+
+ result.appendFormat(" Camera HAL device: EmulatedFakeCamera2\n");
+ result.appendFormat(" Streams:\n");
+ for (size_t i = 0; i < mStreams.size(); i++) {
+ int id = mStreams.keyAt(i);
+ const Stream& s = mStreams.valueAt(i);
+ result.appendFormat(
+ " Stream %d: %d x %d, format 0x%x, stride %d\n",
+ id, s.width, s.height, s.format, s.stride);
+ }
+
+ write(fd, result.string(), result.size());
+
+ return NO_ERROR;
+}
+
+void EmulatedFakeCamera2::signalError() {
+ // TODO: Let parent know so we can shut down cleanly
+ ALOGE("Worker thread is signaling a serious error");
+}
+
+/** Pipeline control worker thread methods */
+
+EmulatedFakeCamera2::ConfigureThread::ConfigureThread(EmulatedFakeCamera2 *parent):
+ Thread(false),
+ mParent(parent),
+ mRequestCount(0),
+ mNextBuffers(NULL) {
+ mRunning = false;
+}
+
+EmulatedFakeCamera2::ConfigureThread::~ConfigureThread() {
+}
+
+status_t EmulatedFakeCamera2::ConfigureThread::readyToRun() {
+ Mutex::Autolock lock(mInputMutex);
+
+ ALOGV("Starting up ConfigureThread");
+ mRequest = NULL;
+ mActive = false;
+ mRunning = true;
+
+ mInputSignal.signal();
+ return NO_ERROR;
+}
+
+status_t EmulatedFakeCamera2::ConfigureThread::waitUntilRunning() {
+ Mutex::Autolock lock(mInputMutex);
+ if (!mRunning) {
+ ALOGV("Waiting for configure thread to start");
+ mInputSignal.wait(mInputMutex);
+ }
+ return OK;
+}
+
+status_t EmulatedFakeCamera2::ConfigureThread::newRequestAvailable() {
+ waitUntilRunning();
+
+ Mutex::Autolock lock(mInputMutex);
+
+ mActive = true;
+ mInputSignal.signal();
+
+ return OK;
+}
+
+bool EmulatedFakeCamera2::ConfigureThread::isStreamInUse(uint32_t id) {
+ Mutex::Autolock lock(mInternalsMutex);
+
+ if (mNextBuffers == NULL) return false;
+ for (size_t i=0; i < mNextBuffers->size(); i++) {
+ if ((*mNextBuffers)[i].streamId == (int)id) return true;
+ }
+ return false;
+}
+
+int EmulatedFakeCamera2::ConfigureThread::getInProgressCount() {
+ Mutex::Autolock lock(mInputMutex);
+ return mRequestCount;
+}
+
+bool EmulatedFakeCamera2::ConfigureThread::threadLoop() {
+ status_t res;
+
+ // Check if we're currently processing or just waiting
+ {
+ Mutex::Autolock lock(mInputMutex);
+ if (!mActive) {
+ // Inactive, keep waiting until we've been signaled
+ status_t res;
+ res = mInputSignal.waitRelative(mInputMutex, kWaitPerLoop);
+ if (res != NO_ERROR && res != TIMED_OUT) {
+ ALOGE("%s: Error waiting for input requests: %d",
+ __FUNCTION__, res);
+ return false;
+ }
+ if (!mActive) return true;
+ ALOGV("New request available");
+ }
+ // Active
+ }
+
+ if (mRequest == NULL) {
+ Mutex::Autolock il(mInternalsMutex);
+
+ ALOGV("Configure: Getting next request");
+ res = mParent->mRequestQueueSrc->dequeue_request(
+ mParent->mRequestQueueSrc,
+ &mRequest);
+ if (res != NO_ERROR) {
+ ALOGE("%s: Error dequeuing next request: %d", __FUNCTION__, res);
+ mParent->signalError();
+ return false;
+ }
+ if (mRequest == NULL) {
+ ALOGV("Configure: Request queue empty, going inactive");
+ // No requests available, go into inactive mode
+ Mutex::Autolock lock(mInputMutex);
+ mActive = false;
+ return true;
+ } else {
+ Mutex::Autolock lock(mInputMutex);
+ mRequestCount++;
+ }
+
+ camera_metadata_entry_t type;
+ res = find_camera_metadata_entry(mRequest,
+ ANDROID_REQUEST_TYPE,
+ &type);
+ if (res != NO_ERROR) {
+ ALOGE("%s: error reading request type", __FUNCTION__);
+ mParent->signalError();
+ return false;
+ }
+ bool success = false;;
+ switch (type.data.u8[0]) {
+ case ANDROID_REQUEST_TYPE_CAPTURE:
+ success = setupCapture();
+ break;
+ case ANDROID_REQUEST_TYPE_REPROCESS:
+ success = setupReprocess();
+ break;
+ default:
+ ALOGE("%s: Unexpected request type %d",
+ __FUNCTION__, type.data.u8[0]);
+ mParent->signalError();
+ break;
+ }
+ if (!success) return false;
+
+ }
+
+ if (mWaitingForReadout) {
+ bool readoutDone;
+ readoutDone = mParent->mReadoutThread->waitForReady(kWaitPerLoop);
+ if (!readoutDone) return true;
+
+ if (mNextNeedsJpeg) {
+ ALOGV("Configure: Waiting for JPEG compressor");
+ } else {
+ ALOGV("Configure: Waiting for sensor");
+ }
+ mWaitingForReadout = false;
+ }
+
+ if (mNextNeedsJpeg) {
+ bool jpegDone;
+ jpegDone = mParent->mJpegCompressor->waitForDone(kWaitPerLoop);
+ if (!jpegDone) return true;
+
+ ALOGV("Configure: Waiting for sensor");
+ mNextNeedsJpeg = false;
+ }
+
+ if (mNextIsCapture) {
+ return configureNextCapture();
+ } else {
+ return configureNextReprocess();
+ }
+}
+
+bool EmulatedFakeCamera2::ConfigureThread::setupCapture() {
+ status_t res;
+
+ mNextIsCapture = true;
+ // Get necessary parameters for sensor config
+ mParent->mControlThread->processRequest(mRequest);
+
+ camera_metadata_entry_t streams;
+ res = find_camera_metadata_entry(mRequest,
+ ANDROID_REQUEST_OUTPUT_STREAMS,
+ &streams);
+ if (res != NO_ERROR) {
+ ALOGE("%s: error reading output stream tag", __FUNCTION__);
+ mParent->signalError();
+ return false;
+ }
+
+ mNextBuffers = new Buffers;
+ mNextNeedsJpeg = false;
+ ALOGV("Configure: Setting up buffers for capture");
+ for (size_t i = 0; i < streams.count; i++) {
+ int streamId = streams.data.i32[i];
+ const Stream &s = mParent->getStreamInfo(streamId);
+ if (s.format == HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED) {
+ ALOGE("%s: Stream %d does not have a concrete pixel format, but "
+ "is included in a request!", __FUNCTION__, streamId);
+ mParent->signalError();
+ return false;
+ }
+ StreamBuffer b;
+ b.streamId = streamId; //streams.data.u8[i];
+ b.width = s.width;
+ b.height = s.height;
+ b.format = s.format;
+ b.stride = s.stride;
+ mNextBuffers->push_back(b);
+ ALOGV("Configure: Buffer %zu: Stream %d, %d x %d, format 0x%x, "
+ "stride %d",
+ i, b.streamId, b.width, b.height, b.format, b.stride);
+ if (b.format == HAL_PIXEL_FORMAT_BLOB) {
+ mNextNeedsJpeg = true;
+ }
+ }
+
+ camera_metadata_entry_t e;
+ res = find_camera_metadata_entry(mRequest,
+ ANDROID_REQUEST_FRAME_COUNT,
+ &e);
+ if (res != NO_ERROR) {
+ ALOGE("%s: error reading frame count tag: %s (%d)",
+ __FUNCTION__, strerror(-res), res);
+ mParent->signalError();
+ return false;
+ }
+ mNextFrameNumber = *e.data.i32;
+
+ res = find_camera_metadata_entry(mRequest,
+ ANDROID_SENSOR_EXPOSURE_TIME,
+ &e);
+ if (res != NO_ERROR) {
+ ALOGE("%s: error reading exposure time tag: %s (%d)",
+ __FUNCTION__, strerror(-res), res);
+ mParent->signalError();
+ return false;
+ }
+ mNextExposureTime = *e.data.i64;
+
+ res = find_camera_metadata_entry(mRequest,
+ ANDROID_SENSOR_FRAME_DURATION,
+ &e);
+ if (res != NO_ERROR) {
+ ALOGE("%s: error reading frame duration tag", __FUNCTION__);
+ mParent->signalError();
+ return false;
+ }
+ mNextFrameDuration = *e.data.i64;
+
+ if (mNextFrameDuration <
+ mNextExposureTime + Sensor::kMinVerticalBlank) {
+ mNextFrameDuration = mNextExposureTime + Sensor::kMinVerticalBlank;
+ }
+ res = find_camera_metadata_entry(mRequest,
+ ANDROID_SENSOR_SENSITIVITY,
+ &e);
+ if (res != NO_ERROR) {
+ ALOGE("%s: error reading sensitivity tag", __FUNCTION__);
+ mParent->signalError();
+ return false;
+ }
+ mNextSensitivity = *e.data.i32;
+
+ // Start waiting on readout thread
+ mWaitingForReadout = true;
+ ALOGV("Configure: Waiting for readout thread");
+
+ return true;
+}
+
+bool EmulatedFakeCamera2::ConfigureThread::configureNextCapture() {
+ bool vsync = mParent->mSensor->waitForVSync(kWaitPerLoop);
+ if (!vsync) return true;
+
+ Mutex::Autolock il(mInternalsMutex);
+ ALOGV("Configure: Configuring sensor for capture %d", mNextFrameNumber);
+ mParent->mSensor->setExposureTime(mNextExposureTime);
+ mParent->mSensor->setFrameDuration(mNextFrameDuration);
+ mParent->mSensor->setSensitivity(mNextSensitivity);
+
+ getBuffers();
+
+ ALOGV("Configure: Done configure for capture %d", mNextFrameNumber);
+ mParent->mReadoutThread->setNextOperation(true, mRequest, mNextBuffers);
+ mParent->mSensor->setDestinationBuffers(mNextBuffers);
+
+ mRequest = NULL;
+ mNextBuffers = NULL;
+
+ Mutex::Autolock lock(mInputMutex);
+ mRequestCount--;
+
+ return true;
+}
+
+bool EmulatedFakeCamera2::ConfigureThread::setupReprocess() {
+ status_t res;
+
+ mNextNeedsJpeg = true;
+ mNextIsCapture = false;
+
+ camera_metadata_entry_t reprocessStreams;
+ res = find_camera_metadata_entry(mRequest,
+ ANDROID_REQUEST_INPUT_STREAMS,
+ &reprocessStreams);
+ if (res != NO_ERROR) {
+ ALOGE("%s: error reading output stream tag", __FUNCTION__);
+ mParent->signalError();
+ return false;
+ }
+
+ mNextBuffers = new Buffers;
+
+ ALOGV("Configure: Setting up input buffers for reprocess");
+ for (size_t i = 0; i < reprocessStreams.count; i++) {
+ int streamId = reprocessStreams.data.i32[i];
+ const ReprocessStream &s = mParent->getReprocessStreamInfo(streamId);
+ if (s.format != HAL_PIXEL_FORMAT_RGB_888) {
+ ALOGE("%s: Only ZSL reprocessing supported!",
+ __FUNCTION__);
+ mParent->signalError();
+ return false;
+ }
+ StreamBuffer b;
+ b.streamId = -streamId;
+ b.width = s.width;
+ b.height = s.height;
+ b.format = s.format;
+ b.stride = s.stride;
+ mNextBuffers->push_back(b);
+ }
+
+ camera_metadata_entry_t streams;
+ res = find_camera_metadata_entry(mRequest,
+ ANDROID_REQUEST_OUTPUT_STREAMS,
+ &streams);
+ if (res != NO_ERROR) {
+ ALOGE("%s: error reading output stream tag", __FUNCTION__);
+ mParent->signalError();
+ return false;
+ }
+
+ ALOGV("Configure: Setting up output buffers for reprocess");
+ for (size_t i = 0; i < streams.count; i++) {
+ int streamId = streams.data.i32[i];
+ const Stream &s = mParent->getStreamInfo(streamId);
+ if (s.format != HAL_PIXEL_FORMAT_BLOB) {
+ // TODO: Support reprocess to YUV
+ ALOGE("%s: Non-JPEG output stream %d for reprocess not supported",
+ __FUNCTION__, streamId);
+ mParent->signalError();
+ return false;
+ }
+ StreamBuffer b;
+ b.streamId = streams.data.u8[i];
+ b.width = s.width;
+ b.height = s.height;
+ b.format = s.format;
+ b.stride = s.stride;
+ mNextBuffers->push_back(b);
+ ALOGV("Configure: Buffer %zu: Stream %d, %d x %d, format 0x%x, "
+ "stride %d",
+ i, b.streamId, b.width, b.height, b.format, b.stride);
+ }
+
+ camera_metadata_entry_t e;
+ res = find_camera_metadata_entry(mRequest,
+ ANDROID_REQUEST_FRAME_COUNT,
+ &e);
+ if (res != NO_ERROR) {
+ ALOGE("%s: error reading frame count tag: %s (%d)",
+ __FUNCTION__, strerror(-res), res);
+ mParent->signalError();
+ return false;
+ }
+ mNextFrameNumber = *e.data.i32;
+
+ return true;
+}
+
+bool EmulatedFakeCamera2::ConfigureThread::configureNextReprocess() {
+ Mutex::Autolock il(mInternalsMutex);
+
+ getBuffers();
+
+ ALOGV("Configure: Done configure for reprocess %d", mNextFrameNumber);
+ mParent->mReadoutThread->setNextOperation(false, mRequest, mNextBuffers);
+
+ mRequest = NULL;
+ mNextBuffers = NULL;
+
+ Mutex::Autolock lock(mInputMutex);
+ mRequestCount--;
+
+ return true;
+}
+
+bool EmulatedFakeCamera2::ConfigureThread::getBuffers() {
+ status_t res;
+ /** Get buffers to fill for this frame */
+ for (size_t i = 0; i < mNextBuffers->size(); i++) {
+ StreamBuffer &b = mNextBuffers->editItemAt(i);
+
+ if (b.streamId > 0) {
+ ALOGV("Configure: Dequeing buffer from stream %d", b.streamId);
+ Stream s = mParent->getStreamInfo(b.streamId);
+ res = s.ops->dequeue_buffer(s.ops, &(b.buffer) );
+ if (res != NO_ERROR || b.buffer == NULL) {
+ ALOGE("%s: Unable to dequeue buffer from stream %d: %s (%d)",
+ __FUNCTION__, b.streamId, strerror(-res), res);
+ mParent->signalError();
+ return false;
+ }
+
+ /* Lock the buffer from the perspective of the graphics mapper */
+ res = GrallocModule::getInstance().lock(*(b.buffer),
+ GRALLOC_USAGE_HW_CAMERA_WRITE,
+ 0, 0, s.width, s.height,
+ (void**)&(b.img));
+
+
+ if (res != NO_ERROR) {
+ ALOGE("%s: grbuffer_mapper.lock failure: %s (%d)",
+ __FUNCTION__, strerror(-res), res);
+ s.ops->cancel_buffer(s.ops,
+ b.buffer);
+ mParent->signalError();
+ return false;
+ }
+ } else {
+ ALOGV("Configure: Acquiring buffer from reprocess stream %d",
+ -b.streamId);
+ ReprocessStream s = mParent->getReprocessStreamInfo(-b.streamId);
+ res = s.ops->acquire_buffer(s.ops, &(b.buffer) );
+ if (res != NO_ERROR || b.buffer == NULL) {
+ ALOGE("%s: Unable to acquire buffer from reprocess stream %d: "
+ "%s (%d)", __FUNCTION__, -b.streamId,
+ strerror(-res), res);
+ mParent->signalError();
+ return false;
+ }
+
+ /* Lock the buffer from the perspective of the graphics mapper */
+ res = GrallocModule::getInstance().lock(*(b.buffer),
+ GRALLOC_USAGE_HW_CAMERA_READ,
+ 0, 0, s.width, s.height,
+ (void**)&(b.img) );
+ if (res != NO_ERROR) {
+ ALOGE("%s: grbuffer_mapper.lock failure: %s (%d)",
+ __FUNCTION__, strerror(-res), res);
+ s.ops->release_buffer(s.ops,
+ b.buffer);
+ mParent->signalError();
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+EmulatedFakeCamera2::ReadoutThread::ReadoutThread(EmulatedFakeCamera2 *parent):
+ Thread(false),
+ mParent(parent),
+ mRunning(false),
+ mActive(false),
+ mRequestCount(0),
+ mRequest(NULL),
+ mBuffers(NULL) {
+ mInFlightQueue = new InFlightQueue[kInFlightQueueSize];
+ mInFlightHead = 0;
+ mInFlightTail = 0;
+}
+
+EmulatedFakeCamera2::ReadoutThread::~ReadoutThread() {
+ delete[] mInFlightQueue;
+}
+
+status_t EmulatedFakeCamera2::ReadoutThread::readyToRun() {
+ Mutex::Autolock lock(mInputMutex);
+ ALOGV("Starting up ReadoutThread");
+ mRunning = true;
+ mInputSignal.signal();
+ return NO_ERROR;
+}
+
+status_t EmulatedFakeCamera2::ReadoutThread::waitUntilRunning() {
+ Mutex::Autolock lock(mInputMutex);
+ if (!mRunning) {
+ ALOGV("Waiting for readout thread to start");
+ mInputSignal.wait(mInputMutex);
+ }
+ return OK;
+}
+
+bool EmulatedFakeCamera2::ReadoutThread::waitForReady(nsecs_t timeout) {
+ status_t res;
+ Mutex::Autolock lock(mInputMutex);
+ while (!readyForNextCapture()) {
+ res = mReadySignal.waitRelative(mInputMutex, timeout);
+ if (res == TIMED_OUT) return false;
+ if (res != OK) {
+ ALOGE("%s: Error waiting for ready: %s (%d)", __FUNCTION__,
+ strerror(-res), res);
+ return false;
+ }
+ }
+ return true;
+}
+
+bool EmulatedFakeCamera2::ReadoutThread::readyForNextCapture() {
+ return (mInFlightTail + 1) % kInFlightQueueSize != mInFlightHead;
+}
+
+void EmulatedFakeCamera2::ReadoutThread::setNextOperation(
+ bool isCapture,
+ camera_metadata_t *request,
+ Buffers *buffers) {
+ Mutex::Autolock lock(mInputMutex);
+ if ( !readyForNextCapture() ) {
+ ALOGE("In flight queue full, dropping captures");
+ mParent->signalError();
+ return;
+ }
+ mInFlightQueue[mInFlightTail].isCapture = isCapture;
+ mInFlightQueue[mInFlightTail].request = request;
+ mInFlightQueue[mInFlightTail].buffers = buffers;
+ mInFlightTail = (mInFlightTail + 1) % kInFlightQueueSize;
+ mRequestCount++;
+
+ if (!mActive) {
+ mActive = true;
+ mInputSignal.signal();
+ }
+}
+
+bool EmulatedFakeCamera2::ReadoutThread::isStreamInUse(uint32_t id) {
+ // acquire in same order as threadLoop
+ Mutex::Autolock iLock(mInternalsMutex);
+ Mutex::Autolock lock(mInputMutex);
+
+ size_t i = mInFlightHead;
+ while (i != mInFlightTail) {
+ for (size_t j = 0; j < mInFlightQueue[i].buffers->size(); j++) {
+ if ( (*(mInFlightQueue[i].buffers))[j].streamId == (int)id )
+ return true;
+ }
+ i = (i + 1) % kInFlightQueueSize;
+ }
+
+
+ if (mBuffers != NULL) {
+ for (i = 0; i < mBuffers->size(); i++) {
+ if ( (*mBuffers)[i].streamId == (int)id) return true;
+ }
+ }
+
+ return false;
+}
+
+int EmulatedFakeCamera2::ReadoutThread::getInProgressCount() {
+ Mutex::Autolock lock(mInputMutex);
+
+ return mRequestCount;
+}
+
+bool EmulatedFakeCamera2::ReadoutThread::threadLoop() {
+ static const nsecs_t kWaitPerLoop = 10000000L; // 10 ms
+ status_t res;
+ int32_t frameNumber;
+
+ // Check if we're currently processing or just waiting
+ {
+ Mutex::Autolock lock(mInputMutex);
+ if (!mActive) {
+ // Inactive, keep waiting until we've been signaled
+ res = mInputSignal.waitRelative(mInputMutex, kWaitPerLoop);
+ if (res != NO_ERROR && res != TIMED_OUT) {
+ ALOGE("%s: Error waiting for capture requests: %d",
+ __FUNCTION__, res);
+ mParent->signalError();
+ return false;
+ }
+ if (!mActive) return true;
+ }
+ // Active, see if we need a new request
+ if (mRequest == NULL) {
+ if (mInFlightHead == mInFlightTail) {
+ // Go inactive
+ ALOGV("Waiting for sensor data");
+ mActive = false;
+ return true;
+ } else {
+ Mutex::Autolock iLock(mInternalsMutex);
+ mReadySignal.signal();
+ mIsCapture = mInFlightQueue[mInFlightHead].isCapture;
+ mRequest = mInFlightQueue[mInFlightHead].request;
+ mBuffers = mInFlightQueue[mInFlightHead].buffers;
+ mInFlightQueue[mInFlightHead].request = NULL;
+ mInFlightQueue[mInFlightHead].buffers = NULL;
+ mInFlightHead = (mInFlightHead + 1) % kInFlightQueueSize;
+ ALOGV("Ready to read out request %p, %zu buffers",
+ mRequest, mBuffers->size());
+ }
+ }
+ }
+
+ // Active with request, wait on sensor to complete
+
+ nsecs_t captureTime;
+
+ if (mIsCapture) {
+ bool gotFrame;
+ gotFrame = mParent->mSensor->waitForNewFrame(kWaitPerLoop,
+ &captureTime);
+
+ if (!gotFrame) return true;
+ }
+
+ Mutex::Autolock iLock(mInternalsMutex);
+
+ camera_metadata_entry_t entry;
+ if (!mIsCapture) {
+ res = find_camera_metadata_entry(mRequest,
+ ANDROID_SENSOR_TIMESTAMP,
+ &entry);
+ if (res != NO_ERROR) {
+ ALOGE("%s: error reading reprocessing timestamp: %s (%d)",
+ __FUNCTION__, strerror(-res), res);
+ mParent->signalError();
+ return false;
+ }
+ captureTime = entry.data.i64[0];
+ }
+
+ res = find_camera_metadata_entry(mRequest,
+ ANDROID_REQUEST_FRAME_COUNT,
+ &entry);
+ if (res != NO_ERROR) {
+ ALOGE("%s: error reading frame count tag: %s (%d)",
+ __FUNCTION__, strerror(-res), res);
+ mParent->signalError();
+ return false;
+ }
+ frameNumber = *entry.data.i32;
+
+ res = find_camera_metadata_entry(mRequest,
+ ANDROID_REQUEST_METADATA_MODE,
+ &entry);
+ if (res != NO_ERROR) {
+ ALOGE("%s: error reading metadata mode tag: %s (%d)",
+ __FUNCTION__, strerror(-res), res);
+ mParent->signalError();
+ return false;
+ }
+
+ // Got sensor data and request, construct frame and send it out
+ ALOGV("Readout: Constructing metadata and frames for request %d",
+ frameNumber);
+
+ if (*entry.data.u8 == ANDROID_REQUEST_METADATA_MODE_FULL) {
+ ALOGV("Readout: Metadata requested, constructing");
+
+ camera_metadata_t *frame = NULL;
+
+ size_t frame_entries = get_camera_metadata_entry_count(mRequest);
+ size_t frame_data = get_camera_metadata_data_count(mRequest);
+
+ // TODO: Dynamically calculate based on enabled statistics, etc
+ frame_entries += 10;
+ frame_data += 100;
+
+ res = mParent->mFrameQueueDst->dequeue_frame(mParent->mFrameQueueDst,
+ frame_entries, frame_data, &frame);
+
+ if (res != NO_ERROR || frame == NULL) {
+ ALOGE("%s: Unable to dequeue frame metadata buffer", __FUNCTION__);
+ mParent->signalError();
+ return false;
+ }
+
+ res = append_camera_metadata(frame, mRequest);
+ if (res != NO_ERROR) {
+ ALOGE("Unable to append request metadata");
+ }
+
+ if (mIsCapture) {
+ add_camera_metadata_entry(frame,
+ ANDROID_SENSOR_TIMESTAMP,
+ &captureTime,
+ 1);
+
+ collectStatisticsMetadata(frame);
+ // TODO: Collect all final values used from sensor in addition to timestamp
+ }
+
+ ALOGV("Readout: Enqueue frame %d", frameNumber);
+ mParent->mFrameQueueDst->enqueue_frame(mParent->mFrameQueueDst,
+ frame);
+ }
+ ALOGV("Readout: Free request");
+ res = mParent->mRequestQueueSrc->free_request(mParent->mRequestQueueSrc, mRequest);
+ if (res != NO_ERROR) {
+ ALOGE("%s: Unable to return request buffer to queue: %d",
+ __FUNCTION__, res);
+ mParent->signalError();
+ return false;
+ }
+ mRequest = NULL;
+
+ int compressedBufferIndex = -1;
+ ALOGV("Readout: Processing %zu buffers", mBuffers->size());
+ for (size_t i = 0; i < mBuffers->size(); i++) {
+ const StreamBuffer &b = (*mBuffers)[i];
+ ALOGV("Readout: Buffer %zu: Stream %d, %d x %d, format 0x%x, stride %d",
+ i, b.streamId, b.width, b.height, b.format, b.stride);
+ if (b.streamId > 0) {
+ if (b.format == HAL_PIXEL_FORMAT_BLOB) {
+ // Assumes only one BLOB buffer type per capture
+ compressedBufferIndex = i;
+ } else {
+ ALOGV("Readout: Sending image buffer %zu (%p) to output stream %d",
+ i, (void*)*(b.buffer), b.streamId);
+ GrallocModule::getInstance().unlock(*(b.buffer));
+ const Stream &s = mParent->getStreamInfo(b.streamId);
+ res = s.ops->enqueue_buffer(s.ops, captureTime, b.buffer);
+ if (res != OK) {
+ ALOGE("Error enqueuing image buffer %p: %s (%d)", b.buffer,
+ strerror(-res), res);
+ mParent->signalError();
+ }
+ }
+ }
+ }
+
+ if (compressedBufferIndex == -1) {
+ delete mBuffers;
+ } else {
+ ALOGV("Readout: Starting JPEG compression for buffer %d, stream %d",
+ compressedBufferIndex,
+ (*mBuffers)[compressedBufferIndex].streamId);
+ mJpegTimestamp = captureTime;
+ // Takes ownership of mBuffers
+ mParent->mJpegCompressor->start(mBuffers, this);
+ }
+ mBuffers = NULL;
+
+ Mutex::Autolock l(mInputMutex);
+ mRequestCount--;
+ ALOGV("Readout: Done with request %d", frameNumber);
+ return true;
+}
+
+void EmulatedFakeCamera2::ReadoutThread::onJpegDone(
+ const StreamBuffer &jpegBuffer, bool success) {
+ status_t res;
+ if (!success) {
+ ALOGE("%s: Error queueing compressed image buffer %p",
+ __FUNCTION__, jpegBuffer.buffer);
+ mParent->signalError();
+ return;
+ }
+
+ // Write to JPEG output stream
+ ALOGV("%s: Compression complete, pushing to stream %d", __FUNCTION__,
+ jpegBuffer.streamId);
+
+ GrallocModule::getInstance().unlock(*(jpegBuffer.buffer));
+ const Stream &s = mParent->getStreamInfo(jpegBuffer.streamId);
+ res = s.ops->enqueue_buffer(s.ops, mJpegTimestamp, jpegBuffer.buffer);
+}
+
+void EmulatedFakeCamera2::ReadoutThread::onJpegInputDone(
+ const StreamBuffer &inputBuffer) {
+ status_t res;
+ GrallocModule::getInstance().unlock(*(inputBuffer.buffer));
+ const ReprocessStream &s =
+ mParent->getReprocessStreamInfo(-inputBuffer.streamId);
+ res = s.ops->release_buffer(s.ops, inputBuffer.buffer);
+ if (res != OK) {
+ ALOGE("Error releasing reprocess buffer %p: %s (%d)",
+ inputBuffer.buffer, strerror(-res), res);
+ mParent->signalError();
+ }
+}
+
+status_t EmulatedFakeCamera2::ReadoutThread::collectStatisticsMetadata(
+ camera_metadata_t *frame) {
+ // Completely fake face rectangles, don't correspond to real faces in scene
+ ALOGV("Readout: Collecting statistics metadata");
+
+ status_t res;
+ camera_metadata_entry_t entry;
+ res = find_camera_metadata_entry(frame,
+ ANDROID_STATISTICS_FACE_DETECT_MODE,
+ &entry);
+ if (res != OK) {
+ ALOGE("%s: Unable to find face detect mode!", __FUNCTION__);
+ return BAD_VALUE;
+ }
+
+ if (entry.data.u8[0] == ANDROID_STATISTICS_FACE_DETECT_MODE_OFF) return OK;
+
+ // The coordinate system for the face regions is the raw sensor pixel
+ // coordinates. Here, we map from the scene coordinates (0-19 in both axis)
+ // to raw pixels, for the scene defined in fake-pipeline2/Scene.cpp. We
+ // approximately place two faces on top of the windows of the house. No
+ // actual faces exist there, but might one day. Note that this doesn't
+ // account for the offsets used to account for aspect ratio differences, so
+ // the rectangles don't line up quite right.
+ const size_t numFaces = 2;
+ int32_t rects[numFaces * 4] = {
+ static_cast<int32_t>(mParent->mSensorWidth * 10 / 20),
+ static_cast<int32_t>(mParent->mSensorHeight * 15 / 20),
+ static_cast<int32_t>(mParent->mSensorWidth * 12 / 20),
+ static_cast<int32_t>(mParent->mSensorHeight * 17 / 20),
+
+ static_cast<int32_t>(mParent->mSensorWidth * 16 / 20),
+ static_cast<int32_t>(mParent->mSensorHeight * 15 / 20),
+ static_cast<int32_t>(mParent->mSensorWidth * 18 / 20),
+ static_cast<int32_t>(mParent->mSensorHeight * 17 / 20)
+ };
+ // To simulate some kind of real detection going on, we jitter the rectangles on
+ // each frame by a few pixels in each dimension.
+ for (size_t i = 0; i < numFaces * 4; i++) {
+ rects[i] += (int32_t)(((float)rand() / RAND_MAX) * 6 - 3);
+ }
+ // The confidence scores (0-100) are similarly jittered.
+ uint8_t scores[numFaces] = { 85, 95 };
+ for (size_t i = 0; i < numFaces; i++) {
+ scores[i] += (int32_t)(((float)rand() / RAND_MAX) * 10 - 5);
+ }
+
+ res = add_camera_metadata_entry(frame, ANDROID_STATISTICS_FACE_RECTANGLES,
+ rects, numFaces * 4);
+ if (res != OK) {
+ ALOGE("%s: Unable to add face rectangles!", __FUNCTION__);
+ return BAD_VALUE;
+ }
+
+ res = add_camera_metadata_entry(frame, ANDROID_STATISTICS_FACE_SCORES,
+ scores, numFaces);
+ if (res != OK) {
+ ALOGE("%s: Unable to add face scores!", __FUNCTION__);
+ return BAD_VALUE;
+ }
+
+ if (entry.data.u8[0] == ANDROID_STATISTICS_FACE_DETECT_MODE_SIMPLE) return OK;
+
+ // Advanced face detection options - add eye/mouth coordinates. The
+ // coordinates in order are (leftEyeX, leftEyeY, rightEyeX, rightEyeY,
+ // mouthX, mouthY). The mapping is the same as the face rectangles.
+ int32_t features[numFaces * 6] = {
+ static_cast<int32_t>(mParent->mSensorWidth * 10.5 / 20),
+ static_cast<int32_t>(mParent->mSensorHeight * 16 / 20),
+ static_cast<int32_t>(mParent->mSensorWidth * 11.5 / 20),
+ static_cast<int32_t>(mParent->mSensorHeight * 16 / 20),
+ static_cast<int32_t>(mParent->mSensorWidth * 11 / 20),
+ static_cast<int32_t>(mParent->mSensorHeight * 16.5 / 20),
+
+ static_cast<int32_t>(mParent->mSensorWidth * 16.5 / 20),
+ static_cast<int32_t>(mParent->mSensorHeight * 16 / 20),
+ static_cast<int32_t>(mParent->mSensorWidth * 17.5 / 20),
+ static_cast<int32_t>(mParent->mSensorHeight * 16 / 20),
+ static_cast<int32_t>(mParent->mSensorWidth * 17 / 20),
+ static_cast<int32_t>(mParent->mSensorHeight * 16.5 / 20),
+ };
+ // Jitter these a bit less than the rects
+ for (size_t i = 0; i < numFaces * 6; i++) {
+ features[i] += (int32_t)(((float)rand() / RAND_MAX) * 4 - 2);
+ }
+ // These are unique IDs that are used to identify each face while it's
+ // visible to the detector (if a face went away and came back, it'd get a
+ // new ID).
+ int32_t ids[numFaces] = {
+ 100, 200
+ };
+
+ res = add_camera_metadata_entry(frame, ANDROID_STATISTICS_FACE_LANDMARKS,
+ features, numFaces * 6);
+ if (res != OK) {
+ ALOGE("%s: Unable to add face landmarks!", __FUNCTION__);
+ return BAD_VALUE;
+ }
+
+ res = add_camera_metadata_entry(frame, ANDROID_STATISTICS_FACE_IDS,
+ ids, numFaces);
+ if (res != OK) {
+ ALOGE("%s: Unable to add face scores!", __FUNCTION__);
+ return BAD_VALUE;
+ }
+
+ return OK;
+}
+
+EmulatedFakeCamera2::ControlThread::ControlThread(EmulatedFakeCamera2 *parent):
+ Thread(false),
+ mParent(parent) {
+ mRunning = false;
+}
+
+EmulatedFakeCamera2::ControlThread::~ControlThread() {
+}
+
+status_t EmulatedFakeCamera2::ControlThread::readyToRun() {
+ Mutex::Autolock lock(mInputMutex);
+
+ ALOGV("Starting up ControlThread");
+ mRunning = true;
+ mStartAf = false;
+ mCancelAf = false;
+ mStartPrecapture = false;
+
+ mControlMode = ANDROID_CONTROL_MODE_AUTO;
+
+ mEffectMode = ANDROID_CONTROL_EFFECT_MODE_OFF;
+ mSceneMode = ANDROID_CONTROL_SCENE_MODE_FACE_PRIORITY;
+
+ mAfMode = ANDROID_CONTROL_AF_MODE_AUTO;
+ mAfModeChange = false;
+
+ mAeMode = ANDROID_CONTROL_AE_MODE_ON;
+ mAwbMode = ANDROID_CONTROL_AWB_MODE_AUTO;
+
+ mAfTriggerId = 0;
+ mPrecaptureTriggerId = 0;
+
+ mAfState = ANDROID_CONTROL_AF_STATE_INACTIVE;
+ mAeState = ANDROID_CONTROL_AE_STATE_INACTIVE;
+ mAwbState = ANDROID_CONTROL_AWB_STATE_INACTIVE;
+
+ mExposureTime = kNormalExposureTime;
+
+ mInputSignal.signal();
+ return NO_ERROR;
+}
+
+status_t EmulatedFakeCamera2::ControlThread::waitUntilRunning() {
+ Mutex::Autolock lock(mInputMutex);
+ if (!mRunning) {
+ ALOGV("Waiting for control thread to start");
+ mInputSignal.wait(mInputMutex);
+ }
+ return OK;
+}
+
+// Override android.control.* fields with 3A values before sending request to sensor
+status_t EmulatedFakeCamera2::ControlThread::processRequest(camera_metadata_t *request) {
+ Mutex::Autolock lock(mInputMutex);
+ // TODO: Add handling for all android.control.* fields here
+ camera_metadata_entry_t mode;
+ status_t res;
+
+#define READ_IF_OK(res, what, def) \
+ (((res) == OK) ? (what) : (uint8_t)(def))
+
+ res = find_camera_metadata_entry(request,
+ ANDROID_CONTROL_MODE,
+ &mode);
+ mControlMode = READ_IF_OK(res, mode.data.u8[0], ANDROID_CONTROL_MODE_OFF);
+
+ // disable all 3A
+ if (mControlMode == ANDROID_CONTROL_MODE_OFF) {
+ mEffectMode = ANDROID_CONTROL_EFFECT_MODE_OFF;
+#if VSOC_PLATFORM_SDK_AFTER(K)
+ mSceneMode = ANDROID_CONTROL_SCENE_MODE_DISABLED;
+#else
+ mSceneMode = ANDROID_CONTROL_SCENE_MODE_UNSUPPORTED;
+#endif
+ mAfMode = ANDROID_CONTROL_AF_MODE_OFF;
+ mAeLock = ANDROID_CONTROL_AE_LOCK_ON;
+ mAeMode = ANDROID_CONTROL_AE_MODE_OFF;
+ mAfModeChange = true;
+ mStartAf = false;
+ mCancelAf = true;
+ mAeState = ANDROID_CONTROL_AE_STATE_INACTIVE;
+ mAwbMode = ANDROID_CONTROL_AWB_MODE_OFF;
+ return res;
+ }
+
+ res = find_camera_metadata_entry(request,
+ ANDROID_CONTROL_EFFECT_MODE,
+ &mode);
+ mEffectMode = READ_IF_OK(res, mode.data.u8[0],
+ ANDROID_CONTROL_EFFECT_MODE_OFF);
+
+ res = find_camera_metadata_entry(request,
+ ANDROID_CONTROL_SCENE_MODE,
+ &mode);
+#if VSOC_PLATFORM_SDK_AFTER(K)
+ mSceneMode = READ_IF_OK(res, mode.data.u8[0],
+ ANDROID_CONTROL_SCENE_MODE_DISABLED);
+#else
+ mSceneMode = READ_IF_OK(res, mode.data.u8[0],
+ ANDROID_CONTROL_SCENE_MODE_UNSUPPORTED);
+#endif
+
+ res = find_camera_metadata_entry(request,
+ ANDROID_CONTROL_AF_MODE,
+ &mode);
+ if (mAfMode != mode.data.u8[0]) {
+ ALOGV("AF new mode: %d, old mode %d", mode.data.u8[0], mAfMode);
+ mAfMode = mode.data.u8[0];
+ mAfModeChange = true;
+ mStartAf = false;
+ mCancelAf = false;
+ }
+
+ res = find_camera_metadata_entry(request,
+ ANDROID_CONTROL_AE_MODE,
+ &mode);
+ mAeMode = READ_IF_OK(res, mode.data.u8[0],
+ ANDROID_CONTROL_AE_MODE_OFF);
+
+ res = find_camera_metadata_entry(request,
+ ANDROID_CONTROL_AE_LOCK,
+ &mode);
+ uint8_t aeLockVal = READ_IF_OK(res, mode.data.u8[0],
+ ANDROID_CONTROL_AE_LOCK_ON);
+ bool aeLock = (aeLockVal == ANDROID_CONTROL_AE_LOCK_ON);
+ if (mAeLock && !aeLock) {
+ mAeState = ANDROID_CONTROL_AE_STATE_INACTIVE;
+ }
+ mAeLock = aeLock;
+
+ res = find_camera_metadata_entry(request,
+ ANDROID_CONTROL_AWB_MODE,
+ &mode);
+ mAwbMode = READ_IF_OK(res, mode.data.u8[0],
+ ANDROID_CONTROL_AWB_MODE_OFF);
+
+ // TODO: Override more control fields
+
+ if (mAeMode != ANDROID_CONTROL_AE_MODE_OFF) {
+ camera_metadata_entry_t exposureTime;
+ res = find_camera_metadata_entry(request,
+ ANDROID_SENSOR_EXPOSURE_TIME,
+ &exposureTime);
+ if (res == OK) {
+ exposureTime.data.i64[0] = mExposureTime;
+ }
+ }
+
+#undef READ_IF_OK
+
+ return OK;
+}
+
+status_t EmulatedFakeCamera2::ControlThread::triggerAction(uint32_t msgType,
+ int32_t ext1, int32_t ext2) {
+ ALOGV("%s: Triggering %d (%d, %d)", __FUNCTION__, msgType, ext1, ext2);
+ Mutex::Autolock lock(mInputMutex);
+ switch (msgType) {
+ case CAMERA2_TRIGGER_AUTOFOCUS:
+ mAfTriggerId = ext1;
+ mStartAf = true;
+ mCancelAf = false;
+ break;
+ case CAMERA2_TRIGGER_CANCEL_AUTOFOCUS:
+ mAfTriggerId = ext1;
+ mStartAf = false;
+ mCancelAf = true;
+ break;
+ case CAMERA2_TRIGGER_PRECAPTURE_METERING:
+ mPrecaptureTriggerId = ext1;
+ mStartPrecapture = true;
+ break;
+ default:
+ ALOGE("%s: Unknown action triggered: %d (arguments %d %d)",
+ __FUNCTION__, msgType, ext1, ext2);
+ return BAD_VALUE;
+ }
+ return OK;
+}
+
+const nsecs_t EmulatedFakeCamera2::ControlThread::kControlCycleDelay = 100 * MSEC;
+const nsecs_t EmulatedFakeCamera2::ControlThread::kMinAfDuration = 500 * MSEC;
+const nsecs_t EmulatedFakeCamera2::ControlThread::kMaxAfDuration = 900 * MSEC;
+const float EmulatedFakeCamera2::ControlThread::kAfSuccessRate = 0.9;
+ // Once every 5 seconds
+const float EmulatedFakeCamera2::ControlThread::kContinuousAfStartRate =
+ kControlCycleDelay / 5.0 * SEC;
+const nsecs_t EmulatedFakeCamera2::ControlThread::kMinAeDuration = 500 * MSEC;
+const nsecs_t EmulatedFakeCamera2::ControlThread::kMaxAeDuration = 2 * SEC;
+const nsecs_t EmulatedFakeCamera2::ControlThread::kMinPrecaptureAeDuration = 100 * MSEC;
+const nsecs_t EmulatedFakeCamera2::ControlThread::kMaxPrecaptureAeDuration = 400 * MSEC;
+ // Once every 3 seconds
+const float EmulatedFakeCamera2::ControlThread::kAeScanStartRate =
+ kControlCycleDelay / 3000000000.0;
+
+const nsecs_t EmulatedFakeCamera2::ControlThread::kNormalExposureTime = 10 * MSEC;
+const nsecs_t EmulatedFakeCamera2::ControlThread::kExposureJump = 2 * MSEC;
+const nsecs_t EmulatedFakeCamera2::ControlThread::kMinExposureTime = 1 * MSEC;
+
+bool EmulatedFakeCamera2::ControlThread::threadLoop() {
+ bool afModeChange = false;
+ bool afTriggered = false;
+ bool afCancelled = false;
+ uint8_t afState;
+ uint8_t afMode;
+ int32_t afTriggerId;
+ bool precaptureTriggered = false;
+ uint8_t aeState;
+ uint8_t aeMode;
+ bool aeLock;
+ int32_t precaptureTriggerId;
+ nsecs_t nextSleep = kControlCycleDelay;
+
+ {
+ Mutex::Autolock lock(mInputMutex);
+ if (mStartAf) {
+ ALOGD("Starting AF trigger processing");
+ afTriggered = true;
+ mStartAf = false;
+ } else if (mCancelAf) {
+ ALOGD("Starting cancel AF trigger processing");
+ afCancelled = true;
+ mCancelAf = false;
+ }
+ afState = mAfState;
+ afMode = mAfMode;
+ afModeChange = mAfModeChange;
+ mAfModeChange = false;
+
+ afTriggerId = mAfTriggerId;
+
+ if(mStartPrecapture) {
+ ALOGD("Starting precapture trigger processing");
+ precaptureTriggered = true;
+ mStartPrecapture = false;
+ }
+ aeState = mAeState;
+ aeMode = mAeMode;
+ aeLock = mAeLock;
+ precaptureTriggerId = mPrecaptureTriggerId;
+ }
+
+ if (afCancelled || afModeChange) {
+ ALOGV("Resetting AF state due to cancel/mode change");
+ afState = ANDROID_CONTROL_AF_STATE_INACTIVE;
+ updateAfState(afState, afTriggerId);
+ mAfScanDuration = 0;
+ mLockAfterPassiveScan = false;
+ }
+
+ uint8_t oldAfState = afState;
+
+ if (afTriggered) {
+ afState = processAfTrigger(afMode, afState);
+ }
+
+ afState = maybeStartAfScan(afMode, afState);
+ afState = updateAfScan(afMode, afState, &nextSleep);
+ updateAfState(afState, afTriggerId);
+
+ if (precaptureTriggered) {
+ aeState = processPrecaptureTrigger(aeMode, aeState);
+ }
+
+ aeState = maybeStartAeScan(aeMode, aeLock, aeState);
+ aeState = updateAeScan(aeMode, aeLock, aeState, &nextSleep);
+ updateAeState(aeState, precaptureTriggerId);
+
+ int ret;
+ timespec t;
+ t.tv_sec = 0;
+ t.tv_nsec = nextSleep;
+ do {
+ ret = nanosleep(&t, &t);
+ } while (ret != 0);
+
+ if (mAfScanDuration > 0) {
+ mAfScanDuration -= nextSleep;
+ }
+ if (mAeScanDuration > 0) {
+ mAeScanDuration -= nextSleep;
+ }
+
+ return true;
+}
+
+int EmulatedFakeCamera2::ControlThread::processAfTrigger(uint8_t afMode,
+ uint8_t afState) {
+ switch (afMode) {
+ case ANDROID_CONTROL_AF_MODE_OFF:
+ case ANDROID_CONTROL_AF_MODE_EDOF:
+ // Do nothing
+ break;
+ case ANDROID_CONTROL_AF_MODE_MACRO:
+ case ANDROID_CONTROL_AF_MODE_AUTO:
+ switch (afState) {
+ case ANDROID_CONTROL_AF_STATE_INACTIVE:
+ case ANDROID_CONTROL_AF_STATE_FOCUSED_LOCKED:
+ case ANDROID_CONTROL_AF_STATE_NOT_FOCUSED_LOCKED:
+ // Start new focusing cycle
+ mAfScanDuration = ((double)rand() / RAND_MAX) *
+ (kMaxAfDuration - kMinAfDuration) + kMinAfDuration;
+ afState = ANDROID_CONTROL_AF_STATE_ACTIVE_SCAN;
+ ALOGV("%s: AF scan start, duration %" PRId64 " ms",
+ __FUNCTION__, mAfScanDuration / 1000000);
+ break;
+ case ANDROID_CONTROL_AF_STATE_ACTIVE_SCAN:
+ // Ignore new request, already scanning
+ break;
+ default:
+ ALOGE("Unexpected AF state in AUTO/MACRO AF mode: %d",
+ afState);
+ }
+ break;
+ case ANDROID_CONTROL_AF_MODE_CONTINUOUS_PICTURE:
+ switch (afState) {
+ // Picture mode waits for passive scan to complete
+ case ANDROID_CONTROL_AF_STATE_PASSIVE_SCAN:
+ mLockAfterPassiveScan = true;
+ break;
+ case ANDROID_CONTROL_AF_STATE_INACTIVE:
+ afState = ANDROID_CONTROL_AF_STATE_NOT_FOCUSED_LOCKED;
+ break;
+ case ANDROID_CONTROL_AF_STATE_PASSIVE_FOCUSED:
+ afState = ANDROID_CONTROL_AF_STATE_FOCUSED_LOCKED;
+ break;
+ case ANDROID_CONTROL_AF_STATE_FOCUSED_LOCKED:
+ case ANDROID_CONTROL_AF_STATE_NOT_FOCUSED_LOCKED:
+ // Must cancel to get out of these states
+ break;
+ default:
+ ALOGE("Unexpected AF state in CONTINUOUS_PICTURE AF mode: %d",
+ afState);
+ }
+ break;
+ case ANDROID_CONTROL_AF_MODE_CONTINUOUS_VIDEO:
+ switch (afState) {
+ // Video mode does not wait for passive scan to complete
+ case ANDROID_CONTROL_AF_STATE_PASSIVE_SCAN:
+ case ANDROID_CONTROL_AF_STATE_INACTIVE:
+ afState = ANDROID_CONTROL_AF_STATE_NOT_FOCUSED_LOCKED;
+ break;
+ case ANDROID_CONTROL_AF_STATE_PASSIVE_FOCUSED:
+ afState = ANDROID_CONTROL_AF_STATE_FOCUSED_LOCKED;
+ break;
+ case ANDROID_CONTROL_AF_STATE_FOCUSED_LOCKED:
+ case ANDROID_CONTROL_AF_STATE_NOT_FOCUSED_LOCKED:
+ // Must cancel to get out of these states
+ break;
+ default:
+ ALOGE("Unexpected AF state in CONTINUOUS_VIDEO AF mode: %d",
+ afState);
+ }
+ break;
+ default:
+ break;
+ }
+ return afState;
+}
+
+int EmulatedFakeCamera2::ControlThread::maybeStartAfScan(uint8_t afMode,
+ uint8_t afState) {
+ if ((afMode == ANDROID_CONTROL_AF_MODE_CONTINUOUS_VIDEO ||
+ afMode == ANDROID_CONTROL_AF_MODE_CONTINUOUS_PICTURE) &&
+ (afState == ANDROID_CONTROL_AF_STATE_INACTIVE ||
+ afState == ANDROID_CONTROL_AF_STATE_PASSIVE_FOCUSED)) {
+
+ bool startScan = ((double)rand() / RAND_MAX) < kContinuousAfStartRate;
+ if (startScan) {
+ // Start new passive focusing cycle
+ mAfScanDuration = ((double)rand() / RAND_MAX) *
+ (kMaxAfDuration - kMinAfDuration) + kMinAfDuration;
+ afState = ANDROID_CONTROL_AF_STATE_PASSIVE_SCAN;
+ ALOGV("%s: AF passive scan start, duration %" PRId64 " ms",
+ __FUNCTION__, mAfScanDuration / 1000000);
+ }
+ }
+ return afState;
+}
+
+int EmulatedFakeCamera2::ControlThread::updateAfScan(uint8_t afMode,
+ uint8_t afState, nsecs_t *maxSleep) {
+ if (! (afState == ANDROID_CONTROL_AF_STATE_ACTIVE_SCAN ||
+ afState == ANDROID_CONTROL_AF_STATE_PASSIVE_SCAN ) ) {
+ return afState;
+ }
+
+ if (mAfScanDuration <= 0) {
+ ALOGV("%s: AF scan done", __FUNCTION__);
+ switch (afMode) {
+ case ANDROID_CONTROL_AF_MODE_MACRO:
+ case ANDROID_CONTROL_AF_MODE_AUTO: {
+ bool success = ((double)rand() / RAND_MAX) < kAfSuccessRate;
+ if (success) {
+ afState = ANDROID_CONTROL_AF_STATE_FOCUSED_LOCKED;
+ } else {
+ afState = ANDROID_CONTROL_AF_STATE_NOT_FOCUSED_LOCKED;
+ }
+ break;
+ }
+ case ANDROID_CONTROL_AF_MODE_CONTINUOUS_PICTURE:
+ if (mLockAfterPassiveScan) {
+ afState = ANDROID_CONTROL_AF_STATE_FOCUSED_LOCKED;
+ mLockAfterPassiveScan = false;
+ } else {
+ afState = ANDROID_CONTROL_AF_STATE_PASSIVE_FOCUSED;
+ }
+ break;
+ case ANDROID_CONTROL_AF_MODE_CONTINUOUS_VIDEO:
+ afState = ANDROID_CONTROL_AF_STATE_PASSIVE_FOCUSED;
+ break;
+ default:
+ ALOGE("Unexpected AF mode in scan state");
+ }
+ } else {
+ if (mAfScanDuration <= *maxSleep) {
+ *maxSleep = mAfScanDuration;
+ }
+ }
+ return afState;
+}
+
+void EmulatedFakeCamera2::ControlThread::updateAfState(uint8_t newState,
+ int32_t triggerId) {
+ Mutex::Autolock lock(mInputMutex);
+ if (mAfState != newState) {
+ ALOGV("%s: Autofocus state now %d, id %d", __FUNCTION__,
+ newState, triggerId);
+ mAfState = newState;
+ mParent->sendNotification(CAMERA2_MSG_AUTOFOCUS,
+ newState, triggerId, 0);
+ }
+}
+
+int EmulatedFakeCamera2::ControlThread::processPrecaptureTrigger(uint8_t aeMode,
+ uint8_t aeState) {
+ switch (aeMode) {
+ case ANDROID_CONTROL_AE_MODE_OFF:
+ // Don't do anything for these
+ return aeState;
+ case ANDROID_CONTROL_AE_MODE_ON:
+ case ANDROID_CONTROL_AE_MODE_ON_AUTO_FLASH:
+ case ANDROID_CONTROL_AE_MODE_ON_ALWAYS_FLASH:
+ case ANDROID_CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE:
+ // Trigger a precapture cycle
+ aeState = ANDROID_CONTROL_AE_STATE_PRECAPTURE;
+ mAeScanDuration = ((double)rand() / RAND_MAX) *
+ (kMaxPrecaptureAeDuration - kMinPrecaptureAeDuration) +
+ kMinPrecaptureAeDuration;
+ ALOGD("%s: AE precapture scan start, duration %" PRId64 " ms",
+ __FUNCTION__, mAeScanDuration / 1000000);
+
+ }
+ return aeState;
+}
+
+int EmulatedFakeCamera2::ControlThread::maybeStartAeScan(uint8_t aeMode,
+ bool aeLocked,
+ uint8_t aeState) {
+ if (aeLocked) return aeState;
+ switch (aeMode) {
+ case ANDROID_CONTROL_AE_MODE_OFF:
+ break;
+ case ANDROID_CONTROL_AE_MODE_ON:
+ case ANDROID_CONTROL_AE_MODE_ON_AUTO_FLASH:
+ case ANDROID_CONTROL_AE_MODE_ON_ALWAYS_FLASH:
+ case ANDROID_CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE: {
+ if (aeState != ANDROID_CONTROL_AE_STATE_INACTIVE &&
+ aeState != ANDROID_CONTROL_AE_STATE_CONVERGED) break;
+
+ bool startScan = ((double)rand() / RAND_MAX) < kAeScanStartRate;
+ if (startScan) {
+ mAeScanDuration = ((double)rand() / RAND_MAX) *
+ (kMaxAeDuration - kMinAeDuration) + kMinAeDuration;
+ aeState = ANDROID_CONTROL_AE_STATE_SEARCHING;
+ ALOGV("%s: AE scan start, duration %" PRId64 " ms",
+ __FUNCTION__, mAeScanDuration / 1000000);
+ }
+ }
+ }
+
+ return aeState;
+}
+
+int EmulatedFakeCamera2::ControlThread::updateAeScan(uint8_t aeMode,
+ bool aeLock, uint8_t aeState, nsecs_t *maxSleep) {
+ if (aeLock && aeState != ANDROID_CONTROL_AE_STATE_PRECAPTURE) {
+ mAeScanDuration = 0;
+ aeState = ANDROID_CONTROL_AE_STATE_LOCKED;
+ } else if ((aeState == ANDROID_CONTROL_AE_STATE_SEARCHING) ||
+ (aeState == ANDROID_CONTROL_AE_STATE_PRECAPTURE ) ) {
+ if (mAeScanDuration <= 0) {
+ ALOGV("%s: AE scan done", __FUNCTION__);
+ aeState = aeLock ?
+ ANDROID_CONTROL_AE_STATE_LOCKED :ANDROID_CONTROL_AE_STATE_CONVERGED;
+
+ Mutex::Autolock lock(mInputMutex);
+ mExposureTime = kNormalExposureTime;
+ } else {
+ if (mAeScanDuration <= *maxSleep) {
+ *maxSleep = mAeScanDuration;
+ }
+
+ int64_t exposureDelta =
+ ((double)rand() / RAND_MAX) * 2 * kExposureJump -
+ kExposureJump;
+ Mutex::Autolock lock(mInputMutex);
+ mExposureTime = mExposureTime + exposureDelta;
+ if (mExposureTime < kMinExposureTime) mExposureTime = kMinExposureTime;
+ }
+ }
+
+ return aeState;
+}
+
+
+void EmulatedFakeCamera2::ControlThread::updateAeState(uint8_t newState,
+ int32_t triggerId) {
+ Mutex::Autolock lock(mInputMutex);
+ if (mAeState != newState) {
+ ALOGV("%s: Autoexposure state now %d, id %d", __FUNCTION__,
+ newState, triggerId);
+ mAeState = newState;
+ mParent->sendNotification(CAMERA2_MSG_AUTOEXPOSURE,
+ newState, triggerId, 0);
+ }
+}
+
+/** Private methods */
+
+status_t EmulatedFakeCamera2::constructStaticInfo(
+ camera_metadata_t **info,
+ bool sizeRequest) const {
+
+ size_t entryCount = 0;
+ size_t dataCount = 0;
+ status_t ret;
+
+#define ADD_OR_SIZE( tag, data, count ) \
+ if ( ( ret = addOrSize(*info, sizeRequest, &entryCount, &dataCount, \
+ tag, data, count) ) != OK ) return ret
+
+ // android.lens
+
+ // 5 cm min focus distance for back camera, infinity (fixed focus) for front
+ const float minFocusDistance = mFacingBack ? 1.0/0.05 : 0.0;
+ ADD_OR_SIZE(ANDROID_LENS_INFO_MINIMUM_FOCUS_DISTANCE,
+ &minFocusDistance, 1);
+ // 5 m hyperfocal distance for back camera, infinity (fixed focus) for front
+ const float hyperFocalDistance = mFacingBack ? 1.0/5.0 : 0.0;
+ ADD_OR_SIZE(ANDROID_LENS_INFO_HYPERFOCAL_DISTANCE,
+ &minFocusDistance, 1);
+
+ static const float focalLength = 3.30f; // mm
+ ADD_OR_SIZE(ANDROID_LENS_INFO_AVAILABLE_FOCAL_LENGTHS,
+ &focalLength, 1);
+ static const float aperture = 2.8f;
+ ADD_OR_SIZE(ANDROID_LENS_INFO_AVAILABLE_APERTURES,
+ &aperture, 1);
+ static const float filterDensity = 0;
+ ADD_OR_SIZE(ANDROID_LENS_INFO_AVAILABLE_FILTER_DENSITIES,
+ &filterDensity, 1);
+ static const uint8_t availableOpticalStabilization =
+ ANDROID_LENS_OPTICAL_STABILIZATION_MODE_OFF;
+ ADD_OR_SIZE(ANDROID_LENS_INFO_AVAILABLE_OPTICAL_STABILIZATION,
+ &availableOpticalStabilization, 1);
+
+ static const int32_t lensShadingMapSize[] = {1, 1};
+ ADD_OR_SIZE(ANDROID_LENS_INFO_SHADING_MAP_SIZE, lensShadingMapSize,
+ sizeof(lensShadingMapSize)/sizeof(int32_t));
+
+ int32_t lensFacing = mFacingBack ?
+ ANDROID_LENS_FACING_BACK : ANDROID_LENS_FACING_FRONT;
+ ADD_OR_SIZE(ANDROID_LENS_FACING, &lensFacing, 1);
+
+ // android.sensor
+
+ ADD_OR_SIZE(ANDROID_SENSOR_INFO_EXPOSURE_TIME_RANGE,
+ Sensor::kExposureTimeRange, 2);
+
+ ADD_OR_SIZE(ANDROID_SENSOR_INFO_MAX_FRAME_DURATION,
+ &Sensor::kFrameDurationRange[1], 1);
+
+ ADD_OR_SIZE(ANDROID_SENSOR_INFO_SENSITIVITY_RANGE,
+ Sensor::kSensitivityRange,
+ sizeof(Sensor::kSensitivityRange)
+ /sizeof(int32_t));
+
+ ADD_OR_SIZE(ANDROID_SENSOR_INFO_COLOR_FILTER_ARRANGEMENT,
+ &Sensor::kColorFilterArrangement, 1);
+
+ static const float sensorPhysicalSize[2] = {3.20f, 2.40f}; // mm
+ ADD_OR_SIZE(ANDROID_SENSOR_INFO_PHYSICAL_SIZE,
+ sensorPhysicalSize, 2);
+
+ const int32_t pixelArray[] = {mSensorWidth, mSensorHeight};
+ ADD_OR_SIZE(ANDROID_SENSOR_INFO_PIXEL_ARRAY_SIZE,
+ pixelArray, 2);
+
+ ADD_OR_SIZE(ANDROID_SENSOR_INFO_ACTIVE_ARRAY_SIZE,
+ pixelArray, 2);
+
+ ADD_OR_SIZE(ANDROID_SENSOR_INFO_WHITE_LEVEL,
+ &Sensor::kMaxRawValue, 1);
+
+ static const int32_t blackLevelPattern[4] = {
+ static_cast<int32_t>(Sensor::kBlackLevel),
+ static_cast<int32_t>(Sensor::kBlackLevel),
+ static_cast<int32_t>(Sensor::kBlackLevel),
+ static_cast<int32_t>(Sensor::kBlackLevel)
+ };
+ ADD_OR_SIZE(ANDROID_SENSOR_BLACK_LEVEL_PATTERN,
+ blackLevelPattern, sizeof(blackLevelPattern)/sizeof(int32_t));
+
+ //TODO: sensor color calibration fields
+
+ // android.flash
+ static const uint8_t flashAvailable = 0;
+ ADD_OR_SIZE(ANDROID_FLASH_INFO_AVAILABLE, &flashAvailable, 1);
+
+ static const int64_t flashChargeDuration = 0;
+ ADD_OR_SIZE(ANDROID_FLASH_INFO_CHARGE_DURATION, &flashChargeDuration, 1);
+
+ // android.tonemap
+
+ static const int32_t tonemapCurvePoints = 128;
+ ADD_OR_SIZE(ANDROID_TONEMAP_MAX_CURVE_POINTS, &tonemapCurvePoints, 1);
+
+ // android.scaler
+
+ ADD_OR_SIZE(ANDROID_SCALER_AVAILABLE_FORMATS,
+ kAvailableFormats,
+ sizeof(kAvailableFormats)/sizeof(uint32_t));
+
+ ADD_OR_SIZE(ANDROID_SCALER_AVAILABLE_RAW_SIZES,
+ &mAvailableRawSizes.front(),
+ mAvailableRawSizes.size());
+
+ ADD_OR_SIZE(ANDROID_SCALER_AVAILABLE_RAW_MIN_DURATIONS,
+ kAvailableRawMinDurations,
+ sizeof(kAvailableRawMinDurations)/sizeof(uint64_t));
+
+ ADD_OR_SIZE(ANDROID_SCALER_AVAILABLE_PROCESSED_SIZES,
+ &mAvailableProcessedSizes.front(),
+ mAvailableProcessedSizes.size());
+
+ ADD_OR_SIZE(ANDROID_SCALER_AVAILABLE_PROCESSED_MIN_DURATIONS,
+ kAvailableProcessedMinDurations,
+ sizeof(kAvailableProcessedMinDurations)/sizeof(uint64_t));
+
+ ADD_OR_SIZE(ANDROID_SCALER_AVAILABLE_JPEG_SIZES,
+ &mAvailableJpegSizes.front(),
+ mAvailableJpegSizes.size());
+
+ ADD_OR_SIZE(ANDROID_SCALER_AVAILABLE_JPEG_MIN_DURATIONS,
+ kAvailableJpegMinDurations,
+ sizeof(kAvailableJpegMinDurations)/sizeof(uint64_t));
+
+ static const float maxZoom = 10;
+ ADD_OR_SIZE(ANDROID_SCALER_AVAILABLE_MAX_DIGITAL_ZOOM,
+ &maxZoom, 1);
+
+ // android.jpeg
+
+ static const int32_t jpegThumbnailSizes[] = {
+ 0, 0,
+ 160, 120,
+ 320, 240
+ };
+ ADD_OR_SIZE(ANDROID_JPEG_AVAILABLE_THUMBNAIL_SIZES,
+ jpegThumbnailSizes, sizeof(jpegThumbnailSizes)/sizeof(int32_t));
+
+ static const int32_t jpegMaxSize = JpegCompressor::kMaxJpegSize;
+ ADD_OR_SIZE(ANDROID_JPEG_MAX_SIZE, &jpegMaxSize, 1);
+
+ // android.stats
+
+ static const uint8_t availableFaceDetectModes[] = {
+ ANDROID_STATISTICS_FACE_DETECT_MODE_OFF,
+ ANDROID_STATISTICS_FACE_DETECT_MODE_SIMPLE,
+ ANDROID_STATISTICS_FACE_DETECT_MODE_FULL
+ };
+
+ ADD_OR_SIZE(ANDROID_STATISTICS_INFO_AVAILABLE_FACE_DETECT_MODES,
+ availableFaceDetectModes,
+ sizeof(availableFaceDetectModes));
+
+ static const int32_t maxFaceCount = 8;
+ ADD_OR_SIZE(ANDROID_STATISTICS_INFO_MAX_FACE_COUNT,
+ &maxFaceCount, 1);
+
+ static const int32_t histogramSize = 64;
+ ADD_OR_SIZE(ANDROID_STATISTICS_INFO_HISTOGRAM_BUCKET_COUNT,
+ &histogramSize, 1);
+
+ static const int32_t maxHistogramCount = 1000;
+ ADD_OR_SIZE(ANDROID_STATISTICS_INFO_MAX_HISTOGRAM_COUNT,
+ &maxHistogramCount, 1);
+
+ static const int32_t sharpnessMapSize[2] = {64, 64};
+ ADD_OR_SIZE(ANDROID_STATISTICS_INFO_SHARPNESS_MAP_SIZE,
+ sharpnessMapSize, sizeof(sharpnessMapSize)/sizeof(int32_t));
+
+ static const int32_t maxSharpnessMapValue = 1000;
+ ADD_OR_SIZE(ANDROID_STATISTICS_INFO_MAX_SHARPNESS_MAP_VALUE,
+ &maxSharpnessMapValue, 1);
+
+ // android.control
+
+ static const uint8_t availableSceneModes[] = {
+#if VSOC_PLATFORM_SDK_AFTER(K)
+ ANDROID_CONTROL_SCENE_MODE_DISABLED
+#else
+ ANDROID_CONTROL_SCENE_MODE_UNSUPPORTED
+#endif
+ };
+ ADD_OR_SIZE(ANDROID_CONTROL_AVAILABLE_SCENE_MODES,
+ availableSceneModes, sizeof(availableSceneModes));
+
+ static const uint8_t availableEffects[] = {
+ ANDROID_CONTROL_EFFECT_MODE_OFF
+ };
+ ADD_OR_SIZE(ANDROID_CONTROL_AVAILABLE_EFFECTS,
+ availableEffects, sizeof(availableEffects));
+
+ static const int32_t max3aRegions[] = {/*AE*/ 0,/*AWB*/ 0,/*AF*/ 0};
+ ADD_OR_SIZE(ANDROID_CONTROL_MAX_REGIONS,
+ max3aRegions, sizeof(max3aRegions)/sizeof(max3aRegions[0]));
+
+ static const uint8_t availableAeModes[] = {
+ ANDROID_CONTROL_AE_MODE_OFF,
+ ANDROID_CONTROL_AE_MODE_ON
+ };
+ ADD_OR_SIZE(ANDROID_CONTROL_AE_AVAILABLE_MODES,
+ availableAeModes, sizeof(availableAeModes));
+
+ static const camera_metadata_rational exposureCompensationStep = {
+ 1, 3
+ };
+ ADD_OR_SIZE(ANDROID_CONTROL_AE_COMPENSATION_STEP,
+ &exposureCompensationStep, 1);
+
+ int32_t exposureCompensationRange[] = {-9, 9};
+ ADD_OR_SIZE(ANDROID_CONTROL_AE_COMPENSATION_RANGE,
+ exposureCompensationRange,
+ sizeof(exposureCompensationRange)/sizeof(int32_t));
+
+ static const int32_t availableTargetFpsRanges[] = {
+ 5, 30, 15, 30
+ };
+ ADD_OR_SIZE(ANDROID_CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES,
+ availableTargetFpsRanges,
+ sizeof(availableTargetFpsRanges)/sizeof(int32_t));
+
+ static const uint8_t availableAntibandingModes[] = {
+ ANDROID_CONTROL_AE_ANTIBANDING_MODE_OFF,
+ ANDROID_CONTROL_AE_ANTIBANDING_MODE_AUTO
+ };
+ ADD_OR_SIZE(ANDROID_CONTROL_AE_AVAILABLE_ANTIBANDING_MODES,
+ availableAntibandingModes, sizeof(availableAntibandingModes));
+
+ static const uint8_t availableAwbModes[] = {
+ ANDROID_CONTROL_AWB_MODE_OFF,
+ ANDROID_CONTROL_AWB_MODE_AUTO,
+ ANDROID_CONTROL_AWB_MODE_INCANDESCENT,
+ ANDROID_CONTROL_AWB_MODE_FLUORESCENT,
+ ANDROID_CONTROL_AWB_MODE_DAYLIGHT,
+ ANDROID_CONTROL_AWB_MODE_SHADE
+ };
+ ADD_OR_SIZE(ANDROID_CONTROL_AWB_AVAILABLE_MODES,
+ availableAwbModes, sizeof(availableAwbModes));
+
+ static const uint8_t availableAfModesBack[] = {
+ ANDROID_CONTROL_AF_MODE_OFF,
+ ANDROID_CONTROL_AF_MODE_AUTO,
+ ANDROID_CONTROL_AF_MODE_MACRO,
+ ANDROID_CONTROL_AF_MODE_CONTINUOUS_VIDEO,
+ ANDROID_CONTROL_AF_MODE_CONTINUOUS_PICTURE
+ };
+
+ static const uint8_t availableAfModesFront[] = {
+ ANDROID_CONTROL_AF_MODE_OFF
+ };
+
+ if (mFacingBack) {
+ ADD_OR_SIZE(ANDROID_CONTROL_AF_AVAILABLE_MODES,
+ availableAfModesBack, sizeof(availableAfModesBack));
+ } else {
+ ADD_OR_SIZE(ANDROID_CONTROL_AF_AVAILABLE_MODES,
+ availableAfModesFront, sizeof(availableAfModesFront));
+ }
+
+ static const uint8_t availableVstabModes[] = {
+ ANDROID_CONTROL_VIDEO_STABILIZATION_MODE_OFF
+ };
+ ADD_OR_SIZE(ANDROID_CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES,
+ availableVstabModes, sizeof(availableVstabModes));
+
+#undef ADD_OR_SIZE
+ /** Allocate metadata if sizing */
+ if (sizeRequest) {
+ ALOGV("Allocating %zu entries, %zu extra bytes for "
+ "static camera info",
+ entryCount, dataCount);
+ *info = allocate_camera_metadata(entryCount, dataCount);
+ if (*info == NULL) {
+ ALOGE("Unable to allocate camera static info"
+ "(%zu entries, %zu bytes extra data)",
+ entryCount, dataCount);
+ return NO_MEMORY;
+ }
+ }
+ return OK;
+}
+
+status_t EmulatedFakeCamera2::constructDefaultRequest(
+ int request_template,
+ camera_metadata_t **request,
+ bool sizeRequest) const {
+
+ size_t entryCount = 0;
+ size_t dataCount = 0;
+ status_t ret;
+
+#define ADD_OR_SIZE( tag, data, count ) \
+ if ( ( ret = addOrSize(*request, sizeRequest, &entryCount, &dataCount, \
+ tag, data, count) ) != OK ) return ret
+
+ /** android.request */
+
+ static const uint8_t requestType = ANDROID_REQUEST_TYPE_CAPTURE;
+ ADD_OR_SIZE(ANDROID_REQUEST_TYPE, &requestType, 1);
+
+ static const uint8_t metadataMode = ANDROID_REQUEST_METADATA_MODE_FULL;
+ ADD_OR_SIZE(ANDROID_REQUEST_METADATA_MODE, &metadataMode, 1);
+
+ static const int32_t id = 0;
+ ADD_OR_SIZE(ANDROID_REQUEST_ID, &id, 1);
+
+ static const int32_t frameCount = 0;
+ ADD_OR_SIZE(ANDROID_REQUEST_FRAME_COUNT, &frameCount, 1);
+
+ // OUTPUT_STREAMS set by user
+ entryCount += 1;
+ dataCount += 5; // TODO: Should be maximum stream number
+
+ /** android.lens */
+
+ static const float focusDistance = 0;
+ ADD_OR_SIZE(ANDROID_LENS_FOCUS_DISTANCE, &focusDistance, 1);
+
+ static const float aperture = 2.8f;
+ ADD_OR_SIZE(ANDROID_LENS_APERTURE, &aperture, 1);
+
+ static const float focalLength = 5.0f;
+ ADD_OR_SIZE(ANDROID_LENS_FOCAL_LENGTH, &focalLength, 1);
+
+ static const float filterDensity = 0;
+ ADD_OR_SIZE(ANDROID_LENS_FILTER_DENSITY, &filterDensity, 1);
+
+ static const uint8_t opticalStabilizationMode =
+ ANDROID_LENS_OPTICAL_STABILIZATION_MODE_OFF;
+ ADD_OR_SIZE(ANDROID_LENS_OPTICAL_STABILIZATION_MODE,
+ &opticalStabilizationMode, 1);
+
+ // FOCUS_RANGE set only in frame
+
+ /** android.sensor */
+
+ static const int64_t exposureTime = 10 * MSEC;
+ ADD_OR_SIZE(ANDROID_SENSOR_EXPOSURE_TIME, &exposureTime, 1);
+
+ static const int64_t frameDuration = 33333333L; // 1/30 s
+ ADD_OR_SIZE(ANDROID_SENSOR_FRAME_DURATION, &frameDuration, 1);
+
+ static const int32_t sensitivity = 100;
+ ADD_OR_SIZE(ANDROID_SENSOR_SENSITIVITY, &sensitivity, 1);
+
+ // TIMESTAMP set only in frame
+
+ /** android.flash */
+
+ static const uint8_t flashMode = ANDROID_FLASH_MODE_OFF;
+ ADD_OR_SIZE(ANDROID_FLASH_MODE, &flashMode, 1);
+
+ static const uint8_t flashPower = 10;
+ ADD_OR_SIZE(ANDROID_FLASH_FIRING_POWER, &flashPower, 1);
+
+ static const int64_t firingTime = 0;
+ ADD_OR_SIZE(ANDROID_FLASH_FIRING_TIME, &firingTime, 1);
+
+ /** Processing block modes */
+ uint8_t hotPixelMode = 0;
+ uint8_t demosaicMode = 0;
+ uint8_t noiseMode = 0;
+ uint8_t shadingMode = 0;
+ uint8_t colorMode = 0;
+ uint8_t tonemapMode = 0;
+ uint8_t edgeMode = 0;
+ switch (request_template) {
+ case CAMERA2_TEMPLATE_STILL_CAPTURE:
+ // fall-through
+ case CAMERA2_TEMPLATE_VIDEO_SNAPSHOT:
+ // fall-through
+ case CAMERA2_TEMPLATE_ZERO_SHUTTER_LAG:
+ hotPixelMode = ANDROID_HOT_PIXEL_MODE_HIGH_QUALITY;
+ demosaicMode = ANDROID_DEMOSAIC_MODE_HIGH_QUALITY;
+ noiseMode = ANDROID_NOISE_REDUCTION_MODE_HIGH_QUALITY;
+ shadingMode = ANDROID_SHADING_MODE_HIGH_QUALITY;
+ colorMode = ANDROID_COLOR_CORRECTION_MODE_HIGH_QUALITY;
+ tonemapMode = ANDROID_TONEMAP_MODE_HIGH_QUALITY;
+ edgeMode = ANDROID_EDGE_MODE_HIGH_QUALITY;
+ break;
+ case CAMERA2_TEMPLATE_PREVIEW:
+ // fall-through
+ case CAMERA2_TEMPLATE_VIDEO_RECORD:
+ // fall-through
+ default:
+ hotPixelMode = ANDROID_HOT_PIXEL_MODE_FAST;
+ demosaicMode = ANDROID_DEMOSAIC_MODE_FAST;
+ noiseMode = ANDROID_NOISE_REDUCTION_MODE_FAST;
+ shadingMode = ANDROID_SHADING_MODE_FAST;
+ colorMode = ANDROID_COLOR_CORRECTION_MODE_FAST;
+ tonemapMode = ANDROID_TONEMAP_MODE_FAST;
+ edgeMode = ANDROID_EDGE_MODE_FAST;
+ break;
+ }
+ ADD_OR_SIZE(ANDROID_HOT_PIXEL_MODE, &hotPixelMode, 1);
+ ADD_OR_SIZE(ANDROID_DEMOSAIC_MODE, &demosaicMode, 1);
+ ADD_OR_SIZE(ANDROID_NOISE_REDUCTION_MODE, &noiseMode, 1);
+ ADD_OR_SIZE(ANDROID_SHADING_MODE, &shadingMode, 1);
+ ADD_OR_SIZE(ANDROID_COLOR_CORRECTION_MODE, &colorMode, 1);
+ ADD_OR_SIZE(ANDROID_TONEMAP_MODE, &tonemapMode, 1);
+ ADD_OR_SIZE(ANDROID_EDGE_MODE, &edgeMode, 1);
+
+ /** android.noise */
+ static const uint8_t noiseStrength = 5;
+ ADD_OR_SIZE(ANDROID_NOISE_REDUCTION_STRENGTH, &noiseStrength, 1);
+
+ /** android.color */
+ static const float colorTransform[9] = {
+ 1.0f, 0.f, 0.f,
+ 0.f, 1.f, 0.f,
+ 0.f, 0.f, 1.f
+ };
+ ADD_OR_SIZE(ANDROID_COLOR_CORRECTION_TRANSFORM, colorTransform, 9);
+
+ /** android.tonemap */
+ static const float tonemapCurve[4] = {
+ 0.f, 0.f,
+ 1.f, 1.f
+ };
+ ADD_OR_SIZE(ANDROID_TONEMAP_CURVE_RED, tonemapCurve, 4);
+ ADD_OR_SIZE(ANDROID_TONEMAP_CURVE_GREEN, tonemapCurve, 4);
+ ADD_OR_SIZE(ANDROID_TONEMAP_CURVE_BLUE, tonemapCurve, 4);
+
+ /** android.edge */
+ static const uint8_t edgeStrength = 5;
+ ADD_OR_SIZE(ANDROID_EDGE_STRENGTH, &edgeStrength, 1);
+
+ /** android.scaler */
+ static const int32_t cropRegion[3] = {
+ 0, 0, static_cast<int32_t>(mSensorWidth)
+ };
+ ADD_OR_SIZE(ANDROID_SCALER_CROP_REGION, cropRegion, 3);
+
+ /** android.jpeg */
+ static const int32_t jpegQuality = 80;
+ ADD_OR_SIZE(ANDROID_JPEG_QUALITY, &jpegQuality, 1);
+
+ static const int32_t thumbnailSize[2] = {
+ 640, 480
+ };
+ ADD_OR_SIZE(ANDROID_JPEG_THUMBNAIL_SIZE, thumbnailSize, 2);
+
+ static const int32_t thumbnailQuality = 80;
+ ADD_OR_SIZE(ANDROID_JPEG_THUMBNAIL_QUALITY, &thumbnailQuality, 1);
+
+ static const double gpsCoordinates[2] = {
+ 0, 0
+ };
+ ADD_OR_SIZE(ANDROID_JPEG_GPS_COORDINATES, gpsCoordinates, 2);
+
+ static const uint8_t gpsProcessingMethod[32] = "None";
+ ADD_OR_SIZE(ANDROID_JPEG_GPS_PROCESSING_METHOD, gpsProcessingMethod, 32);
+
+ static const int64_t gpsTimestamp = 0;
+ ADD_OR_SIZE(ANDROID_JPEG_GPS_TIMESTAMP, &gpsTimestamp, 1);
+
+ static const int32_t jpegOrientation = 0;
+ ADD_OR_SIZE(ANDROID_JPEG_ORIENTATION, &jpegOrientation, 1);
+
+ /** android.stats */
+
+ static const uint8_t faceDetectMode =
+ ANDROID_STATISTICS_FACE_DETECT_MODE_OFF;
+ ADD_OR_SIZE(ANDROID_STATISTICS_FACE_DETECT_MODE, &faceDetectMode, 1);
+
+ static const uint8_t histogramMode = ANDROID_STATISTICS_HISTOGRAM_MODE_OFF;
+ ADD_OR_SIZE(ANDROID_STATISTICS_HISTOGRAM_MODE, &histogramMode, 1);
+
+ static const uint8_t sharpnessMapMode =
+ ANDROID_STATISTICS_SHARPNESS_MAP_MODE_OFF;
+ ADD_OR_SIZE(ANDROID_STATISTICS_SHARPNESS_MAP_MODE, &sharpnessMapMode, 1);
+
+ // faceRectangles, faceScores, faceLandmarks, faceIds, histogram,
+ // sharpnessMap only in frames
+
+ /** android.control */
+
+ uint8_t controlIntent = 0;
+ switch (request_template) {
+ case CAMERA2_TEMPLATE_PREVIEW:
+ controlIntent = ANDROID_CONTROL_CAPTURE_INTENT_PREVIEW;
+ break;
+ case CAMERA2_TEMPLATE_STILL_CAPTURE:
+ controlIntent = ANDROID_CONTROL_CAPTURE_INTENT_STILL_CAPTURE;
+ break;
+ case CAMERA2_TEMPLATE_VIDEO_RECORD:
+ controlIntent = ANDROID_CONTROL_CAPTURE_INTENT_VIDEO_RECORD;
+ break;
+ case CAMERA2_TEMPLATE_VIDEO_SNAPSHOT:
+ controlIntent = ANDROID_CONTROL_CAPTURE_INTENT_VIDEO_SNAPSHOT;
+ break;
+ case CAMERA2_TEMPLATE_ZERO_SHUTTER_LAG:
+ controlIntent = ANDROID_CONTROL_CAPTURE_INTENT_ZERO_SHUTTER_LAG;
+ break;
+ default:
+ controlIntent = ANDROID_CONTROL_CAPTURE_INTENT_CUSTOM;
+ break;
+ }
+ ADD_OR_SIZE(ANDROID_CONTROL_CAPTURE_INTENT, &controlIntent, 1);
+
+ static const uint8_t controlMode = ANDROID_CONTROL_MODE_AUTO;
+ ADD_OR_SIZE(ANDROID_CONTROL_MODE, &controlMode, 1);
+
+ static const uint8_t effectMode = ANDROID_CONTROL_EFFECT_MODE_OFF;
+ ADD_OR_SIZE(ANDROID_CONTROL_EFFECT_MODE, &effectMode, 1);
+
+ static const uint8_t sceneMode = ANDROID_CONTROL_SCENE_MODE_FACE_PRIORITY;
+ ADD_OR_SIZE(ANDROID_CONTROL_SCENE_MODE, &sceneMode, 1);
+
+ static const uint8_t aeMode = ANDROID_CONTROL_AE_MODE_ON_AUTO_FLASH;
+ ADD_OR_SIZE(ANDROID_CONTROL_AE_MODE, &aeMode, 1);
+
+ static const uint8_t aeLock = ANDROID_CONTROL_AE_LOCK_OFF;
+ ADD_OR_SIZE(ANDROID_CONTROL_AE_LOCK, &aeLock, 1);
+
+ static const int32_t controlRegions[5] = {
+ 0, 0,
+ static_cast<int32_t>(mSensorWidth),
+ static_cast<int32_t>(mSensorHeight),
+ 1000
+ };
+ ADD_OR_SIZE(ANDROID_CONTROL_AE_REGIONS, controlRegions, 5);
+
+ static const int32_t aeExpCompensation = 0;
+ ADD_OR_SIZE(ANDROID_CONTROL_AE_EXPOSURE_COMPENSATION, &aeExpCompensation, 1);
+
+ static const int32_t aeTargetFpsRange[2] = {
+ 10, 30
+ };
+ ADD_OR_SIZE(ANDROID_CONTROL_AE_TARGET_FPS_RANGE, aeTargetFpsRange, 2);
+
+ static const uint8_t aeAntibandingMode =
+ ANDROID_CONTROL_AE_ANTIBANDING_MODE_AUTO;
+ ADD_OR_SIZE(ANDROID_CONTROL_AE_ANTIBANDING_MODE, &aeAntibandingMode, 1);
+
+ static const uint8_t awbMode =
+ ANDROID_CONTROL_AWB_MODE_AUTO;
+ ADD_OR_SIZE(ANDROID_CONTROL_AWB_MODE, &awbMode, 1);
+
+ static const uint8_t awbLock = ANDROID_CONTROL_AWB_LOCK_OFF;
+ ADD_OR_SIZE(ANDROID_CONTROL_AWB_LOCK, &awbLock, 1);
+
+ ADD_OR_SIZE(ANDROID_CONTROL_AWB_REGIONS, controlRegions, 5);
+
+ uint8_t afMode = 0;
+ switch (request_template) {
+ case CAMERA2_TEMPLATE_PREVIEW:
+ afMode = ANDROID_CONTROL_AF_MODE_AUTO;
+ break;
+ case CAMERA2_TEMPLATE_STILL_CAPTURE:
+ afMode = ANDROID_CONTROL_AF_MODE_AUTO;
+ break;
+ case CAMERA2_TEMPLATE_VIDEO_RECORD:
+ afMode = ANDROID_CONTROL_AF_MODE_CONTINUOUS_VIDEO;
+ break;
+ case CAMERA2_TEMPLATE_VIDEO_SNAPSHOT:
+ afMode = ANDROID_CONTROL_AF_MODE_CONTINUOUS_VIDEO;
+ break;
+ case CAMERA2_TEMPLATE_ZERO_SHUTTER_LAG:
+ afMode = ANDROID_CONTROL_AF_MODE_CONTINUOUS_PICTURE;
+ break;
+ default:
+ afMode = ANDROID_CONTROL_AF_MODE_AUTO;
+ break;
+ }
+ ADD_OR_SIZE(ANDROID_CONTROL_AF_MODE, &afMode, 1);
+
+ ADD_OR_SIZE(ANDROID_CONTROL_AF_REGIONS, controlRegions, 5);
+
+ static const uint8_t vstabMode =
+ ANDROID_CONTROL_VIDEO_STABILIZATION_MODE_OFF;
+ ADD_OR_SIZE(ANDROID_CONTROL_VIDEO_STABILIZATION_MODE, &vstabMode, 1);
+
+ // aeState, awbState, afState only in frame
+
+ /** Allocate metadata if sizing */
+ if (sizeRequest) {
+ ALOGV("Allocating %zu entries, %zu extra bytes for "
+ "request template type %d",
+ entryCount, dataCount, request_template);
+ *request = allocate_camera_metadata(entryCount, dataCount);
+ if (*request == NULL) {
+ ALOGE("Unable to allocate new request template type %d "
+ "(%zu entries, %zu bytes extra data)", request_template,
+ entryCount, dataCount);
+ return NO_MEMORY;
+ }
+ }
+ return OK;
+#undef ADD_OR_SIZE
+}
+
+status_t EmulatedFakeCamera2::addOrSize(camera_metadata_t *request,
+ bool sizeRequest,
+ size_t *entryCount,
+ size_t *dataCount,
+ uint32_t tag,
+ const void *entryData,
+ size_t entryDataCount) {
+ status_t res;
+ if (!sizeRequest) {
+ return add_camera_metadata_entry(request, tag, entryData,
+ entryDataCount);
+ } else {
+ int type = get_camera_metadata_tag_type(tag);
+ if (type < 0 ) return BAD_VALUE;
+ (*entryCount)++;
+ (*dataCount) += calculate_camera_metadata_entry_data_size(type,
+ entryDataCount);
+ return OK;
+ }
+}
+
+bool EmulatedFakeCamera2::isStreamInUse(uint32_t id) {
+ // Assumes mMutex is locked; otherwise new requests could enter
+ // configureThread while readoutThread is being checked
+
+ // Order of isStreamInUse calls matters
+ if (mConfigureThread->isStreamInUse(id) ||
+ mReadoutThread->isStreamInUse(id) ||
+ mJpegCompressor->isStreamInUse(id) ) {
+ ALOGE("%s: Stream %d is in use in active requests!",
+ __FUNCTION__, id);
+ return true;
+ }
+ return false;
+}
+
+bool EmulatedFakeCamera2::isReprocessStreamInUse(uint32_t id) {
+ // TODO: implement
+ return false;
+}
+
+const Stream& EmulatedFakeCamera2::getStreamInfo(uint32_t streamId) {
+ Mutex::Autolock lock(mMutex);
+
+ return mStreams.valueFor(streamId);
+}
+
+const ReprocessStream& EmulatedFakeCamera2::getReprocessStreamInfo(uint32_t streamId) {
+ Mutex::Autolock lock(mMutex);
+
+ return mReprocessStreams.valueFor(streamId);
+}
+
+}; /* namespace android */
diff --git a/guest/hals/camera/EmulatedFakeCamera2.h b/guest/hals/camera/EmulatedFakeCamera2.h
new file mode 100644
index 0000000..c622d26
--- /dev/null
+++ b/guest/hals/camera/EmulatedFakeCamera2.h
@@ -0,0 +1,437 @@
+/*
+ * Copyright (C) 2012 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 HW_EMULATOR_CAMERA_EMULATED_FAKE_CAMERA2_H
+#define HW_EMULATOR_CAMERA_EMULATED_FAKE_CAMERA2_H
+
+/*
+ * Contains declaration of a class EmulatedFakeCamera2 that encapsulates
+ * functionality of a fake camera that implements version 2 of the camera device
+ * interface.
+ */
+
+#include <vector>
+
+#include "EmulatedCamera2.h"
+#include "fake-pipeline2/Base.h"
+#include "fake-pipeline2/Sensor.h"
+#include "fake-pipeline2/JpegCompressor.h"
+#include <utils/Condition.h>
+#include <utils/KeyedVector.h>
+#include <utils/String8.h>
+#include <utils/String16.h>
+
+namespace android {
+
+/* Encapsulates functionality of an advanced fake camera. This camera contains
+ * a simple simulation of a scene, sensor, and image processing pipeline.
+ */
+class EmulatedFakeCamera2 : public EmulatedCamera2 {
+public:
+ /* Constructs EmulatedFakeCamera instance. */
+ EmulatedFakeCamera2(int cameraId, bool facingBack, struct hw_module_t* module);
+
+ /* Destructs EmulatedFakeCamera instance. */
+ ~EmulatedFakeCamera2();
+
+ /****************************************************************************
+ * EmulatedCamera2 virtual overrides.
+ ***************************************************************************/
+
+public:
+ /* Initializes EmulatedFakeCamera2 instance. */
+ status_t Initialize(const cvd::CameraDefinition& props);
+
+ /****************************************************************************
+ * Camera Module API and generic hardware device API implementation
+ ***************************************************************************/
+public:
+
+ virtual status_t connectCamera(hw_device_t** device);
+
+ virtual status_t plugCamera();
+ virtual status_t unplugCamera();
+ virtual camera_device_status_t getHotplugStatus();
+
+ virtual status_t closeCamera();
+
+ virtual status_t getCameraInfo(struct camera_info *info);
+
+ /****************************************************************************
+ * EmulatedCamera2 abstract API implementation.
+ ***************************************************************************/
+protected:
+ /** Request input queue */
+
+ virtual int requestQueueNotify();
+
+ /** Count of requests in flight */
+ virtual int getInProgressCount();
+
+ /** Cancel all captures in flight */
+ //virtual int flushCapturesInProgress();
+
+ /** Construct default request */
+ virtual int constructDefaultRequest(
+ int request_template,
+ camera_metadata_t **request);
+
+ virtual int allocateStream(
+ uint32_t width,
+ uint32_t height,
+ int format,
+ const camera2_stream_ops_t *stream_ops,
+ uint32_t *stream_id,
+ uint32_t *format_actual,
+ uint32_t *usage,
+ uint32_t *max_buffers);
+
+ virtual int registerStreamBuffers(
+ uint32_t stream_id,
+ int num_buffers,
+ buffer_handle_t *buffers);
+
+ virtual int releaseStream(uint32_t stream_id);
+
+ // virtual int allocateReprocessStream(
+ // uint32_t width,
+ // uint32_t height,
+ // uint32_t format,
+ // const camera2_stream_ops_t *stream_ops,
+ // uint32_t *stream_id,
+ // uint32_t *format_actual,
+ // uint32_t *usage,
+ // uint32_t *max_buffers);
+
+ virtual int allocateReprocessStreamFromStream(
+ uint32_t output_stream_id,
+ const camera2_stream_in_ops_t *stream_ops,
+ uint32_t *stream_id);
+
+ virtual int releaseReprocessStream(uint32_t stream_id);
+
+ virtual int triggerAction(uint32_t trigger_id,
+ int32_t ext1,
+ int32_t ext2);
+
+ /** Debug methods */
+
+ virtual int dump(int fd);
+
+public:
+ /****************************************************************************
+ * Utility methods called by configure/readout threads and pipeline
+ ***************************************************************************/
+
+ // Get information about a given stream. Will lock mMutex
+ const Stream &getStreamInfo(uint32_t streamId);
+ const ReprocessStream &getReprocessStreamInfo(uint32_t streamId);
+
+ // Notifies rest of camera subsystem of serious error
+ void signalError();
+
+private:
+ /****************************************************************************
+ * Utility methods
+ ***************************************************************************/
+ /** Construct static camera metadata, two-pass */
+ status_t constructStaticInfo(
+ camera_metadata_t **info,
+ bool sizeRequest) const;
+
+ /** Two-pass implementation of constructDefaultRequest */
+ status_t constructDefaultRequest(
+ int request_template,
+ camera_metadata_t **request,
+ bool sizeRequest) const;
+ /** Helper function for constructDefaultRequest */
+ static status_t addOrSize( camera_metadata_t *request,
+ bool sizeRequest,
+ size_t *entryCount,
+ size_t *dataCount,
+ uint32_t tag,
+ const void *entry_data,
+ size_t entry_count);
+
+ /** Determine if the stream id is listed in any currently-in-flight
+ * requests. Assumes mMutex is locked */
+ bool isStreamInUse(uint32_t streamId);
+
+ /** Determine if the reprocess stream id is listed in any
+ * currently-in-flight requests. Assumes mMutex is locked */
+ bool isReprocessStreamInUse(uint32_t streamId);
+
+ /****************************************************************************
+ * Pipeline controller threads
+ ***************************************************************************/
+
+ class ConfigureThread: public Thread {
+ public:
+ ConfigureThread(EmulatedFakeCamera2 *parent);
+ ~ConfigureThread();
+
+ status_t waitUntilRunning();
+ status_t newRequestAvailable();
+ status_t readyToRun();
+
+ bool isStreamInUse(uint32_t id);
+ int getInProgressCount();
+ private:
+ EmulatedFakeCamera2 *mParent;
+ static const nsecs_t kWaitPerLoop = 10000000L; // 10 ms
+
+ bool mRunning;
+ bool threadLoop();
+
+ bool setupCapture();
+ bool setupReprocess();
+
+ bool configureNextCapture();
+ bool configureNextReprocess();
+
+ bool getBuffers();
+
+ Mutex mInputMutex; // Protects mActive, mRequestCount
+ Condition mInputSignal;
+ bool mActive; // Whether we're waiting for input requests or actively
+ // working on them
+ size_t mRequestCount;
+
+ camera_metadata_t *mRequest;
+
+ Mutex mInternalsMutex; // Lock before accessing below members.
+ bool mWaitingForReadout;
+ bool mNextNeedsJpeg;
+ bool mNextIsCapture;
+ int32_t mNextFrameNumber;
+ int64_t mNextExposureTime;
+ int64_t mNextFrameDuration;
+ int32_t mNextSensitivity;
+ Buffers *mNextBuffers;
+ };
+
+ class ReadoutThread: public Thread, private JpegCompressor::JpegListener {
+ public:
+ ReadoutThread(EmulatedFakeCamera2 *parent);
+ ~ReadoutThread();
+
+ status_t readyToRun();
+
+ // Input
+ status_t waitUntilRunning();
+ bool waitForReady(nsecs_t timeout);
+ void setNextOperation(bool isCapture,
+ camera_metadata_t *request,
+ Buffers *buffers);
+ bool isStreamInUse(uint32_t id);
+ int getInProgressCount();
+ private:
+ EmulatedFakeCamera2 *mParent;
+
+ bool mRunning;
+ bool threadLoop();
+
+ bool readyForNextCapture();
+ status_t collectStatisticsMetadata(camera_metadata_t *frame);
+
+ // Inputs
+ Mutex mInputMutex; // Protects mActive, mInFlightQueue, mRequestCount
+ Condition mInputSignal;
+ Condition mReadySignal;
+
+ bool mActive;
+
+ static const int kInFlightQueueSize = 4;
+ struct InFlightQueue {
+ bool isCapture;
+ camera_metadata_t *request;
+ Buffers *buffers;
+ } *mInFlightQueue;
+
+ size_t mInFlightHead;
+ size_t mInFlightTail;
+
+ size_t mRequestCount;
+
+ // Internals
+ Mutex mInternalsMutex;
+
+ bool mIsCapture;
+ camera_metadata_t *mRequest;
+ Buffers *mBuffers;
+
+ // Jpeg completion listeners
+ void onJpegDone(const StreamBuffer &jpegBuffer, bool success);
+ void onJpegInputDone(const StreamBuffer &inputBuffer);
+ nsecs_t mJpegTimestamp;
+ };
+
+ // 3A management thread (auto-exposure, focus, white balance)
+ class ControlThread: public Thread {
+ public:
+ ControlThread(EmulatedFakeCamera2 *parent);
+ ~ControlThread();
+
+ status_t readyToRun();
+
+ status_t waitUntilRunning();
+
+ // Interpret request's control parameters and override
+ // capture settings as needed
+ status_t processRequest(camera_metadata_t *request);
+
+ status_t triggerAction(uint32_t msgType,
+ int32_t ext1, int32_t ext2);
+ private:
+ ControlThread(const ControlThread &t);
+ ControlThread& operator=(const ControlThread &t);
+
+ // Constants controlling fake 3A behavior
+ static const nsecs_t kControlCycleDelay;
+ static const nsecs_t kMinAfDuration;
+ static const nsecs_t kMaxAfDuration;
+ static const float kAfSuccessRate;
+ static const float kContinuousAfStartRate;
+
+ static const float kAeScanStartRate;
+ static const nsecs_t kMinAeDuration;
+ static const nsecs_t kMaxAeDuration;
+ static const nsecs_t kMinPrecaptureAeDuration;
+ static const nsecs_t kMaxPrecaptureAeDuration;
+
+ static const nsecs_t kNormalExposureTime;
+ static const nsecs_t kExposureJump;
+ static const nsecs_t kMinExposureTime;
+
+ EmulatedFakeCamera2 *mParent;
+
+ bool mRunning;
+ bool threadLoop();
+
+ Mutex mInputMutex; // Protects input methods
+ Condition mInputSignal;
+
+ // Trigger notifications
+ bool mStartAf;
+ bool mCancelAf;
+ bool mStartPrecapture;
+
+ // Latest state for 3A request fields
+ uint8_t mControlMode;
+
+ uint8_t mEffectMode;
+ uint8_t mSceneMode;
+
+ uint8_t mAfMode;
+ bool mAfModeChange;
+
+ uint8_t mAwbMode;
+ uint8_t mAeMode;
+
+ // Latest trigger IDs
+ int32_t mAfTriggerId;
+ int32_t mPrecaptureTriggerId;
+
+ // Current state for 3A algorithms
+ uint8_t mAfState;
+ uint8_t mAeState;
+ uint8_t mAwbState;
+ bool mAeLock;
+
+ // Current control parameters
+ nsecs_t mExposureTime;
+
+ // Private to threadLoop and its utility methods
+
+ nsecs_t mAfScanDuration;
+ nsecs_t mAeScanDuration;
+ bool mLockAfterPassiveScan;
+
+ // Utility methods for AF
+ int processAfTrigger(uint8_t afMode, uint8_t afState);
+ int maybeStartAfScan(uint8_t afMode, uint8_t afState);
+ int updateAfScan(uint8_t afMode, uint8_t afState, nsecs_t *maxSleep);
+ void updateAfState(uint8_t newState, int32_t triggerId);
+
+ // Utility methods for precapture trigger
+ int processPrecaptureTrigger(uint8_t aeMode, uint8_t aeState);
+ int maybeStartAeScan(uint8_t aeMode, bool aeLock, uint8_t aeState);
+ int updateAeScan(uint8_t aeMode, bool aeLock, uint8_t aeState,
+ nsecs_t *maxSleep);
+ void updateAeState(uint8_t newState, int32_t triggerId);
+ };
+
+ /****************************************************************************
+ * Static configuration information
+ ***************************************************************************/
+private:
+ static const uint32_t kMaxRawStreamCount = 1;
+ static const uint32_t kMaxProcessedStreamCount = 3;
+ static const uint32_t kMaxJpegStreamCount = 1;
+ static const uint32_t kMaxReprocessStreamCount = 2;
+ static const uint32_t kMaxBufferCount = 4;
+ static const uint32_t kAvailableFormats[];
+ static const uint32_t kAvailableRawSizes[];
+ static const uint64_t kAvailableRawMinDurations[];
+ static const uint32_t kAvailableProcessedSizesBack[];
+ static const uint32_t kAvailableProcessedSizesFront[];
+ static const uint64_t kAvailableProcessedMinDurations[];
+ static const uint32_t kAvailableJpegSizesBack[];
+ static const uint32_t kAvailableJpegSizesFront[];
+ static const uint64_t kAvailableJpegMinDurations[];
+
+ /****************************************************************************
+ * Data members.
+ ***************************************************************************/
+
+protected:
+ /* Facing back (true) or front (false) switch. */
+ bool mFacingBack;
+
+private:
+ bool mIsConnected;
+
+ int32_t mSensorWidth, mSensorHeight;
+
+ /** Stream manipulation */
+ uint32_t mNextStreamId;
+ uint32_t mRawStreamCount;
+ uint32_t mProcessedStreamCount;
+ uint32_t mJpegStreamCount;
+
+ std::vector<uint32_t> mAvailableRawSizes;
+ std::vector<uint32_t> mAvailableProcessedSizes;
+ std::vector<uint32_t> mAvailableJpegSizes;
+
+ uint32_t mNextReprocessStreamId;
+ uint32_t mReprocessStreamCount;
+
+ KeyedVector<uint32_t, Stream> mStreams;
+ KeyedVector<uint32_t, ReprocessStream> mReprocessStreams;
+
+ /** Simulated hardware interfaces */
+ sp<Sensor> mSensor;
+ sp<JpegCompressor> mJpegCompressor;
+
+ /** Pipeline control threads */
+ sp<ConfigureThread> mConfigureThread;
+ sp<ReadoutThread> mReadoutThread;
+ sp<ControlThread> mControlThread;
+};
+
+}; /* namespace android */
+
+#endif /* HW_EMULATOR_CAMERA_EMULATED_FAKE_CAMERA2_H */
diff --git a/guest/hals/camera/EmulatedFakeCamera3.cpp b/guest/hals/camera/EmulatedFakeCamera3.cpp
new file mode 100644
index 0000000..dcfc34c
--- /dev/null
+++ b/guest/hals/camera/EmulatedFakeCamera3.cpp
@@ -0,0 +1,2625 @@
+/*
+ * Copyright (C) 2013 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.
+ */
+
+/*
+ * Contains implementation of a class EmulatedFakeCamera3 that encapsulates
+ * functionality of an advanced fake camera.
+ */
+
+#include <cstdint>
+
+//#define LOG_NDEBUG 0
+//#define LOG_NNDEBUG 0
+#define LOG_TAG "EmulatedCamera_FakeCamera3"
+#include <cutils/properties.h>
+#include <utils/Log.h>
+
+#include "EmulatedFakeCamera3.h"
+#include "EmulatedCameraFactory.h"
+#include <ui/Fence.h>
+#include "GrallocModule.h"
+
+#include "fake-pipeline2/Sensor.h"
+#include "fake-pipeline2/JpegCompressor.h"
+#include <cmath>
+
+#include <vector>
+
+#if defined(LOG_NNDEBUG) && LOG_NNDEBUG == 0
+#define ALOGVV ALOGV
+#else
+#define ALOGVV(...) ((void)0)
+#endif
+
+namespace android {
+
+/**
+ * Constants for camera capabilities
+ */
+
+const int64_t USEC = 1000LL;
+const int64_t MSEC = USEC * 1000LL;
+const int64_t SEC = MSEC * 1000LL;
+
+const int32_t EmulatedFakeCamera3::kAvailableFormats[] = {
+ HAL_PIXEL_FORMAT_RAW16,
+ HAL_PIXEL_FORMAT_BLOB,
+ HAL_PIXEL_FORMAT_RGBA_8888,
+ HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED,
+ // These are handled by YCbCr_420_888
+ // HAL_PIXEL_FORMAT_YV12,
+ // HAL_PIXEL_FORMAT_YCrCb_420_SP,
+ HAL_PIXEL_FORMAT_YCbCr_420_888,
+ HAL_PIXEL_FORMAT_Y16
+};
+
+/**
+ * 3A constants
+ */
+
+// Default exposure and gain targets for different scenarios
+const nsecs_t EmulatedFakeCamera3::kNormalExposureTime = 10 * MSEC;
+const nsecs_t EmulatedFakeCamera3::kFacePriorityExposureTime = 30 * MSEC;
+const int EmulatedFakeCamera3::kNormalSensitivity = 100;
+const int EmulatedFakeCamera3::kFacePrioritySensitivity = 400;
+const float EmulatedFakeCamera3::kExposureTrackRate = 0.1;
+const int EmulatedFakeCamera3::kPrecaptureMinFrames = 10;
+const int EmulatedFakeCamera3::kStableAeMaxFrames = 100;
+const float EmulatedFakeCamera3::kExposureWanderMin = -2;
+const float EmulatedFakeCamera3::kExposureWanderMax = 1;
+
+/**
+ * Camera device lifecycle methods
+ */
+
+EmulatedFakeCamera3::EmulatedFakeCamera3(int cameraId, bool facingBack,
+ struct hw_module_t* module) :
+ EmulatedCamera3(cameraId, module),
+ mFacingBack(facingBack) {
+ ALOGI("Constructing emulated fake camera 3: ID %d, facing %s",
+ mCameraID, facingBack ? "back" : "front");
+
+ for (size_t i = 0; i < CAMERA3_TEMPLATE_COUNT; i++) {
+ mDefaultTemplates[i] = NULL;
+ }
+}
+
+EmulatedFakeCamera3::~EmulatedFakeCamera3() {
+ for (size_t i = 0; i < CAMERA3_TEMPLATE_COUNT; i++) {
+ if (mDefaultTemplates[i] != NULL) {
+ free_camera_metadata(mDefaultTemplates[i]);
+ }
+ }
+}
+
+status_t EmulatedFakeCamera3::Initialize(const cvd::CameraDefinition& params) {
+ ALOGV("%s: E", __FUNCTION__);
+ status_t res;
+
+ if (mStatus != STATUS_ERROR) {
+ ALOGE("%s: Already initialized!", __FUNCTION__);
+ return INVALID_OPERATION;
+ }
+
+ res = getCameraCapabilities();
+ if (res != OK) {
+ ALOGE("%s: Unable to get camera capabilities: %s (%d)",
+ __FUNCTION__, strerror(-res), res);
+ return res;
+ }
+
+ res = constructStaticInfo(params);
+ if (res != OK) {
+ ALOGE("%s: Unable to allocate static info: %s (%d)",
+ __FUNCTION__, strerror(-res), res);
+ return res;
+ }
+
+ return EmulatedCamera3::Initialize(params);
+}
+
+status_t EmulatedFakeCamera3::connectCamera(hw_device_t** device) {
+ ALOGV("%s: E", __FUNCTION__);
+ Mutex::Autolock l(mLock);
+ status_t res;
+
+ if (mStatus != STATUS_CLOSED) {
+ ALOGE("%s: Can't connect in state %d", __FUNCTION__, mStatus);
+ return INVALID_OPERATION;
+ }
+
+ mSensor = new Sensor(mSensorWidth, mSensorHeight);
+ mSensor->setSensorListener(this);
+
+ res = mSensor->startUp();
+ if (res != NO_ERROR) return res;
+
+ mReadoutThread = new ReadoutThread(this);
+ mJpegCompressor = new JpegCompressor();
+
+ res = mReadoutThread->run("EmuCam3::readoutThread");
+ if (res != NO_ERROR) return res;
+
+ // Initialize fake 3A
+
+ mControlMode = ANDROID_CONTROL_MODE_AUTO;
+ mFacePriority = false;
+ mAeMode = ANDROID_CONTROL_AE_MODE_ON;
+ mAfMode = ANDROID_CONTROL_AF_MODE_AUTO;
+ mAwbMode = ANDROID_CONTROL_AWB_MODE_AUTO;
+ mAeState = ANDROID_CONTROL_AE_STATE_INACTIVE;
+ mAfState = ANDROID_CONTROL_AF_STATE_INACTIVE;
+ mAwbState = ANDROID_CONTROL_AWB_STATE_INACTIVE;
+ mAeCounter = 0;
+ mAeTargetExposureTime = kNormalExposureTime;
+ mAeCurrentExposureTime = kNormalExposureTime;
+ mAeCurrentSensitivity = kNormalSensitivity;
+
+ return EmulatedCamera3::connectCamera(device);
+}
+
+status_t EmulatedFakeCamera3::closeCamera() {
+ ALOGV("%s: E", __FUNCTION__);
+ status_t res;
+ {
+ Mutex::Autolock l(mLock);
+ if (mStatus == STATUS_CLOSED) return OK;
+
+ res = mSensor->shutDown();
+ if (res != NO_ERROR) {
+ ALOGE("%s: Unable to shut down sensor: %d", __FUNCTION__, res);
+ return res;
+ }
+ mSensor.clear();
+
+ mReadoutThread->requestExit();
+ }
+
+ mReadoutThread->join();
+
+ {
+ Mutex::Autolock l(mLock);
+ // Clear out private stream information
+ for (StreamIterator s = mStreams.begin(); s != mStreams.end(); s++) {
+ PrivateStreamInfo *privStream =
+ static_cast<PrivateStreamInfo*>((*s)->priv);
+ delete privStream;
+ (*s)->priv = NULL;
+ }
+ mStreams.clear();
+ mReadoutThread.clear();
+ }
+
+ return EmulatedCamera3::closeCamera();
+}
+
+status_t EmulatedFakeCamera3::getCameraInfo(struct camera_info *info) {
+ info->facing = mFacingBack ? CAMERA_FACING_BACK : CAMERA_FACING_FRONT;
+ info->orientation = EmulatedCameraFactory::Instance().getFakeCameraOrientation();
+#if VSOC_PLATFORM_SDK_AFTER(L_MR1)
+ info->resource_cost = 100;
+ info->conflicting_devices = NULL;
+ info->conflicting_devices_length = 0;
+#endif
+ return EmulatedCamera3::getCameraInfo(info);
+}
+
+status_t EmulatedFakeCamera3::setTorchMode(bool enabled) {
+ if (!mFacingBack) {
+ ALOGE("%s: Front camera does not have flash unit", __FUNCTION__);
+ return INVALID_OPERATION;
+ }
+ EmulatedCameraFactory::Instance().onTorchModeStatusChanged(
+ mCameraID, enabled ?
+ TORCH_MODE_STATUS_AVAILABLE_ON :
+ TORCH_MODE_STATUS_AVAILABLE_OFF);
+ return NO_ERROR;
+}
+
+/**
+ * Camera3 interface methods
+ */
+
+status_t EmulatedFakeCamera3::configureStreams(
+ camera3_stream_configuration *streamList) {
+ Mutex::Autolock l(mLock);
+ ALOGV("%s: %d streams", __FUNCTION__, streamList->num_streams);
+
+ if (mStatus != STATUS_OPEN && mStatus != STATUS_READY) {
+ ALOGE("%s: Cannot configure streams in state %d",
+ __FUNCTION__, mStatus);
+ return NO_INIT;
+ }
+
+ /**
+ * Sanity-check input list.
+ */
+ if (streamList == NULL) {
+ ALOGE("%s: NULL stream configuration", __FUNCTION__);
+ return BAD_VALUE;
+ }
+
+ if (streamList->streams == NULL) {
+ ALOGE("%s: NULL stream list", __FUNCTION__);
+ return BAD_VALUE;
+ }
+
+ if (streamList->num_streams < 1) {
+ ALOGE("%s: Bad number of streams requested: %d", __FUNCTION__,
+ streamList->num_streams);
+ return BAD_VALUE;
+ }
+
+ camera3_stream_t *inputStream = NULL;
+ for (size_t i = 0; i < streamList->num_streams; i++) {
+ camera3_stream_t *newStream = streamList->streams[i];
+
+ if (newStream == NULL) {
+ ALOGE("%s: Stream index %zu was NULL",
+ __FUNCTION__, i);
+ return BAD_VALUE;
+ }
+
+ ALOGV("%s: Stream %p (id %zu), type %d, usage 0x%x, format 0x%x",
+ __FUNCTION__, newStream, i, newStream->stream_type,
+ newStream->usage,
+ newStream->format);
+
+ if (newStream->stream_type == CAMERA3_STREAM_INPUT ||
+ newStream->stream_type == CAMERA3_STREAM_BIDIRECTIONAL) {
+ if (inputStream != NULL) {
+
+ ALOGE("%s: Multiple input streams requested!", __FUNCTION__);
+ return BAD_VALUE;
+ }
+ inputStream = newStream;
+ }
+
+ bool validFormat = false;
+ for (size_t f = 0;
+ f < sizeof(kAvailableFormats)/sizeof(kAvailableFormats[0]);
+ f++) {
+ if (newStream->format == kAvailableFormats[f]) {
+ validFormat = true;
+ break;
+ }
+ }
+ if (!validFormat) {
+ ALOGE("%s: Unsupported stream format 0x%x requested",
+ __FUNCTION__, newStream->format);
+ return BAD_VALUE;
+ }
+ }
+ mInputStream = inputStream;
+
+ /**
+ * Initially mark all existing streams as not alive
+ */
+ for (StreamIterator s = mStreams.begin(); s != mStreams.end(); ++s) {
+ PrivateStreamInfo *privStream =
+ static_cast<PrivateStreamInfo*>((*s)->priv);
+ privStream->alive = false;
+ }
+
+ /**
+ * Find new streams and mark still-alive ones
+ */
+ for (size_t i = 0; i < streamList->num_streams; i++) {
+ camera3_stream_t *newStream = streamList->streams[i];
+ if (newStream->priv == NULL) {
+ // New stream, construct info
+ PrivateStreamInfo *privStream = new PrivateStreamInfo();
+ privStream->alive = true;
+
+ newStream->max_buffers = kMaxBufferCount;
+ newStream->priv = privStream;
+ mStreams.push_back(newStream);
+ } else {
+ // Existing stream, mark as still alive.
+ PrivateStreamInfo *privStream =
+ static_cast<PrivateStreamInfo*>(newStream->priv);
+ privStream->alive = true;
+ }
+ // Always update usage and max buffers
+ newStream->max_buffers = kMaxBufferCount;
+ switch (newStream->stream_type) {
+ case CAMERA3_STREAM_OUTPUT:
+ newStream->usage = GRALLOC_USAGE_HW_CAMERA_WRITE;
+ break;
+ case CAMERA3_STREAM_INPUT:
+ newStream->usage = GRALLOC_USAGE_HW_CAMERA_READ;
+ break;
+ case CAMERA3_STREAM_BIDIRECTIONAL:
+ newStream->usage = GRALLOC_USAGE_HW_CAMERA_READ |
+ GRALLOC_USAGE_HW_CAMERA_WRITE;
+ break;
+ }
+ }
+
+ /**
+ * Reap the dead streams
+ */
+ for (StreamIterator s = mStreams.begin(); s != mStreams.end();) {
+ PrivateStreamInfo *privStream =
+ static_cast<PrivateStreamInfo*>((*s)->priv);
+ if (!privStream->alive) {
+ (*s)->priv = NULL;
+ delete privStream;
+ s = mStreams.erase(s);
+ } else {
+ ++s;
+ }
+ }
+
+ /**
+ * Can't reuse settings across configure call
+ */
+ mPrevSettings.clear();
+
+ return OK;
+}
+
+status_t EmulatedFakeCamera3::registerStreamBuffers(
+ const camera3_stream_buffer_set *bufferSet) {
+ ALOGV("%s: E", __FUNCTION__);
+ Mutex::Autolock l(mLock);
+
+ // Should not be called in HAL versions >= 3.2
+
+ ALOGE("%s: Should not be invoked on new HALs!",
+ __FUNCTION__);
+ return NO_INIT;
+}
+
+const camera_metadata_t* EmulatedFakeCamera3::constructDefaultRequestSettings(
+ int type) {
+ ALOGV("%s: E", __FUNCTION__);
+ Mutex::Autolock l(mLock);
+
+ if (type < 0 || type >= CAMERA3_TEMPLATE_COUNT) {
+ ALOGE("%s: Unknown request settings template: %d",
+ __FUNCTION__, type);
+ return NULL;
+ }
+
+ if (!hasCapability(BACKWARD_COMPATIBLE) && type != CAMERA3_TEMPLATE_PREVIEW) {
+ ALOGE("%s: Template %d not supported w/o BACKWARD_COMPATIBLE capability",
+ __FUNCTION__, type);
+ return NULL;
+ }
+
+ /**
+ * Cache is not just an optimization - pointer returned has to live at
+ * least as long as the camera device instance does.
+ */
+ if (mDefaultTemplates[type] != NULL) {
+ return mDefaultTemplates[type];
+ }
+
+ CameraMetadata settings;
+
+ /** android.request */
+
+ static const uint8_t metadataMode = ANDROID_REQUEST_METADATA_MODE_FULL;
+ settings.update(ANDROID_REQUEST_METADATA_MODE, &metadataMode, 1);
+
+ static const int32_t id = 0;
+ settings.update(ANDROID_REQUEST_ID, &id, 1);
+
+ static const int32_t frameCount = 0;
+ settings.update(ANDROID_REQUEST_FRAME_COUNT, &frameCount, 1);
+
+ /** android.lens */
+
+ static const float focalLength = 5.0f;
+ settings.update(ANDROID_LENS_FOCAL_LENGTH, &focalLength, 1);
+
+ if (hasCapability(BACKWARD_COMPATIBLE)) {
+ static const float focusDistance = 0;
+ settings.update(ANDROID_LENS_FOCUS_DISTANCE, &focusDistance, 1);
+
+ static const float aperture = 2.8f;
+ settings.update(ANDROID_LENS_APERTURE, &aperture, 1);
+
+ static const float filterDensity = 0;
+ settings.update(ANDROID_LENS_FILTER_DENSITY, &filterDensity, 1);
+
+ static const uint8_t opticalStabilizationMode =
+ ANDROID_LENS_OPTICAL_STABILIZATION_MODE_OFF;
+ settings.update(ANDROID_LENS_OPTICAL_STABILIZATION_MODE,
+ &opticalStabilizationMode, 1);
+
+ // FOCUS_RANGE set only in frame
+ }
+
+ /** android.sensor */
+
+ if (hasCapability(MANUAL_SENSOR)) {
+ static const int64_t exposureTime = 10 * MSEC;
+ settings.update(ANDROID_SENSOR_EXPOSURE_TIME, &exposureTime, 1);
+
+ static const int64_t frameDuration = 33333333L; // 1/30 s
+ settings.update(ANDROID_SENSOR_FRAME_DURATION, &frameDuration, 1);
+
+ static const int32_t sensitivity = 100;
+ settings.update(ANDROID_SENSOR_SENSITIVITY, &sensitivity, 1);
+ }
+
+ // TIMESTAMP set only in frame
+
+ /** android.flash */
+
+ if (hasCapability(BACKWARD_COMPATIBLE)) {
+ static const uint8_t flashMode = ANDROID_FLASH_MODE_OFF;
+ settings.update(ANDROID_FLASH_MODE, &flashMode, 1);
+
+ static const uint8_t flashPower = 10;
+ settings.update(ANDROID_FLASH_FIRING_POWER, &flashPower, 1);
+
+ static const int64_t firingTime = 0;
+ settings.update(ANDROID_FLASH_FIRING_TIME, &firingTime, 1);
+ }
+
+ /** Processing block modes */
+ if (hasCapability(MANUAL_POST_PROCESSING)) {
+ uint8_t hotPixelMode = 0;
+ uint8_t demosaicMode = 0;
+ uint8_t noiseMode = 0;
+ uint8_t shadingMode = 0;
+ uint8_t colorMode = 0;
+ uint8_t tonemapMode = 0;
+ uint8_t edgeMode = 0;
+ switch (type) {
+ case CAMERA3_TEMPLATE_STILL_CAPTURE:
+ // fall-through
+ case CAMERA3_TEMPLATE_VIDEO_SNAPSHOT:
+ // fall-through
+ case CAMERA3_TEMPLATE_ZERO_SHUTTER_LAG:
+ hotPixelMode = ANDROID_HOT_PIXEL_MODE_HIGH_QUALITY;
+ demosaicMode = ANDROID_DEMOSAIC_MODE_HIGH_QUALITY;
+ noiseMode = ANDROID_NOISE_REDUCTION_MODE_HIGH_QUALITY;
+ shadingMode = ANDROID_SHADING_MODE_HIGH_QUALITY;
+ colorMode = ANDROID_COLOR_CORRECTION_MODE_HIGH_QUALITY;
+ tonemapMode = ANDROID_TONEMAP_MODE_HIGH_QUALITY;
+ edgeMode = ANDROID_EDGE_MODE_HIGH_QUALITY;
+ break;
+ case CAMERA3_TEMPLATE_PREVIEW:
+ // fall-through
+ case CAMERA3_TEMPLATE_VIDEO_RECORD:
+ // fall-through
+ default:
+ hotPixelMode = ANDROID_HOT_PIXEL_MODE_FAST;
+ demosaicMode = ANDROID_DEMOSAIC_MODE_FAST;
+ noiseMode = ANDROID_NOISE_REDUCTION_MODE_FAST;
+ shadingMode = ANDROID_SHADING_MODE_FAST;
+ colorMode = ANDROID_COLOR_CORRECTION_MODE_FAST;
+ tonemapMode = ANDROID_TONEMAP_MODE_FAST;
+ edgeMode = ANDROID_EDGE_MODE_FAST;
+ break;
+ }
+ settings.update(ANDROID_HOT_PIXEL_MODE, &hotPixelMode, 1);
+ settings.update(ANDROID_DEMOSAIC_MODE, &demosaicMode, 1);
+ settings.update(ANDROID_NOISE_REDUCTION_MODE, &noiseMode, 1);
+ settings.update(ANDROID_SHADING_MODE, &shadingMode, 1);
+ settings.update(ANDROID_COLOR_CORRECTION_MODE, &colorMode, 1);
+ settings.update(ANDROID_TONEMAP_MODE, &tonemapMode, 1);
+ settings.update(ANDROID_EDGE_MODE, &edgeMode, 1);
+ }
+
+ /** android.colorCorrection */
+
+ if (hasCapability(MANUAL_POST_PROCESSING)) {
+ static const camera_metadata_rational colorTransform[9] = {
+ {1,1}, {0,1}, {0,1},
+ {0,1}, {1,1}, {0,1},
+ {0,1}, {0,1}, {1,1}
+ };
+ settings.update(ANDROID_COLOR_CORRECTION_TRANSFORM, colorTransform, 9);
+
+ static const float colorGains[4] = {
+ 1.0f, 1.0f, 1.0f, 1.0f
+ };
+ settings.update(ANDROID_COLOR_CORRECTION_GAINS, colorGains, 4);
+ }
+
+ /** android.tonemap */
+
+ if (hasCapability(MANUAL_POST_PROCESSING)) {
+ static const float tonemapCurve[4] = {
+ 0.f, 0.f,
+ 1.f, 1.f
+ };
+ settings.update(ANDROID_TONEMAP_CURVE_RED, tonemapCurve, 4);
+ settings.update(ANDROID_TONEMAP_CURVE_GREEN, tonemapCurve, 4);
+ settings.update(ANDROID_TONEMAP_CURVE_BLUE, tonemapCurve, 4);
+ }
+
+ /** android.scaler */
+ if (hasCapability(BACKWARD_COMPATIBLE)) {
+ static const int32_t cropRegion[4] = {
+ 0, 0, mSensorWidth, mSensorHeight
+ };
+ settings.update(ANDROID_SCALER_CROP_REGION, cropRegion, 4);
+
+ }
+
+ /** android.jpeg */
+ if (hasCapability(BACKWARD_COMPATIBLE)) {
+ static const uint8_t jpegQuality = 80;
+ settings.update(ANDROID_JPEG_QUALITY, &jpegQuality, 1);
+
+ static const int32_t thumbnailSize[2] = {
+ 640, 480
+ };
+ settings.update(ANDROID_JPEG_THUMBNAIL_SIZE, thumbnailSize, 2);
+
+ static const uint8_t thumbnailQuality = 80;
+ settings.update(ANDROID_JPEG_THUMBNAIL_QUALITY, &thumbnailQuality, 1);
+
+ static const double gpsCoordinates[2] = {
+ 0, 0
+ };
+ settings.update(ANDROID_JPEG_GPS_COORDINATES, gpsCoordinates, 2);
+
+ static const uint8_t gpsProcessingMethod[32] = "None";
+ settings.update(ANDROID_JPEG_GPS_PROCESSING_METHOD, gpsProcessingMethod, 32);
+
+ static const int64_t gpsTimestamp = 0;
+ settings.update(ANDROID_JPEG_GPS_TIMESTAMP, &gpsTimestamp, 1);
+
+ static const int32_t jpegOrientation = 0;
+ settings.update(ANDROID_JPEG_ORIENTATION, &jpegOrientation, 1);
+ }
+
+ /** android.stats */
+
+ if (hasCapability(BACKWARD_COMPATIBLE)) {
+ static const uint8_t faceDetectMode =
+ ANDROID_STATISTICS_FACE_DETECT_MODE_OFF;
+ settings.update(ANDROID_STATISTICS_FACE_DETECT_MODE, &faceDetectMode, 1);
+
+ static const uint8_t hotPixelMapMode =
+ ANDROID_STATISTICS_HOT_PIXEL_MAP_MODE_OFF;
+ settings.update(ANDROID_STATISTICS_HOT_PIXEL_MAP_MODE, &hotPixelMapMode, 1);
+ }
+
+ // faceRectangles, faceScores, faceLandmarks, faceIds, histogram,
+ // sharpnessMap only in frames
+
+ /** android.control */
+
+ uint8_t controlIntent = 0;
+ switch (type) {
+ case CAMERA3_TEMPLATE_PREVIEW:
+ controlIntent = ANDROID_CONTROL_CAPTURE_INTENT_PREVIEW;
+ break;
+ case CAMERA3_TEMPLATE_STILL_CAPTURE:
+ controlIntent = ANDROID_CONTROL_CAPTURE_INTENT_STILL_CAPTURE;
+ break;
+ case CAMERA3_TEMPLATE_VIDEO_RECORD:
+ controlIntent = ANDROID_CONTROL_CAPTURE_INTENT_VIDEO_RECORD;
+ break;
+ case CAMERA3_TEMPLATE_VIDEO_SNAPSHOT:
+ controlIntent = ANDROID_CONTROL_CAPTURE_INTENT_VIDEO_SNAPSHOT;
+ break;
+ case CAMERA3_TEMPLATE_ZERO_SHUTTER_LAG:
+ controlIntent = ANDROID_CONTROL_CAPTURE_INTENT_ZERO_SHUTTER_LAG;
+ break;
+ case CAMERA3_TEMPLATE_MANUAL:
+ controlIntent = ANDROID_CONTROL_CAPTURE_INTENT_MANUAL;
+ break;
+ default:
+ controlIntent = ANDROID_CONTROL_CAPTURE_INTENT_CUSTOM;
+ break;
+ }
+ settings.update(ANDROID_CONTROL_CAPTURE_INTENT, &controlIntent, 1);
+
+ const uint8_t controlMode = (type == CAMERA3_TEMPLATE_MANUAL) ?
+ ANDROID_CONTROL_MODE_OFF :
+ ANDROID_CONTROL_MODE_AUTO;
+ settings.update(ANDROID_CONTROL_MODE, &controlMode, 1);
+
+ int32_t aeTargetFpsRange[2] = {
+ 5, 30
+ };
+ if (type == CAMERA3_TEMPLATE_VIDEO_RECORD || type == CAMERA3_TEMPLATE_VIDEO_SNAPSHOT) {
+ aeTargetFpsRange[0] = 30;
+ }
+ settings.update(ANDROID_CONTROL_AE_TARGET_FPS_RANGE, aeTargetFpsRange, 2);
+
+ if (hasCapability(BACKWARD_COMPATIBLE)) {
+
+ static const uint8_t effectMode = ANDROID_CONTROL_EFFECT_MODE_OFF;
+ settings.update(ANDROID_CONTROL_EFFECT_MODE, &effectMode, 1);
+
+ static const uint8_t sceneMode = ANDROID_CONTROL_SCENE_MODE_FACE_PRIORITY;
+ settings.update(ANDROID_CONTROL_SCENE_MODE, &sceneMode, 1);
+
+ const uint8_t aeMode = (type == CAMERA3_TEMPLATE_MANUAL) ?
+ ANDROID_CONTROL_AE_MODE_OFF :
+ ANDROID_CONTROL_AE_MODE_ON;
+ settings.update(ANDROID_CONTROL_AE_MODE, &aeMode, 1);
+
+ static const uint8_t aeLock = ANDROID_CONTROL_AE_LOCK_OFF;
+ settings.update(ANDROID_CONTROL_AE_LOCK, &aeLock, 1);
+
+ static const int32_t controlRegions[5] = {
+ 0, 0, 0, 0, 0
+ };
+ settings.update(ANDROID_CONTROL_AE_REGIONS, controlRegions, 5);
+
+ static const int32_t aeExpCompensation = 0;
+ settings.update(ANDROID_CONTROL_AE_EXPOSURE_COMPENSATION, &aeExpCompensation, 1);
+
+
+ static const uint8_t aeAntibandingMode =
+ ANDROID_CONTROL_AE_ANTIBANDING_MODE_AUTO;
+ settings.update(ANDROID_CONTROL_AE_ANTIBANDING_MODE, &aeAntibandingMode, 1);
+
+ static const uint8_t aePrecaptureTrigger = ANDROID_CONTROL_AE_PRECAPTURE_TRIGGER_IDLE;
+ settings.update(ANDROID_CONTROL_AE_PRECAPTURE_TRIGGER, &aePrecaptureTrigger, 1);
+
+ const uint8_t awbMode = (type == CAMERA3_TEMPLATE_MANUAL) ?
+ ANDROID_CONTROL_AWB_MODE_OFF :
+ ANDROID_CONTROL_AWB_MODE_AUTO;
+ settings.update(ANDROID_CONTROL_AWB_MODE, &awbMode, 1);
+
+ static const uint8_t awbLock = ANDROID_CONTROL_AWB_LOCK_OFF;
+ settings.update(ANDROID_CONTROL_AWB_LOCK, &awbLock, 1);
+
+ uint8_t afMode = 0;
+
+ if (mFacingBack) {
+ switch (type) {
+ case CAMERA3_TEMPLATE_PREVIEW:
+ afMode = ANDROID_CONTROL_AF_MODE_CONTINUOUS_PICTURE;
+ break;
+ case CAMERA3_TEMPLATE_STILL_CAPTURE:
+ afMode = ANDROID_CONTROL_AF_MODE_CONTINUOUS_PICTURE;
+ break;
+ case CAMERA3_TEMPLATE_VIDEO_RECORD:
+ afMode = ANDROID_CONTROL_AF_MODE_CONTINUOUS_VIDEO;
+ break;
+ case CAMERA3_TEMPLATE_VIDEO_SNAPSHOT:
+ afMode = ANDROID_CONTROL_AF_MODE_CONTINUOUS_VIDEO;
+ break;
+ case CAMERA3_TEMPLATE_ZERO_SHUTTER_LAG:
+ afMode = ANDROID_CONTROL_AF_MODE_CONTINUOUS_PICTURE;
+ break;
+ case CAMERA3_TEMPLATE_MANUAL:
+ afMode = ANDROID_CONTROL_AF_MODE_OFF;
+ break;
+ default:
+ afMode = ANDROID_CONTROL_AF_MODE_AUTO;
+ break;
+ }
+ } else {
+ afMode = ANDROID_CONTROL_AF_MODE_OFF;
+ }
+ settings.update(ANDROID_CONTROL_AF_MODE, &afMode, 1);
+
+ settings.update(ANDROID_CONTROL_AF_REGIONS, controlRegions, 5);
+
+ static const uint8_t afTrigger = ANDROID_CONTROL_AF_TRIGGER_IDLE;
+ settings.update(ANDROID_CONTROL_AF_TRIGGER, &afTrigger, 1);
+
+ static const uint8_t vstabMode =
+ ANDROID_CONTROL_VIDEO_STABILIZATION_MODE_OFF;
+ settings.update(ANDROID_CONTROL_VIDEO_STABILIZATION_MODE, &vstabMode, 1);
+
+ static const uint8_t blackLevelLock = ANDROID_BLACK_LEVEL_LOCK_OFF;
+ settings.update(ANDROID_BLACK_LEVEL_LOCK, &blackLevelLock, 1);
+
+ static const uint8_t lensShadingMapMode = ANDROID_STATISTICS_LENS_SHADING_MAP_MODE_OFF;
+ settings.update(ANDROID_STATISTICS_LENS_SHADING_MAP_MODE, &lensShadingMapMode, 1);
+
+ static const uint8_t aberrationMode = ANDROID_COLOR_CORRECTION_ABERRATION_MODE_FAST;
+ settings.update(ANDROID_COLOR_CORRECTION_ABERRATION_MODE, &aberrationMode, 1);
+
+ static const int32_t testPatternMode = ANDROID_SENSOR_TEST_PATTERN_MODE_OFF;
+ settings.update(ANDROID_SENSOR_TEST_PATTERN_MODE, &testPatternMode, 1);
+ }
+
+ mDefaultTemplates[type] = settings.release();
+
+ return mDefaultTemplates[type];
+}
+
+status_t EmulatedFakeCamera3::processCaptureRequest(
+ camera3_capture_request *request) {
+
+ Mutex::Autolock l(mLock);
+ status_t res;
+
+ /** Validation */
+
+ if (mStatus < STATUS_READY) {
+ ALOGE("%s: Can't submit capture requests in state %d", __FUNCTION__,
+ mStatus);
+ return INVALID_OPERATION;
+ }
+
+ if (request == NULL) {
+ ALOGE("%s: NULL request!", __FUNCTION__);
+ return BAD_VALUE;
+ }
+
+ uint32_t frameNumber = request->frame_number;
+
+ if (request->settings == NULL && mPrevSettings.isEmpty()) {
+ ALOGE("%s: Request %d: NULL settings for first request after"
+ "configureStreams()", __FUNCTION__, frameNumber);
+ return BAD_VALUE;
+ }
+
+ if (request->input_buffer != NULL &&
+ request->input_buffer->stream != mInputStream) {
+ ALOGE("%s: Request %d: Input buffer not from input stream!",
+ __FUNCTION__, frameNumber);
+ ALOGV("%s: Bad stream %p, expected: %p",
+ __FUNCTION__, request->input_buffer->stream,
+ mInputStream);
+ ALOGV("%s: Bad stream type %d, expected stream type %d",
+ __FUNCTION__, request->input_buffer->stream->stream_type,
+ mInputStream ? mInputStream->stream_type : -1);
+
+ return BAD_VALUE;
+ }
+
+ if (request->num_output_buffers < 1 || request->output_buffers == NULL) {
+ ALOGE("%s: Request %d: No output buffers provided!",
+ __FUNCTION__, frameNumber);
+ return BAD_VALUE;
+ }
+
+ // Validate all buffers, starting with input buffer if it's given
+
+ ssize_t idx;
+ const camera3_stream_buffer_t *b;
+ if (request->input_buffer != NULL) {
+ idx = -1;
+ b = request->input_buffer;
+ } else {
+ idx = 0;
+ b = request->output_buffers;
+ }
+ do {
+ PrivateStreamInfo *priv =
+ static_cast<PrivateStreamInfo*>(b->stream->priv);
+ if (priv == NULL) {
+ ALOGE("%s: Request %d: Buffer %zu: Unconfigured stream!",
+ __FUNCTION__, frameNumber, idx);
+ return BAD_VALUE;
+ }
+ if (!priv->alive) {
+ ALOGE("%s: Request %d: Buffer %zu: Dead stream!",
+ __FUNCTION__, frameNumber, idx);
+ return BAD_VALUE;
+ }
+ if (b->status != CAMERA3_BUFFER_STATUS_OK) {
+ ALOGE("%s: Request %d: Buffer %zu: Status not OK!",
+ __FUNCTION__, frameNumber, idx);
+ return BAD_VALUE;
+ }
+ if (b->release_fence != -1) {
+ ALOGE("%s: Request %d: Buffer %zu: Has a release fence!",
+ __FUNCTION__, frameNumber, idx);
+ return BAD_VALUE;
+ }
+ if (b->buffer == NULL) {
+ ALOGE("%s: Request %d: Buffer %zu: NULL buffer handle!",
+ __FUNCTION__, frameNumber, idx);
+ return BAD_VALUE;
+ }
+ idx++;
+ b = &(request->output_buffers[idx]);
+ } while (idx < (ssize_t)request->num_output_buffers);
+
+ // TODO: Validate settings parameters
+
+ /**
+ * Start processing this request
+ */
+
+ mStatus = STATUS_ACTIVE;
+
+ CameraMetadata settings;
+
+ if (request->settings == NULL) {
+ settings.acquire(mPrevSettings);
+ } else {
+ settings = request->settings;
+ }
+
+ res = process3A(settings);
+ if (res != OK) {
+ return res;
+ }
+
+ // TODO: Handle reprocessing
+
+ /**
+ * Get ready for sensor config
+ */
+
+ nsecs_t exposureTime;
+ nsecs_t frameDuration;
+ uint32_t sensitivity;
+ bool needJpeg = false;
+ camera_metadata_entry_t entry;
+
+ entry = settings.find(ANDROID_SENSOR_EXPOSURE_TIME);
+ exposureTime = (entry.count > 0) ? entry.data.i64[0] : Sensor::kExposureTimeRange[0];
+ entry = settings.find(ANDROID_SENSOR_FRAME_DURATION);
+ frameDuration = (entry.count > 0)? entry.data.i64[0] : Sensor::kFrameDurationRange[0];
+ entry = settings.find(ANDROID_SENSOR_SENSITIVITY);
+ sensitivity = (entry.count > 0) ? entry.data.i32[0] : Sensor::kSensitivityRange[0];
+
+ if (exposureTime > frameDuration) {
+ frameDuration = exposureTime + Sensor::kMinVerticalBlank;
+ settings.update(ANDROID_SENSOR_FRAME_DURATION, &frameDuration, 1);
+ }
+
+ Buffers *sensorBuffers = new Buffers();
+ HalBufferVector *buffers = new HalBufferVector();
+
+ sensorBuffers->setCapacity(request->num_output_buffers);
+ buffers->setCapacity(request->num_output_buffers);
+
+ // Process all the buffers we got for output, constructing internal buffer
+ // structures for them, and lock them for writing.
+ for (size_t i = 0; i < request->num_output_buffers; i++) {
+ const camera3_stream_buffer &srcBuf = request->output_buffers[i];
+ StreamBuffer destBuf;
+ destBuf.streamId = kGenericStreamId;
+ destBuf.width = srcBuf.stream->width;
+ destBuf.height = srcBuf.stream->height;
+ // For GCE, IMPLEMENTATION_DEFINED is always RGBx_8888
+ destBuf.format = (srcBuf.stream->format == HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED) ?
+ HAL_PIXEL_FORMAT_RGBA_8888 :
+ srcBuf.stream->format;
+ destBuf.stride = srcBuf.stream->width;
+ destBuf.dataSpace = srcBuf.stream->data_space;
+ destBuf.buffer = srcBuf.buffer;
+
+ if (destBuf.format == HAL_PIXEL_FORMAT_BLOB) {
+ needJpeg = true;
+ }
+
+ // Wait on fence
+ sp<Fence> bufferAcquireFence = new Fence(srcBuf.acquire_fence);
+ res = bufferAcquireFence->wait(kFenceTimeoutMs);
+ if (res == TIMED_OUT) {
+ ALOGE("%s: Request %d: Buffer %zu: Fence timed out after %d ms",
+ __FUNCTION__, frameNumber, i, kFenceTimeoutMs);
+ }
+ if (res == OK) {
+ // Lock buffer for writing
+ if (srcBuf.stream->format == HAL_PIXEL_FORMAT_YCbCr_420_888) {
+ if (destBuf.format == HAL_PIXEL_FORMAT_YCbCr_420_888) {
+ android_ycbcr ycbcr = android_ycbcr();
+ res = GrallocModule::getInstance().lock_ycbcr(
+ *(destBuf.buffer),
+ GRALLOC_USAGE_HW_CAMERA_WRITE,
+ 0, 0, destBuf.width, destBuf.height,
+ &ycbcr);
+ // This is only valid because we know that emulator's
+ // YCbCr_420_888 is really contiguous NV21 under the hood
+ destBuf.img = static_cast<uint8_t*>(ycbcr.y);
+ } else {
+ ALOGE("Unexpected private format for flexible YUV: 0x%x",
+ destBuf.format);
+ res = INVALID_OPERATION;
+ }
+ } else {
+ res = GrallocModule::getInstance().lock(
+ *(destBuf.buffer),
+ GRALLOC_USAGE_HW_CAMERA_WRITE,
+ 0, 0, destBuf.width, destBuf.height,
+ (void**)&(destBuf.img));
+
+ }
+ if (res != OK) {
+ ALOGE("%s: Request %d: Buffer %zu: Unable to lock buffer",
+ __FUNCTION__, frameNumber, i);
+ }
+ }
+
+ if (res != OK) {
+ // Either waiting or locking failed. Unlock locked buffers and bail
+ // out.
+ for (size_t j = 0; j < i; j++) {
+ GrallocModule::getInstance().unlock(
+ *(request->output_buffers[i].buffer));
+ }
+ delete sensorBuffers;
+ delete buffers;
+ return NO_INIT;
+ }
+
+ sensorBuffers->push_back(destBuf);
+ buffers->push_back(srcBuf);
+ }
+
+ /**
+ * Wait for JPEG compressor to not be busy, if needed
+ */
+ if (needJpeg) {
+ bool ready = mJpegCompressor->waitForDone(kJpegTimeoutNs);
+ if (!ready) {
+ ALOGE("%s: Timeout waiting for JPEG compression to complete!",
+ __FUNCTION__);
+ return NO_INIT;
+ }
+ res = mJpegCompressor->reserve();
+ if (res != OK) {
+ ALOGE("%s: Error managing JPEG compressor resources, can't reserve it!", __FUNCTION__);
+ return NO_INIT;
+ }
+ }
+
+ /**
+ * Wait until the in-flight queue has room
+ */
+ res = mReadoutThread->waitForReadout();
+ if (res != OK) {
+ ALOGE("%s: Timeout waiting for previous requests to complete!",
+ __FUNCTION__);
+ return NO_INIT;
+ }
+
+ /**
+ * Wait until sensor's ready. This waits for lengthy amounts of time with
+ * mLock held, but the interface spec is that no other calls may by done to
+ * the HAL by the framework while process_capture_request is happening.
+ */
+ int syncTimeoutCount = 0;
+ while(!mSensor->waitForVSync(kSyncWaitTimeout)) {
+ if (mStatus == STATUS_ERROR) {
+ return NO_INIT;
+ }
+ if (syncTimeoutCount == kMaxSyncTimeoutCount) {
+ ALOGE("%s: Request %d: Sensor sync timed out after %" PRId64 " ms",
+ __FUNCTION__, frameNumber,
+ kSyncWaitTimeout * kMaxSyncTimeoutCount / 1000000);
+ return NO_INIT;
+ }
+ syncTimeoutCount++;
+ }
+
+ /**
+ * Configure sensor and queue up the request to the readout thread
+ */
+ mSensor->setExposureTime(exposureTime);
+ mSensor->setFrameDuration(frameDuration);
+ mSensor->setSensitivity(sensitivity);
+ mSensor->setDestinationBuffers(sensorBuffers);
+ mSensor->setFrameNumber(request->frame_number);
+
+ ReadoutThread::Request r;
+ r.frameNumber = request->frame_number;
+ r.settings = settings;
+ r.sensorBuffers = sensorBuffers;
+ r.buffers = buffers;
+
+ mReadoutThread->queueCaptureRequest(r);
+ ALOGVV("%s: Queued frame %d", __FUNCTION__, request->frame_number);
+
+ // Cache the settings for next time
+ mPrevSettings.acquire(settings);
+
+ return OK;
+}
+
+status_t EmulatedFakeCamera3::flush() {
+ ALOGW("%s: Not implemented; ignored", __FUNCTION__);
+ return OK;
+}
+
+/** Debug methods */
+
+void EmulatedFakeCamera3::dump(int fd) {
+
+}
+
+/**
+ * Private methods
+ */
+
+status_t EmulatedFakeCamera3::getCameraCapabilities() {
+
+ const char *key = mFacingBack ? "qemu.sf.back_camera_caps" : "qemu.sf.front_camera_caps";
+
+ /* Defined by 'qemu.sf.*_camera_caps' boot property: if the
+ * property doesn't exist, it is assumed to list FULL. */
+ char prop[PROPERTY_VALUE_MAX];
+ if (property_get(key, prop, NULL) > 0) {
+ char *saveptr = nullptr;
+ char *cap = strtok_r(prop, " ,", &saveptr);
+ while (cap != NULL) {
+ for (int i = 0; i < NUM_CAPABILITIES; i++) {
+ if (!strcasecmp(cap, sAvailableCapabilitiesStrings[i])) {
+ mCapabilities.add(static_cast<AvailableCapabilities>(i));
+ break;
+ }
+ }
+ cap = strtok_r(NULL, " ,", &saveptr);
+ }
+ if (mCapabilities.size() == 0) {
+ ALOGE("qemu.sf.back_camera_caps had no valid capabilities: %s", prop);
+ }
+ }
+ // Default to FULL_LEVEL plus RAW if nothing is defined
+ if (mCapabilities.size() == 0) {
+ mCapabilities.add(FULL_LEVEL);
+ mCapabilities.add(RAW);
+ }
+
+ // Add level-based caps
+ if (hasCapability(FULL_LEVEL)) {
+ mCapabilities.add(BURST_CAPTURE);
+ mCapabilities.add(READ_SENSOR_SETTINGS);
+ mCapabilities.add(MANUAL_SENSOR);
+ mCapabilities.add(MANUAL_POST_PROCESSING);
+ };
+
+ // Backwards-compatible is required for most other caps
+ // Not required for DEPTH_OUTPUT, though.
+ if (hasCapability(BURST_CAPTURE) ||
+ hasCapability(READ_SENSOR_SETTINGS) ||
+ hasCapability(RAW) ||
+ hasCapability(MANUAL_SENSOR) ||
+ hasCapability(MANUAL_POST_PROCESSING) ||
+ hasCapability(PRIVATE_REPROCESSING) ||
+ hasCapability(YUV_REPROCESSING) ||
+ hasCapability(CONSTRAINED_HIGH_SPEED_VIDEO)) {
+ mCapabilities.add(BACKWARD_COMPATIBLE);
+ }
+
+ ALOGI("Camera %d capabilities:", mCameraID);
+ for (size_t i = 0; i < mCapabilities.size(); i++) {
+ ALOGI(" %s", sAvailableCapabilitiesStrings[mCapabilities[i]]);
+ }
+
+ return OK;
+}
+
+bool EmulatedFakeCamera3::hasCapability(AvailableCapabilities cap) {
+ ssize_t idx = mCapabilities.indexOf(cap);
+ return idx >= 0;
+}
+
+status_t EmulatedFakeCamera3::constructStaticInfo(
+ const cvd::CameraDefinition& params) {
+
+ CameraMetadata info;
+ Vector<int32_t> availableCharacteristicsKeys;
+ status_t res;
+
+ char* param_end;
+ int32_t width = 0, height = 0;
+
+ /* TODO(ender): this currently supports only maximum resolution. */
+ for (size_t index = 0; index < params.resolutions.size(); ++index) {
+ if (width <= params.resolutions[index].width &&
+ height <= params.resolutions[index].height) {
+ width = params.resolutions[index].width;
+ height = params.resolutions[index].height;
+ }
+ }
+
+ if (width < 640 || height < 480) {
+ width = 640;
+ height = 480;
+ }
+
+ mSensorWidth = width;
+ mSensorHeight = height;
+
+#define ADD_STATIC_ENTRY(name, varptr, count) \
+ availableCharacteristicsKeys.add(name); \
+ res = info.update(name, varptr, count); \
+ if (res != OK) return res
+
+ // android.sensor
+
+ if (hasCapability(MANUAL_SENSOR)) {
+
+ ADD_STATIC_ENTRY(ANDROID_SENSOR_INFO_EXPOSURE_TIME_RANGE,
+ Sensor::kExposureTimeRange, 2);
+
+ ADD_STATIC_ENTRY(ANDROID_SENSOR_INFO_MAX_FRAME_DURATION,
+ &Sensor::kFrameDurationRange[1], 1);
+
+ ADD_STATIC_ENTRY(ANDROID_SENSOR_INFO_SENSITIVITY_RANGE,
+ Sensor::kSensitivityRange,
+ sizeof(Sensor::kSensitivityRange)
+ /sizeof(int32_t));
+
+ ADD_STATIC_ENTRY(ANDROID_SENSOR_MAX_ANALOG_SENSITIVITY,
+ &Sensor::kSensitivityRange[1], 1);
+ }
+
+ static const float sensorPhysicalSize[2] = {3.20f, 2.40f}; // mm
+ ADD_STATIC_ENTRY(ANDROID_SENSOR_INFO_PHYSICAL_SIZE,
+ sensorPhysicalSize, 2);
+
+ const int32_t pixelArray[] = {mSensorWidth, mSensorHeight};
+ ADD_STATIC_ENTRY(ANDROID_SENSOR_INFO_PIXEL_ARRAY_SIZE,
+ pixelArray, 2);
+ const int32_t activeArray[] = {0, 0, mSensorWidth, mSensorHeight};
+ ADD_STATIC_ENTRY(ANDROID_SENSOR_INFO_ACTIVE_ARRAY_SIZE,
+ activeArray, 4);
+
+ static const int32_t orientation = 90; // Aligned with 'long edge'
+ ADD_STATIC_ENTRY(ANDROID_SENSOR_ORIENTATION, &orientation, 1);
+
+ static const uint8_t timestampSource = ANDROID_SENSOR_INFO_TIMESTAMP_SOURCE_REALTIME;
+ ADD_STATIC_ENTRY(ANDROID_SENSOR_INFO_TIMESTAMP_SOURCE, ×tampSource, 1);
+
+ if (hasCapability(RAW)) {
+ ADD_STATIC_ENTRY(ANDROID_SENSOR_INFO_COLOR_FILTER_ARRANGEMENT,
+ &Sensor::kColorFilterArrangement, 1);
+
+ ADD_STATIC_ENTRY(ANDROID_SENSOR_INFO_WHITE_LEVEL,
+ (int32_t*)&Sensor::kMaxRawValue, 1);
+
+ static const int32_t blackLevelPattern[4] = {
+ (int32_t)Sensor::kBlackLevel, (int32_t)Sensor::kBlackLevel,
+ (int32_t)Sensor::kBlackLevel, (int32_t)Sensor::kBlackLevel
+ };
+ ADD_STATIC_ENTRY(ANDROID_SENSOR_BLACK_LEVEL_PATTERN,
+ blackLevelPattern, sizeof(blackLevelPattern)/sizeof(int32_t));
+ }
+
+ if (hasCapability(BACKWARD_COMPATIBLE)) {
+ static const int32_t availableTestPatternModes[] = {
+ ANDROID_SENSOR_TEST_PATTERN_MODE_OFF
+ };
+ ADD_STATIC_ENTRY(ANDROID_SENSOR_AVAILABLE_TEST_PATTERN_MODES,
+ availableTestPatternModes, sizeof(availableTestPatternModes)/sizeof(int32_t));
+ }
+
+ // android.lens
+
+ static const float focalLength = 3.30f; // mm
+ ADD_STATIC_ENTRY(ANDROID_LENS_INFO_AVAILABLE_FOCAL_LENGTHS,
+ &focalLength, 1);
+
+ if (hasCapability(BACKWARD_COMPATIBLE)) {
+ // 5 cm min focus distance for back camera, infinity (fixed focus) for front
+ const float minFocusDistance = mFacingBack ? 1.0/0.05 : 0.0;
+ ADD_STATIC_ENTRY(ANDROID_LENS_INFO_MINIMUM_FOCUS_DISTANCE,
+ &minFocusDistance, 1);
+
+ // 5 m hyperfocal distance for back camera, infinity (fixed focus) for front
+ const float hyperFocalDistance = mFacingBack ? 1.0/5.0 : 0.0;
+ ADD_STATIC_ENTRY(ANDROID_LENS_INFO_HYPERFOCAL_DISTANCE,
+ &hyperFocalDistance, 1);
+
+ static const float aperture = 2.8f;
+ ADD_STATIC_ENTRY(ANDROID_LENS_INFO_AVAILABLE_APERTURES,
+ &aperture, 1);
+ static const float filterDensity = 0;
+ ADD_STATIC_ENTRY(ANDROID_LENS_INFO_AVAILABLE_FILTER_DENSITIES,
+ &filterDensity, 1);
+ static const uint8_t availableOpticalStabilization =
+ ANDROID_LENS_OPTICAL_STABILIZATION_MODE_OFF;
+ ADD_STATIC_ENTRY(ANDROID_LENS_INFO_AVAILABLE_OPTICAL_STABILIZATION,
+ &availableOpticalStabilization, 1);
+
+ static const int32_t lensShadingMapSize[] = {1, 1};
+ ADD_STATIC_ENTRY(ANDROID_LENS_INFO_SHADING_MAP_SIZE, lensShadingMapSize,
+ sizeof(lensShadingMapSize)/sizeof(int32_t));
+
+ static const uint8_t lensFocusCalibration =
+ ANDROID_LENS_INFO_FOCUS_DISTANCE_CALIBRATION_APPROXIMATE;
+ ADD_STATIC_ENTRY(ANDROID_LENS_INFO_FOCUS_DISTANCE_CALIBRATION, &lensFocusCalibration, 1);
+ }
+
+ if (hasCapability(DEPTH_OUTPUT)) {
+ // These could be included for non-DEPTH capability as well, but making this variable for
+ // testing coverage
+
+ // 90 degree rotation to align with long edge of a phone device that's by default portrait
+ static const float qO[] = { 0.707107f, 0.f, 0.f, 0.707107f};
+
+ // Either a 180-degree rotation for back-facing, or no rotation for front-facing
+ const float qF[] = {0, (mFacingBack ? 1.f : 0.f), 0, (mFacingBack ? 0.f : 1.f)};
+
+ // Quarternion product, orientation change then facing
+ const float lensPoseRotation[] = {qO[0]*qF[0] - qO[1]*qF[1] - qO[2]*qF[2] - qO[3]*qF[3],
+ qO[0]*qF[1] + qO[1]*qF[0] + qO[2]*qF[3] - qO[3]*qF[2],
+ qO[0]*qF[2] + qO[2]*qF[0] + qO[1]*qF[3] - qO[3]*qF[1],
+ qO[0]*qF[3] + qO[3]*qF[0] + qO[1]*qF[2] - qO[2]*qF[1]};
+
+ ADD_STATIC_ENTRY(ANDROID_LENS_POSE_ROTATION, lensPoseRotation,
+ sizeof(lensPoseRotation)/sizeof(float));
+
+ // Only one camera facing each way, so 0 translation needed to the center of the 'main'
+ // camera
+ static const float lensPoseTranslation[] = {0.f, 0.f, 0.f};
+
+ ADD_STATIC_ENTRY(ANDROID_LENS_POSE_TRANSLATION, lensPoseTranslation,
+ sizeof(lensPoseTranslation)/sizeof(float));
+
+ // Intrinsics are 'ideal' (f_x, f_y, c_x, c_y, s) match focal length and active array size
+ float f_x = focalLength * mSensorWidth / sensorPhysicalSize[0];
+ float f_y = focalLength * mSensorHeight / sensorPhysicalSize[1];
+ float c_x = mSensorWidth / 2.f;
+ float c_y = mSensorHeight / 2.f;
+ float s = 0.f;
+ const float lensIntrinsics[] = { f_x, f_y, c_x, c_y, s };
+
+ ADD_STATIC_ENTRY(ANDROID_LENS_INTRINSIC_CALIBRATION, lensIntrinsics,
+ sizeof(lensIntrinsics)/sizeof(float));
+
+ // No radial or tangential distortion
+
+ float lensRadialDistortion[] = {1.0f, 0.f, 0.f, 0.f, 0.f, 0.f};
+
+ ADD_STATIC_ENTRY(ANDROID_LENS_RADIAL_DISTORTION, lensRadialDistortion,
+ sizeof(lensRadialDistortion)/sizeof(float));
+
+ }
+
+
+ const uint8_t lensFacing = mFacingBack ?
+ ANDROID_LENS_FACING_BACK : ANDROID_LENS_FACING_FRONT;
+ ADD_STATIC_ENTRY(ANDROID_LENS_FACING, &lensFacing, 1);
+
+ // android.flash
+
+ const uint8_t flashAvailable = mFacingBack;
+ ADD_STATIC_ENTRY(ANDROID_FLASH_INFO_AVAILABLE, &flashAvailable, 1);
+
+ // android.tonemap
+
+ if (hasCapability(MANUAL_POST_PROCESSING)) {
+ static const int32_t tonemapCurvePoints = 128;
+ ADD_STATIC_ENTRY(ANDROID_TONEMAP_MAX_CURVE_POINTS, &tonemapCurvePoints, 1);
+
+ static const uint8_t availableToneMapModes[] = {
+ ANDROID_TONEMAP_MODE_CONTRAST_CURVE, ANDROID_TONEMAP_MODE_FAST,
+ ANDROID_TONEMAP_MODE_HIGH_QUALITY
+ };
+ ADD_STATIC_ENTRY(ANDROID_TONEMAP_AVAILABLE_TONE_MAP_MODES, availableToneMapModes,
+ sizeof(availableToneMapModes));
+ }
+
+ // android.scaler
+
+ const std::vector<int32_t> availableStreamConfigurationsBasic = {
+ HAL_PIXEL_FORMAT_BLOB, width, height, ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_OUTPUT,
+ HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED, 320, 240, ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_OUTPUT,
+ HAL_PIXEL_FORMAT_YCbCr_420_888, 320, 240, ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_OUTPUT,
+ HAL_PIXEL_FORMAT_BLOB, 320, 240, ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_OUTPUT,
+ };
+
+ // Always need to include 640x480 in basic formats
+ const std::vector<int32_t> availableStreamConfigurationsBasic640 = {
+ HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED, 640, 480, ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_OUTPUT,
+ HAL_PIXEL_FORMAT_YCbCr_420_888, 640, 480, ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_OUTPUT,
+ HAL_PIXEL_FORMAT_BLOB, 640, 480, ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_OUTPUT
+ };
+
+ const std::vector<int32_t> availableStreamConfigurationsRaw = {
+ HAL_PIXEL_FORMAT_RAW16, width, height, ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_OUTPUT,
+ };
+
+ const std::vector<int32_t> availableStreamConfigurationsBurst = {
+ HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED, width, height, ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_OUTPUT,
+ HAL_PIXEL_FORMAT_YCbCr_420_888, width, height, ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_OUTPUT,
+ HAL_PIXEL_FORMAT_RGBA_8888, width, height, ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_OUTPUT,
+ };
+
+ std::vector<int32_t> availableStreamConfigurations;
+
+ if (hasCapability(BACKWARD_COMPATIBLE)) {
+ availableStreamConfigurations.insert(availableStreamConfigurations.end(),
+ availableStreamConfigurationsBasic.begin(),
+ availableStreamConfigurationsBasic.end());
+ if (width > 640) {
+ availableStreamConfigurations.insert(availableStreamConfigurations.end(),
+ availableStreamConfigurationsBasic640.begin(),
+ availableStreamConfigurationsBasic640.end());
+ }
+ }
+ if (hasCapability(RAW)) {
+ availableStreamConfigurations.insert(availableStreamConfigurations.end(),
+ availableStreamConfigurationsRaw.begin(),
+ availableStreamConfigurationsRaw.end());
+ }
+ if (hasCapability(BURST_CAPTURE)) {
+ availableStreamConfigurations.insert(availableStreamConfigurations.end(),
+ availableStreamConfigurationsBurst.begin(),
+ availableStreamConfigurationsBurst.end());
+ }
+
+ if (availableStreamConfigurations.size() > 0) {
+ ADD_STATIC_ENTRY(ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS,
+ &availableStreamConfigurations[0],
+ availableStreamConfigurations.size());
+ }
+
+ const std::vector<int64_t> availableMinFrameDurationsBasic = {
+ HAL_PIXEL_FORMAT_BLOB, width, height, Sensor::kFrameDurationRange[0],
+ HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED, 320, 240, Sensor::kFrameDurationRange[0],
+ HAL_PIXEL_FORMAT_YCbCr_420_888, 320, 240, Sensor::kFrameDurationRange[0],
+ HAL_PIXEL_FORMAT_BLOB, 320, 240, Sensor::kFrameDurationRange[0],
+ };
+
+ // Always need to include 640x480 in basic formats
+ const std::vector<int64_t> availableMinFrameDurationsBasic640 = {
+ HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED, 640, 480, Sensor::kFrameDurationRange[0],
+ HAL_PIXEL_FORMAT_YCbCr_420_888, 640, 480, Sensor::kFrameDurationRange[0],
+ HAL_PIXEL_FORMAT_BLOB, 640, 480, Sensor::kFrameDurationRange[0]
+ };
+
+ const std::vector<int64_t> availableMinFrameDurationsRaw = {
+ HAL_PIXEL_FORMAT_RAW16, width, height, Sensor::kFrameDurationRange[0],
+ };
+
+ const std::vector<int64_t> availableMinFrameDurationsBurst = {
+ HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED, width, height, Sensor::kFrameDurationRange[0],
+ HAL_PIXEL_FORMAT_YCbCr_420_888, width, height, Sensor::kFrameDurationRange[0],
+ HAL_PIXEL_FORMAT_RGBA_8888, width, height, Sensor::kFrameDurationRange[0],
+ };
+
+ std::vector<int64_t> availableMinFrameDurations;
+
+ if (hasCapability(BACKWARD_COMPATIBLE)) {
+ availableMinFrameDurations.insert(availableMinFrameDurations.end(),
+ availableMinFrameDurationsBasic.begin(),
+ availableMinFrameDurationsBasic.end());
+ if (width > 640) {
+ availableMinFrameDurations.insert(availableMinFrameDurations.end(),
+ availableMinFrameDurationsBasic640.begin(),
+ availableMinFrameDurationsBasic640.end());
+ }
+ }
+ if (hasCapability(RAW)) {
+ availableMinFrameDurations.insert(availableMinFrameDurations.end(),
+ availableMinFrameDurationsRaw.begin(),
+ availableMinFrameDurationsRaw.end());
+ }
+ if (hasCapability(BURST_CAPTURE)) {
+ availableMinFrameDurations.insert(availableMinFrameDurations.end(),
+ availableMinFrameDurationsBurst.begin(),
+ availableMinFrameDurationsBurst.end());
+ }
+
+ if (availableMinFrameDurations.size() > 0) {
+ ADD_STATIC_ENTRY(ANDROID_SCALER_AVAILABLE_MIN_FRAME_DURATIONS,
+ &availableMinFrameDurations[0],
+ availableMinFrameDurations.size());
+ }
+
+ const std::vector<int64_t> availableStallDurationsBasic = {
+ HAL_PIXEL_FORMAT_BLOB, width, height, Sensor::kFrameDurationRange[0],
+ HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED, 320, 240, 0,
+ HAL_PIXEL_FORMAT_YCbCr_420_888, 320, 240, 0,
+ HAL_PIXEL_FORMAT_RGBA_8888, 320, 240, 0,
+ };
+
+ // Always need to include 640x480 in basic formats
+ const std::vector<int64_t> availableStallDurationsBasic640 = {
+ HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED, 640, 480, 0,
+ HAL_PIXEL_FORMAT_YCbCr_420_888, 640, 480, 0,
+ HAL_PIXEL_FORMAT_BLOB, 640, 480, Sensor::kFrameDurationRange[0]
+ };
+
+ const std::vector<int64_t> availableStallDurationsRaw = {
+ HAL_PIXEL_FORMAT_RAW16, width, height, Sensor::kFrameDurationRange[0]
+ };
+ const std::vector<int64_t> availableStallDurationsBurst = {
+ HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED, width, height, 0,
+ HAL_PIXEL_FORMAT_YCbCr_420_888, width, height, 0,
+ HAL_PIXEL_FORMAT_RGBA_8888, width, height, 0
+ };
+
+ std::vector<int64_t> availableStallDurations;
+
+ if (hasCapability(BACKWARD_COMPATIBLE)) {
+ availableStallDurations.insert(availableStallDurations.end(),
+ availableStallDurationsBasic.begin(),
+ availableStallDurationsBasic.end());
+ if (width > 640) {
+ availableStallDurations.insert(availableStallDurations.end(),
+ availableStallDurationsBasic640.begin(),
+ availableStallDurationsBasic640.end());
+ }
+ }
+ if (hasCapability(RAW)) {
+ availableStallDurations.insert(availableStallDurations.end(),
+ availableStallDurationsRaw.begin(),
+ availableStallDurationsRaw.end());
+ }
+ if (hasCapability(BURST_CAPTURE)) {
+ availableStallDurations.insert(availableStallDurations.end(),
+ availableStallDurationsBurst.begin(),
+ availableStallDurationsBurst.end());
+ }
+
+ if (availableStallDurations.size() > 0) {
+ ADD_STATIC_ENTRY(ANDROID_SCALER_AVAILABLE_STALL_DURATIONS,
+ &availableStallDurations[0],
+ availableStallDurations.size());
+ }
+
+ if (hasCapability(BACKWARD_COMPATIBLE)) {
+ static const uint8_t croppingType = ANDROID_SCALER_CROPPING_TYPE_FREEFORM;
+ ADD_STATIC_ENTRY(ANDROID_SCALER_CROPPING_TYPE,
+ &croppingType, 1);
+
+ static const float maxZoom = 10;
+ ADD_STATIC_ENTRY(ANDROID_SCALER_AVAILABLE_MAX_DIGITAL_ZOOM,
+ &maxZoom, 1);
+ }
+
+ // android.jpeg
+
+ if (hasCapability(BACKWARD_COMPATIBLE)) {
+ static const int32_t jpegThumbnailSizes[] = {
+ 0, 0,
+ 160, 120,
+ 320, 240
+ };
+ ADD_STATIC_ENTRY(ANDROID_JPEG_AVAILABLE_THUMBNAIL_SIZES,
+ jpegThumbnailSizes, sizeof(jpegThumbnailSizes)/sizeof(int32_t));
+
+ static const int32_t jpegMaxSize = JpegCompressor::kMaxJpegSize;
+ ADD_STATIC_ENTRY(ANDROID_JPEG_MAX_SIZE, &jpegMaxSize, 1);
+ }
+
+ // android.stats
+
+ if (hasCapability(BACKWARD_COMPATIBLE)) {
+ static const uint8_t availableFaceDetectModes[] = {
+ ANDROID_STATISTICS_FACE_DETECT_MODE_OFF,
+ ANDROID_STATISTICS_FACE_DETECT_MODE_SIMPLE,
+ ANDROID_STATISTICS_FACE_DETECT_MODE_FULL
+ };
+ ADD_STATIC_ENTRY(ANDROID_STATISTICS_INFO_AVAILABLE_FACE_DETECT_MODES,
+ availableFaceDetectModes,
+ sizeof(availableFaceDetectModes));
+
+ static const int32_t maxFaceCount = 8;
+ ADD_STATIC_ENTRY(ANDROID_STATISTICS_INFO_MAX_FACE_COUNT,
+ &maxFaceCount, 1);
+
+
+ static const uint8_t availableShadingMapModes[] = {
+ ANDROID_STATISTICS_LENS_SHADING_MAP_MODE_OFF
+ };
+ ADD_STATIC_ENTRY(ANDROID_STATISTICS_INFO_AVAILABLE_LENS_SHADING_MAP_MODES,
+ availableShadingMapModes, sizeof(availableShadingMapModes));
+ }
+
+ // android.sync
+
+ static const int32_t maxLatency =
+ hasCapability(FULL_LEVEL) ? ANDROID_SYNC_MAX_LATENCY_PER_FRAME_CONTROL : 3;
+ ADD_STATIC_ENTRY(ANDROID_SYNC_MAX_LATENCY, &maxLatency, 1);
+
+ // android.control
+
+ if (hasCapability(BACKWARD_COMPATIBLE)) {
+ static const uint8_t availableControlModes[] = {
+ ANDROID_CONTROL_MODE_OFF, ANDROID_CONTROL_MODE_AUTO, ANDROID_CONTROL_MODE_USE_SCENE_MODE
+ };
+ ADD_STATIC_ENTRY(ANDROID_CONTROL_AVAILABLE_MODES,
+ availableControlModes, sizeof(availableControlModes));
+ } else {
+ static const uint8_t availableControlModes[] = {
+ ANDROID_CONTROL_MODE_AUTO
+ };
+ ADD_STATIC_ENTRY(ANDROID_CONTROL_AVAILABLE_MODES,
+ availableControlModes, sizeof(availableControlModes));
+ }
+
+ static const uint8_t availableSceneModes[] = {
+ hasCapability(BACKWARD_COMPATIBLE) ?
+ ANDROID_CONTROL_SCENE_MODE_FACE_PRIORITY :
+ ANDROID_CONTROL_SCENE_MODE_DISABLED
+ };
+ ADD_STATIC_ENTRY(ANDROID_CONTROL_AVAILABLE_SCENE_MODES,
+ availableSceneModes, sizeof(availableSceneModes));
+
+ if (hasCapability(BACKWARD_COMPATIBLE)) {
+ static const uint8_t availableEffects[] = {
+ ANDROID_CONTROL_EFFECT_MODE_OFF
+ };
+ ADD_STATIC_ENTRY(ANDROID_CONTROL_AVAILABLE_EFFECTS,
+ availableEffects, sizeof(availableEffects));
+ }
+
+ if (hasCapability(BACKWARD_COMPATIBLE)) {
+ static const int32_t max3aRegions[] = {/*AE*/ 1,/*AWB*/ 0,/*AF*/ 1};
+ ADD_STATIC_ENTRY(ANDROID_CONTROL_MAX_REGIONS,
+ max3aRegions, sizeof(max3aRegions)/sizeof(max3aRegions[0]));
+
+ static const uint8_t availableAeModes[] = {
+ ANDROID_CONTROL_AE_MODE_OFF,
+ ANDROID_CONTROL_AE_MODE_ON
+ };
+ ADD_STATIC_ENTRY(ANDROID_CONTROL_AE_AVAILABLE_MODES,
+ availableAeModes, sizeof(availableAeModes));
+
+ static const camera_metadata_rational exposureCompensationStep = {
+ 1, 3
+ };
+ ADD_STATIC_ENTRY(ANDROID_CONTROL_AE_COMPENSATION_STEP,
+ &exposureCompensationStep, 1);
+
+ int32_t exposureCompensationRange[] = {-9, 9};
+ ADD_STATIC_ENTRY(ANDROID_CONTROL_AE_COMPENSATION_RANGE,
+ exposureCompensationRange,
+ sizeof(exposureCompensationRange)/sizeof(int32_t));
+ }
+
+ static const int32_t availableTargetFpsRanges[] = {
+ 5, 30, 15, 30, 15, 15, 30, 30
+ };
+ ADD_STATIC_ENTRY(ANDROID_CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES,
+ availableTargetFpsRanges,
+ sizeof(availableTargetFpsRanges)/sizeof(int32_t));
+
+ if (hasCapability(BACKWARD_COMPATIBLE)) {
+ static const uint8_t availableAntibandingModes[] = {
+ ANDROID_CONTROL_AE_ANTIBANDING_MODE_OFF,
+ ANDROID_CONTROL_AE_ANTIBANDING_MODE_AUTO
+ };
+ ADD_STATIC_ENTRY(ANDROID_CONTROL_AE_AVAILABLE_ANTIBANDING_MODES,
+ availableAntibandingModes, sizeof(availableAntibandingModes));
+ }
+
+ static const uint8_t aeLockAvailable = hasCapability(BACKWARD_COMPATIBLE) ?
+ ANDROID_CONTROL_AE_LOCK_AVAILABLE_TRUE : ANDROID_CONTROL_AE_LOCK_AVAILABLE_FALSE;
+
+ ADD_STATIC_ENTRY(ANDROID_CONTROL_AE_LOCK_AVAILABLE,
+ &aeLockAvailable, 1);
+
+ if (hasCapability(BACKWARD_COMPATIBLE)) {
+ static const uint8_t availableAwbModes[] = {
+ ANDROID_CONTROL_AWB_MODE_OFF,
+ ANDROID_CONTROL_AWB_MODE_AUTO,
+ ANDROID_CONTROL_AWB_MODE_INCANDESCENT,
+ ANDROID_CONTROL_AWB_MODE_FLUORESCENT,
+ ANDROID_CONTROL_AWB_MODE_DAYLIGHT,
+ ANDROID_CONTROL_AWB_MODE_SHADE
+ };
+ ADD_STATIC_ENTRY(ANDROID_CONTROL_AWB_AVAILABLE_MODES,
+ availableAwbModes, sizeof(availableAwbModes));
+ }
+
+ static const uint8_t awbLockAvailable = hasCapability(BACKWARD_COMPATIBLE) ?
+ ANDROID_CONTROL_AWB_LOCK_AVAILABLE_TRUE : ANDROID_CONTROL_AWB_LOCK_AVAILABLE_FALSE;
+
+ ADD_STATIC_ENTRY(ANDROID_CONTROL_AWB_LOCK_AVAILABLE,
+ &awbLockAvailable, 1);
+
+ static const uint8_t availableAfModesBack[] = {
+ ANDROID_CONTROL_AF_MODE_OFF,
+ ANDROID_CONTROL_AF_MODE_AUTO,
+ ANDROID_CONTROL_AF_MODE_MACRO,
+ ANDROID_CONTROL_AF_MODE_CONTINUOUS_VIDEO,
+ ANDROID_CONTROL_AF_MODE_CONTINUOUS_PICTURE
+ };
+
+ static const uint8_t availableAfModesFront[] = {
+ ANDROID_CONTROL_AF_MODE_OFF
+ };
+
+ if (mFacingBack && hasCapability(BACKWARD_COMPATIBLE)) {
+ ADD_STATIC_ENTRY(ANDROID_CONTROL_AF_AVAILABLE_MODES,
+ availableAfModesBack, sizeof(availableAfModesBack));
+ } else {
+ ADD_STATIC_ENTRY(ANDROID_CONTROL_AF_AVAILABLE_MODES,
+ availableAfModesFront, sizeof(availableAfModesFront));
+ }
+
+ static const uint8_t availableVstabModes[] = {
+ ANDROID_CONTROL_VIDEO_STABILIZATION_MODE_OFF
+ };
+ ADD_STATIC_ENTRY(ANDROID_CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES,
+ availableVstabModes, sizeof(availableVstabModes));
+
+ // android.colorCorrection
+
+ if (hasCapability(BACKWARD_COMPATIBLE)) {
+ static const uint8_t availableAberrationModes[] = {
+ ANDROID_COLOR_CORRECTION_ABERRATION_MODE_OFF,
+ ANDROID_COLOR_CORRECTION_ABERRATION_MODE_FAST,
+ ANDROID_COLOR_CORRECTION_ABERRATION_MODE_HIGH_QUALITY
+ };
+ ADD_STATIC_ENTRY(ANDROID_COLOR_CORRECTION_AVAILABLE_ABERRATION_MODES,
+ availableAberrationModes, sizeof(availableAberrationModes));
+ } else {
+ static const uint8_t availableAberrationModes[] = {
+ ANDROID_COLOR_CORRECTION_ABERRATION_MODE_OFF,
+ };
+ ADD_STATIC_ENTRY(ANDROID_COLOR_CORRECTION_AVAILABLE_ABERRATION_MODES,
+ availableAberrationModes, sizeof(availableAberrationModes));
+ }
+ // android.edge
+
+ if (hasCapability(BACKWARD_COMPATIBLE)) {
+ static const uint8_t availableEdgeModes[] = {
+ ANDROID_EDGE_MODE_OFF, ANDROID_EDGE_MODE_FAST, ANDROID_EDGE_MODE_HIGH_QUALITY
+ };
+ ADD_STATIC_ENTRY(ANDROID_EDGE_AVAILABLE_EDGE_MODES,
+ availableEdgeModes, sizeof(availableEdgeModes));
+ } else {
+ static const uint8_t availableEdgeModes[] = {
+ ANDROID_EDGE_MODE_OFF
+ };
+ ADD_STATIC_ENTRY(ANDROID_EDGE_AVAILABLE_EDGE_MODES,
+ availableEdgeModes, sizeof(availableEdgeModes));
+ }
+
+ // android.info
+
+ static const uint8_t supportedHardwareLevel =
+ hasCapability(FULL_LEVEL) ? ANDROID_INFO_SUPPORTED_HARDWARE_LEVEL_FULL :
+ ANDROID_INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED;
+ ADD_STATIC_ENTRY(ANDROID_INFO_SUPPORTED_HARDWARE_LEVEL,
+ &supportedHardwareLevel,
+ /*count*/1);
+
+ // android.noiseReduction
+
+ if (hasCapability(BACKWARD_COMPATIBLE)) {
+ static const uint8_t availableNoiseReductionModes[] = {
+ ANDROID_NOISE_REDUCTION_MODE_OFF,
+ ANDROID_NOISE_REDUCTION_MODE_FAST,
+ ANDROID_NOISE_REDUCTION_MODE_HIGH_QUALITY
+ };
+ ADD_STATIC_ENTRY(ANDROID_NOISE_REDUCTION_AVAILABLE_NOISE_REDUCTION_MODES,
+ availableNoiseReductionModes, sizeof(availableNoiseReductionModes));
+ } else {
+ static const uint8_t availableNoiseReductionModes[] = {
+ ANDROID_NOISE_REDUCTION_MODE_OFF,
+ };
+ ADD_STATIC_ENTRY(ANDROID_NOISE_REDUCTION_AVAILABLE_NOISE_REDUCTION_MODES,
+ availableNoiseReductionModes, sizeof(availableNoiseReductionModes));
+ }
+
+ // android.depth
+
+ if (hasCapability(DEPTH_OUTPUT)) {
+
+ static const int32_t maxDepthSamples = 100;
+ ADD_STATIC_ENTRY(ANDROID_DEPTH_MAX_DEPTH_SAMPLES,
+ &maxDepthSamples, 1);
+
+ static const int32_t availableDepthStreamConfigurations[] = {
+ HAL_PIXEL_FORMAT_Y16, 160, 120, ANDROID_DEPTH_AVAILABLE_DEPTH_STREAM_CONFIGURATIONS_OUTPUT,
+ HAL_PIXEL_FORMAT_BLOB, maxDepthSamples,1, ANDROID_DEPTH_AVAILABLE_DEPTH_STREAM_CONFIGURATIONS_OUTPUT
+ };
+ ADD_STATIC_ENTRY(ANDROID_DEPTH_AVAILABLE_DEPTH_STREAM_CONFIGURATIONS,
+ availableDepthStreamConfigurations,
+ sizeof(availableDepthStreamConfigurations)/sizeof(int32_t));
+
+ static const int64_t availableDepthMinFrameDurations[] = {
+ HAL_PIXEL_FORMAT_Y16, 160, 120, Sensor::kFrameDurationRange[0],
+ HAL_PIXEL_FORMAT_BLOB, maxDepthSamples,1, Sensor::kFrameDurationRange[0]
+ };
+ ADD_STATIC_ENTRY(ANDROID_DEPTH_AVAILABLE_DEPTH_MIN_FRAME_DURATIONS,
+ availableDepthMinFrameDurations,
+ sizeof(availableDepthMinFrameDurations)/sizeof(int64_t));
+
+ static const int64_t availableDepthStallDurations[] = {
+ HAL_PIXEL_FORMAT_Y16, 160, 120, Sensor::kFrameDurationRange[0],
+ HAL_PIXEL_FORMAT_BLOB, maxDepthSamples,1, Sensor::kFrameDurationRange[0]
+ };
+ ADD_STATIC_ENTRY(ANDROID_DEPTH_AVAILABLE_DEPTH_STALL_DURATIONS,
+ availableDepthStallDurations,
+ sizeof(availableDepthStallDurations)/sizeof(int64_t));
+
+ uint8_t depthIsExclusive = ANDROID_DEPTH_DEPTH_IS_EXCLUSIVE_FALSE;
+ ADD_STATIC_ENTRY(ANDROID_DEPTH_DEPTH_IS_EXCLUSIVE,
+ &depthIsExclusive, 1);
+ }
+
+ // android.shading
+
+ if (hasCapability(BACKWARD_COMPATIBLE)) {
+ static const uint8_t availableShadingModes[] = {
+ ANDROID_SHADING_MODE_OFF, ANDROID_SHADING_MODE_FAST, ANDROID_SHADING_MODE_HIGH_QUALITY
+ };
+ ADD_STATIC_ENTRY(ANDROID_SHADING_AVAILABLE_MODES, availableShadingModes,
+ sizeof(availableShadingModes));
+ } else {
+ static const uint8_t availableShadingModes[] = {
+ ANDROID_SHADING_MODE_OFF
+ };
+ ADD_STATIC_ENTRY(ANDROID_SHADING_AVAILABLE_MODES, availableShadingModes,
+ sizeof(availableShadingModes));
+ }
+
+ // android.request
+
+ static const int32_t maxNumOutputStreams[] = {
+ kMaxRawStreamCount, kMaxProcessedStreamCount, kMaxJpegStreamCount
+ };
+ ADD_STATIC_ENTRY(ANDROID_REQUEST_MAX_NUM_OUTPUT_STREAMS, maxNumOutputStreams, 3);
+
+ static const uint8_t maxPipelineDepth = kMaxBufferCount;
+ ADD_STATIC_ENTRY(ANDROID_REQUEST_PIPELINE_MAX_DEPTH, &maxPipelineDepth, 1);
+
+ static const int32_t partialResultCount = 1;
+ ADD_STATIC_ENTRY(ANDROID_REQUEST_PARTIAL_RESULT_COUNT,
+ &partialResultCount, /*count*/1);
+
+ SortedVector<uint8_t> caps;
+ for (size_t i = 0; i < mCapabilities.size(); i++) {
+ switch(mCapabilities[i]) {
+ case BACKWARD_COMPATIBLE:
+ caps.add(ANDROID_REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE);
+ break;
+ case MANUAL_SENSOR:
+ caps.add(ANDROID_REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR);
+ break;
+ case MANUAL_POST_PROCESSING:
+ caps.add(ANDROID_REQUEST_AVAILABLE_CAPABILITIES_MANUAL_POST_PROCESSING);
+ break;
+ case RAW:
+ caps.add(ANDROID_REQUEST_AVAILABLE_CAPABILITIES_RAW);
+ break;
+ case PRIVATE_REPROCESSING:
+ caps.add(ANDROID_REQUEST_AVAILABLE_CAPABILITIES_PRIVATE_REPROCESSING);
+ break;
+ case READ_SENSOR_SETTINGS:
+ caps.add(ANDROID_REQUEST_AVAILABLE_CAPABILITIES_READ_SENSOR_SETTINGS);
+ break;
+ case BURST_CAPTURE:
+ caps.add(ANDROID_REQUEST_AVAILABLE_CAPABILITIES_BURST_CAPTURE);
+ break;
+ case YUV_REPROCESSING:
+ caps.add(ANDROID_REQUEST_AVAILABLE_CAPABILITIES_YUV_REPROCESSING);
+ break;
+ case DEPTH_OUTPUT:
+ caps.add(ANDROID_REQUEST_AVAILABLE_CAPABILITIES_DEPTH_OUTPUT);
+ break;
+ case CONSTRAINED_HIGH_SPEED_VIDEO:
+ caps.add(ANDROID_REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO);
+ break;
+ default:
+ // Ignore LEVELs
+ break;
+ }
+ }
+ ADD_STATIC_ENTRY(ANDROID_REQUEST_AVAILABLE_CAPABILITIES, caps.array(), caps.size());
+
+ // Scan a default request template for included request keys
+ Vector<int32_t> availableRequestKeys;
+ const camera_metadata_t *previewRequest =
+ constructDefaultRequestSettings(CAMERA3_TEMPLATE_PREVIEW);
+ for (size_t i = 0; i < get_camera_metadata_entry_count(previewRequest); i++) {
+ camera_metadata_ro_entry_t entry;
+ get_camera_metadata_ro_entry(previewRequest, i, &entry);
+ availableRequestKeys.add(entry.tag);
+ }
+ ADD_STATIC_ENTRY(ANDROID_REQUEST_AVAILABLE_REQUEST_KEYS, availableRequestKeys.array(),
+ availableRequestKeys.size());
+
+ // Add a few more result keys. Must be kept up to date with the various places that add these
+
+ Vector<int32_t> availableResultKeys(availableRequestKeys);
+ if (hasCapability(BACKWARD_COMPATIBLE)) {
+ availableResultKeys.add(ANDROID_CONTROL_AE_STATE);
+ availableResultKeys.add(ANDROID_CONTROL_AF_STATE);
+ availableResultKeys.add(ANDROID_CONTROL_AWB_STATE);
+ availableResultKeys.add(ANDROID_FLASH_STATE);
+ availableResultKeys.add(ANDROID_LENS_STATE);
+ availableResultKeys.add(ANDROID_LENS_FOCUS_RANGE);
+ availableResultKeys.add(ANDROID_SENSOR_ROLLING_SHUTTER_SKEW);
+ availableResultKeys.add(ANDROID_STATISTICS_SCENE_FLICKER);
+ }
+
+ if (hasCapability(DEPTH_OUTPUT)) {
+ availableResultKeys.add(ANDROID_LENS_POSE_ROTATION);
+ availableResultKeys.add(ANDROID_LENS_POSE_TRANSLATION);
+ availableResultKeys.add(ANDROID_LENS_INTRINSIC_CALIBRATION);
+ availableResultKeys.add(ANDROID_LENS_RADIAL_DISTORTION);
+ }
+
+ availableResultKeys.add(ANDROID_REQUEST_PIPELINE_DEPTH);
+ availableResultKeys.add(ANDROID_SENSOR_TIMESTAMP);
+
+ ADD_STATIC_ENTRY(ANDROID_REQUEST_AVAILABLE_RESULT_KEYS, availableResultKeys.array(),
+ availableResultKeys.size());
+
+ // Needs to be last, to collect all the keys set
+
+ availableCharacteristicsKeys.add(ANDROID_REQUEST_AVAILABLE_CHARACTERISTICS_KEYS);
+ info.update(ANDROID_REQUEST_AVAILABLE_CHARACTERISTICS_KEYS,
+ availableCharacteristicsKeys);
+
+ mCameraInfo = info.release();
+
+#undef ADD_STATIC_ENTRY
+ return OK;
+}
+
+status_t EmulatedFakeCamera3::process3A(CameraMetadata &settings) {
+ /**
+ * Extract top-level 3A controls
+ */
+ status_t res;
+
+ bool facePriority = false;
+
+ camera_metadata_entry e;
+
+ e = settings.find(ANDROID_CONTROL_MODE);
+ if (e.count == 0) {
+ ALOGE("%s: No control mode entry!", __FUNCTION__);
+ return BAD_VALUE;
+ }
+ uint8_t controlMode = e.data.u8[0];
+
+ if (controlMode == ANDROID_CONTROL_MODE_OFF) {
+ mAeMode = ANDROID_CONTROL_AE_MODE_OFF;
+ mAfMode = ANDROID_CONTROL_AF_MODE_OFF;
+ mAwbMode = ANDROID_CONTROL_AWB_MODE_OFF;
+ mAeState = ANDROID_CONTROL_AE_STATE_INACTIVE;
+ mAfState = ANDROID_CONTROL_AF_STATE_INACTIVE;
+ mAwbState = ANDROID_CONTROL_AWB_STATE_INACTIVE;
+ update3A(settings);
+ return OK;
+ } else if (controlMode == ANDROID_CONTROL_MODE_USE_SCENE_MODE) {
+ if (!hasCapability(BACKWARD_COMPATIBLE)) {
+ ALOGE("%s: Can't use scene mode when BACKWARD_COMPATIBLE not supported!",
+ __FUNCTION__);
+ return BAD_VALUE;
+ }
+
+ e = settings.find(ANDROID_CONTROL_SCENE_MODE);
+ if (e.count == 0) {
+ ALOGE("%s: No scene mode entry!", __FUNCTION__);
+ return BAD_VALUE;
+ }
+ uint8_t sceneMode = e.data.u8[0];
+
+ switch(sceneMode) {
+ case ANDROID_CONTROL_SCENE_MODE_FACE_PRIORITY:
+ mFacePriority = true;
+ break;
+ default:
+ ALOGE("%s: Emulator doesn't support scene mode %d",
+ __FUNCTION__, sceneMode);
+ return BAD_VALUE;
+ }
+ } else {
+ mFacePriority = false;
+ }
+
+ // controlMode == AUTO or sceneMode = FACE_PRIORITY
+ // Process individual 3A controls
+
+ res = doFakeAE(settings);
+ if (res != OK) return res;
+
+ res = doFakeAF(settings);
+ if (res != OK) return res;
+
+ res = doFakeAWB(settings);
+ if (res != OK) return res;
+
+ update3A(settings);
+ return OK;
+}
+
+status_t EmulatedFakeCamera3::doFakeAE(CameraMetadata &settings) {
+ camera_metadata_entry e;
+
+ e = settings.find(ANDROID_CONTROL_AE_MODE);
+ if (e.count == 0 && hasCapability(BACKWARD_COMPATIBLE)) {
+ ALOGE("%s: No AE mode entry!", __FUNCTION__);
+ return BAD_VALUE;
+ }
+ uint8_t aeMode = (e.count > 0) ? e.data.u8[0] : (uint8_t)ANDROID_CONTROL_AE_MODE_ON;
+ mAeMode = aeMode;
+
+ switch (aeMode) {
+ case ANDROID_CONTROL_AE_MODE_OFF:
+ // AE is OFF
+ mAeState = ANDROID_CONTROL_AE_STATE_INACTIVE;
+ return OK;
+ case ANDROID_CONTROL_AE_MODE_ON:
+ // OK for AUTO modes
+ break;
+ default:
+ // Mostly silently ignore unsupported modes
+ ALOGV("%s: Emulator doesn't support AE mode %d, assuming ON",
+ __FUNCTION__, aeMode);
+ break;
+ }
+
+ e = settings.find(ANDROID_CONTROL_AE_LOCK);
+ bool aeLocked = (e.count > 0) ? (e.data.u8[0] == ANDROID_CONTROL_AE_LOCK_ON) : false;
+
+ e = settings.find(ANDROID_CONTROL_AE_PRECAPTURE_TRIGGER);
+ bool precaptureTrigger = false;
+ if (e.count != 0) {
+ precaptureTrigger =
+ (e.data.u8[0] == ANDROID_CONTROL_AE_PRECAPTURE_TRIGGER_START);
+ }
+
+ if (precaptureTrigger) {
+ ALOGV("%s: Pre capture trigger = %d", __FUNCTION__, precaptureTrigger);
+ } else if (e.count > 0) {
+ ALOGV("%s: Pre capture trigger was present? %zu",
+ __FUNCTION__,
+ e.count);
+ }
+
+ if (precaptureTrigger || mAeState == ANDROID_CONTROL_AE_STATE_PRECAPTURE) {
+ // Run precapture sequence
+ if (mAeState != ANDROID_CONTROL_AE_STATE_PRECAPTURE) {
+ mAeCounter = 0;
+ }
+
+ if (mFacePriority) {
+ mAeTargetExposureTime = kFacePriorityExposureTime;
+ } else {
+ mAeTargetExposureTime = kNormalExposureTime;
+ }
+
+ if (mAeCounter > kPrecaptureMinFrames &&
+ (mAeTargetExposureTime - mAeCurrentExposureTime) <
+ mAeTargetExposureTime / 10) {
+ // Done with precapture
+ mAeCounter = 0;
+ mAeState = aeLocked ? ANDROID_CONTROL_AE_STATE_LOCKED :
+ ANDROID_CONTROL_AE_STATE_CONVERGED;
+ } else {
+ // Converge some more
+ mAeCurrentExposureTime +=
+ (mAeTargetExposureTime - mAeCurrentExposureTime) *
+ kExposureTrackRate;
+ mAeCounter++;
+ mAeState = ANDROID_CONTROL_AE_STATE_PRECAPTURE;
+ }
+
+ } else if (!aeLocked) {
+ // Run standard occasional AE scan
+ switch (mAeState) {
+ case ANDROID_CONTROL_AE_STATE_CONVERGED:
+ case ANDROID_CONTROL_AE_STATE_INACTIVE:
+ mAeCounter++;
+ if (mAeCounter > kStableAeMaxFrames) {
+ mAeTargetExposureTime =
+ mFacePriority ? kFacePriorityExposureTime :
+ kNormalExposureTime;
+ float exposureStep = ((double)rand() / RAND_MAX) *
+ (kExposureWanderMax - kExposureWanderMin) +
+ kExposureWanderMin;
+ mAeTargetExposureTime *= std::pow(2, exposureStep);
+ mAeState = ANDROID_CONTROL_AE_STATE_SEARCHING;
+ }
+ break;
+ case ANDROID_CONTROL_AE_STATE_SEARCHING:
+ mAeCurrentExposureTime +=
+ (mAeTargetExposureTime - mAeCurrentExposureTime) *
+ kExposureTrackRate;
+ if (abs(mAeTargetExposureTime - mAeCurrentExposureTime) <
+ mAeTargetExposureTime / 10) {
+ // Close enough
+ mAeState = ANDROID_CONTROL_AE_STATE_CONVERGED;
+ mAeCounter = 0;
+ }
+ break;
+ case ANDROID_CONTROL_AE_STATE_LOCKED:
+ mAeState = ANDROID_CONTROL_AE_STATE_CONVERGED;
+ mAeCounter = 0;
+ break;
+ default:
+ ALOGE("%s: Emulator in unexpected AE state %d",
+ __FUNCTION__, mAeState);
+ return INVALID_OPERATION;
+ }
+ } else {
+ // AE is locked
+ mAeState = ANDROID_CONTROL_AE_STATE_LOCKED;
+ }
+
+ return OK;
+}
+
+status_t EmulatedFakeCamera3::doFakeAF(CameraMetadata &settings) {
+ camera_metadata_entry e;
+
+ e = settings.find(ANDROID_CONTROL_AF_MODE);
+ if (e.count == 0 && hasCapability(BACKWARD_COMPATIBLE)) {
+ ALOGE("%s: No AF mode entry!", __FUNCTION__);
+ return BAD_VALUE;
+ }
+ uint8_t afMode = (e.count > 0) ? e.data.u8[0] : (uint8_t)ANDROID_CONTROL_AF_MODE_OFF;
+
+ e = settings.find(ANDROID_CONTROL_AF_TRIGGER);
+ typedef camera_metadata_enum_android_control_af_trigger af_trigger_t;
+ af_trigger_t afTrigger;
+ if (e.count != 0) {
+ afTrigger = static_cast<af_trigger_t>(e.data.u8[0]);
+
+ ALOGV("%s: AF trigger set to 0x%x", __FUNCTION__, afTrigger);
+ ALOGV("%s: AF mode is 0x%x", __FUNCTION__, afMode);
+ } else {
+ afTrigger = ANDROID_CONTROL_AF_TRIGGER_IDLE;
+ }
+
+ switch (afMode) {
+ case ANDROID_CONTROL_AF_MODE_OFF:
+ mAfState = ANDROID_CONTROL_AF_STATE_INACTIVE;
+ return OK;
+ case ANDROID_CONTROL_AF_MODE_AUTO:
+ case ANDROID_CONTROL_AF_MODE_MACRO:
+ case ANDROID_CONTROL_AF_MODE_CONTINUOUS_VIDEO:
+ case ANDROID_CONTROL_AF_MODE_CONTINUOUS_PICTURE:
+ if (!mFacingBack) {
+ ALOGE("%s: Front camera doesn't support AF mode %d",
+ __FUNCTION__, afMode);
+ return BAD_VALUE;
+ }
+ // OK, handle transitions lower on
+ break;
+ default:
+ ALOGE("%s: Emulator doesn't support AF mode %d",
+ __FUNCTION__, afMode);
+ return BAD_VALUE;
+ }
+
+ bool afModeChanged = mAfMode != afMode;
+ mAfMode = afMode;
+
+ /**
+ * Simulate AF triggers. Transition at most 1 state per frame.
+ * - Focusing always succeeds (goes into locked, or PASSIVE_SCAN).
+ */
+
+ bool afTriggerStart = false;
+ bool afTriggerCancel = false;
+ switch (afTrigger) {
+ case ANDROID_CONTROL_AF_TRIGGER_IDLE:
+ break;
+ case ANDROID_CONTROL_AF_TRIGGER_START:
+ afTriggerStart = true;
+ break;
+ case ANDROID_CONTROL_AF_TRIGGER_CANCEL:
+ afTriggerCancel = true;
+ // Cancel trigger always transitions into INACTIVE
+ mAfState = ANDROID_CONTROL_AF_STATE_INACTIVE;
+
+ ALOGV("%s: AF State transition to STATE_INACTIVE", __FUNCTION__);
+
+ // Stay in 'inactive' until at least next frame
+ return OK;
+ default:
+ ALOGE("%s: Unknown af trigger value %d", __FUNCTION__, afTrigger);
+ return BAD_VALUE;
+ }
+
+ // If we get down here, we're either in an autofocus mode
+ // or in a continuous focus mode (and no other modes)
+
+ int oldAfState = mAfState;
+ switch (mAfState) {
+ case ANDROID_CONTROL_AF_STATE_INACTIVE:
+ if (afTriggerStart) {
+ switch (afMode) {
+ case ANDROID_CONTROL_AF_MODE_AUTO:
+ // fall-through
+ case ANDROID_CONTROL_AF_MODE_MACRO:
+ mAfState = ANDROID_CONTROL_AF_STATE_ACTIVE_SCAN;
+ break;
+ case ANDROID_CONTROL_AF_MODE_CONTINUOUS_VIDEO:
+ // fall-through
+ case ANDROID_CONTROL_AF_MODE_CONTINUOUS_PICTURE:
+ mAfState = ANDROID_CONTROL_AF_STATE_NOT_FOCUSED_LOCKED;
+ break;
+ }
+ } else {
+ // At least one frame stays in INACTIVE
+ if (!afModeChanged) {
+ switch (afMode) {
+ case ANDROID_CONTROL_AF_MODE_CONTINUOUS_VIDEO:
+ // fall-through
+ case ANDROID_CONTROL_AF_MODE_CONTINUOUS_PICTURE:
+ mAfState = ANDROID_CONTROL_AF_STATE_PASSIVE_SCAN;
+ break;
+ }
+ }
+ }
+ break;
+ case ANDROID_CONTROL_AF_STATE_PASSIVE_SCAN:
+ /**
+ * When the AF trigger is activated, the algorithm should finish
+ * its PASSIVE_SCAN if active, and then transition into AF_FOCUSED
+ * or AF_NOT_FOCUSED as appropriate
+ */
+ if (afTriggerStart) {
+ // Randomly transition to focused or not focused
+ if (rand() % 3) {
+ mAfState = ANDROID_CONTROL_AF_STATE_FOCUSED_LOCKED;
+ } else {
+ mAfState = ANDROID_CONTROL_AF_STATE_NOT_FOCUSED_LOCKED;
+ }
+ }
+ /**
+ * When the AF trigger is not involved, the AF algorithm should
+ * start in INACTIVE state, and then transition into PASSIVE_SCAN
+ * and PASSIVE_FOCUSED states
+ */
+ else if (!afTriggerCancel) {
+ // Randomly transition to passive focus
+ if (rand() % 3 == 0) {
+ mAfState = ANDROID_CONTROL_AF_STATE_PASSIVE_FOCUSED;
+ }
+ }
+
+ break;
+ case ANDROID_CONTROL_AF_STATE_PASSIVE_FOCUSED:
+ if (afTriggerStart) {
+ // Randomly transition to focused or not focused
+ if (rand() % 3) {
+ mAfState = ANDROID_CONTROL_AF_STATE_FOCUSED_LOCKED;
+ } else {
+ mAfState = ANDROID_CONTROL_AF_STATE_NOT_FOCUSED_LOCKED;
+ }
+ }
+ // TODO: initiate passive scan (PASSIVE_SCAN)
+ break;
+ case ANDROID_CONTROL_AF_STATE_ACTIVE_SCAN:
+ // Simulate AF sweep completing instantaneously
+
+ // Randomly transition to focused or not focused
+ if (rand() % 3) {
+ mAfState = ANDROID_CONTROL_AF_STATE_FOCUSED_LOCKED;
+ } else {
+ mAfState = ANDROID_CONTROL_AF_STATE_NOT_FOCUSED_LOCKED;
+ }
+ break;
+ case ANDROID_CONTROL_AF_STATE_FOCUSED_LOCKED:
+ if (afTriggerStart) {
+ switch (afMode) {
+ case ANDROID_CONTROL_AF_MODE_AUTO:
+ // fall-through
+ case ANDROID_CONTROL_AF_MODE_MACRO:
+ mAfState = ANDROID_CONTROL_AF_STATE_ACTIVE_SCAN;
+ break;
+ case ANDROID_CONTROL_AF_MODE_CONTINUOUS_VIDEO:
+ // fall-through
+ case ANDROID_CONTROL_AF_MODE_CONTINUOUS_PICTURE:
+ // continuous autofocus => trigger start has no effect
+ break;
+ }
+ }
+ break;
+ case ANDROID_CONTROL_AF_STATE_NOT_FOCUSED_LOCKED:
+ if (afTriggerStart) {
+ switch (afMode) {
+ case ANDROID_CONTROL_AF_MODE_AUTO:
+ // fall-through
+ case ANDROID_CONTROL_AF_MODE_MACRO:
+ mAfState = ANDROID_CONTROL_AF_STATE_ACTIVE_SCAN;
+ break;
+ case ANDROID_CONTROL_AF_MODE_CONTINUOUS_VIDEO:
+ // fall-through
+ case ANDROID_CONTROL_AF_MODE_CONTINUOUS_PICTURE:
+ // continuous autofocus => trigger start has no effect
+ break;
+ }
+ }
+ break;
+ default:
+ ALOGE("%s: Bad af state %d", __FUNCTION__, mAfState);
+ }
+
+ {
+ char afStateString[100] = {0,};
+ camera_metadata_enum_snprint(ANDROID_CONTROL_AF_STATE,
+ oldAfState,
+ afStateString,
+ sizeof(afStateString));
+
+ char afNewStateString[100] = {0,};
+ camera_metadata_enum_snprint(ANDROID_CONTROL_AF_STATE,
+ mAfState,
+ afNewStateString,
+ sizeof(afNewStateString));
+ ALOGVV("%s: AF state transitioned from %s to %s",
+ __FUNCTION__, afStateString, afNewStateString);
+ }
+
+
+ return OK;
+}
+
+status_t EmulatedFakeCamera3::doFakeAWB(CameraMetadata &settings) {
+ camera_metadata_entry e;
+
+ e = settings.find(ANDROID_CONTROL_AWB_MODE);
+ if (e.count == 0 && hasCapability(BACKWARD_COMPATIBLE)) {
+ ALOGE("%s: No AWB mode entry!", __FUNCTION__);
+ return BAD_VALUE;
+ }
+ uint8_t awbMode = (e.count > 0) ? e.data.u8[0] : (uint8_t)ANDROID_CONTROL_AWB_MODE_AUTO;
+
+ // TODO: Add white balance simulation
+
+ e = settings.find(ANDROID_CONTROL_AWB_LOCK);
+ bool awbLocked = (e.count > 0) ? (e.data.u8[0] == ANDROID_CONTROL_AWB_LOCK_ON) : false;
+
+ switch (awbMode) {
+ case ANDROID_CONTROL_AWB_MODE_OFF:
+ mAwbState = ANDROID_CONTROL_AWB_STATE_INACTIVE;
+ break;
+ case ANDROID_CONTROL_AWB_MODE_AUTO:
+ case ANDROID_CONTROL_AWB_MODE_INCANDESCENT:
+ case ANDROID_CONTROL_AWB_MODE_FLUORESCENT:
+ case ANDROID_CONTROL_AWB_MODE_DAYLIGHT:
+ case ANDROID_CONTROL_AWB_MODE_SHADE:
+ // Always magically right, or locked
+ mAwbState = awbLocked ? ANDROID_CONTROL_AWB_STATE_LOCKED :
+ ANDROID_CONTROL_AWB_STATE_CONVERGED;
+ break;
+ default:
+ ALOGE("%s: Emulator doesn't support AWB mode %d",
+ __FUNCTION__, awbMode);
+ return BAD_VALUE;
+ }
+
+ return OK;
+}
+
+
+void EmulatedFakeCamera3::update3A(CameraMetadata &settings) {
+ if (mAeMode != ANDROID_CONTROL_AE_MODE_OFF) {
+ settings.update(ANDROID_SENSOR_EXPOSURE_TIME,
+ &mAeCurrentExposureTime, 1);
+ settings.update(ANDROID_SENSOR_SENSITIVITY,
+ &mAeCurrentSensitivity, 1);
+ }
+
+ settings.update(ANDROID_CONTROL_AE_STATE,
+ &mAeState, 1);
+ settings.update(ANDROID_CONTROL_AF_STATE,
+ &mAfState, 1);
+ settings.update(ANDROID_CONTROL_AWB_STATE,
+ &mAwbState, 1);
+
+ uint8_t lensState;
+ switch (mAfState) {
+ case ANDROID_CONTROL_AF_STATE_PASSIVE_SCAN:
+ case ANDROID_CONTROL_AF_STATE_ACTIVE_SCAN:
+ lensState = ANDROID_LENS_STATE_MOVING;
+ break;
+ case ANDROID_CONTROL_AF_STATE_INACTIVE:
+ case ANDROID_CONTROL_AF_STATE_PASSIVE_FOCUSED:
+ case ANDROID_CONTROL_AF_STATE_FOCUSED_LOCKED:
+ case ANDROID_CONTROL_AF_STATE_NOT_FOCUSED_LOCKED:
+ case ANDROID_CONTROL_AF_STATE_PASSIVE_UNFOCUSED:
+ default:
+ lensState = ANDROID_LENS_STATE_STATIONARY;
+ break;
+ }
+ settings.update(ANDROID_LENS_STATE, &lensState, 1);
+
+}
+
+void EmulatedFakeCamera3::signalReadoutIdle() {
+ Mutex::Autolock l(mLock);
+ // Need to chek isIdle again because waiting on mLock may have allowed
+ // something to be placed in the in-flight queue.
+ if (mStatus == STATUS_ACTIVE && mReadoutThread->isIdle()) {
+ ALOGV("Now idle");
+ mStatus = STATUS_READY;
+ }
+}
+
+void EmulatedFakeCamera3::onSensorEvent(uint32_t frameNumber, Event e,
+ nsecs_t timestamp) {
+ switch(e) {
+ case Sensor::SensorListener::EXPOSURE_START: {
+ ALOGVV("%s: Frame %d: Sensor started exposure at %lld",
+ __FUNCTION__, frameNumber, timestamp);
+ // Trigger shutter notify to framework
+ camera3_notify_msg_t msg;
+ msg.type = CAMERA3_MSG_SHUTTER;
+ msg.message.shutter.frame_number = frameNumber;
+ msg.message.shutter.timestamp = timestamp;
+ sendNotify(&msg);
+ break;
+ }
+ default:
+ ALOGW("%s: Unexpected sensor event %d at %" PRId64, __FUNCTION__,
+ e, timestamp);
+ break;
+ }
+}
+
+EmulatedFakeCamera3::ReadoutThread::ReadoutThread(EmulatedFakeCamera3 *parent) :
+ mParent(parent), mJpegWaiting(false) {
+}
+
+EmulatedFakeCamera3::ReadoutThread::~ReadoutThread() {
+ for (List<Request>::iterator i = mInFlightQueue.begin();
+ i != mInFlightQueue.end(); i++) {
+ delete i->buffers;
+ delete i->sensorBuffers;
+ }
+}
+
+void EmulatedFakeCamera3::ReadoutThread::queueCaptureRequest(const Request &r) {
+ Mutex::Autolock l(mLock);
+
+ mInFlightQueue.push_back(r);
+ mInFlightSignal.signal();
+}
+
+bool EmulatedFakeCamera3::ReadoutThread::isIdle() {
+ Mutex::Autolock l(mLock);
+ return mInFlightQueue.empty() && !mThreadActive;
+}
+
+status_t EmulatedFakeCamera3::ReadoutThread::waitForReadout() {
+ status_t res;
+ Mutex::Autolock l(mLock);
+ int loopCount = 0;
+ while (mInFlightQueue.size() >= kMaxQueueSize) {
+ res = mInFlightSignal.waitRelative(mLock, kWaitPerLoop);
+ if (res != OK && res != TIMED_OUT) {
+ ALOGE("%s: Error waiting for in-flight queue to shrink",
+ __FUNCTION__);
+ return INVALID_OPERATION;
+ }
+ if (loopCount == kMaxWaitLoops) {
+ ALOGE("%s: Timed out waiting for in-flight queue to shrink",
+ __FUNCTION__);
+ return TIMED_OUT;
+ }
+ loopCount++;
+ }
+ return OK;
+}
+
+bool EmulatedFakeCamera3::ReadoutThread::threadLoop() {
+ status_t res;
+
+ ALOGVV("%s: ReadoutThread waiting for request", __FUNCTION__);
+
+ // First wait for a request from the in-flight queue
+
+ if (mCurrentRequest.settings.isEmpty()) {
+ Mutex::Autolock l(mLock);
+ if (mInFlightQueue.empty()) {
+ res = mInFlightSignal.waitRelative(mLock, kWaitPerLoop);
+ if (res == TIMED_OUT) {
+ ALOGVV("%s: ReadoutThread: Timed out waiting for request",
+ __FUNCTION__);
+ return true;
+ } else if (res != NO_ERROR) {
+ ALOGE("%s: Error waiting for capture requests: %d",
+ __FUNCTION__, res);
+ return false;
+ }
+ }
+ mCurrentRequest.frameNumber = mInFlightQueue.begin()->frameNumber;
+ mCurrentRequest.settings.acquire(mInFlightQueue.begin()->settings);
+ mCurrentRequest.buffers = mInFlightQueue.begin()->buffers;
+ mCurrentRequest.sensorBuffers = mInFlightQueue.begin()->sensorBuffers;
+ mInFlightQueue.erase(mInFlightQueue.begin());
+ mInFlightSignal.signal();
+ mThreadActive = true;
+ ALOGVV("%s: Beginning readout of frame %d", __FUNCTION__,
+ mCurrentRequest.frameNumber);
+ }
+
+ // Then wait for it to be delivered from the sensor
+ ALOGVV("%s: ReadoutThread: Wait for frame to be delivered from sensor",
+ __FUNCTION__);
+
+ nsecs_t captureTime;
+ bool gotFrame =
+ mParent->mSensor->waitForNewFrame(kWaitPerLoop, &captureTime);
+ if (!gotFrame) {
+ ALOGVV("%s: ReadoutThread: Timed out waiting for sensor frame",
+ __FUNCTION__);
+ return true;
+ }
+
+ ALOGVV("Sensor done with readout for frame %d, captured at %lld ",
+ mCurrentRequest.frameNumber, captureTime);
+
+ // Check if we need to JPEG encode a buffer, and send it for async
+ // compression if so. Otherwise prepare the buffer for return.
+ bool needJpeg = false;
+ HalBufferVector::iterator buf = mCurrentRequest.buffers->begin();
+ while(buf != mCurrentRequest.buffers->end()) {
+ bool goodBuffer = true;
+ if ( buf->stream->format ==
+ HAL_PIXEL_FORMAT_BLOB && buf->stream->data_space != HAL_DATASPACE_DEPTH) {
+ Mutex::Autolock jl(mJpegLock);
+ if (mJpegWaiting) {
+ // This shouldn't happen, because processCaptureRequest should
+ // be stalling until JPEG compressor is free.
+ ALOGE("%s: Already processing a JPEG!", __FUNCTION__);
+ goodBuffer = false;
+ }
+ if (goodBuffer) {
+ // Compressor takes ownership of sensorBuffers here
+ res = mParent->mJpegCompressor->start(mCurrentRequest.sensorBuffers,
+ this);
+ goodBuffer = (res == OK);
+ }
+ if (goodBuffer) {
+ needJpeg = true;
+
+ mJpegHalBuffer = *buf;
+ mJpegFrameNumber = mCurrentRequest.frameNumber;
+ mJpegWaiting = true;
+
+ mCurrentRequest.sensorBuffers = NULL;
+ buf = mCurrentRequest.buffers->erase(buf);
+
+ continue;
+ }
+ ALOGE("%s: Error compressing output buffer: %s (%d)",
+ __FUNCTION__, strerror(-res), res);
+ // fallthrough for cleanup
+ }
+ GrallocModule::getInstance().unlock(*(buf->buffer));
+
+ buf->status = goodBuffer ? CAMERA3_BUFFER_STATUS_OK :
+ CAMERA3_BUFFER_STATUS_ERROR;
+ buf->acquire_fence = -1;
+ buf->release_fence = -1;
+
+ ++buf;
+ } // end while
+
+ // Construct result for all completed buffers and results
+
+ camera3_capture_result result;
+
+ if (mParent->hasCapability(BACKWARD_COMPATIBLE)) {
+ static const uint8_t sceneFlicker = ANDROID_STATISTICS_SCENE_FLICKER_NONE;
+ mCurrentRequest.settings.update(ANDROID_STATISTICS_SCENE_FLICKER,
+ &sceneFlicker, 1);
+
+ static const uint8_t flashState = ANDROID_FLASH_STATE_UNAVAILABLE;
+ mCurrentRequest.settings.update(ANDROID_FLASH_STATE,
+ &flashState, 1);
+
+ nsecs_t rollingShutterSkew = Sensor::kFrameDurationRange[0];
+ mCurrentRequest.settings.update(ANDROID_SENSOR_ROLLING_SHUTTER_SKEW,
+ &rollingShutterSkew, 1);
+
+ float focusRange[] = { 1.0f/5.0f, 0 }; // 5 m to infinity in focus
+ mCurrentRequest.settings.update(ANDROID_LENS_FOCUS_RANGE,
+ focusRange, sizeof(focusRange)/sizeof(float));
+ }
+
+ if (mParent->hasCapability(DEPTH_OUTPUT)) {
+ camera_metadata_entry_t entry;
+
+ find_camera_metadata_entry(mParent->mCameraInfo, ANDROID_LENS_POSE_TRANSLATION, &entry);
+ mCurrentRequest.settings.update(ANDROID_LENS_POSE_TRANSLATION,
+ entry.data.f, entry.count);
+
+ find_camera_metadata_entry(mParent->mCameraInfo, ANDROID_LENS_POSE_ROTATION, &entry);
+ mCurrentRequest.settings.update(ANDROID_LENS_POSE_ROTATION,
+ entry.data.f, entry.count);
+
+ find_camera_metadata_entry(mParent->mCameraInfo, ANDROID_LENS_INTRINSIC_CALIBRATION, &entry);
+ mCurrentRequest.settings.update(ANDROID_LENS_INTRINSIC_CALIBRATION,
+ entry.data.f, entry.count);
+
+ find_camera_metadata_entry(mParent->mCameraInfo, ANDROID_LENS_RADIAL_DISTORTION, &entry);
+ mCurrentRequest.settings.update(ANDROID_LENS_RADIAL_DISTORTION,
+ entry.data.f, entry.count);
+ }
+
+ mCurrentRequest.settings.update(ANDROID_SENSOR_TIMESTAMP,
+ &captureTime, 1);
+
+
+ // JPEGs take a stage longer
+ const uint8_t pipelineDepth = needJpeg ? kMaxBufferCount : kMaxBufferCount - 1;
+ mCurrentRequest.settings.update(ANDROID_REQUEST_PIPELINE_DEPTH,
+ &pipelineDepth, 1);
+
+ result.frame_number = mCurrentRequest.frameNumber;
+ result.result = mCurrentRequest.settings.getAndLock();
+ result.num_output_buffers = mCurrentRequest.buffers->size();
+ result.output_buffers = mCurrentRequest.buffers->array();
+ result.input_buffer = nullptr;
+ result.partial_result = 1;
+
+ // Go idle if queue is empty, before sending result
+ bool signalIdle = false;
+ {
+ Mutex::Autolock l(mLock);
+ if (mInFlightQueue.empty()) {
+ mThreadActive = false;
+ signalIdle = true;
+ }
+ }
+ if (signalIdle) mParent->signalReadoutIdle();
+
+ // Send it off to the framework
+ ALOGVV("%s: ReadoutThread: Send result to framework",
+ __FUNCTION__);
+ mParent->sendCaptureResult(&result);
+
+ // Clean up
+ mCurrentRequest.settings.unlock(result.result);
+
+ delete mCurrentRequest.buffers;
+ mCurrentRequest.buffers = NULL;
+ if (!needJpeg) {
+ delete mCurrentRequest.sensorBuffers;
+ mCurrentRequest.sensorBuffers = NULL;
+ }
+ mCurrentRequest.settings.clear();
+
+ return true;
+}
+
+void EmulatedFakeCamera3::ReadoutThread::onJpegDone(
+ const StreamBuffer &jpegBuffer, bool success) {
+ Mutex::Autolock jl(mJpegLock);
+
+ GrallocModule::getInstance().unlock(*(jpegBuffer.buffer));
+
+ mJpegHalBuffer.status = success ?
+ CAMERA3_BUFFER_STATUS_OK : CAMERA3_BUFFER_STATUS_ERROR;
+ mJpegHalBuffer.acquire_fence = -1;
+ mJpegHalBuffer.release_fence = -1;
+ mJpegWaiting = false;
+
+ camera3_capture_result result;
+
+ result.frame_number = mJpegFrameNumber;
+ result.result = NULL;
+ result.num_output_buffers = 1;
+ result.output_buffers = &mJpegHalBuffer;
+ result.input_buffer = nullptr;
+ result.partial_result = 0;
+
+ if (!success) {
+ ALOGE("%s: Compression failure, returning error state buffer to"
+ " framework", __FUNCTION__);
+ } else {
+ ALOGV("%s: Compression complete, returning buffer to framework",
+ __FUNCTION__);
+ }
+
+ mParent->sendCaptureResult(&result);
+}
+
+void EmulatedFakeCamera3::ReadoutThread::onJpegInputDone(
+ const StreamBuffer &inputBuffer) {
+ // Should never get here, since the input buffer has to be returned
+ // by end of processCaptureRequest
+ ALOGE("%s: Unexpected input buffer from JPEG compressor!", __FUNCTION__);
+}
+
+
+}; // namespace android
diff --git a/guest/hals/camera/EmulatedFakeCamera3.h b/guest/hals/camera/EmulatedFakeCamera3.h
new file mode 100644
index 0000000..f0cf68f
--- /dev/null
+++ b/guest/hals/camera/EmulatedFakeCamera3.h
@@ -0,0 +1,294 @@
+/*
+ * Copyright (C) 2013 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 HW_EMULATOR_CAMERA_EMULATED_FAKE_CAMERA3_H
+#define HW_EMULATOR_CAMERA_EMULATED_FAKE_CAMERA3_H
+
+/**
+ * Contains declaration of a class EmulatedCamera that encapsulates
+ * functionality of a fake camera that implements version 3 of the camera device
+ * interace.
+ */
+
+#include "EmulatedCamera3.h"
+#include "fake-pipeline2/Base.h"
+#include "fake-pipeline2/Sensor.h"
+#include "fake-pipeline2/JpegCompressor.h"
+#include <camera/CameraMetadata.h>
+#include <utils/SortedVector.h>
+#include <utils/List.h>
+#include <utils/Mutex.h>
+
+namespace android {
+
+/**
+ * Encapsulates functionality for a v3 HAL camera which produces synthetic data.
+ *
+ * Note that EmulatedCameraFactory instantiates an object of this class just
+ * once, when EmulatedCameraFactory instance gets constructed. Connection to /
+ * disconnection from the actual camera device is handled by calls to
+ * connectDevice(), and closeCamera() methods of this class that are invoked in
+ * response to hw_module_methods_t::open, and camera_device::close callbacks.
+ */
+class EmulatedFakeCamera3 : public EmulatedCamera3,
+ private Sensor::SensorListener {
+public:
+
+ EmulatedFakeCamera3(int cameraId, bool facingBack,
+ struct hw_module_t* module);
+
+ virtual ~EmulatedFakeCamera3();
+
+ /****************************************************************************
+ * EmulatedCamera3 virtual overrides
+ ***************************************************************************/
+
+public:
+
+ virtual status_t Initialize(const cvd::CameraDefinition& params);
+
+ /****************************************************************************
+ * Camera module API and generic hardware device API implementation
+ ***************************************************************************/
+
+public:
+ virtual status_t connectCamera(hw_device_t** device);
+
+ virtual status_t closeCamera();
+
+ virtual status_t getCameraInfo(struct camera_info *info);
+
+ virtual status_t setTorchMode(bool enabled);
+
+ /****************************************************************************
+ * EmulatedCamera3 abstract API implementation
+ ***************************************************************************/
+
+protected:
+
+ virtual status_t configureStreams(
+ camera3_stream_configuration *streamList);
+
+ virtual status_t registerStreamBuffers(
+ const camera3_stream_buffer_set *bufferSet) ;
+
+ virtual const camera_metadata_t* constructDefaultRequestSettings(
+ int type);
+
+ virtual status_t processCaptureRequest(camera3_capture_request *request);
+
+ virtual status_t flush();
+
+ /** Debug methods */
+
+ virtual void dump(int fd);
+
+private:
+
+ /**
+ * Get the requested capability set for this camera
+ */
+ status_t getCameraCapabilities();
+
+ bool hasCapability(AvailableCapabilities cap);
+
+ /**
+ * Build the static info metadata buffer for this device
+ */
+ status_t constructStaticInfo(const cvd::CameraDefinition& params);
+
+ /**
+ * Run the fake 3A algorithms as needed. May override/modify settings
+ * values.
+ */
+ status_t process3A(CameraMetadata &settings);
+
+ status_t doFakeAE(CameraMetadata &settings);
+ status_t doFakeAF(CameraMetadata &settings);
+ status_t doFakeAWB(CameraMetadata &settings);
+ void update3A(CameraMetadata &settings);
+
+ /** Signal from readout thread that it doesn't have anything to do */
+ void signalReadoutIdle();
+
+ /** Handle interrupt events from the sensor */
+ void onSensorEvent(uint32_t frameNumber, Event e, nsecs_t timestamp);
+
+ /****************************************************************************
+ * Static configuration information
+ ***************************************************************************/
+private:
+ static const uint32_t kMaxRawStreamCount = 1;
+ static const uint32_t kMaxProcessedStreamCount = 3;
+ static const uint32_t kMaxJpegStreamCount = 1;
+ static const uint32_t kMaxReprocessStreamCount = 2;
+ static const uint32_t kMaxBufferCount = 4;
+ // We need a positive stream ID to distinguish external buffers from
+ // sensor-generated buffers which use a nonpositive ID. Otherwise, HAL3 has
+ // no concept of a stream id.
+ static const uint32_t kGenericStreamId = 1;
+ static const int32_t kAvailableFormats[];
+
+ static const int64_t kSyncWaitTimeout = 10000000; // 10 ms
+ static const int32_t kMaxSyncTimeoutCount = 1000; // 1000 kSyncWaitTimeouts
+ static const uint32_t kFenceTimeoutMs = 2000; // 2 s
+ static const nsecs_t kJpegTimeoutNs = 5000000000l; // 5 s
+
+ /****************************************************************************
+ * Data members.
+ ***************************************************************************/
+
+ /* HAL interface serialization lock. */
+ Mutex mLock;
+
+ /* Facing back (true) or front (false) switch. */
+ bool mFacingBack;
+ int32_t mSensorWidth;
+ int32_t mSensorHeight;
+
+ SortedVector<AvailableCapabilities> mCapabilities;
+
+ /**
+ * Cache for default templates. Once one is requested, the pointer must be
+ * valid at least until close() is called on the device
+ */
+ camera_metadata_t *mDefaultTemplates[CAMERA3_TEMPLATE_COUNT];
+
+ /**
+ * Private stream information, stored in camera3_stream_t->priv.
+ */
+ struct PrivateStreamInfo {
+ bool alive;
+ };
+
+ // Shortcut to the input stream
+ camera3_stream_t* mInputStream;
+
+ typedef List<camera3_stream_t*> StreamList;
+ typedef List<camera3_stream_t*>::iterator StreamIterator;
+ typedef Vector<camera3_stream_buffer> HalBufferVector;
+
+ // All streams, including input stream
+ StreamList mStreams;
+
+ // Cached settings from latest submitted request
+ CameraMetadata mPrevSettings;
+
+ /** Fake hardware interfaces */
+ sp<Sensor> mSensor;
+ sp<JpegCompressor> mJpegCompressor;
+ friend class JpegCompressor;
+
+ /** Processing thread for sending out results */
+
+ class ReadoutThread : public Thread, private JpegCompressor::JpegListener {
+ public:
+ ReadoutThread(EmulatedFakeCamera3 *parent);
+ ~ReadoutThread();
+
+ struct Request {
+ uint32_t frameNumber;
+ CameraMetadata settings;
+ HalBufferVector *buffers;
+ Buffers *sensorBuffers;
+ };
+
+ /**
+ * Interface to parent class
+ */
+
+ // Place request in the in-flight queue to wait for sensor capture
+ void queueCaptureRequest(const Request &r);
+
+ // Test if the readout thread is idle (no in-flight requests, not
+ // currently reading out anything
+ bool isIdle();
+
+ // Wait until isIdle is true
+ status_t waitForReadout();
+
+ private:
+ static const nsecs_t kWaitPerLoop = 10000000L; // 10 ms
+ static const nsecs_t kMaxWaitLoops = 1000;
+ static const size_t kMaxQueueSize = 2;
+
+ EmulatedFakeCamera3 *mParent;
+ Mutex mLock;
+
+ List<Request> mInFlightQueue;
+ Condition mInFlightSignal;
+ bool mThreadActive;
+
+ virtual bool threadLoop();
+
+ // Only accessed by threadLoop
+
+ Request mCurrentRequest;
+
+ // Jpeg completion callbacks
+
+ Mutex mJpegLock;
+ bool mJpegWaiting;
+ camera3_stream_buffer mJpegHalBuffer;
+ uint32_t mJpegFrameNumber;
+ virtual void onJpegDone(const StreamBuffer &jpegBuffer, bool success);
+ virtual void onJpegInputDone(const StreamBuffer &inputBuffer);
+ };
+
+ sp<ReadoutThread> mReadoutThread;
+
+ /** Fake 3A constants */
+
+ static const nsecs_t kNormalExposureTime;
+ static const nsecs_t kFacePriorityExposureTime;
+ static const int kNormalSensitivity;
+ static const int kFacePrioritySensitivity;
+ // Rate of converging AE to new target value, as fraction of difference between
+ // current and target value.
+ static const float kExposureTrackRate;
+ // Minimum duration for precapture state. May be longer if slow to converge
+ // to target exposure
+ static const int kPrecaptureMinFrames;
+ // How often to restart AE 'scanning'
+ static const int kStableAeMaxFrames;
+ // Maximum stop below 'normal' exposure time that we'll wander to while
+ // pretending to converge AE. In powers of 2. (-2 == 1/4 as bright)
+ static const float kExposureWanderMin;
+ // Maximum stop above 'normal' exposure time that we'll wander to while
+ // pretending to converge AE. In powers of 2. (2 == 4x as bright)
+ static const float kExposureWanderMax;
+
+ /** Fake 3A state */
+
+ uint8_t mControlMode;
+ bool mFacePriority;
+ uint8_t mAeState;
+ uint8_t mAfState;
+ uint8_t mAwbState;
+ uint8_t mAeMode;
+ uint8_t mAfMode;
+ uint8_t mAwbMode;
+
+ int mAeCounter;
+ nsecs_t mAeCurrentExposureTime;
+ nsecs_t mAeTargetExposureTime;
+ int mAeCurrentSensitivity;
+
+};
+
+} // namespace android
+
+#endif // HW_EMULATOR_CAMERA_EMULATED_CAMERA3_H
diff --git a/guest/hals/camera/EmulatedFakeCameraDevice.cpp b/guest/hals/camera/EmulatedFakeCameraDevice.cpp
new file mode 100755
index 0000000..8043add
--- /dev/null
+++ b/guest/hals/camera/EmulatedFakeCameraDevice.cpp
@@ -0,0 +1,440 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+/*
+ * Contains implementation of a class EmulatedFakeCameraDevice that encapsulates
+ * fake camera device.
+ */
+
+#define LOG_NDEBUG 0
+#define LOG_TAG "EmulatedCamera_FakeDevice"
+#include <cutils/log.h>
+#include "EmulatedFakeCamera.h"
+#include "EmulatedFakeCameraDevice.h"
+
+namespace android {
+
+EmulatedFakeCameraDevice::EmulatedFakeCameraDevice(EmulatedFakeCamera* camera_hal)
+ : EmulatedCameraDevice(camera_hal),
+ mBlackYUV(kBlack32),
+ mWhiteYUV(kWhite32),
+ mRedYUV(kRed8),
+ mGreenYUV(kGreen8),
+ mBlueYUV(kBlue8),
+ mLastRedrawn(0),
+ mCheckX(0),
+ mCheckY(0),
+ mCcounter(0)
+#if EFCD_ROTATE_FRAME
+ , mLastRotatedAt(0),
+ mCurrentFrameType(0),
+ mCurrentColor(&mWhiteYUV)
+#endif // EFCD_ROTATE_FRAME
+{
+ // Makes the image with the original exposure compensation darker.
+ // So the effects of changing the exposure compensation can be seen.
+ mBlackYUV.Y = mBlackYUV.Y / 2;
+ mWhiteYUV.Y = mWhiteYUV.Y / 2;
+ mRedYUV.Y = mRedYUV.Y / 2;
+ mGreenYUV.Y = mGreenYUV.Y / 2;
+ mBlueYUV.Y = mBlueYUV.Y / 2;
+}
+
+EmulatedFakeCameraDevice::~EmulatedFakeCameraDevice()
+{
+}
+
+/****************************************************************************
+ * Emulated camera device abstract interface implementation.
+ ***************************************************************************/
+
+status_t EmulatedFakeCameraDevice::connectDevice()
+{
+ ALOGV("%s", __FUNCTION__);
+
+ Mutex::Autolock locker(&mObjectLock);
+ if (!isInitialized()) {
+ ALOGE("%s: Fake camera device is not initialized.", __FUNCTION__);
+ return EINVAL;
+ }
+ if (isConnected()) {
+ ALOGW("%s: Fake camera device is already connected.", __FUNCTION__);
+ return NO_ERROR;
+ }
+
+ /* There is no device to connect to. */
+ mState = ECDS_CONNECTED;
+
+ return NO_ERROR;
+}
+
+status_t EmulatedFakeCameraDevice::disconnectDevice()
+{
+ ALOGV("%s", __FUNCTION__);
+
+ Mutex::Autolock locker(&mObjectLock);
+ if (!isConnected()) {
+ ALOGW("%s: Fake camera device is already disconnected.", __FUNCTION__);
+ return NO_ERROR;
+ }
+ if (isStarted()) {
+ ALOGE("%s: Cannot disconnect from the started device.", __FUNCTION__);
+ return EINVAL;
+ }
+
+ /* There is no device to disconnect from. */
+ mState = ECDS_INITIALIZED;
+
+ return NO_ERROR;
+}
+
+status_t EmulatedFakeCameraDevice::startDevice(int width,
+ int height,
+ uint32_t pix_fmt,
+ int fps)
+{
+ ALOGV("%s", __FUNCTION__);
+
+ Mutex::Autolock locker(&mObjectLock);
+ if (!isConnected()) {
+ ALOGE("%s: Fake camera device is not connected.", __FUNCTION__);
+ return EINVAL;
+ }
+ if (isStarted()) {
+ ALOGE("%s: Fake camera device is already started.", __FUNCTION__);
+ return EINVAL;
+ }
+
+ /* Initialize the base class. */
+ const status_t res =
+ EmulatedCameraDevice::commonStartDevice(width, height, pix_fmt, fps);
+ if (res == NO_ERROR) {
+ /* Calculate U/V panes inside the framebuffer. */
+ switch (mPixelFormat) {
+ case V4L2_PIX_FMT_YVU420:
+ mFrameV = mCurrentFrame + mTotalPixels;
+ mFrameU = mFrameU + mTotalPixels / 4;
+ mUVStep = 1;
+ mUVTotalNum = mTotalPixels / 4;
+ break;
+
+ case V4L2_PIX_FMT_YUV420:
+ mFrameU = mCurrentFrame + mTotalPixels;
+ mFrameV = mFrameU + mTotalPixels / 4;
+ mUVStep = 1;
+ mUVTotalNum = mTotalPixels / 4;
+ break;
+
+ case V4L2_PIX_FMT_NV21:
+ /* Interleaved UV pane, V first. */
+ mFrameV = mCurrentFrame + mTotalPixels;
+ mFrameU = mFrameV + 1;
+ mUVStep = 2;
+ mUVTotalNum = mTotalPixels / 4;
+ break;
+
+ case V4L2_PIX_FMT_NV12:
+ /* Interleaved UV pane, U first. */
+ mFrameU = mCurrentFrame + mTotalPixels;
+ mFrameV = mFrameU + 1;
+ mUVStep = 2;
+ mUVTotalNum = mTotalPixels / 4;
+ break;
+
+ default:
+ ALOGE("%s: Unknown pixel format %.4s", __FUNCTION__,
+ reinterpret_cast<const char*>(&mPixelFormat));
+ return EINVAL;
+ }
+ /* Number of items in a single row inside U/V panes. */
+ mUVInRow = (width / 2) * mUVStep;
+ mState = ECDS_STARTED;
+ mCurFrameTimestamp = 0;
+ } else {
+ ALOGE("%s: commonStartDevice failed", __FUNCTION__);
+ }
+
+ return res;
+}
+
+status_t EmulatedFakeCameraDevice::stopDevice()
+{
+ ALOGV("%s", __FUNCTION__);
+
+ Mutex::Autolock locker(&mObjectLock);
+ if (!isStarted()) {
+ ALOGW("%s: Fake camera device is not started.", __FUNCTION__);
+ return NO_ERROR;
+ }
+
+ mFrameU = mFrameV = NULL;
+ EmulatedCameraDevice::commonStopDevice();
+ mState = ECDS_CONNECTED;
+
+ return NO_ERROR;
+}
+
+/****************************************************************************
+ * Worker thread management overrides.
+ ***************************************************************************/
+
+bool EmulatedFakeCameraDevice::inWorkerThread()
+{
+ /* Wait till FPS timeout expires, or thread exit message is received. */
+ WorkerThread::SelectRes res =
+ getWorkerThread()->Select(-1, 1000000 / (mTargetFps / 1000));
+ if (res == WorkerThread::EXIT_THREAD) {
+ ALOGV("%s: Worker thread has been terminated.", __FUNCTION__);
+ return false;
+ }
+
+ /* Lets see if we need to generate a new frame. */
+ if ((systemTime(SYSTEM_TIME_MONOTONIC) - mLastRedrawn) >= mRedrawAfter) {
+ /*
+ * Time to generate a new frame.
+ */
+
+#if EFCD_ROTATE_FRAME
+ const int frame_type = rotateFrame();
+ switch (frame_type) {
+ case 0:
+ drawCheckerboard();
+ break;
+ case 1:
+ drawStripes();
+ break;
+ case 2:
+ drawSolid(mCurrentColor);
+ break;
+ }
+#else
+ /* Draw the checker board. */
+ drawCheckerboard();
+
+#endif // EFCD_ROTATE_FRAME
+
+ // mCurFrameTimestamp = systemTime(SYSTEM_TIME_MONOTONIC);
+ mCurFrameTimestamp += (1000000000 / (mTargetFps / 1000));
+ /* Timestamp the current frame, and notify the camera HAL about new frame. */
+ mLastRedrawn = systemTime(SYSTEM_TIME_MONOTONIC);
+ mCameraHAL->onNextFrameAvailable(mCurrentFrame, mCurFrameTimestamp, this);
+ }
+
+
+ return true;
+}
+
+/****************************************************************************
+ * Fake camera device private API
+ ***************************************************************************/
+
+void EmulatedFakeCameraDevice::drawCheckerboard()
+{
+ const int size = mFrameWidth / 10;
+ bool black = true;
+
+ if (size == 0) {
+ // When this happens, it happens at a very high rate,
+ // so don't log any messages and just return.
+ return;
+ }
+
+
+ if((mCheckX / size) & 1)
+ black = false;
+ if((mCheckY / size) & 1)
+ black = !black;
+
+ int county = mCheckY % size;
+ int checkxremainder = mCheckX % size;
+ uint8_t* Y = mCurrentFrame;
+ uint8_t* U_pos = mFrameU;
+ uint8_t* V_pos = mFrameV;
+ uint8_t* U = U_pos;
+ uint8_t* V = V_pos;
+
+ YUVPixel adjustedWhite = YUVPixel(mWhiteYUV);
+ changeWhiteBalance(adjustedWhite.Y, adjustedWhite.U, adjustedWhite.V);
+
+ for(int y = 0; y < mFrameHeight; y++) {
+ int countx = checkxremainder;
+ bool current = black;
+ for(int x = 0; x < mFrameWidth; x += 2) {
+ if (current) {
+ mBlackYUV.get(Y, U, V);
+ } else {
+ adjustedWhite.get(Y, U, V);
+ }
+ *Y = changeExposure(*Y);
+ Y[1] = *Y;
+ Y += 2; U += mUVStep; V += mUVStep;
+ countx += 2;
+ if(countx >= size) {
+ countx = 0;
+ current = !current;
+ }
+ }
+ if (y & 0x1) {
+ U_pos = U;
+ V_pos = V;
+ } else {
+ U = U_pos;
+ V = V_pos;
+ }
+ if(county++ >= size) {
+ county = 0;
+ black = !black;
+ }
+ }
+ mCheckX += 3;
+ mCheckY++;
+
+ /* Run the square. */
+ int sqx = ((mCcounter * 3) & 255);
+ if(sqx > 128) sqx = 255 - sqx;
+ int sqy = ((mCcounter * 5) & 255);
+ if(sqy > 128) sqy = 255 - sqy;
+ const int sqsize = mFrameWidth / 10;
+ drawSquare(sqx * sqsize / 32, sqy * sqsize / 32, (sqsize * 5) >> 1,
+ (mCcounter & 0x100) ? &mRedYUV : &mGreenYUV);
+ mCcounter++;
+}
+
+void EmulatedFakeCameraDevice::drawSquare(int x,
+ int y,
+ int size,
+ const YUVPixel* color)
+{
+ const int square_xstop = min(mFrameWidth, x + size);
+ const int square_ystop = min(mFrameHeight, y + size);
+ uint8_t* Y_pos = mCurrentFrame + y * mFrameWidth + x;
+
+ YUVPixel adjustedColor = *color;
+ changeWhiteBalance(adjustedColor.Y, adjustedColor.U, adjustedColor.V);
+
+ // Draw the square.
+ for (; y < square_ystop; y++) {
+ const int iUV = (y / 2) * mUVInRow + (x / 2) * mUVStep;
+ uint8_t* sqU = mFrameU + iUV;
+ uint8_t* sqV = mFrameV + iUV;
+ uint8_t* sqY = Y_pos;
+ for (int i = x; i < square_xstop; i += 2) {
+ adjustedColor.get(sqY, sqU, sqV);
+ *sqY = changeExposure(*sqY);
+ sqY[1] = *sqY;
+ sqY += 2; sqU += mUVStep; sqV += mUVStep;
+ }
+ Y_pos += mFrameWidth;
+ }
+}
+
+#if EFCD_ROTATE_FRAME
+
+void EmulatedFakeCameraDevice::drawSolid(YUVPixel* color)
+{
+ YUVPixel adjustedColor = *color;
+ changeWhiteBalance(adjustedColor.Y, adjustedColor.U, adjustedColor.V);
+
+ /* All Ys are the same. */
+ memset(mCurrentFrame, changeExposure(adjustedColor.Y), mTotalPixels);
+
+ /* Fill U, and V panes. */
+ uint8_t* U = mFrameU;
+ uint8_t* V = mFrameV;
+ for (int k = 0; k < mUVTotalNum; k++, U += mUVStep, V += mUVStep) {
+ *U = color->U;
+ *V = color->V;
+ }
+}
+
+void EmulatedFakeCameraDevice::drawStripes()
+{
+ /* Divide frame into 4 stripes. */
+ const int change_color_at = mFrameHeight / 4;
+ const int each_in_row = mUVInRow / mUVStep;
+ uint8_t* pY = mCurrentFrame;
+ for (int y = 0; y < mFrameHeight; y++, pY += mFrameWidth) {
+ /* Select the color. */
+ YUVPixel* color;
+ const int color_index = y / change_color_at;
+ if (color_index == 0) {
+ /* White stripe on top. */
+ color = &mWhiteYUV;
+ } else if (color_index == 1) {
+ /* Then the red stripe. */
+ color = &mRedYUV;
+ } else if (color_index == 2) {
+ /* Then the green stripe. */
+ color = &mGreenYUV;
+ } else {
+ /* And the blue stripe at the bottom. */
+ color = &mBlueYUV;
+ }
+ changeWhiteBalance(color->Y, color->U, color->V);
+
+ /* All Ys at the row are the same. */
+ memset(pY, changeExposure(color->Y), mFrameWidth);
+
+ /* Offset of the current row inside U/V panes. */
+ const int uv_off = (y / 2) * mUVInRow;
+ /* Fill U, and V panes. */
+ uint8_t* U = mFrameU + uv_off;
+ uint8_t* V = mFrameV + uv_off;
+ for (int k = 0; k < each_in_row; k++, U += mUVStep, V += mUVStep) {
+ *U = color->U;
+ *V = color->V;
+ }
+ }
+}
+
+int EmulatedFakeCameraDevice::rotateFrame()
+{
+ if ((systemTime(SYSTEM_TIME_MONOTONIC) - mLastRotatedAt) >= mRotateFreq) {
+ mLastRotatedAt = systemTime(SYSTEM_TIME_MONOTONIC);
+ mCurrentFrameType++;
+ if (mCurrentFrameType > 2) {
+ mCurrentFrameType = 0;
+ }
+ if (mCurrentFrameType == 2) {
+ ALOGD("********** Rotated to the SOLID COLOR frame **********");
+ /* Solid color: lets rotate color too. */
+ if (mCurrentColor == &mWhiteYUV) {
+ ALOGD("----- Painting a solid RED frame -----");
+ mCurrentColor = &mRedYUV;
+ } else if (mCurrentColor == &mRedYUV) {
+ ALOGD("----- Painting a solid GREEN frame -----");
+ mCurrentColor = &mGreenYUV;
+ } else if (mCurrentColor == &mGreenYUV) {
+ ALOGD("----- Painting a solid BLUE frame -----");
+ mCurrentColor = &mBlueYUV;
+ } else {
+ /* Back to white. */
+ ALOGD("----- Painting a solid WHITE frame -----");
+ mCurrentColor = &mWhiteYUV;
+ }
+ } else if (mCurrentFrameType == 0) {
+ ALOGD("********** Rotated to the CHECKERBOARD frame **********");
+ } else if (mCurrentFrameType == 1) {
+ ALOGD("********** Rotated to the STRIPED frame **********");
+ }
+ }
+
+ return mCurrentFrameType;
+}
+
+#endif // EFCD_ROTATE_FRAME
+
+}; /* namespace android */
diff --git a/guest/hals/camera/EmulatedFakeCameraDevice.h b/guest/hals/camera/EmulatedFakeCameraDevice.h
new file mode 100755
index 0000000..a02d301
--- /dev/null
+++ b/guest/hals/camera/EmulatedFakeCameraDevice.h
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2011 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 HW_EMULATOR_CAMERA_EMULATED_FAKE_CAMERA_DEVICE_H
+#define HW_EMULATOR_CAMERA_EMULATED_FAKE_CAMERA_DEVICE_H
+
+/*
+ * Contains declaration of a class EmulatedFakeCameraDevice that encapsulates
+ * a fake camera device.
+ */
+
+#include "Converters.h"
+#include "EmulatedCameraDevice.h"
+
+/* This is used for debugging format / conversion issues. If EFCD_ROTATE_FRAME is
+ * set to 0, the frame content will be always the "checkerboard". Otherwise, if
+ * EFCD_ROTATE_FRAME is set to a non-zero value, the frame content will "rotate"
+ * from a "checkerboard" frame to a "white/red/green/blue stripes" frame, to a
+ * "white/red/green/blue" frame. Frame content rotation helps finding bugs in
+ * format conversions.
+ */
+#define EFCD_ROTATE_FRAME 0
+
+namespace android {
+
+class EmulatedFakeCamera;
+
+/* Encapsulates a fake camera device.
+ * Fake camera device emulates a camera device by providing frames containing
+ * a black and white checker board, moving diagonally towards the 0,0 corner.
+ * There is also a green, or red square that bounces inside the frame, changing
+ * its color when bouncing off the 0,0 corner.
+ */
+class EmulatedFakeCameraDevice : public EmulatedCameraDevice {
+public:
+ /* Constructs EmulatedFakeCameraDevice instance. */
+ explicit EmulatedFakeCameraDevice(EmulatedFakeCamera* camera_hal);
+
+ /* Destructs EmulatedFakeCameraDevice instance. */
+ ~EmulatedFakeCameraDevice();
+
+ /***************************************************************************
+ * Emulated camera device abstract interface implementation.
+ * See declarations of these methods in EmulatedCameraDevice class for
+ * information on each of these methods.
+ **************************************************************************/
+
+public:
+ /* Connects to the camera device.
+ * Since there is no real device to connect to, this method does nothing,
+ * but changes the state.
+ */
+ status_t connectDevice();
+
+ /* Disconnects from the camera device.
+ * Since there is no real device to disconnect from, this method does
+ * nothing, but changes the state.
+ */
+ status_t disconnectDevice();
+
+ /* Starts the camera device. */
+ status_t startDevice(int width, int height, uint32_t pix_fmt, int fps);
+
+ /* Stops the camera device. */
+ status_t stopDevice();
+
+ /* Gets current preview fame into provided buffer. */
+ status_t getPreviewFrame(void* buffer) {
+ return OK;
+ }
+
+ /***************************************************************************
+ * Worker thread management overrides.
+ * See declarations of these methods in EmulatedCameraDevice class for
+ * information on each of these methods.
+ **************************************************************************/
+
+protected:
+ /* Implementation of the worker thread routine.
+ * This method simply sleeps for a period of time defined by the FPS property
+ * of the fake camera (simulating frame frequency), and then calls emulated
+ * camera's onNextFrameAvailable method.
+ */
+ bool inWorkerThread();
+
+ /****************************************************************************
+ * Fake camera device private API
+ ***************************************************************************/
+
+private:
+
+ /* Draws a black and white checker board in the current frame buffer. */
+ void drawCheckerboard();
+
+ /* Draws a square of the given color in the current frame buffer.
+ * Param:
+ * x, y - Coordinates of the top left corner of the square in the buffer.
+ * size - Size of the square's side.
+ * color - Square's color.
+ */
+ void drawSquare(int x, int y, int size, const YUVPixel* color);
+
+#if EFCD_ROTATE_FRAME
+ void drawSolid(YUVPixel* color);
+ void drawStripes();
+ int rotateFrame();
+#endif // EFCD_ROTATE_FRAME
+
+ /****************************************************************************
+ * Fake camera device data members
+ ***************************************************************************/
+
+private:
+ /*
+ * Pixel colors in YUV format used when drawing the checker board.
+ */
+
+ YUVPixel mBlackYUV;
+ YUVPixel mWhiteYUV;
+ YUVPixel mRedYUV;
+ YUVPixel mGreenYUV;
+ YUVPixel mBlueYUV;
+
+ /* Last time the frame has been redrawn. */
+ nsecs_t mLastRedrawn;
+
+ /*
+ * Precalculated values related to U/V panes.
+ */
+
+ /* U pane inside the framebuffer. */
+ uint8_t* mFrameU;
+
+ /* V pane inside the framebuffer. */
+ uint8_t* mFrameV;
+
+ /* Defines byte distance between adjacent U, and V values. */
+ int mUVStep;
+
+ /* Defines number of Us and Vs in a row inside the U/V panes.
+ * Note that if U/V panes are interleaved, this value reflects the total
+ * number of both, Us and Vs in a single row in the interleaved UV pane. */
+ int mUVInRow;
+
+ /* Total number of each, U, and V elements in the framebuffer. */
+ int mUVTotalNum;
+
+ /*
+ * Checkerboard drawing related stuff
+ */
+
+ int mCheckX;
+ int mCheckY;
+ int mCcounter;
+
+ /* Defines time (in nanoseconds) between redrawing the checker board.
+ * We will redraw the checker board every 15 milliseconds. */
+ static const nsecs_t mRedrawAfter = 15000000LL;
+
+#if EFCD_ROTATE_FRAME
+ /* Frame rotation frequency in nanosec (currently - 3 sec) */
+ static const nsecs_t mRotateFreq = 3000000000LL;
+
+ /* Last time the frame has rotated. */
+ nsecs_t mLastRotatedAt;
+
+ /* Type of the frame to display in the current rotation:
+ * 0 - Checkerboard.
+ * 1 - White/Red/Green/Blue horisontal stripes
+ * 2 - Solid color. */
+ int mCurrentFrameType;
+
+ /* Color to use to paint the solid color frame. Colors will rotate between
+ * white, red, gree, and blue each time rotation comes to the solid color
+ * frame. */
+ YUVPixel* mCurrentColor;
+#endif // EFCD_ROTATE_FRAME
+};
+
+}; /* namespace android */
+
+#endif /* HW_EMULATOR_CAMERA_EMULATED_FAKE_CAMERA_DEVICE_H */
diff --git a/guest/hals/camera/EmulatedQemuCamera.cpp b/guest/hals/camera/EmulatedQemuCamera.cpp
new file mode 100755
index 0000000..af1e324
--- /dev/null
+++ b/guest/hals/camera/EmulatedQemuCamera.cpp
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+/*
+ * Contains implementation of a class EmulatedQemuCamera that encapsulates
+ * functionality of an emulated camera connected to the host.
+ */
+
+#define LOG_NDEBUG 0
+#define LOG_TAG "EmulatedCamera_QemuCamera"
+#include <cutils/log.h>
+#include "EmulatedQemuCamera.h"
+#include "EmulatedCameraFactory.h"
+
+namespace android {
+
+EmulatedQemuCamera::EmulatedQemuCamera(int cameraId, struct hw_module_t* module)
+ : EmulatedCamera(cameraId, module),
+ mQemuCameraDevice(this)
+{
+}
+
+EmulatedQemuCamera::~EmulatedQemuCamera()
+{
+}
+
+/****************************************************************************
+ * EmulatedCamera virtual overrides.
+ ***************************************************************************/
+
+status_t EmulatedQemuCamera::Initialize(const char* device_name,
+ const char* frame_dims,
+ const char* facing_dir)
+{
+ ALOGV("%s:\n Name=%s\n Facing '%s'\n Dimensions=%s",
+ __FUNCTION__, device_name, facing_dir, frame_dims);
+ /* Save dimensions. */
+ mFrameDims = frame_dims;
+
+ /* Initialize camera device. */
+ status_t res = mQemuCameraDevice.Initialize(device_name);
+ if (res != NO_ERROR) {
+ return res;
+ }
+
+ /* Initialize base class. */
+ res = EmulatedCamera::Initialize();
+ if (res != NO_ERROR) {
+ return res;
+ }
+
+ /*
+ * Set customizable parameters.
+ */
+
+ mParameters.set(EmulatedCamera::FACING_KEY, facing_dir);
+ mParameters.set(EmulatedCamera::ORIENTATION_KEY,
+ gEmulatedCameraFactory.getQemuCameraOrientation());
+ mParameters.set(CameraParameters::KEY_SUPPORTED_PICTURE_SIZES, frame_dims);
+ mParameters.set(CameraParameters::KEY_SUPPORTED_PREVIEW_SIZES, frame_dims);
+
+ /*
+ * Use first dimension reported by the device to set current preview and
+ * picture sizes.
+ */
+
+ char first_dim[128];
+ /* Dimensions are separated with ',' */
+ const char* c = strchr(frame_dims, ',');
+ if (c == NULL) {
+ strncpy(first_dim, frame_dims, sizeof(first_dim));
+ first_dim[sizeof(first_dim)-1] = '\0';
+ } else if (static_cast<size_t>(c - frame_dims) < sizeof(first_dim)) {
+ memcpy(first_dim, frame_dims, c - frame_dims);
+ first_dim[c - frame_dims] = '\0';
+ } else {
+ memcpy(first_dim, frame_dims, sizeof(first_dim));
+ first_dim[sizeof(first_dim)-1] = '\0';
+ }
+
+ /* Width and height are separated with 'x' */
+ char* sep = strchr(first_dim, 'x');
+ if (sep == NULL) {
+ ALOGE("%s: Invalid first dimension format in %s",
+ __FUNCTION__, frame_dims);
+ return EINVAL;
+ }
+
+ *sep = '\0';
+ const int x = atoi(first_dim);
+ const int y = atoi(sep + 1);
+ mParameters.setPreviewSize(x, y);
+ mParameters.setPictureSize(x, y);
+
+ ALOGV("%s: Qemu camera %s is initialized. Current frame is %dx%d",
+ __FUNCTION__, device_name, x, y);
+
+ return NO_ERROR;
+}
+
+EmulatedCameraDevice* EmulatedQemuCamera::getCameraDevice()
+{
+ return &mQemuCameraDevice;
+}
+
+}; /* namespace android */
diff --git a/guest/hals/camera/EmulatedQemuCamera.h b/guest/hals/camera/EmulatedQemuCamera.h
new file mode 100755
index 0000000..1b826c7
--- /dev/null
+++ b/guest/hals/camera/EmulatedQemuCamera.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2011 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 HW_EMULATOR_CAMERA_EMULATED_QEMU_CAMERA_H
+#define HW_EMULATOR_CAMERA_EMULATED_QEMU_CAMERA_H
+
+/*
+ * Contains declaration of a class EmulatedQemuCamera that encapsulates
+ * functionality of an emulated camera connected to the host.
+ */
+
+#include "EmulatedCamera.h"
+#include "EmulatedQemuCameraDevice.h"
+
+namespace android {
+
+/* Encapsulates functionality of an emulated camera connected to the host.
+ */
+class EmulatedQemuCamera : public EmulatedCamera {
+public:
+ /* Constructs EmulatedQemuCamera instance. */
+ EmulatedQemuCamera(int cameraId, struct hw_module_t* module);
+
+ /* Destructs EmulatedQemuCamera instance. */
+ ~EmulatedQemuCamera();
+
+ /***************************************************************************
+ * EmulatedCamera virtual overrides.
+ **************************************************************************/
+
+public:
+ /* Initializes EmulatedQemuCamera instance. */
+ status_t Initialize(const char* device_name,
+ const char* frame_dims,
+ const char* facing_dir);
+
+ /***************************************************************************
+ * EmulatedCamera abstract API implementation.
+ **************************************************************************/
+
+protected:
+ /* Gets emulated camera device ised by this instance of the emulated camera.
+ */
+ EmulatedCameraDevice* getCameraDevice();
+
+ /***************************************************************************
+ * Data memebers.
+ **************************************************************************/
+
+protected:
+ /* Contained qemu camera device object. */
+ EmulatedQemuCameraDevice mQemuCameraDevice;
+
+ /* Supported frame dimensions reported by the camera device. */
+ String8 mFrameDims;
+};
+
+}; /* namespace android */
+
+#endif /* HW_EMULATOR_CAMERA_EMULATED_QEMU_CAMERA_H */
diff --git a/guest/hals/camera/EmulatedQemuCamera2.cpp b/guest/hals/camera/EmulatedQemuCamera2.cpp
new file mode 100644
index 0000000..2c94f0e
--- /dev/null
+++ b/guest/hals/camera/EmulatedQemuCamera2.cpp
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+/*
+ * Contains implementation of a class EmulatedQemuCamera2 that encapsulates
+ * functionality of a host webcam with further processing to simulate the
+ * capabilities of a v2 camera device.
+ */
+
+#define LOG_NDEBUG 0
+#define LOG_TAG "EmulatedCamera_QemuCamera2"
+#include <cutils/log.h>
+#include <cutils/properties.h>
+#include "EmulatedQemuCamera2.h"
+#include "EmulatedCameraFactory.h"
+
+namespace android {
+
+EmulatedQemuCamera2::EmulatedQemuCamera2(int cameraId,
+ bool facingBack,
+ struct hw_module_t* module)
+ : EmulatedCamera2(cameraId,module),
+ mFacingBack(facingBack)
+{
+ ALOGD("Constructing emulated qemu camera 2 facing %s",
+ facingBack ? "back" : "front");
+}
+
+EmulatedQemuCamera2::~EmulatedQemuCamera2()
+{
+}
+
+/****************************************************************************
+ * Public API overrides
+ ***************************************************************************/
+
+status_t EmulatedQemuCamera2::Initialize()
+{
+ return NO_ERROR;
+}
+
+}; /* namespace android */
diff --git a/guest/hals/camera/EmulatedQemuCamera2.h b/guest/hals/camera/EmulatedQemuCamera2.h
new file mode 100644
index 0000000..520ccce
--- /dev/null
+++ b/guest/hals/camera/EmulatedQemuCamera2.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2012 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 HW_EMULATOR_CAMERA_EMULATED_QEMU_CAMERA2_H
+#define HW_EMULATOR_CAMERA_EMULATED_QEMU_CAMERA2_H
+
+/*
+ * Contains declaration of a class EmulatedQemuCamera2 that encapsulates
+ * functionality of a host webcam with added processing to implement version 2
+ * of the camera device interface.
+ */
+
+#include "EmulatedCamera2.h"
+
+namespace android {
+
+/* Encapsulates functionality of an advanced fake camera based on real host camera data.
+ */
+class EmulatedQemuCamera2 : public EmulatedCamera2 {
+public:
+ /* Constructs EmulatedFakeCamera instance. */
+ EmulatedQemuCamera2(int cameraId, bool facingBack, struct hw_module_t* module);
+
+ /* Destructs EmulatedFakeCamera instance. */
+ ~EmulatedQemuCamera2();
+
+ /****************************************************************************
+ * EmulatedCamera2 virtual overrides.
+ ***************************************************************************/
+
+public:
+ /* Initializes EmulatedQemuCamera2 instance. */
+ status_t Initialize();
+
+ /****************************************************************************
+ * EmulatedCamera abstract API implementation.
+ ***************************************************************************/
+
+protected:
+
+ /****************************************************************************
+ * Data memebers.
+ ***************************************************************************/
+
+protected:
+ /* Facing back (true) or front (false) switch. */
+ bool mFacingBack;
+
+};
+
+}; /* namespace android */
+
+#endif /* HW_EMULATOR_CAMERA_EMULATED_QEMU_CAMERA2_H */
diff --git a/guest/hals/camera/EmulatedQemuCameraDevice.cpp b/guest/hals/camera/EmulatedQemuCameraDevice.cpp
new file mode 100755
index 0000000..ef5406b
--- /dev/null
+++ b/guest/hals/camera/EmulatedQemuCameraDevice.cpp
@@ -0,0 +1,267 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+/*
+ * Contains implementation of a class EmulatedQemuCameraDevice that encapsulates
+ * an emulated camera device connected to the host.
+ */
+
+#define LOG_NDEBUG 0
+#define LOG_TAG "EmulatedCamera_QemuDevice"
+#include <cutils/log.h>
+#include "EmulatedQemuCamera.h"
+#include "EmulatedQemuCameraDevice.h"
+
+namespace android {
+
+EmulatedQemuCameraDevice::EmulatedQemuCameraDevice(EmulatedQemuCamera* camera_hal)
+ : EmulatedCameraDevice(camera_hal),
+ mQemuClient(),
+ mPreviewFrame(NULL)
+{
+}
+
+EmulatedQemuCameraDevice::~EmulatedQemuCameraDevice()
+{
+ if (mPreviewFrame != NULL) {
+ delete[] mPreviewFrame;
+ }
+}
+
+/****************************************************************************
+ * Public API
+ ***************************************************************************/
+
+status_t EmulatedQemuCameraDevice::Initialize(const char* device_name)
+{
+ /* Connect to the service. */
+ char connect_str[256];
+ snprintf(connect_str, sizeof(connect_str), "name=%s", device_name);
+ status_t res = mQemuClient.connectClient(connect_str);
+ if (res != NO_ERROR) {
+ return res;
+ }
+
+ /* Initialize base class. */
+ res = EmulatedCameraDevice::Initialize();
+ if (res == NO_ERROR) {
+ ALOGV("%s: Connected to the emulated camera service '%s'",
+ __FUNCTION__, device_name);
+ mDeviceName = device_name;
+ } else {
+ mQemuClient.queryDisconnect();
+ }
+
+ return res;
+}
+
+/****************************************************************************
+ * Emulated camera device abstract interface implementation.
+ ***************************************************************************/
+
+status_t EmulatedQemuCameraDevice::connectDevice()
+{
+ ALOGV("%s", __FUNCTION__);
+
+ Mutex::Autolock locker(&mObjectLock);
+ if (!isInitialized()) {
+ ALOGE("%s: Qemu camera device is not initialized.", __FUNCTION__);
+ return EINVAL;
+ }
+ if (isConnected()) {
+ ALOGW("%s: Qemu camera device '%s' is already connected.",
+ __FUNCTION__, (const char*)mDeviceName);
+ return NO_ERROR;
+ }
+
+ /* Connect to the camera device via emulator. */
+ const status_t res = mQemuClient.queryConnect();
+ if (res == NO_ERROR) {
+ ALOGV("%s: Connected to device '%s'",
+ __FUNCTION__, (const char*)mDeviceName);
+ mState = ECDS_CONNECTED;
+ } else {
+ ALOGE("%s: Connection to device '%s' failed",
+ __FUNCTION__, (const char*)mDeviceName);
+ }
+
+ return res;
+}
+
+status_t EmulatedQemuCameraDevice::disconnectDevice()
+{
+ ALOGV("%s", __FUNCTION__);
+
+ Mutex::Autolock locker(&mObjectLock);
+ if (!isConnected()) {
+ ALOGW("%s: Qemu camera device '%s' is already disconnected.",
+ __FUNCTION__, (const char*)mDeviceName);
+ return NO_ERROR;
+ }
+ if (isStarted()) {
+ ALOGE("%s: Cannot disconnect from the started device '%s.",
+ __FUNCTION__, (const char*)mDeviceName);
+ return EINVAL;
+ }
+
+ /* Disconnect from the camera device via emulator. */
+ const status_t res = mQemuClient.queryDisconnect();
+ if (res == NO_ERROR) {
+ ALOGV("%s: Disonnected from device '%s'",
+ __FUNCTION__, (const char*)mDeviceName);
+ mState = ECDS_INITIALIZED;
+ } else {
+ ALOGE("%s: Disconnection from device '%s' failed",
+ __FUNCTION__, (const char*)mDeviceName);
+ }
+
+ return res;
+}
+
+status_t EmulatedQemuCameraDevice::startDevice(int width,
+ int height,
+ uint32_t pix_fmt,
+ int fps)
+{
+ ALOGV("%s", __FUNCTION__);
+
+ Mutex::Autolock locker(&mObjectLock);
+ if (!isConnected()) {
+ ALOGE("%s: Qemu camera device '%s' is not connected.",
+ __FUNCTION__, (const char*)mDeviceName);
+ return EINVAL;
+ }
+ if (isStarted()) {
+ ALOGW("%s: Qemu camera device '%s' is already started.",
+ __FUNCTION__, (const char*)mDeviceName);
+ return NO_ERROR;
+ }
+
+ status_t res = EmulatedCameraDevice::commonStartDevice(
+ width, height, pix_fmt, fps);
+ if (res != NO_ERROR) {
+ ALOGE("%s: commonStartDevice failed", __FUNCTION__);
+ return res;
+ }
+
+ /* Allocate preview frame buffer. */
+ /* TODO: Watch out for preview format changes! At this point we implement
+ * RGB32 only.*/
+ mPreviewFrame = new uint32_t[mTotalPixels];
+ if (mPreviewFrame == NULL) {
+ ALOGE("%s: Unable to allocate %d bytes for preview frame",
+ __FUNCTION__, mTotalPixels);
+ return ENOMEM;
+ }
+
+ /* Start the actual camera device. */
+ res = mQemuClient.queryStart(mPixelFormat, mFrameWidth, mFrameHeight);
+ if (res == NO_ERROR) {
+ ALOGV("%s: Qemu camera device '%s' is started for %.4s[%dx%d] frames",
+ __FUNCTION__, (const char*)mDeviceName,
+ reinterpret_cast<const char*>(&mPixelFormat),
+ mFrameWidth, mFrameHeight);
+ mState = ECDS_STARTED;
+ } else {
+ ALOGE("%s: Unable to start device '%s' for %.4s[%dx%d] frames",
+ __FUNCTION__, (const char*)mDeviceName,
+ reinterpret_cast<const char*>(&pix_fmt), width, height);
+ }
+
+ return res;
+}
+
+status_t EmulatedQemuCameraDevice::stopDevice()
+{
+ ALOGV("%s", __FUNCTION__);
+
+ Mutex::Autolock locker(&mObjectLock);
+ if (!isStarted()) {
+ ALOGW("%s: Qemu camera device '%s' is not started.",
+ __FUNCTION__, (const char*)mDeviceName);
+ return NO_ERROR;
+ }
+
+ /* Stop the actual camera device. */
+ status_t res = mQemuClient.queryStop();
+ if (res == NO_ERROR) {
+ if (mPreviewFrame == NULL) {
+ delete[] mPreviewFrame;
+ mPreviewFrame = NULL;
+ }
+ EmulatedCameraDevice::commonStopDevice();
+ mState = ECDS_CONNECTED;
+ ALOGV("%s: Qemu camera device '%s' is stopped",
+ __FUNCTION__, (const char*)mDeviceName);
+ } else {
+ ALOGE("%s: Unable to stop device '%s'",
+ __FUNCTION__, (const char*)mDeviceName);
+ }
+
+ return res;
+}
+
+/****************************************************************************
+ * EmulatedCameraDevice virtual overrides
+ ***************************************************************************/
+
+status_t EmulatedQemuCameraDevice::getCurrentPreviewFrame(void* buffer)
+{
+ ALOGW_IF(mPreviewFrame == NULL, "%s: No preview frame", __FUNCTION__);
+ if (mPreviewFrame != NULL) {
+ memcpy(buffer, mPreviewFrame, mTotalPixels * 4);
+ return 0;
+ } else {
+ return EmulatedCameraDevice::getCurrentPreviewFrame(buffer);
+ }
+}
+
+/****************************************************************************
+ * Worker thread management overrides.
+ ***************************************************************************/
+
+bool EmulatedQemuCameraDevice::inWorkerThread()
+{
+ /* Wait till FPS timeout expires, or thread exit message is received. */
+ WorkerThread::SelectRes res =
+ getWorkerThread()->Select(-1, 1000000 / mEmulatedFPS);
+ if (res == WorkerThread::EXIT_THREAD) {
+ ALOGV("%s: Worker thread has been terminated.", __FUNCTION__);
+ return false;
+ }
+
+ /* Query frames from the service. */
+ status_t query_res = mQemuClient.queryFrame(mCurrentFrame, mPreviewFrame,
+ mFrameBufferSize,
+ mTotalPixels * 4,
+ mWhiteBalanceScale[0],
+ mWhiteBalanceScale[1],
+ mWhiteBalanceScale[2],
+ mExposureCompensation);
+ if (query_res == NO_ERROR) {
+ /* Timestamp the current frame, and notify the camera HAL. */
+ mCurFrameTimestamp = systemTime(SYSTEM_TIME_MONOTONIC);
+ mCameraHAL->onNextFrameAvailable(mCurrentFrame, mCurFrameTimestamp, this);
+ return true;
+ } else {
+ ALOGE("%s: Unable to get current video frame: %s",
+ __FUNCTION__, strerror(query_res));
+ mCameraHAL->onCameraDeviceError(CAMERA_ERROR_SERVER_DIED);
+ return false;
+ }
+}
+
+}; /* namespace android */
diff --git a/guest/hals/camera/EmulatedQemuCameraDevice.h b/guest/hals/camera/EmulatedQemuCameraDevice.h
new file mode 100755
index 0000000..6664f44
--- /dev/null
+++ b/guest/hals/camera/EmulatedQemuCameraDevice.h
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2011 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 HW_EMULATOR_CAMERA_EMULATED_QEMU_CAMERA_DEVICE_H
+#define HW_EMULATOR_CAMERA_EMULATED_QEMU_CAMERA_DEVICE_H
+
+/*
+ * Contains declaration of a class EmulatedQemuCameraDevice that encapsulates
+ * an emulated camera device connected to the host.
+ */
+
+#include "EmulatedCameraDevice.h"
+#include "QemuClient.h"
+
+namespace android {
+
+class EmulatedQemuCamera;
+
+/* Encapsulates an emulated camera device connected to the host.
+ */
+class EmulatedQemuCameraDevice : public EmulatedCameraDevice {
+public:
+ /* Constructs EmulatedQemuCameraDevice instance. */
+ explicit EmulatedQemuCameraDevice(EmulatedQemuCamera* camera_hal);
+
+ /* Destructs EmulatedQemuCameraDevice instance. */
+ ~EmulatedQemuCameraDevice();
+
+ /***************************************************************************
+ * Public API
+ **************************************************************************/
+
+public:
+ /* Initializes EmulatedQemuCameraDevice instance.
+ * Param:
+ * device_name - Name of the camera device connected to the host. The name
+ * that is used here must have been reported by the 'factory' camera
+ * service when it listed camera devices connected to the host.
+ * Return:
+ * NO_ERROR on success, or an appropriate error status.
+ */
+ status_t Initialize(const char* device_name);
+
+ /***************************************************************************
+ * Emulated camera device abstract interface implementation.
+ * See declarations of these methods in EmulatedCameraDevice class for
+ * information on each of these methods.
+ **************************************************************************/
+
+public:
+ /* Connects to the camera device. */
+ status_t connectDevice();
+
+ /* Disconnects from the camera device. */
+ status_t disconnectDevice();
+
+ /* Starts capturing frames from the camera device. */
+ status_t startDevice(int width, int height, uint32_t pix_fmt, int fps);
+
+ /* Stops capturing frames from the camera device. */
+ status_t stopDevice();
+
+ /***************************************************************************
+ * EmulatedCameraDevice virtual overrides
+ * See declarations of these methods in EmulatedCameraDevice class for
+ * information on each of these methods.
+ **************************************************************************/
+
+public:
+ /* Gets current preview fame into provided buffer.
+ * We override this method in order to provide preview frames cached in this
+ * object.
+ */
+ status_t getCurrentPreviewFrame(void* buffer);
+
+ /***************************************************************************
+ * Worker thread management overrides.
+ * See declarations of these methods in EmulatedCameraDevice class for
+ * information on each of these methods.
+ **************************************************************************/
+
+protected:
+ /* Implementation of the worker thread routine. */
+ bool inWorkerThread();
+
+ /***************************************************************************
+ * Qemu camera device data members
+ **************************************************************************/
+
+private:
+ /* Qemu client that is used to communicate with the 'emulated camera'
+ * service, created for this instance in the emulator. */
+ CameraQemuClient mQemuClient;
+
+ /* Name of the camera device connected to the host. */
+ String8 mDeviceName;
+
+ /* Current preview framebuffer. */
+ uint32_t* mPreviewFrame;
+
+ /* Emulated FPS (frames per second).
+ * We will emulate 50 FPS. */
+ static const int mEmulatedFPS = 50;
+};
+
+}; /* namespace android */
+
+#endif /* HW_EMULATOR_CAMERA_EMULATED_QEMU_CAMERA_DEVICE_H */
diff --git a/guest/hals/camera/ExifMetadataBuilder.cpp b/guest/hals/camera/ExifMetadataBuilder.cpp
new file mode 100644
index 0000000..71eaebc
--- /dev/null
+++ b/guest/hals/camera/ExifMetadataBuilder.cpp
@@ -0,0 +1,550 @@
+#include "ExifMetadataBuilder.h"
+
+#define LOG_NDEBUG 0
+#define LOG_TAG "ExifMetadataBuilder"
+#include <cutils/log.h>
+
+#include <cmath>
+#include <stdlib.h>
+
+namespace android {
+// All supported EXIF data types.
+enum ExifDataType {
+ ExifUInt8 = 1,
+ ExifString = 2,
+ ExifUInt16 = 3,
+ ExifUInt32 = 4,
+ ExifRational = 5,
+ ExifUndefined = 7,
+ ExifSInt16 = 8,
+ ExifSInt32 = 9,
+ ExifFloat = 11,
+ ExifDouble = 12,
+};
+
+enum ExifTagId {
+ kExifTagGpsLatitudeRef = 0x1,
+ kExifTagGpsLatitude = 0x2,
+ kExifTagGpsLongitudeRef = 0x3,
+ kExifTagGpsLongitude = 0x4,
+ kExifTagGpsAltitudeRef = 0x5,
+ kExifTagGpsAltitude = 0x6,
+ kExifTagGpsTimestamp = 0x7,
+ kExifTagGpsProcessingMethod = 0x1b,
+ kExifTagGpsDatestamp = 0x1d,
+ kExifTagImageWidth = 0x100,
+ kExifTagImageHeight = 0x101,
+ kExifTagImageDateTime = 0x132,
+ kExifTagJpegData = 0x201,
+ kExifTagJpegLength = 0x202,
+ kExifTagCameraSubIFD = 0x8769,
+ kExifTagGpsSubIFD = 0x8825,
+ kExifTagCameraFocalLength = 0x920a,
+};
+
+const char kExifCharArrayAscii[8] = "ASCII";
+const char kExifCharArrayUnicode[8] = "UNICODE";
+
+// Structure of an individual EXIF tag.
+struct ExifTagInfo {
+ uint16_t tag; // Describes unique tag type.
+ uint16_t type; // Describes data type (see ExifDataType).
+ uint32_t count; // Number of data elements.
+ uint32_t value; // Value (for shorter items) or offset (for longer).
+};
+
+// Interface describing individual EXIF tag.
+class ExifTag {
+ public:
+ virtual ~ExifTag() {}
+ virtual size_t DataSize() { return 0; }
+ virtual void AppendTag(ExifTagInfo* info, size_t data_offset) = 0;
+ virtual void AppendData(uint8_t* target) {}
+};
+
+// EXIF structure.
+// This structure describes all types of tags:
+// - Main image,
+// - Thumbnail image,
+// - Sub-IFDs.
+class ExifStructure {
+ public:
+ typedef std::vector<ExifTag*> TagMap;
+
+ ExifStructure() {}
+
+ ~ExifStructure() {
+ for (TagMap::iterator it = mTags.begin(); it != mTags.end(); ++it) {
+ delete *it;
+ }
+ }
+
+ size_t TagSize() {
+ // Target tag structure:
+ // - uint16_t: mTags.size();
+ // - ExifTagInfo[mTags.size()]
+ // - uint32_t: next_structure_available ? self_offset + Size() : NULL
+ return sizeof(uint16_t) // mTags.size()
+ + mTags.size() * sizeof(ExifTagInfo) // [tags]
+ + sizeof(uint32_t); // next offset
+ }
+
+ size_t DataSize() {
+ size_t data_size = 0;
+ for (TagMap::iterator it = mTags.begin(); it != mTags.end(); ++it) {
+ data_size += (*it)->DataSize();
+ }
+ return data_size;
+ }
+
+ size_t Size() {
+ return TagSize() + DataSize();
+ }
+
+ uint32_t Build(
+ uint8_t* buffer,
+ const uint32_t self_offset,
+ const bool next_structure_available) {
+ // Write number of items.
+ uint16_t num_elements = mTags.size();
+ memcpy(buffer, &num_elements, sizeof(num_elements));
+ buffer += sizeof(num_elements);
+
+ // Offset describes where tag data will be placed.
+ uint32_t offset = self_offset + TagSize();
+
+ // Combine EXIF for main image.
+ for (TagMap::iterator it = mTags.begin(); it != mTags.end(); ++it) {
+ // Each tag is exactly 12 bytes long, but data length can be anything.
+ // We supply the data offset to anyone who wants to use data.
+ (*it)->AppendTag(reinterpret_cast<ExifTagInfo*>(buffer), offset);
+ offset += (*it)->DataSize();
+ buffer += sizeof(ExifTagInfo);
+ }
+
+ // Append information about the second tag offset.
+ // |offset| holds exactly the right position:
+ // self_offset + TagSize() + DataSize().
+ uint32_t next_tag_offset = next_structure_available ? offset : 0;
+ memcpy(buffer, &next_tag_offset, sizeof(next_tag_offset));
+ buffer += sizeof(next_tag_offset);
+
+ // Combine EXIF data for main image.
+ for (TagMap::iterator it = mTags.begin(); it != mTags.end(); ++it) {
+ (*it)->AppendData(buffer);
+ buffer += (*it)->DataSize();
+ }
+
+ return offset;
+ }
+
+ void PushTag(ExifTag* tag) {
+ mTags.push_back(tag);
+ }
+
+ private:
+ TagMap mTags;
+};
+
+
+// EXIF tags.
+namespace {
+// Tag with 8-bit unsigned integer.
+class ExifUInt8Tag : public ExifTag {
+ public:
+ ExifUInt8Tag(uint16_t tag, size_t value) : mTag(tag), mValue(value) {}
+
+ void AppendTag(ExifTagInfo* info, size_t data_offset) {
+ info->tag = mTag;
+ info->type = ExifUInt8;
+ info->count = 1;
+ info->value = mValue << 24;
+ }
+
+ private:
+ uint16_t mTag;
+ uint32_t mValue;
+};
+
+// Tag with 32-bit unsigned integer.
+class ExifUInt32Tag : public ExifTag {
+ public:
+ ExifUInt32Tag(uint16_t tag, size_t value) : mTag(tag), mValue(value) {}
+
+ void AppendTag(ExifTagInfo* info, size_t data_offset) {
+ info->tag = mTag;
+ info->type = ExifUInt32;
+ info->count = 1;
+ info->value = mValue;
+ }
+
+ private:
+ uint16_t mTag;
+ uint32_t mValue;
+};
+
+// Char array tag.
+class ExifCharArrayTag : public ExifTag {
+ public:
+ ExifCharArrayTag(uint16_t tag, const char (&type)[8], const std::string& str)
+ : mTag(tag),
+ mType(type),
+ mString(str) {}
+
+ void AppendTag(ExifTagInfo* info, size_t data_offset) {
+ info->tag = mTag;
+ info->type = ExifUndefined;
+ info->count = DataSize();
+ info->value = data_offset;
+ }
+
+ size_t DataSize() {
+ return sizeof(mType) + mString.size();
+ }
+
+ void AppendData(uint8_t* data) {
+ memcpy(data, mType, sizeof(mType));
+ data += sizeof(mType);
+ memcpy(data, mString.data(), mString.size());
+ }
+
+ private:
+ uint16_t mTag;
+ const char (&mType)[8];
+ std::string mString;
+};
+
+// Data tag; writes LONG (pointer) and appends data.
+class ExifPointerTag : public ExifTag {
+ public:
+ ExifPointerTag(uint16_t tag, void* data, int size)
+ : mTag(tag),
+ mData(data),
+ mSize(size) {}
+
+ ~ExifPointerTag() {
+ free(mData);
+ }
+
+ void AppendTag(ExifTagInfo* info, size_t data_offset) {
+ info->tag = mTag;
+ info->type = ExifUInt32;
+ info->count = 1;
+ info->value = data_offset;
+ }
+
+ size_t DataSize() {
+ return mSize;
+ }
+
+ void AppendData(uint8_t* data) {
+ memcpy(data, mData, mSize);
+ }
+
+ private:
+ uint16_t mTag;
+ void* mData;
+ int mSize;
+};
+
+// String tag.
+class ExifStringTag : public ExifTag {
+ public:
+ ExifStringTag(uint16_t tag, const std::string& str)
+ : mTag(tag),
+ mString(str) {}
+
+ void AppendTag(ExifTagInfo* info, size_t data_offset) {
+ info->tag = mTag;
+ info->type = ExifString;
+ info->count = DataSize();
+ info->value = data_offset;
+ }
+
+ size_t DataSize() {
+ // Include padding \0.
+ return mString.size() + 1;
+ }
+
+ void AppendData(uint8_t* data) {
+ memcpy(data, mString.data(), DataSize());
+ }
+
+ private:
+ uint16_t mTag;
+ std::string mString;
+};
+
+// SubIFD: sub-tags.
+class ExifSubIfdTag : public ExifTag {
+ public:
+ ExifSubIfdTag(uint16_t tag)
+ : mTag(tag),
+ mSubStructure(new ExifStructure) {}
+
+ ~ExifSubIfdTag() {
+ delete mSubStructure;
+ }
+
+ void AppendTag(ExifTagInfo* info, size_t data_offset) {
+ info->tag = mTag;
+ info->type = ExifUInt32;
+ info->count = 1;
+ info->value = data_offset;
+ mDataOffset = data_offset;
+ }
+
+ size_t DataSize() {
+ return mSubStructure->Size();
+ }
+
+ void AppendData(uint8_t* data) {
+ mSubStructure->Build(data, mDataOffset, false);
+ }
+
+ ExifStructure* GetSubStructure() {
+ return mSubStructure;
+ }
+
+ private:
+ uint16_t mTag;
+ ExifStructure* mSubStructure;
+ size_t mDataOffset;
+};
+
+// Unsigned rational tag.
+class ExifURationalTag : public ExifTag {
+ public:
+ ExifURationalTag(uint16_t tag, double value)
+ : mTag(tag),
+ mCount(1) {
+ DoubleToRational(value,
+ &mRationals[0].mNumerator, &mRationals[0].mDenominator);
+ }
+
+ ExifURationalTag(uint16_t tag, double value1, double value2, double value3)
+ : mTag(tag),
+ mCount(3) {
+ DoubleToRational(value1,
+ &mRationals[0].mNumerator, &mRationals[0].mDenominator);
+ DoubleToRational(value2,
+ &mRationals[1].mNumerator, &mRationals[1].mDenominator);
+ DoubleToRational(value3,
+ &mRationals[2].mNumerator, &mRationals[2].mDenominator);
+ }
+
+ void DoubleToRational(double value,
+ int32_t* numerator, int32_t* denominator) {
+ int sign = 1;
+ if (value < 0) {
+ sign = -sign;
+ value = -value;
+ }
+ // Take shortcuts. Plenty.
+ *numerator = value;
+ *denominator = 1;
+
+ // Set some (arbitrary) threshold beyond which we do not proceed.
+ while (*numerator < (1 << 24)) {
+ if (std::fabs((*numerator / *denominator) - value) <= 0.0001) break;
+ *denominator *= 10;
+ *numerator = value * (*denominator);
+ }
+ *numerator *= sign;
+ }
+
+ void AppendTag(ExifTagInfo* info, size_t data_offset) {
+ info->tag = mTag;
+ info->type = ExifRational;
+ info->count = mCount;
+ info->value = data_offset;
+ }
+
+ size_t DataSize() {
+ return sizeof(mRationals[0]) * mCount;
+ }
+
+ void AppendData(uint8_t* data) {
+ memcpy(data, &mRationals[0], DataSize());
+ }
+
+ private:
+ static const int kMaxSupportedRationals = 3;
+ uint16_t mTag;
+ uint16_t mCount;
+ struct {
+ int32_t mNumerator;
+ int32_t mDenominator;
+ } mRationals[kMaxSupportedRationals];
+};
+
+std::string ToAsciiDate(time_t time) {
+ struct tm loc;
+ char res[12]; // YYYY:MM:DD\0\0
+ localtime_r(&time, &loc);
+ strftime(res, sizeof(res), "%Y:%m:%d", &loc);
+ return res;
+}
+
+std::string ToAsciiTime(time_t time) {
+ struct tm loc;
+ char res[10]; // HH:MM:SS\0\0
+ localtime_r(&time, &loc);
+ strftime(res, sizeof(res), "%H:%M:%S", &loc);
+ return res;
+}
+
+} // namespace
+
+ExifMetadataBuilder::ExifMetadataBuilder()
+ : mImageIfd(new ExifStructure),
+ mThumbnailIfd(new ExifStructure) {
+ // Mandatory tag: camera details.
+ ExifSubIfdTag* sub_ifd = new ExifSubIfdTag(kExifTagCameraSubIFD);
+ // Pass ownership to mImageIfd.
+ mImageIfd->PushTag(sub_ifd);
+ mCameraSubIfd = sub_ifd->GetSubStructure();
+
+ // Optional (yet, required) tag: GPS data.
+ sub_ifd = new ExifSubIfdTag(kExifTagGpsSubIFD);
+ // Pass ownership to mImageIfd.
+ mImageIfd->PushTag(sub_ifd);
+ mGpsSubIfd = sub_ifd->GetSubStructure();
+}
+
+ExifMetadataBuilder::~ExifMetadataBuilder() {
+ delete mImageIfd;
+ delete mThumbnailIfd;
+}
+
+void ExifMetadataBuilder::SetWidth(int width) {
+ mImageIfd->PushTag(new ExifUInt32Tag(kExifTagImageWidth, width));
+}
+
+void ExifMetadataBuilder::SetHeight(int height) {
+ mImageIfd->PushTag(new ExifUInt32Tag(kExifTagImageHeight, height));
+}
+
+void ExifMetadataBuilder::SetThumbnailWidth(int width) {
+ mThumbnailIfd->PushTag(new ExifUInt32Tag(kExifTagImageWidth, width));
+}
+
+void ExifMetadataBuilder::SetThumbnailHeight(int height) {
+ mThumbnailIfd->PushTag(new ExifUInt32Tag(kExifTagImageHeight, height));
+}
+
+void ExifMetadataBuilder::SetThumbnail(void* thumbnail, int size) {
+ mThumbnailIfd->PushTag(new ExifUInt32Tag(kExifTagJpegLength, size));
+ mThumbnailIfd->PushTag(new ExifPointerTag(kExifTagJpegData, thumbnail, size));
+}
+
+void ExifMetadataBuilder::SetDateTime(time_t date_time) {
+ std::string res = ToAsciiDate(date_time) + " " + ToAsciiTime(date_time);
+ mImageIfd->PushTag(new ExifStringTag(kExifTagImageDateTime, res));
+}
+
+void ExifMetadataBuilder::SetGpsLatitude(double latitude) {
+ if (latitude < 0) {
+ mGpsSubIfd->PushTag(new ExifStringTag(kExifTagGpsLatitudeRef, "S"));
+ } else {
+ mGpsSubIfd->PushTag(new ExifStringTag(kExifTagGpsLatitudeRef, "N"));
+ }
+ int degrees = latitude;
+ latitude = (latitude - degrees) * 60.;
+ int minutes = latitude;
+ latitude = (latitude - minutes) * 60.;
+ double seconds = latitude;
+ mGpsSubIfd->PushTag(new ExifURationalTag(kExifTagGpsLatitude,
+ degrees, minutes, seconds));
+}
+
+void ExifMetadataBuilder::SetGpsLongitude(double longitude) {
+ if (longitude < 0) {
+ mGpsSubIfd->PushTag(new ExifStringTag(kExifTagGpsLongitudeRef, "W"));
+ } else {
+ mGpsSubIfd->PushTag(new ExifStringTag(kExifTagGpsLongitudeRef, "E"));
+ }
+ int32_t degrees = longitude;
+ longitude = (longitude - degrees) * 60.;
+ int32_t minutes = longitude;
+ longitude = (longitude - minutes) * 60.;
+ double seconds = longitude;
+ mGpsSubIfd->PushTag(new ExifURationalTag(kExifTagGpsLongitude,
+ degrees, minutes, seconds));
+}
+
+void ExifMetadataBuilder::SetGpsAltitude(double altitude) {
+ mGpsSubIfd->PushTag(new ExifUInt8Tag(kExifTagGpsAltitudeRef, 0));
+ mGpsSubIfd->PushTag(new ExifURationalTag(kExifTagGpsAltitude, altitude));
+}
+
+void ExifMetadataBuilder::SetGpsProcessingMethod(const std::string& method) {
+ mGpsSubIfd->PushTag(new ExifCharArrayTag(
+ kExifTagGpsProcessingMethod, kExifCharArrayAscii, method));
+}
+
+void ExifMetadataBuilder::SetGpsDateTime(time_t timestamp) {
+ std::string date = ToAsciiDate(timestamp);
+ int32_t seconds = (timestamp % 60);
+ timestamp /= 60;
+ int32_t minutes = (timestamp % 60);
+ timestamp /= 60;
+ mGpsSubIfd->PushTag(new ExifURationalTag(
+ kExifTagGpsTimestamp, timestamp % 24, minutes, seconds));
+ mGpsSubIfd->PushTag(new ExifStringTag(
+ kExifTagGpsDatestamp, date));
+}
+
+void ExifMetadataBuilder::SetLensFocalLength(double length) {
+ mCameraSubIfd->PushTag(
+ new ExifURationalTag(kExifTagCameraFocalLength, length));
+}
+
+void ExifMetadataBuilder::Build() {
+ const uint8_t exif_header[] = {
+ 'E', 'x', 'i', 'f', 0, 0, // EXIF header.
+ };
+
+ const uint8_t tiff_header[] = {
+ 'I', 'I', 0x2a, 0x00, // TIFF Little endian header.
+ };
+
+ // EXIF data should be exactly this much.
+ size_t exif_size = sizeof(exif_header) + sizeof(tiff_header) +
+ sizeof(uint32_t) + // Offset of the following descriptors.
+ mImageIfd->Size() + mThumbnailIfd->Size();
+
+ const uint8_t marker[] = {
+ 0xff, 0xd8, 0xff, 0xe1, // EXIF marker.
+ uint8_t((exif_size + 2) >> 8), // Data length (including the length field)
+ uint8_t((exif_size + 2) & 0xff),
+ };
+
+ // Reserve data for our exif info.
+ mData.Resize(sizeof(marker) + exif_size);
+ uint8_t* b = (uint8_t*)(mData.data());
+
+ // Initialize marker & headers.
+ memcpy(b, &marker, sizeof(marker));
+ b += sizeof(marker);
+ memcpy(b, &exif_header, sizeof(exif_header));
+ b += sizeof(exif_header);
+ memcpy(b, &tiff_header, sizeof(tiff_header));
+ uint32_t data_offset = sizeof(tiff_header);
+
+ // Write offset of the first IFD item.
+ uint32_t first_tag_offset = data_offset + sizeof(uint32_t);
+ memcpy(&b[data_offset], &first_tag_offset, sizeof(first_tag_offset));
+ data_offset += sizeof(first_tag_offset);
+
+ // At this moment |b| points to EXIF structure.
+ // TIFF header is part of the structure itself and explains endianness of the
+ // embedded data.
+ ALOGI("%s: Building Main image EXIF tags", __FUNCTION__);
+ data_offset = mImageIfd->Build(&b[data_offset], data_offset, true);
+ ALOGI("%s: Building Thumbnail image EXIF tags", __FUNCTION__);
+ data_offset = mThumbnailIfd->Build(&b[data_offset], data_offset, false);
+ ALOGI("%s: EXIF metadata constructed (%d bytes).", __FUNCTION__, data_offset);
+}
+
+} // namespace android
diff --git a/guest/hals/camera/ExifMetadataBuilder.h b/guest/hals/camera/ExifMetadataBuilder.h
new file mode 100644
index 0000000..6180c45
--- /dev/null
+++ b/guest/hals/camera/ExifMetadataBuilder.h
@@ -0,0 +1,57 @@
+#ifndef EXIFMETADATAWRITER_H_
+#define EXIFMETADATAWRITER_H_
+
+#include <stddef.h>
+#include <stdint.h>
+#include <time.h>
+
+#include <string>
+#include <vector>
+
+#include "common/libs/auto_resources/auto_resources.h"
+
+namespace android {
+class ExifStructure;
+
+// Simplistic EXIF metadata builder.
+// www.exif.org/Exif2-2.PDF
+//
+// TODO(ender): Revisit using libexif after we drop support for JB.
+class ExifMetadataBuilder {
+ public:
+ ExifMetadataBuilder();
+ ~ExifMetadataBuilder();
+
+ void SetWidth(int width);
+ void SetHeight(int height);
+ void SetThumbnailWidth(int width);
+ void SetThumbnailHeight(int height);
+ void SetThumbnail(void* thumbnail, int size);
+ void SetGpsLatitude(double degrees);
+ void SetGpsLongitude(double degrees);
+ void SetGpsAltitude(double altitude);
+ void SetGpsDateTime(time_t timestamp);
+ void SetGpsProcessingMethod(const std::string& method);
+ void SetDateTime(time_t t);
+ void SetLensFocalLength(double length);
+ void Build();
+
+ const AutoFreeBuffer& Buffer() {
+ return mData;
+ }
+
+ private:
+ ExifStructure* mImageIfd;
+ ExifStructure* mThumbnailIfd;
+ ExifStructure* mCameraSubIfd;
+ ExifStructure* mGpsSubIfd;
+
+ AutoFreeBuffer mData;
+
+ ExifMetadataBuilder(const ExifMetadataBuilder&);
+ ExifMetadataBuilder& operator= (const ExifMetadataBuilder&);
+};
+
+} // namespace android
+
+#endif // EXIFMETADATAWRITER_H_
diff --git a/guest/hals/camera/GrallocModule.h b/guest/hals/camera/GrallocModule.h
new file mode 100644
index 0000000..ad1eab4
--- /dev/null
+++ b/guest/hals/camera/GrallocModule.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2017 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 GUEST_HALS_CAMERA_GRALLOCMODULE_H_
+#define GUEST_HALS_CAMERA_GRALLOCMODULE_H_
+
+#include <hardware/gralloc.h>
+
+class GrallocModule
+{
+public:
+ static GrallocModule &getInstance() {
+ static GrallocModule instance;
+ return instance;
+ }
+
+ int lock(buffer_handle_t handle,
+ int usage, int l, int t, int w, int h, void **vaddr) {
+ return mModule->lock(mModule, handle, usage, l, t, w, h, vaddr);
+ }
+
+#ifdef GRALLOC_MODULE_API_VERSION_0_2
+ int lock_ycbcr(buffer_handle_t handle,
+ int usage, int l, int t, int w, int h,
+ struct android_ycbcr *ycbcr) {
+ return mModule->lock_ycbcr(mModule, handle, usage, l, t, w, h, ycbcr);
+ }
+#endif
+
+ int unlock(buffer_handle_t handle) {
+ return mModule->unlock(mModule, handle);
+ }
+
+private:
+ GrallocModule() {
+ const hw_module_t *module = NULL;
+ int ret = hw_get_module(GRALLOC_HARDWARE_MODULE_ID, &module);
+ if (ret) {
+ ALOGE("%s: Failed to get gralloc module: %d", __FUNCTION__, ret);
+ }
+ mModule = reinterpret_cast<const gralloc_module_t*>(module);
+ }
+ const gralloc_module_t *mModule;
+};
+
+#endif // GUEST_HALS_CAMERA_GRALLOCMODULE_H_
diff --git a/guest/hals/camera/ImageMetadata.h b/guest/hals/camera/ImageMetadata.h
new file mode 100644
index 0000000..56020f2
--- /dev/null
+++ b/guest/hals/camera/ImageMetadata.h
@@ -0,0 +1,23 @@
+#ifndef IMAGEMETADATA_H_
+#define IMAGEMETADATA_H_
+
+#include <string>
+#include <stdint.h>
+
+extern "C" {
+/* Describes various attributes of the picture. */
+struct ImageMetadata {
+ int mWidth;
+ int mHeight;
+ int mThumbnailWidth;
+ int mThumbnailHeight;
+ double mLensFocalLength;
+ double mGpsLatitude;
+ double mGpsLongitude;
+ double mGpsAltitude;
+ time_t mGpsTimestamp;
+ std::string mGpsProcessingMethod;
+};
+}
+
+#endif // IMAGEMETADATA_H_
diff --git a/guest/hals/camera/JpegCompressor.cpp b/guest/hals/camera/JpegCompressor.cpp
new file mode 100644
index 0000000..8ee069f
--- /dev/null
+++ b/guest/hals/camera/JpegCompressor.cpp
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+/*
+ * Contains implementation of a class NV21JpegCompressor that encapsulates a
+ * converter between NV21, and JPEG formats.
+ */
+
+#define LOG_NDEBUG 0
+#define LOG_TAG "EmulatedCamera_JPEG"
+#include <cutils/log.h>
+#include <assert.h>
+#include <dlfcn.h>
+#include "JpegCompressor.h"
+
+namespace android {
+
+void* NV21JpegCompressor::mDl = NULL;
+
+static void* getSymbol(void* dl, const char* signature) {
+ void* res = dlsym(dl, signature);
+ assert (res != NULL);
+
+ return res;
+}
+
+typedef void (*InitFunc)(JpegStub* stub, int* strides);
+typedef void (*CleanupFunc)(JpegStub* stub);
+typedef int (*CompressFunc)(JpegStub* stub, const void* image,
+ int quality, const ImageMetadata* meta);
+typedef void (*GetCompressedImageFunc)(JpegStub* stub, void* buff);
+typedef size_t (*GetCompressedSizeFunc)(JpegStub* stub);
+
+NV21JpegCompressor::NV21JpegCompressor()
+{
+ if (mDl == NULL) {
+ mDl = dlopen("/vendor/lib/hw/camera.gce_x86.jpeg.so", RTLD_NOW);
+ }
+ if (mDl == NULL) {
+ mDl = dlopen("/system/lib/hw/camera.gce_x86.jpeg.so", RTLD_NOW);
+ }
+ assert(mDl != NULL);
+
+ InitFunc f = (InitFunc)getSymbol(mDl, "JpegStub_init");
+ (*f)(&mStub, mStrides);
+}
+
+NV21JpegCompressor::~NV21JpegCompressor()
+{
+ CleanupFunc f = (CleanupFunc)getSymbol(mDl, "JpegStub_cleanup");
+ (*f)(&mStub);
+}
+
+/****************************************************************************
+ * Public API
+ ***************************************************************************/
+
+status_t NV21JpegCompressor::compressRawImage(
+ const void* image, const ImageMetadata* meta, int quality)
+{
+ mStrides[0] = meta->mWidth;
+ mStrides[1] = meta->mWidth;
+ CompressFunc f = (CompressFunc)getSymbol(mDl, "JpegStub_compress");
+ return (status_t)(*f)(&mStub, image, quality, meta);
+}
+
+
+size_t NV21JpegCompressor::getCompressedSize()
+{
+ GetCompressedSizeFunc f = (GetCompressedSizeFunc)getSymbol(mDl,
+ "JpegStub_getCompressedSize");
+ return (*f)(&mStub);
+}
+
+void NV21JpegCompressor::getCompressedImage(void* buff)
+{
+ GetCompressedImageFunc f = (GetCompressedImageFunc)getSymbol(mDl,
+ "JpegStub_getCompressedImage");
+ (*f)(&mStub, buff);
+}
+
+}; /* namespace android */
diff --git a/guest/hals/camera/JpegCompressor.h b/guest/hals/camera/JpegCompressor.h
new file mode 100644
index 0000000..e27fd7d
--- /dev/null
+++ b/guest/hals/camera/JpegCompressor.h
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2011 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 HW_EMULATOR_CAMERA_JPEG_COMPRESSOR_H
+#define HW_EMULATOR_CAMERA_JPEG_COMPRESSOR_H
+
+/*
+ * Contains declaration of a class NV21JpegCompressor that encapsulates a
+ * converter between YV21, and JPEG formats.
+ */
+
+#include "JpegStub.h"
+#include <utils/threads.h>
+
+namespace android {
+
+/* Encapsulates a converter between YV12, and JPEG formats.
+ */
+class NV21JpegCompressor
+{
+public:
+ /* Constructs JpegCompressor instance. */
+ NV21JpegCompressor();
+ /* Destructs JpegCompressor instance. */
+ ~NV21JpegCompressor();
+
+ /****************************************************************************
+ * Public API
+ ***************************************************************************/
+
+public:
+ /* Compresses raw NV21 image into a JPEG.
+ * The compressed image will be saved in mStream member of this class. Use
+ * getCompressedSize method to obtain buffer size of the compressed image,
+ * and getCompressedImage to copy out the compressed image.
+ * Param:
+ * image - Raw NV21 image.
+ * metadata - Image metadata (dimensions, location etc).
+ * quality - JPEG quality.
+ * Return:
+ * NO_ERROR on success, or an appropriate error status.
+ *
+ */
+ status_t compressRawImage(const void* image,
+ const ImageMetadata* metadata,
+ int quality);
+
+ /* Get size of the compressed JPEG buffer.
+ * This method must be called only after a successful completion of
+ * compressRawImage call.
+ * Return:
+ * Size of the compressed JPEG buffer.
+ */
+ size_t getCompressedSize();
+
+ /* Copies out compressed JPEG buffer.
+ * This method must be called only after a successful completion of
+ * compressRawImage call.
+ * Param:
+ * buff - Buffer where to copy the JPEG. Must be large enough to contain the
+ * entire image.
+ */
+ void getCompressedImage(void* buff);
+
+ /****************************************************************************
+ * Class data
+ ***************************************************************************/
+
+protected:
+ /* Strides for Y (the first element), and UV (the second one) panes. */
+ int mStrides[2];
+
+private:
+ // library handle to dlopen
+ static void* mDl;
+ JpegStub mStub;
+};
+
+}; /* namespace android */
+
+#endif /* HW_EMULATOR_CAMERA_JPEG_COMPRESSOR_H */
diff --git a/guest/hals/camera/JpegStub.cpp b/guest/hals/camera/JpegStub.cpp
new file mode 100644
index 0000000..ff23d4b
--- /dev/null
+++ b/guest/hals/camera/JpegStub.cpp
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2013 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_NDEBUG 0
+#define LOG_TAG "EmulatedCamera_JPEGStub"
+#include <errno.h>
+#include <cutils/log.h>
+#include <libyuv.h>
+#include <YuvToJpegEncoder.h>
+
+#include "ExifMetadataBuilder.h"
+#include "JpegStub.h"
+
+namespace {
+bool GenerateThumbnail(
+ const uint8_t* source_nv21, int source_width, int source_height,
+ int thumbnail_width, int thumbnail_height, SkDynamicMemoryWStream* target) {
+ // We need to convert the image to Y'UV420SP to I420, which seems to be the
+ // only scalable format by the LibYUV.
+ // These formats are similar in their memory occupancy (both use about 3/2 of
+ // the total pixels).
+ int temp_y_size = source_width * source_height;
+ int temp_uv_size = temp_y_size / 4;
+ uint8_t* temp_y = (uint8_t*)malloc(temp_y_size + temp_uv_size + temp_uv_size);
+ uint8_t* temp_u = temp_y + temp_y_size;
+ uint8_t* temp_v = temp_u + temp_uv_size;
+
+ libyuv::NV12ToI420(
+ source_nv21, source_width,
+ source_nv21 + temp_y_size, source_width,
+ temp_y, source_width,
+ temp_u, source_width / 2,
+ temp_v, source_width / 2,
+ source_width, source_height);
+
+ // Compute and allocate memory for thumbnail I420.
+ int thumb_y_size = thumbnail_width * thumbnail_height;
+ int thumb_uv_size = thumb_y_size / 4;
+ uint8_t* thumb_y = (uint8_t*)malloc(thumb_y_size + thumb_uv_size + thumb_uv_size);
+ uint8_t* thumb_u = thumb_y + thumb_y_size;
+ uint8_t* thumb_v = thumb_u + thumb_uv_size;
+
+ libyuv::I420Scale(
+ temp_y, source_width,
+ temp_u, source_width / 2,
+ temp_v, source_width / 2,
+ source_width, source_height,
+ thumb_y, thumbnail_width,
+ thumb_u, thumbnail_width / 2,
+ thumb_v, thumbnail_width / 2,
+ thumbnail_width, thumbnail_height,
+ libyuv::kFilterBilinear);
+
+ // Combine U and V components back to NV21 format.
+ // We can re-use temp_y buffer for our needs at this point.
+ for (int pix = 0; pix < thumb_uv_size; ++pix) {
+ temp_y[2 * pix] = thumb_v[pix];
+ temp_y[2 * pix + 1] = thumb_u[pix];
+ }
+
+ // Put the memory back. After this, the thumb_y points to beginning of NV21
+ // image which we can compress.
+ memcpy(thumb_u, temp_y, thumb_uv_size * 2);
+
+ // Compress image.
+ int strides[2] = { thumbnail_width, thumbnail_width };
+ int offsets[2] = { 0, thumb_y_size };
+ Yuv420SpToJpegEncoder* encoder = new Yuv420SpToJpegEncoder(strides);
+
+ bool result = encoder->encode(
+ target, thumb_y, thumbnail_width, thumbnail_height, offsets, 90);
+
+ if (!result) {
+ ALOGE("%s: Thumbnail compression failed", __FUNCTION__);
+ }
+
+ delete(encoder);
+ free(thumb_y);
+ free(temp_y);
+
+ return result;
+}
+} // namespace
+
+extern "C" void JpegStub_init(JpegStub* stub, int* strides) {
+ stub->mInternalEncoder = (void*) new Yuv420SpToJpegEncoder(strides);
+ stub->mInternalStream = (void*)new SkDynamicMemoryWStream();
+ stub->mExifBuilder = (void*)new android::ExifMetadataBuilder();
+}
+
+extern "C" void JpegStub_cleanup(JpegStub* stub) {
+ delete((Yuv420SpToJpegEncoder*)stub->mInternalEncoder);
+ delete((SkDynamicMemoryWStream*)stub->mInternalStream);
+ delete((android::ExifMetadataBuilder*)stub->mExifBuilder);
+}
+
+extern "C" int JpegStub_compress(JpegStub* stub, const void* image,
+ int quality, const ImageMetadata* meta)
+{
+ void* pY = const_cast<void*>(image);
+
+ int offsets[2];
+ offsets[0] = 0;
+ offsets[1] = meta->mWidth * meta->mHeight;
+
+ Yuv420SpToJpegEncoder* encoder =
+ (Yuv420SpToJpegEncoder*)stub->mInternalEncoder;
+ SkDynamicMemoryWStream* stream =
+ (SkDynamicMemoryWStream*)stub->mInternalStream;
+ android::ExifMetadataBuilder* exif =
+ (android::ExifMetadataBuilder*)stub->mExifBuilder;
+
+ exif->SetWidth(meta->mWidth);
+ exif->SetHeight(meta->mHeight);
+ exif->SetDateTime(time(NULL));
+ if (meta->mLensFocalLength != -1)
+ exif->SetLensFocalLength(meta->mLensFocalLength);
+ if (meta->mGpsTimestamp != -1) {
+ exif->SetGpsLatitude(meta->mGpsLatitude);
+ exif->SetGpsLongitude(meta->mGpsLongitude);
+ exif->SetGpsAltitude(meta->mGpsAltitude);
+ exif->SetGpsDateTime(meta->mGpsTimestamp);
+ exif->SetGpsProcessingMethod(meta->mGpsProcessingMethod);
+ }
+
+ ALOGV("%s: Requested thumbnail size: %dx%d",
+ __FUNCTION__, meta->mThumbnailWidth, meta->mThumbnailHeight);
+
+ // Thumbnail requested?
+ if (meta->mThumbnailWidth > 0 && meta->mThumbnailHeight > 0) {
+ exif->SetThumbnailWidth(meta->mThumbnailWidth);
+ exif->SetThumbnailHeight(meta->mThumbnailHeight);
+ SkDynamicMemoryWStream* thumbnail = new SkDynamicMemoryWStream();
+ GenerateThumbnail(
+ (uint8_t*)pY,
+ meta->mWidth, meta->mHeight,
+ meta->mThumbnailWidth, meta->mThumbnailHeight,
+ thumbnail);
+
+ int thumbnail_size = thumbnail->bytesWritten();
+ void* thumbnail_data = malloc(thumbnail_size);
+ thumbnail->read(thumbnail_data, 0, thumbnail_size);
+ // Pass ownership to EXIF builder.
+ exif->SetThumbnail(thumbnail_data, thumbnail_size);
+ delete thumbnail;
+ }
+
+ exif->Build();
+
+ if (encoder->encode(stream, pY, meta->mWidth, meta->mHeight,
+ offsets, quality)) {
+ ALOGI("%s: Compressed JPEG: %d[%dx%d] -> %zu bytes",
+ __FUNCTION__, (meta->mWidth * meta->mHeight * 12) / 8,
+ meta->mWidth, meta->mHeight, stream->bytesWritten());
+ return 0;
+ } else {
+ ALOGE("%s: JPEG compression failed", __FUNCTION__);
+ return errno ? errno: EINVAL;
+ }
+}
+
+extern "C" void JpegStub_getCompressedImage(JpegStub* stub, void* buff) {
+ SkDynamicMemoryWStream* stream =
+ (SkDynamicMemoryWStream*)stub->mInternalStream;
+ android::ExifMetadataBuilder* exif =
+ (android::ExifMetadataBuilder*)stub->mExifBuilder;
+ char* target = (char*)buff;
+ memcpy(buff, exif->Buffer().data(), exif->Buffer().size());
+ target += exif->Buffer().size();
+
+ // Skip 0xFFD8 marker. This marker has already been included in Metadata.
+ stream->read(target, 2, stream->bytesWritten() - 2);
+}
+
+extern "C" size_t JpegStub_getCompressedSize(JpegStub* stub) {
+ SkDynamicMemoryWStream* stream =
+ (SkDynamicMemoryWStream*)stub->mInternalStream;
+ android::ExifMetadataBuilder* exif =
+ (android::ExifMetadataBuilder*)stub->mExifBuilder;
+ return stream->bytesWritten() + exif->Buffer().size() - 2;
+}
diff --git a/guest/hals/camera/JpegStub.h b/guest/hals/camera/JpegStub.h
new file mode 100644
index 0000000..10e5bf8
--- /dev/null
+++ b/guest/hals/camera/JpegStub.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2013 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 JPEGSTUB_H_
+#define JPEGSTUB_H_
+
+#include "ImageMetadata.h"
+
+extern "C" {
+
+struct JpegStub {
+ void* mInternalEncoder;
+ void* mInternalStream;
+ void* mExifBuilder;
+};
+
+void JpegStub_init(JpegStub* stub, int* strides);
+void JpegStub_cleanup(JpegStub* stub);
+int JpegStub_compress(JpegStub* stub, const void* image, int quality,
+ const ImageMetadata* metadata);
+void JpegStub_getCompressedImage(JpegStub* stub, void* buff);
+size_t JpegStub_getCompressedSize(JpegStub* stub);
+
+};
+#endif // JPEGSTUB_H_
diff --git a/guest/hals/camera/PreviewWindow.cpp b/guest/hals/camera/PreviewWindow.cpp
new file mode 100755
index 0000000..281a3fa
--- /dev/null
+++ b/guest/hals/camera/PreviewWindow.cpp
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+/*
+ * Contains implementation of a class PreviewWindow that encapsulates
+ * functionality of a preview window set via set_preview_window camera HAL API.
+ */
+
+#define LOG_NDEBUG 0
+#define LOG_TAG "EmulatedCamera_Preview"
+#include <cutils/log.h>
+#include "EmulatedCameraDevice.h"
+#include "PreviewWindow.h"
+#include "GrallocModule.h"
+
+namespace android {
+
+PreviewWindow::PreviewWindow()
+ : mPreviewWindow(NULL),
+ mLastPreviewed(0),
+ mPreviewFrameWidth(0),
+ mPreviewFrameHeight(0),
+ mPreviewEnabled(false)
+{
+}
+
+PreviewWindow::~PreviewWindow()
+{
+}
+
+/****************************************************************************
+ * Camera API
+ ***************************************************************************/
+
+status_t PreviewWindow::setPreviewWindow(struct preview_stream_ops* window,
+ int preview_fps)
+{
+ ALOGV("%s: current: %p -> new: %p", __FUNCTION__, mPreviewWindow, window);
+
+ status_t res = NO_ERROR;
+ Mutex::Autolock locker(&mObjectLock);
+
+ /* Reset preview info. */
+ mPreviewFrameWidth = mPreviewFrameHeight = 0;
+ mPreviewAfter = 0;
+ mLastPreviewed = 0;
+
+ if (window != NULL) {
+ /* The CPU will write each frame to the preview window buffer.
+ * Note that we delay setting preview window buffer geometry until
+ * frames start to come in. */
+ res = window->set_usage(window, GRALLOC_USAGE_SW_WRITE_OFTEN);
+ if (res == NO_ERROR) {
+ /* Set preview frequency. */
+ mPreviewAfter = 1000000 / preview_fps;
+ } else {
+ window = NULL;
+ res = -res; // set_usage returns a negative errno.
+ ALOGE("%s: Error setting preview window usage %d -> %s",
+ __FUNCTION__, res, strerror(res));
+ }
+ }
+ mPreviewWindow = window;
+
+ return res;
+}
+
+status_t PreviewWindow::startPreview()
+{
+ ALOGV("%s", __FUNCTION__);
+
+ Mutex::Autolock locker(&mObjectLock);
+ mPreviewEnabled = true;
+
+ return NO_ERROR;
+}
+
+void PreviewWindow::stopPreview()
+{
+ ALOGV("%s", __FUNCTION__);
+
+ Mutex::Autolock locker(&mObjectLock);
+ mPreviewEnabled = false;
+}
+
+/****************************************************************************
+ * Public API
+ ***************************************************************************/
+
+void PreviewWindow::onNextFrameAvailable(const void* frame,
+ nsecs_t timestamp,
+ EmulatedCameraDevice* camera_dev)
+{
+ int res;
+ Mutex::Autolock locker(&mObjectLock);
+
+ if (!isPreviewEnabled() || mPreviewWindow == NULL || !isPreviewTime()) {
+ return;
+ }
+
+ /* Make sure that preview window dimensions are OK with the camera device */
+ if (adjustPreviewDimensions(camera_dev)) {
+ /* Need to set / adjust buffer geometry for the preview window.
+ * Note that in the emulator preview window uses only RGB for pixel
+ * formats. */
+ ALOGV("%s: Adjusting preview windows %p geometry to %dx%d",
+ __FUNCTION__, mPreviewWindow, mPreviewFrameWidth,
+ mPreviewFrameHeight);
+ res = mPreviewWindow->set_buffers_geometry(mPreviewWindow,
+ mPreviewFrameWidth,
+ mPreviewFrameHeight,
+ HAL_PIXEL_FORMAT_RGBA_8888);
+ if (res != NO_ERROR) {
+ ALOGE("%s: Error in set_buffers_geometry %d -> %s",
+ __FUNCTION__, -res, strerror(-res));
+ return;
+ }
+ }
+
+ /*
+ * Push new frame to the preview window.
+ */
+
+ /* Dequeue preview window buffer for the frame. */
+ buffer_handle_t* buffer = NULL;
+ int stride = 0;
+ res = mPreviewWindow->dequeue_buffer(mPreviewWindow, &buffer, &stride);
+ if (res != NO_ERROR || buffer == NULL) {
+ ALOGE("%s: Unable to dequeue preview window buffer: %d -> %s",
+ __FUNCTION__, -res, strerror(-res));
+ return;
+ }
+
+ /* Let the preview window to lock the buffer. */
+ res = mPreviewWindow->lock_buffer(mPreviewWindow, buffer);
+ if (res != NO_ERROR) {
+ ALOGE("%s: Unable to lock preview window buffer: %d -> %s",
+ __FUNCTION__, -res, strerror(-res));
+ mPreviewWindow->cancel_buffer(mPreviewWindow, buffer);
+ return;
+ }
+
+ /* Now let the graphics framework to lock the buffer, and provide
+ * us with the framebuffer data address. */
+ void* img = NULL;
+ res = GrallocModule::getInstance().lock(
+ *buffer, GRALLOC_USAGE_SW_WRITE_OFTEN,
+ 0, 0, mPreviewFrameWidth, mPreviewFrameHeight, &img);
+ if (res != NO_ERROR) {
+ ALOGE("%s: gralloc.lock failure: %d -> %s",
+ __FUNCTION__, res, strerror(res));
+ mPreviewWindow->cancel_buffer(mPreviewWindow, buffer);
+ return;
+ }
+
+ /* Frames come in in YV12/NV12/NV21 format. Since preview window doesn't
+ * supports those formats, we need to obtain the frame in RGB565. */
+ res = camera_dev->getCurrentPreviewFrame(img);
+ if (res == NO_ERROR) {
+ /* Show it. */
+ mPreviewWindow->set_timestamp(mPreviewWindow, timestamp);
+ mPreviewWindow->enqueue_buffer(mPreviewWindow, buffer);
+ } else {
+ ALOGE("%s: Unable to obtain preview frame: %d", __FUNCTION__, res);
+ mPreviewWindow->cancel_buffer(mPreviewWindow, buffer);
+ }
+ GrallocModule::getInstance().unlock(*buffer);
+}
+
+/***************************************************************************
+ * Private API
+ **************************************************************************/
+
+bool PreviewWindow::adjustPreviewDimensions(EmulatedCameraDevice* camera_dev)
+{
+ /* Match the cached frame dimensions against the actual ones. */
+ if (mPreviewFrameWidth == camera_dev->getFrameWidth() &&
+ mPreviewFrameHeight == camera_dev->getFrameHeight()) {
+ /* They match. */
+ return false;
+ }
+
+ /* They don't match: adjust the cache. */
+ mPreviewFrameWidth = camera_dev->getFrameWidth();
+ mPreviewFrameHeight = camera_dev->getFrameHeight();
+
+ return true;
+}
+
+bool PreviewWindow::isPreviewTime()
+{
+ timeval cur_time;
+ gettimeofday(&cur_time, NULL);
+ const uint64_t cur_mks = cur_time.tv_sec * 1000000LL + cur_time.tv_usec;
+ if ((cur_mks - mLastPreviewed) >= mPreviewAfter) {
+ mLastPreviewed = cur_mks;
+ return true;
+ }
+ return false;
+}
+
+}; /* namespace android */
diff --git a/guest/hals/camera/PreviewWindow.h b/guest/hals/camera/PreviewWindow.h
new file mode 100755
index 0000000..d037c95
--- /dev/null
+++ b/guest/hals/camera/PreviewWindow.h
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2011 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 HW_EMULATOR_CAMERA_PREVIEW_WINDOW_H
+#define HW_EMULATOR_CAMERA_PREVIEW_WINDOW_H
+
+/*
+ * Contains declaration of a class PreviewWindow that encapsulates functionality
+ * of a preview window set via set_preview_window camera HAL API.
+ */
+
+namespace android {
+
+class EmulatedCameraDevice;
+
+/* Encapsulates functionality of a preview window set via set_preview_window
+ * camera HAL API.
+ *
+ * Objects of this class are contained in EmulatedCamera objects, and handle
+ * relevant camera API callbacks.
+ */
+class PreviewWindow {
+public:
+ /* Constructs PreviewWindow instance. */
+ PreviewWindow();
+
+ /* Destructs PreviewWindow instance. */
+ ~PreviewWindow();
+
+ /***************************************************************************
+ * Camera API
+ **************************************************************************/
+
+public:
+ /* Actual handler for camera_device_ops_t::set_preview_window callback.
+ * This method is called by the containing emulated camera object when it is
+ * handing the camera_device_ops_t::set_preview_window callback.
+ * Param:
+ * window - Preview window to set. This parameter might be NULL, which
+ * indicates preview window reset.
+ * preview_fps - Preview's frame frequency. This parameter determins when
+ * a frame received via onNextFrameAvailable call will be pushed to
+ * the preview window. If 'window' parameter passed to this method is
+ * NULL, this parameter is ignored.
+ * Return:
+ * NO_ERROR on success, or an appropriate error status.
+ */
+ status_t setPreviewWindow(struct preview_stream_ops* window,
+ int preview_fps);
+
+ /* Starts the preview.
+ * This method is called by the containing emulated camera object when it is
+ * handing the camera_device_ops_t::start_preview callback.
+ */
+ status_t startPreview();
+
+ /* Stops the preview.
+ * This method is called by the containing emulated camera object when it is
+ * handing the camera_device_ops_t::start_preview callback.
+ */
+ void stopPreview();
+
+ /* Checks if preview is enabled. */
+ inline bool isPreviewEnabled()
+ {
+ return mPreviewEnabled;
+ }
+
+ /****************************************************************************
+ * Public API
+ ***************************************************************************/
+
+public:
+ /* Next frame is available in the camera device.
+ * This is a notification callback that is invoked by the camera device when
+ * a new frame is available.
+ * Note that most likely this method is called in context of a worker thread
+ * that camera device has created for frame capturing.
+ * Param:
+ * frame - Captured frame, or NULL if camera device didn't pull the frame
+ * yet. If NULL is passed in this parameter use GetCurrentFrame method
+ * of the camera device class to obtain the next frame. Also note that
+ * the size of the frame that is passed here (as well as the frame
+ * returned from the GetCurrentFrame method) is defined by the current
+ * frame settings (width + height + pixel format) for the camera device.
+ * timestamp - Frame's timestamp.
+ * camera_dev - Camera device instance that delivered the frame.
+ */
+ void onNextFrameAvailable(const void* frame,
+ nsecs_t timestamp,
+ EmulatedCameraDevice* camera_dev);
+
+ /***************************************************************************
+ * Private API
+ **************************************************************************/
+
+protected:
+ /* Adjusts cached dimensions of the preview window frame according to the
+ * frame dimensions used by the camera device.
+ *
+ * When preview is started, it's not known (hard to define) what are going
+ * to be the dimensions of the frames that are going to be displayed. Plus,
+ * it might be possible, that such dimensions can be changed on the fly. So,
+ * in order to be always in sync with frame dimensions, this method is
+ * called for each frame passed to onNextFrameAvailable method, in order to
+ * properly adjust frame dimensions, used by the preview window.
+ * Note that this method must be called while object is locked.
+ * Param:
+ * camera_dev - Camera device, prpviding frames displayed in the preview
+ * window.
+ * Return:
+ * true if cached dimensions have been adjusted, or false if cached
+ * dimensions match device's frame dimensions.
+ */
+ bool adjustPreviewDimensions(EmulatedCameraDevice* camera_dev);
+
+ /* Checks if it's the time to push new frame to the preview window.
+ * Note that this method must be called while object is locked. */
+ bool isPreviewTime();
+
+ /***************************************************************************
+ * Data members
+ **************************************************************************/
+
+protected:
+ /* Locks this instance for data changes. */
+ Mutex mObjectLock;
+
+ /* Preview window instance. */
+ preview_stream_ops* mPreviewWindow;
+
+ /* Timestamp (abs. microseconds) when last frame has been pushed to the
+ * preview window. */
+ uint64_t mLastPreviewed;
+
+ /* Preview frequency in microseconds. */
+ uint32_t mPreviewAfter;
+
+ /*
+ * Cached preview window frame dimensions.
+ */
+
+ int mPreviewFrameWidth;
+ int mPreviewFrameHeight;
+
+ /* Preview status. */
+ bool mPreviewEnabled;
+};
+
+}; /* namespace android */
+
+#endif /* HW_EMULATOR_CAMERA_PREVIEW_WINDOW_H */
diff --git a/guest/hals/camera/QemuClient.cpp b/guest/hals/camera/QemuClient.cpp
new file mode 100755
index 0000000..6c10dab
--- /dev/null
+++ b/guest/hals/camera/QemuClient.cpp
@@ -0,0 +1,559 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+/*
+ * Contains implementation of classes that encapsulate connection to camera
+ * services in the emulator via qemu pipe.
+ */
+
+#define LOG_NDEBUG 0
+#define LOG_TAG "EmulatedCamera_QemuClient"
+#include <cutils/log.h>
+#include "EmulatedCamera.h"
+#include "QemuClient.h"
+
+#define LOG_QUERIES 0
+#if LOG_QUERIES
+#define LOGQ(...) ALOGD(__VA_ARGS__)
+#else
+#define LOGQ(...) (void(0))
+
+#endif // LOG_QUERIES
+namespace android {
+
+/****************************************************************************
+ * Qemu query
+ ***************************************************************************/
+
+QemuQuery::QemuQuery()
+ : mQuery(mQueryPrealloc),
+ mQueryDeliveryStatus(NO_ERROR),
+ mReplyBuffer(NULL),
+ mReplyData(NULL),
+ mReplySize(0),
+ mReplyDataSize(0),
+ mReplyStatus(0)
+{
+ *mQuery = '\0';
+}
+
+QemuQuery::QemuQuery(const char* query_string)
+ : mQuery(mQueryPrealloc),
+ mQueryDeliveryStatus(NO_ERROR),
+ mReplyBuffer(NULL),
+ mReplyData(NULL),
+ mReplySize(0),
+ mReplyDataSize(0),
+ mReplyStatus(0)
+{
+ mQueryDeliveryStatus = QemuQuery::createQuery(query_string, NULL);
+}
+
+QemuQuery::QemuQuery(const char* query_name, const char* query_param)
+ : mQuery(mQueryPrealloc),
+ mQueryDeliveryStatus(NO_ERROR),
+ mReplyBuffer(NULL),
+ mReplyData(NULL),
+ mReplySize(0),
+ mReplyDataSize(0),
+ mReplyStatus(0)
+{
+ mQueryDeliveryStatus = QemuQuery::createQuery(query_name, query_param);
+}
+
+QemuQuery::~QemuQuery()
+{
+ QemuQuery::resetQuery();
+}
+
+status_t QemuQuery::createQuery(const char* name, const char* param)
+{
+ /* Reset from the previous use. */
+ resetQuery();
+
+ /* Query name cannot be NULL or an empty string. */
+ if (name == NULL || *name == '\0') {
+ ALOGE("%s: NULL or an empty string is passed as query name.",
+ __FUNCTION__);
+ mQueryDeliveryStatus = EINVAL;
+ return EINVAL;
+ }
+
+ const size_t name_len = strlen(name);
+ const size_t param_len = (param != NULL) ? strlen(param) : 0;
+ const size_t required = strlen(name) + (param_len ? (param_len + 2) : 1);
+
+ if (required > sizeof(mQueryPrealloc)) {
+ /* Preallocated buffer was too small. Allocate a bigger query buffer. */
+ mQuery = new char[required];
+ if (mQuery == NULL) {
+ ALOGE("%s: Unable to allocate %zu bytes for query buffer",
+ __FUNCTION__, required);
+ mQueryDeliveryStatus = ENOMEM;
+ return ENOMEM;
+ }
+ }
+
+ /* At this point mQuery buffer is big enough for the query. */
+ if (param_len) {
+ sprintf(mQuery, "%s %s", name, param);
+ } else {
+ memcpy(mQuery, name, name_len + 1);
+ }
+
+ return NO_ERROR;
+}
+
+status_t QemuQuery::completeQuery(status_t status)
+{
+ /* Save query completion status. */
+ mQueryDeliveryStatus = status;
+ if (mQueryDeliveryStatus != NO_ERROR) {
+ return mQueryDeliveryStatus;
+ }
+
+ /* Make sure reply buffer contains at least 'ok', or 'ko'.
+ * Note that 'ok', or 'ko' prefixes are always 3 characters long: in case
+ * there are more data in the reply, that data will be separated from 'ok'/'ko'
+ * with a ':'. If there is no more data in the reply, the prefix will be
+ * zero-terminated, and the terminator will be inculded in the reply. */
+ if (mReplyBuffer == NULL || mReplySize < 3) {
+ ALOGE("%s: Invalid reply to the query", __FUNCTION__);
+ mQueryDeliveryStatus = EINVAL;
+ return EINVAL;
+ }
+
+ /* Lets see the reply status. */
+ if (!memcmp(mReplyBuffer, "ok", 2)) {
+ mReplyStatus = 1;
+ } else if (!memcmp(mReplyBuffer, "ko", 2)) {
+ mReplyStatus = 0;
+ } else {
+ ALOGE("%s: Invalid query reply: '%s'", __FUNCTION__, mReplyBuffer);
+ mQueryDeliveryStatus = EINVAL;
+ return EINVAL;
+ }
+
+ /* Lets see if there are reply data that follow. */
+ if (mReplySize > 3) {
+ /* There are extra data. Make sure they are separated from the status
+ * with a ':' */
+ if (mReplyBuffer[2] != ':') {
+ ALOGE("%s: Invalid query reply: '%s'", __FUNCTION__, mReplyBuffer);
+ mQueryDeliveryStatus = EINVAL;
+ return EINVAL;
+ }
+ mReplyData = mReplyBuffer + 3;
+ mReplyDataSize = mReplySize - 3;
+ } else {
+ /* Make sure reply buffer containing just 'ok'/'ko' ends with
+ * zero-terminator. */
+ if (mReplyBuffer[2] != '\0') {
+ ALOGE("%s: Invalid query reply: '%s'", __FUNCTION__, mReplyBuffer);
+ mQueryDeliveryStatus = EINVAL;
+ return EINVAL;
+ }
+ }
+
+ return NO_ERROR;
+}
+
+void QemuQuery::resetQuery()
+{
+ if (mQuery != NULL && mQuery != mQueryPrealloc) {
+ delete[] mQuery;
+ }
+ mQuery = mQueryPrealloc;
+ mQueryDeliveryStatus = NO_ERROR;
+ if (mReplyBuffer != NULL) {
+ free(mReplyBuffer);
+ mReplyBuffer = NULL;
+ }
+ mReplyData = NULL;
+ mReplySize = mReplyDataSize = 0;
+ mReplyStatus = 0;
+}
+
+/****************************************************************************
+ * Qemu client base
+ ***************************************************************************/
+
+/* Camera service name. */
+const char QemuClient::mCameraServiceName[] = "camera";
+
+QemuClient::QemuClient()
+ : mPipeFD(-1)
+{
+}
+
+QemuClient::~QemuClient()
+{
+ if (mPipeFD >= 0) {
+ close(mPipeFD);
+ }
+}
+
+/****************************************************************************
+ * Qemu client API
+ ***************************************************************************/
+
+status_t QemuClient::connectClient(const char* param)
+{
+ ALOGV("%s: '%s'", __FUNCTION__, param ? param : "");
+
+ /* Make sure that client is not connected already. */
+ if (mPipeFD >= 0) {
+ ALOGE("%s: Qemu client is already connected", __FUNCTION__);
+ return EINVAL;
+ }
+
+ /* Select one of the two: 'factory', or 'emulated camera' service */
+ if (param == NULL || *param == '\0') {
+ /* No parameters: connect to the factory service. */
+ char pipe_name[512];
+ snprintf(pipe_name, sizeof(pipe_name), "qemud:%s", mCameraServiceName);
+ mPipeFD = qemu_pipe_open(pipe_name);
+ } else {
+ /* One extra char ':' that separates service name and parameters + six
+ * characters for 'qemud:'. This is required by qemu pipe protocol. */
+ char* connection_str = new char[strlen(mCameraServiceName) +
+ strlen(param) + 8];
+ sprintf(connection_str, "qemud:%s:%s", mCameraServiceName, param);
+
+ mPipeFD = qemu_pipe_open(connection_str);
+ delete[] connection_str;
+ }
+ if (mPipeFD < 0) {
+ ALOGE("%s: Unable to connect to the camera service '%s': %s",
+ __FUNCTION__, param ? param : "Factory", strerror(errno));
+ return errno ? errno : EINVAL;
+ }
+
+ return NO_ERROR;
+}
+
+void QemuClient::disconnectClient()
+{
+ ALOGV("%s", __FUNCTION__);
+
+ if (mPipeFD >= 0) {
+ close(mPipeFD);
+ mPipeFD = -1;
+ }
+}
+
+status_t QemuClient::sendMessage(const void* data, size_t data_size)
+{
+ if (mPipeFD < 0) {
+ ALOGE("%s: Qemu client is not connected", __FUNCTION__);
+ return EINVAL;
+ }
+
+ /* Note that we don't use here qemud_client_send, since with qemu pipes we
+ * don't need to provide payload size prior to payload when we're writing to
+ * the pipe. So, we can use simple write, and qemu pipe will take care of the
+ * rest, calling the receiving end with the number of bytes transferred. */
+ const size_t written = qemud_fd_write(mPipeFD, data, data_size);
+ if (written == data_size) {
+ return NO_ERROR;
+ } else {
+ ALOGE("%s: Error sending data via qemu pipe: '%s'",
+ __FUNCTION__, strerror(errno));
+ return errno ? errno : EIO;
+ }
+}
+
+status_t QemuClient::receiveMessage(void** data, size_t* data_size)
+{
+ *data = NULL;
+ *data_size = 0;
+
+ if (mPipeFD < 0) {
+ ALOGE("%s: Qemu client is not connected", __FUNCTION__);
+ return EINVAL;
+ }
+
+ /* The way the service replies to a query, it sends payload size first, and
+ * then it sends the payload itself. Note that payload size is sent as a
+ * string, containing 8 characters representing a hexadecimal payload size
+ * value. Note also, that the string doesn't contain zero-terminator. */
+ size_t payload_size;
+ char payload_size_str[9];
+ int rd_res = qemud_fd_read(mPipeFD, payload_size_str, 8);
+ if (rd_res != 8) {
+ ALOGE("%s: Unable to obtain payload size: %s",
+ __FUNCTION__, strerror(errno));
+ return errno ? errno : EIO;
+ }
+
+ /* Convert payload size. */
+ errno = 0;
+ payload_size_str[8] = '\0';
+ payload_size = strtol(payload_size_str, NULL, 16);
+ if (errno) {
+ ALOGE("%s: Invalid payload size '%s'", __FUNCTION__, payload_size_str);
+ return EIO;
+ }
+
+ /* Allocate payload data buffer, and read the payload there. */
+ *data = malloc(payload_size);
+ if (*data == NULL) {
+ ALOGE("%s: Unable to allocate %zu bytes payload buffer",
+ __FUNCTION__, payload_size);
+ return ENOMEM;
+ }
+ rd_res = qemud_fd_read(mPipeFD, *data, payload_size);
+ if (static_cast<size_t>(rd_res) == payload_size) {
+ *data_size = payload_size;
+ return NO_ERROR;
+ } else {
+ ALOGE("%s: Read size %d doesnt match expected payload size %zu: %s",
+ __FUNCTION__, rd_res, payload_size, strerror(errno));
+ free(*data);
+ *data = NULL;
+ return errno ? errno : EIO;
+ }
+}
+
+status_t QemuClient::doQuery(QemuQuery* query)
+{
+ /* Make sure that query has been successfuly constructed. */
+ if (query->mQueryDeliveryStatus != NO_ERROR) {
+ ALOGE("%s: Query is invalid", __FUNCTION__);
+ return query->mQueryDeliveryStatus;
+ }
+
+ LOGQ("Send query '%s'", query->mQuery);
+
+ /* Send the query. */
+ status_t res = sendMessage(query->mQuery, strlen(query->mQuery) + 1);
+ if (res == NO_ERROR) {
+ /* Read the response. */
+ res = receiveMessage(reinterpret_cast<void**>(&query->mReplyBuffer),
+ &query->mReplySize);
+ if (res == NO_ERROR) {
+ LOGQ("Response to query '%s': Status = '%.2s', %d bytes in response",
+ query->mQuery, query->mReplyBuffer, query->mReplySize);
+ } else {
+ ALOGE("%s Response to query '%s' has failed: %s",
+ __FUNCTION__, query->mQuery, strerror(res));
+ }
+ } else {
+ ALOGE("%s: Send query '%s' failed: %s",
+ __FUNCTION__, query->mQuery, strerror(res));
+ }
+
+ /* Complete the query, and return its completion handling status. */
+ const status_t res1 = query->completeQuery(res);
+ ALOGE_IF(res1 != NO_ERROR && res1 != res,
+ "%s: Error %d in query '%s' completion",
+ __FUNCTION__, res1, query->mQuery);
+ return res1;
+}
+
+/****************************************************************************
+ * Qemu client for the 'factory' service.
+ ***************************************************************************/
+
+/*
+ * Factory service queries.
+ */
+
+/* Queries list of cameras connected to the host. */
+const char FactoryQemuClient::mQueryList[] = "list";
+
+FactoryQemuClient::FactoryQemuClient()
+ : QemuClient()
+{
+}
+
+FactoryQemuClient::~FactoryQemuClient()
+{
+}
+
+status_t FactoryQemuClient::listCameras(char** list)
+{
+ ALOGV("%s", __FUNCTION__);
+
+ QemuQuery query(mQueryList);
+ if (doQuery(&query) || !query.isQuerySucceeded()) {
+ ALOGE("%s: List cameras query failed: %s", __FUNCTION__,
+ query.mReplyData ? query.mReplyData : "No error message");
+ return query.getCompletionStatus();
+ }
+
+ /* Make sure there is a list returned. */
+ if (query.mReplyDataSize == 0) {
+ ALOGE("%s: No camera list is returned.", __FUNCTION__);
+ return EINVAL;
+ }
+
+ /* Copy the list over. */
+ *list = (char*)malloc(query.mReplyDataSize);
+ if (*list != NULL) {
+ memcpy(*list, query.mReplyData, query.mReplyDataSize);
+ ALOGD("Emulated camera list: %s", *list);
+ return NO_ERROR;
+ } else {
+ ALOGE("%s: Unable to allocate %zu bytes",
+ __FUNCTION__, query.mReplyDataSize);
+ return ENOMEM;
+ }
+}
+
+/****************************************************************************
+ * Qemu client for an 'emulated camera' service.
+ ***************************************************************************/
+
+/*
+ * Emulated camera queries
+ */
+
+/* Connect to the camera device. */
+const char CameraQemuClient::mQueryConnect[] = "connect";
+/* Disconect from the camera device. */
+const char CameraQemuClient::mQueryDisconnect[] = "disconnect";
+/* Start capturing video from the camera device. */
+const char CameraQemuClient::mQueryStart[] = "start";
+/* Stop capturing video from the camera device. */
+const char CameraQemuClient::mQueryStop[] = "stop";
+/* Get next video frame from the camera device. */
+const char CameraQemuClient::mQueryFrame[] = "frame";
+
+CameraQemuClient::CameraQemuClient()
+ : QemuClient()
+{
+}
+
+CameraQemuClient::~CameraQemuClient()
+{
+
+}
+
+status_t CameraQemuClient::queryConnect()
+{
+ ALOGV("%s", __FUNCTION__);
+
+ QemuQuery query(mQueryConnect);
+ doQuery(&query);
+ const status_t res = query.getCompletionStatus();
+ ALOGE_IF(res != NO_ERROR, "%s: Query failed: %s",
+ __FUNCTION__, query.mReplyData ? query.mReplyData :
+ "No error message");
+ return res;
+}
+
+status_t CameraQemuClient::queryDisconnect()
+{
+ ALOGV("%s", __FUNCTION__);
+
+ QemuQuery query(mQueryDisconnect);
+ doQuery(&query);
+ const status_t res = query.getCompletionStatus();
+ ALOGE_IF(res != NO_ERROR, "%s: Query failed: %s",
+ __FUNCTION__, query.mReplyData ? query.mReplyData :
+ "No error message");
+ return res;
+}
+
+status_t CameraQemuClient::queryStart(uint32_t pixel_format,
+ int width,
+ int height)
+{
+ ALOGV("%s", __FUNCTION__);
+
+ char query_str[256];
+ snprintf(query_str, sizeof(query_str), "%s dim=%dx%d pix=%d",
+ mQueryStart, width, height, pixel_format);
+ QemuQuery query(query_str);
+ doQuery(&query);
+ const status_t res = query.getCompletionStatus();
+ ALOGE_IF(res != NO_ERROR, "%s: Query failed: %s",
+ __FUNCTION__, query.mReplyData ? query.mReplyData :
+ "No error message");
+ return res;
+}
+
+status_t CameraQemuClient::queryStop()
+{
+ ALOGV("%s", __FUNCTION__);
+
+ QemuQuery query(mQueryStop);
+ doQuery(&query);
+ const status_t res = query.getCompletionStatus();
+ ALOGE_IF(res != NO_ERROR, "%s: Query failed: %s",
+ __FUNCTION__, query.mReplyData ? query.mReplyData :
+ "No error message");
+ return res;
+}
+
+status_t CameraQemuClient::queryFrame(void* vframe,
+ void* pframe,
+ size_t vframe_size,
+ size_t pframe_size,
+ float r_scale,
+ float g_scale,
+ float b_scale,
+ float exposure_comp)
+{
+ ALOGV("%s", __FUNCTION__);
+
+ char query_str[256];
+ snprintf(query_str, sizeof(query_str), "%s video=%zu preview=%zu whiteb=%g,%g,%g expcomp=%g",
+ mQueryFrame, (vframe && vframe_size) ? vframe_size : 0,
+ (pframe && pframe_size) ? pframe_size : 0, r_scale, g_scale, b_scale,
+ exposure_comp);
+ QemuQuery query(query_str);
+ doQuery(&query);
+ const status_t res = query.getCompletionStatus();
+ if( res != NO_ERROR) {
+ ALOGE("%s: Query failed: %s",
+ __FUNCTION__, query.mReplyData ? query.mReplyData :
+ "No error message");
+ return res;
+ }
+
+ /* Copy requested frames. */
+ size_t cur_offset = 0;
+ const uint8_t* frame = reinterpret_cast<const uint8_t*>(query.mReplyData);
+ /* Video frame is always first. */
+ if (vframe != NULL && vframe_size != 0) {
+ /* Make sure that video frame is in. */
+ if ((query.mReplyDataSize - cur_offset) >= vframe_size) {
+ memcpy(vframe, frame, vframe_size);
+ cur_offset += vframe_size;
+ } else {
+ ALOGE("%s: Reply %zu bytes is to small to contain %zu bytes video frame",
+ __FUNCTION__, query.mReplyDataSize - cur_offset, vframe_size);
+ return EINVAL;
+ }
+ }
+ if (pframe != NULL && pframe_size != 0) {
+ /* Make sure that preview frame is in. */
+ if ((query.mReplyDataSize - cur_offset) >= pframe_size) {
+ memcpy(pframe, frame + cur_offset, pframe_size);
+ cur_offset += pframe_size;
+ } else {
+ ALOGE("%s: Reply %zu bytes is to small to contain %zu bytes preview frame",
+ __FUNCTION__, query.mReplyDataSize - cur_offset, pframe_size);
+ return EINVAL;
+ }
+ }
+
+ return NO_ERROR;
+}
+
+}; /* namespace android */
diff --git a/guest/hals/camera/QemuClient.h b/guest/hals/camera/QemuClient.h
new file mode 100755
index 0000000..1644321
--- /dev/null
+++ b/guest/hals/camera/QemuClient.h
@@ -0,0 +1,437 @@
+/*
+ * Copyright (C) 2011 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 HW_EMULATOR_CAMERA_QEMU_CLIENT_H
+#define HW_EMULATOR_CAMERA_QEMU_CLIENT_H
+
+/*
+ * Contains declaration of classes that encapsulate connection to camera services
+ * in the emulator via qemu pipe.
+ */
+
+#include <hardware/qemud.h>
+
+namespace android {
+
+/****************************************************************************
+ * Qemu query
+ ***************************************************************************/
+
+/* Encapsulates a query to the emulator.
+ * Guest exchanges data with the emulator via queries sent over the qemu pipe.
+ * The queries as well as replies to the queries are all strings (except for the
+ * 'frame' query where reply is a framebuffer).
+ * Each query is formatted as such:
+ *
+ * "<query name>[ <parameters>]",
+ *
+ * where <query name> is a string representing query name, and <parameters> are
+ * optional parameters for the query. If parameters are present, they must be
+ * separated from the query name with a single space, and they must be formatted
+ * as such:
+ *
+ * "<name1>=<value1> <name2>=<value2> ... <nameN>=<valueN>"
+ *
+ * I.e.:
+ * - Every parameter must have a name, and a value.
+ * - Name and value must be separated with '='.
+ * - No spaces are allowed around '=' separating name and value.
+ * - Parameters must be separated with a single space character.
+ * - No '=' character is allowed in name and in value.
+ *
+ * There are certain restrictions on strings used in the query:
+ * - Spaces are allowed only as separators.
+ * - '=' are allowed only to divide parameter names from parameter values.
+ *
+ * Emulator replies to each query in two chunks:
+ * - 8 bytes encoding the payload size as a string containing hexadecimal
+ * representation of the payload size value. This is done in order to simplify
+ * dealing with different endianness on the host, and on the guest.
+ * - Payload, whose size is defined by the first chunk.
+ *
+ * Every payload always begins with two characters, encoding the result of the
+ * query:
+ * - 'ok' Encoding the success
+ * - 'ko' Encoding a failure.
+ * After that payload may have optional data. If payload has more data following
+ * the query result, there is a ':' character separating them. If payload carries
+ * only the result, it always ends with a zero-terminator. So, payload 'ok'/'ko'
+ * prefix is always 3 bytes long: it either includes a zero-terminator, if there
+ * is no data, or a ':' separator.
+ */
+class QemuQuery {
+public:
+ /* Constructs an uninitialized QemuQuery instance. */
+ QemuQuery();
+
+ /* Constructs and initializes QemuQuery instance for a query.
+ * Param:
+ * query_string - Query string. This constructor can also be used to
+ * construct a query that doesn't have parameters. In this case query
+ * name can be passed as a parameter here.
+ */
+ explicit QemuQuery(const char* query_string);
+
+ /* Constructs and initializes QemuQuery instance for a query with parameters.
+ * Param:
+ * query_name - Query name.
+ * query_param - Query parameters. Can be NULL.
+ */
+ QemuQuery(const char* query_name, const char* query_param);
+
+ /* Destructs QemuQuery instance. */
+ ~QemuQuery();
+
+ /****************************************************************************
+ * Public API
+ ***************************************************************************/
+
+ /* Creates new query.
+ * Note: this method will reset this instance prior to creating a new query
+ * in order to discard possible "leftovers" from the previous query.
+ * Param:
+ * query_name - Query name.
+ * query_param - Query parameters. Can be NULL.
+ * Return:
+ * NO_ERROR on success, or an appropriate error status.
+ */
+ status_t createQuery(const char* name, const char* param);
+
+ /* Completes the query after a reply from the emulator.
+ * This method will parse the reply buffer, and calculate the final query
+ * status, which depends not only on the transport success / failure, but
+ * also on 'ok' / 'ko' in the reply buffer.
+ * Param:
+ * status - Query delivery status. This status doesn't necessarily reflects
+ * the final query status (which is defined by 'ok'/'ko' prefix in the
+ * reply buffer). This status simply states whether or not the query has
+ * been sent, and a reply has been received successfuly. However, if
+ * this status indicates a failure, it means that the entire query has
+ * failed.
+ * Return:
+ * NO_ERROR on success, or an appropriate error status on failure. Note that
+ * status returned here just signals whether or not the method has succeeded.
+ * Use isQuerySucceeded() / getCompletionStatus() methods of this class to
+ * check the final query status.
+ */
+ status_t completeQuery(status_t status);
+
+ /* Resets the query from a previous use. */
+ void resetQuery();
+
+ /* Checks if query has succeeded.
+ * Note that this method must be called after completeQuery() method of this
+ * class has been executed.
+ */
+ inline bool isQuerySucceeded() const {
+ return mQueryDeliveryStatus == NO_ERROR && mReplyStatus != 0;
+ }
+
+ /* Gets final completion status of the query.
+ * Note that this method must be called after completeQuery() method of this
+ * class has been executed.
+ * Return:
+ * NO_ERROR if query has succeeded, or an appropriate error status on query
+ * failure.
+ */
+ inline status_t getCompletionStatus() const {
+ if (mQueryDeliveryStatus == NO_ERROR) {
+ if (mReplyStatus) {
+ return NO_ERROR;
+ } else {
+ return EINVAL;
+ }
+ } else {
+ return mQueryDeliveryStatus;
+ }
+ }
+
+ /****************************************************************************
+ * Public data memebers
+ ***************************************************************************/
+
+public:
+ /* Query string. */
+ char* mQuery;
+ /* Query delivery status. */
+ status_t mQueryDeliveryStatus;
+ /* Reply buffer */
+ char* mReplyBuffer;
+ /* Reply data (past 'ok'/'ko'). If NULL, there were no data in reply. */
+ char* mReplyData;
+ /* Reply buffer size. */
+ size_t mReplySize;
+ /* Reply data size. */
+ size_t mReplyDataSize;
+ /* Reply status: 1 - ok, 0 - ko. */
+ int mReplyStatus;
+
+ /****************************************************************************
+ * Private data memebers
+ ***************************************************************************/
+
+protected:
+ /* Preallocated buffer for small queries. */
+ char mQueryPrealloc[256];
+};
+
+/****************************************************************************
+ * Qemu client base
+ ***************************************************************************/
+
+/* Encapsulates a connection to the 'camera' service in the emulator via qemu
+ * pipe.
+ */
+class QemuClient {
+public:
+ /* Constructs QemuClient instance. */
+ QemuClient();
+
+ /* Destructs QemuClient instance. */
+ virtual ~QemuClient();
+
+ /****************************************************************************
+ * Qemu client API
+ ***************************************************************************/
+
+public:
+ /* Connects to the 'camera' service in the emulator via qemu pipe.
+ * Param:
+ * param - Parameters to pass to the camera service. There are two types of
+ * camera services implemented by the emulator. The first one is a
+ * 'camera factory' type of service that provides list of cameras
+ * connected to the host. Another one is an 'emulated camera' type of
+ * service that provides interface to a camera connected to the host. At
+ * the connection time emulator makes distinction between the two by
+ * looking at connection parameters: no parameters means connection to
+ * the 'factory' service, while connection with parameters means
+ * connection to an 'emulated camera' service, where camera is identified
+ * by one of the connection parameters. So, passing NULL, or an empty
+ * string to this method will establish a connection with the 'factory'
+ * service, while not empty string passed here will establish connection
+ * with an 'emulated camera' service. Parameters defining the emulated
+ * camera must be formatted as such:
+ *
+ * "name=<device name> [inp_channel=<input channel #>]",
+ *
+ * where 'device name' is a required parameter defining name of the
+ * camera device, and 'input channel' is an optional parameter (positive
+ * integer), defining the input channel to use on the camera device.
+ * Note that device name passed here must have been previously obtained
+ * from the factory service using 'list' query.
+ * Return:
+ * NO_ERROR on success, or an appropriate error status.
+ */
+ virtual status_t connectClient(const char* param);
+
+ /* Disconnects from the service. */
+ virtual void disconnectClient();
+
+ /* Sends data to the service.
+ * Param:
+ * data, data_size - Data to send.
+ * Return:
+ * NO_ERROR on success, or an appropriate error status on failure.
+ */
+ virtual status_t sendMessage(const void* data, size_t data_size);
+
+ /* Receives data from the service.
+ * This method assumes that data to receive will come in two chunks: 8
+ * characters encoding the payload size in hexadecimal string, followed by
+ * the paylod (if any).
+ * This method will allocate data buffer where to receive the response.
+ * Param:
+ * data - Upon success contains address of the allocated data buffer with
+ * the data received from the service. The caller is responsible for
+ * freeing allocated data buffer.
+ * data_size - Upon success contains size of the data received from the
+ * service.
+ * Return:
+ * NO_ERROR on success, or an appropriate error status on failure.
+ */
+ virtual status_t receiveMessage(void** data, size_t* data_size);
+
+ /* Sends a query, and receives a response from the service.
+ * Param:
+ * query - Query to send to the service. When this method returns, the query
+ * is completed, and all its relevant data members are properly initialized.
+ * Return:
+ * NO_ERROR on success, or an appropriate error status on failure. Note that
+ * status returned here is not the final query status. Use isQuerySucceeded(),
+ * or getCompletionStatus() method on the query object to see if it has
+ * succeeded. However, if this method returns a failure, it means that the
+ * query has failed, and there is no guarantee that its data members are
+ * properly initialized (except for the 'mQueryDeliveryStatus', which is
+ * always in the proper state).
+ */
+ virtual status_t doQuery(QemuQuery* query);
+
+ /****************************************************************************
+ * Data members
+ ***************************************************************************/
+
+protected:
+ /* Qemu pipe handle. */
+ int mPipeFD;
+
+private:
+ /* Camera service name. */
+ static const char mCameraServiceName[];
+};
+
+/****************************************************************************
+ * Qemu client for the 'factory' service.
+ ***************************************************************************/
+
+/* Encapsulates QemuClient for the 'factory' service. */
+class FactoryQemuClient : public QemuClient {
+public:
+ /* Constructs FactoryQemuClient instance. */
+ FactoryQemuClient();
+
+ /* Destructs FactoryQemuClient instance. */
+ ~FactoryQemuClient();
+
+ /****************************************************************************
+ * Public API
+ ***************************************************************************/
+
+public:
+ /* Lists camera devices connected to the host.
+ * Param:
+ * list - Upon success contains a list of cameras connected to the host. The
+ * list returned here is represented as a string, containing multiple
+ * lines separated with '\n', where each line represents a camera. Each
+ * camera line is formatted as such:
+ *
+ * "name=<device name> channel=<num> pix=<num> framedims=<dimensions>\n"
+ *
+ * Where:
+ * - 'name' is the name of the camera device attached to the host. This
+ * name must be used for subsequent connection to the 'emulated camera'
+ * service for that camera.
+ * - 'channel' - input channel number (positive int) to use to communicate
+ * with the camera.
+ * - 'pix' - pixel format (a "fourcc" uint), chosen for the video frames
+ * by the camera service.
+ * - 'framedims' contains a list of frame dimensions supported by the
+ * camera for the chosen pixel format. Each etry in the list is in form
+ * '<width>x<height>', where 'width' and 'height' are numeric values
+ * for width and height of a supported frame dimension. Entries in
+ * this list are separated with ',' with no spaces between the entries.
+ * Return:
+ * NO_ERROR on success, or an appropriate error status on failure.
+ */
+ status_t listCameras(char** list);
+
+ /****************************************************************************
+ * Names of the queries available for the emulated camera factory.
+ ***************************************************************************/
+
+private:
+ /* List cameras connected to the host. */
+ static const char mQueryList[];
+};
+
+/****************************************************************************
+ * Qemu client for an 'emulated camera' service.
+ ***************************************************************************/
+
+/* Encapsulates QemuClient for an 'emulated camera' service.
+ */
+class CameraQemuClient : public QemuClient {
+public:
+ /* Constructs CameraQemuClient instance. */
+ CameraQemuClient();
+
+ /* Destructs CameraQemuClient instance. */
+ ~CameraQemuClient();
+
+ /****************************************************************************
+ * Public API
+ ***************************************************************************/
+
+public:
+ /* Queries camera connection.
+ * Return:
+ * NO_ERROR on success, or an appropriate error status on failure.
+ */
+ status_t queryConnect();
+
+ /* Queries camera disconnection.
+ * Return:
+ * NO_ERROR on success, or an appropriate error status on failure.
+ */
+ status_t queryDisconnect();
+
+ /* Queries camera to start capturing video.
+ * Param:
+ * pixel_format - Pixel format that is used by the client to push video
+ * frames to the camera framework.
+ * width, height - Frame dimensions, requested by the framework.
+ * Return:
+ * NO_ERROR on success, or an appropriate error status on failure.
+ */
+ status_t queryStart(uint32_t pixel_format, int width, int height);
+
+ /* Queries camera to stop capturing video.
+ * Return:
+ * NO_ERROR on success, or an appropriate error status on failure.
+ */
+ status_t queryStop();
+
+ /* Queries camera for the next video frame.
+ * Param:
+ * vframe, vframe_size - Define buffer, allocated to receive a video frame.
+ * Any of these parameters can be 0, indicating that the caller is
+ * interested only in preview frame.
+ * pframe, pframe_size - Define buffer, allocated to receive a preview frame.
+ * Any of these parameters can be 0, indicating that the caller is
+ * interested only in video frame.
+ * r_scale, g_scale, b_scale - White balance scale.
+ * exposure_comp - Expsoure compensation.
+ * Return:
+ * NO_ERROR on success, or an appropriate error status on failure.
+ */
+ status_t queryFrame(void* vframe,
+ void* pframe,
+ size_t vframe_size,
+ size_t pframe_size,
+ float r_scale,
+ float g_scale,
+ float b_scale,
+ float exposure_comp);
+
+ /****************************************************************************
+ * Names of the queries available for the emulated camera.
+ ***************************************************************************/
+
+private:
+ /* Connect to the camera. */
+ static const char mQueryConnect[];
+ /* Disconnect from the camera. */
+ static const char mQueryDisconnect[];
+ /* Start video capturing. */
+ static const char mQueryStart[];
+ /* Stop video capturing. */
+ static const char mQueryStop[];
+ /* Query frame(s). */
+ static const char mQueryFrame[];
+};
+
+}; /* namespace android */
+
+#endif /* HW_EMULATOR_CAMERA_QEMU_CLIENT_H */
diff --git a/guest/hals/camera/VSoCEmulatedCameraHotplugThread.cpp b/guest/hals/camera/VSoCEmulatedCameraHotplugThread.cpp
new file mode 100644
index 0000000..1f70fbf
--- /dev/null
+++ b/guest/hals/camera/VSoCEmulatedCameraHotplugThread.cpp
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2017 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_NDEBUG 0
+#define LOG_TAG "EmulatedCamera_HotplugThread"
+#include <cutils/log.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include "EmulatedCameraHotplugThread.h"
+#include "EmulatedCameraFactory.h"
+
+#define SubscriberInfo EmulatedCameraHotplugThread::SubscriberInfo
+
+namespace android {
+
+EmulatedCameraHotplugThread::EmulatedCameraHotplugThread(
+ size_t totalCameraCount) :
+ Thread(/*canCallJava*/false) {}
+
+EmulatedCameraHotplugThread::~EmulatedCameraHotplugThread() {}
+
+status_t EmulatedCameraHotplugThread::requestExitAndWait() {
+ ALOGE("%s: Not implemented. Use requestExit + join instead",
+ __FUNCTION__);
+ return INVALID_OPERATION;
+}
+
+void EmulatedCameraHotplugThread::requestExit() {
+ ALOGV("%s: Requesting thread exit", __FUNCTION__);
+ mRunning = false;
+}
+
+status_t EmulatedCameraHotplugThread::readyToRun() {
+ return OK;
+}
+
+bool EmulatedCameraHotplugThread::threadLoop() {
+ // Thread is irrelevant right now; hoplug is not supported.
+ return false;
+}
+
+String8 EmulatedCameraHotplugThread::getFilePath(int cameraId) const {
+ return String8();
+}
+
+bool EmulatedCameraHotplugThread::createFileIfNotExists(int cameraId) const
+{
+ return true;
+}
+
+int EmulatedCameraHotplugThread::getCameraId(String8 filePath) const {
+ // Not used anywhere.
+ return NAME_NOT_FOUND;
+}
+
+int EmulatedCameraHotplugThread::getCameraId(int wd) const {
+ // Not used anywhere.
+ return NAME_NOT_FOUND;
+}
+
+SubscriberInfo* EmulatedCameraHotplugThread::getSubscriberInfo(int cameraId)
+{
+ // Not used anywhere.
+ return NULL;
+}
+
+bool EmulatedCameraHotplugThread::addWatch(int cameraId) {
+ return true;
+}
+
+bool EmulatedCameraHotplugThread::removeWatch(int cameraId) {
+ return true;
+}
+
+int EmulatedCameraHotplugThread::readFile(String8 filePath) const {
+ return 1;
+}
+
+} //namespace android
diff --git a/guest/hals/camera/fake-pipeline2/Base.h b/guest/hals/camera/fake-pipeline2/Base.h
new file mode 100644
index 0000000..dfeeeca
--- /dev/null
+++ b/guest/hals/camera/fake-pipeline2/Base.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+/**
+ * This file includes various basic structures that are needed by multiple parts
+ * of the fake camera 2 implementation.
+ */
+
+#ifndef HW_EMULATOR_CAMERA2_BASE_H
+#define HW_EMULATOR_CAMERA2_BASE_H
+
+#include "guest/libs/platform_support/api_level_fixes.h"
+
+#if VSOC_PLATFORM_SDK_BEFORE(O_MR1)
+#include <system/window.h>
+#endif
+#include <hardware/camera2.h>
+#include <utils/Vector.h>
+
+namespace android {
+
+
+/* Internal structure for passing buffers across threads */
+struct StreamBuffer {
+ // Positive numbers are output streams
+ // Negative numbers are input reprocess streams
+ // Zero is an auxillary buffer
+ int streamId;
+ uint32_t width, height;
+ uint32_t format;
+ uint32_t dataSpace;
+ uint32_t stride;
+ buffer_handle_t *buffer;
+ uint8_t *img;
+};
+typedef Vector<StreamBuffer> Buffers;
+
+struct Stream {
+ const camera2_stream_ops_t *ops;
+ uint32_t width, height;
+ int32_t format;
+ uint32_t stride;
+};
+
+struct ReprocessStream {
+ const camera2_stream_in_ops_t *ops;
+ uint32_t width, height;
+ int32_t format;
+ uint32_t stride;
+ // -1 if the reprocessing stream is independent
+ int32_t sourceStreamId;
+};
+
+} // namespace android;
+
+#endif
diff --git a/guest/hals/camera/fake-pipeline2/JpegCompressor.cpp b/guest/hals/camera/fake-pipeline2/JpegCompressor.cpp
new file mode 100644
index 0000000..f7ba234
--- /dev/null
+++ b/guest/hals/camera/fake-pipeline2/JpegCompressor.cpp
@@ -0,0 +1,295 @@
+/*
+ * Copyright (C) 2012 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_NDEBUG 0
+#define LOG_TAG "EmulatedCamera2_JpegCompressor"
+
+#include <utils/Log.h>
+
+#include "JpegCompressor.h"
+#include "../EmulatedFakeCamera2.h"
+#include "../EmulatedFakeCamera3.h"
+
+namespace android {
+
+JpegCompressor::JpegCompressor():
+ Thread(false),
+ mIsBusy(false),
+ mSynchronous(false),
+ mBuffers(NULL),
+ mListener(NULL) {
+}
+
+JpegCompressor::~JpegCompressor() {
+ Mutex::Autolock lock(mMutex);
+}
+
+status_t JpegCompressor::reserve() {
+ Mutex::Autolock busyLock(mBusyMutex);
+ if (mIsBusy) {
+ ALOGE("%s: Already processing a buffer!", __FUNCTION__);
+ return INVALID_OPERATION;
+ }
+ mIsBusy = true;
+ return OK;
+}
+
+status_t JpegCompressor::start(Buffers *buffers, JpegListener *listener) {
+ if (listener == NULL) {
+ ALOGE("%s: NULL listener not allowed!", __FUNCTION__);
+ return BAD_VALUE;
+ }
+ ALOGV("%s: Starting JPEG compression thread", __FUNCTION__);
+ Mutex::Autolock lock(mMutex);
+ {
+ Mutex::Autolock busyLock(mBusyMutex);
+
+ if (!mIsBusy) {
+ ALOGE("Called start without reserve() first!");
+ return INVALID_OPERATION;
+ }
+ mSynchronous = false;
+ mBuffers = buffers;
+ mListener = listener;
+ }
+
+ status_t res;
+ res = run("EmulatedFakeCamera2::JpegCompressor");
+ if (res != OK) {
+ ALOGE("%s: Unable to start up compression thread: %s (%d)",
+ __FUNCTION__, strerror(-res), res);
+ delete mBuffers;
+ }
+ return res;
+}
+
+status_t JpegCompressor::compressSynchronous(Buffers *buffers) {
+ status_t res;
+
+ Mutex::Autolock lock(mMutex);
+ {
+ Mutex::Autolock busyLock(mBusyMutex);
+
+ if (mIsBusy) {
+ ALOGE("%s: Already processing a buffer!", __FUNCTION__);
+ return INVALID_OPERATION;
+ }
+
+ mIsBusy = true;
+ mSynchronous = true;
+ mBuffers = buffers;
+ }
+
+ res = compress();
+
+ cleanUp();
+
+ return res;
+}
+
+status_t JpegCompressor::cancel() {
+ requestExitAndWait();
+ return OK;
+}
+
+status_t JpegCompressor::readyToRun() {
+ return OK;
+}
+
+bool JpegCompressor::threadLoop() {
+ status_t res;
+ ALOGV("%s: Starting compression thread", __FUNCTION__);
+
+ res = compress();
+
+ mListener->onJpegDone(mJpegBuffer, res == OK);
+
+ cleanUp();
+
+ return false;
+}
+
+status_t JpegCompressor::compress() {
+ // Find source and target buffers. Assumes only one buffer matches
+ // each condition!
+ ALOGV("%s: Compressing start", __FUNCTION__);
+ bool foundJpeg = false, mFoundAux = false;
+ for (size_t i = 0; i < mBuffers->size(); i++) {
+ const StreamBuffer &b = (*mBuffers)[i];
+ if (b.format == HAL_PIXEL_FORMAT_BLOB) {
+ mJpegBuffer = b;
+ mFoundJpeg = true;
+ } else if (b.streamId <= 0) {
+ mAuxBuffer = b;
+ mFoundAux = true;
+ }
+ if (mFoundJpeg && mFoundAux) break;
+ }
+ if (!mFoundJpeg || !mFoundAux) {
+ ALOGE("%s: Unable to find buffers for JPEG source/destination",
+ __FUNCTION__);
+ return BAD_VALUE;
+ }
+
+ // Set up error management
+
+ mJpegErrorInfo = NULL;
+ JpegError error;
+ error.parent = this;
+
+ mCInfo.err = jpeg_std_error(&error);
+ mCInfo.err->error_exit = jpegErrorHandler;
+
+ jpeg_create_compress(&mCInfo);
+ if (checkError("Error initializing compression")) return NO_INIT;
+
+ // Route compressed data straight to output stream buffer
+
+ JpegDestination jpegDestMgr;
+ jpegDestMgr.parent = this;
+ jpegDestMgr.init_destination = jpegInitDestination;
+ jpegDestMgr.empty_output_buffer = jpegEmptyOutputBuffer;
+ jpegDestMgr.term_destination = jpegTermDestination;
+
+ mCInfo.dest = &jpegDestMgr;
+
+ // Set up compression parameters
+
+ mCInfo.image_width = mAuxBuffer.width;
+ mCInfo.image_height = mAuxBuffer.height;
+ mCInfo.input_components = 3;
+ mCInfo.in_color_space = JCS_RGB;
+
+ jpeg_set_defaults(&mCInfo);
+ if (checkError("Error configuring defaults")) return NO_INIT;
+
+ // Do compression
+
+ jpeg_start_compress(&mCInfo, TRUE);
+ if (checkError("Error starting compression")) return NO_INIT;
+
+ size_t rowStride = mAuxBuffer.stride * 3;
+ const size_t kChunkSize = 32;
+ while (mCInfo.next_scanline < mCInfo.image_height) {
+ JSAMPROW chunk[kChunkSize];
+ for (size_t i = 0 ; i < kChunkSize; i++) {
+ chunk[i] = (JSAMPROW)
+ (mAuxBuffer.img + (i + mCInfo.next_scanline) * rowStride);
+ }
+ jpeg_write_scanlines(&mCInfo, chunk, kChunkSize);
+ if (checkError("Error while compressing")) return NO_INIT;
+ if (exitPending()) {
+ ALOGV("%s: Cancel called, exiting early", __FUNCTION__);
+ return TIMED_OUT;
+ }
+ }
+
+ jpeg_finish_compress(&mCInfo);
+ if (checkError("Error while finishing compression")) return NO_INIT;
+
+ // All done
+ ALOGV("%s: Compressing done", __FUNCTION__);
+
+ return OK;
+}
+
+bool JpegCompressor::isBusy() {
+ Mutex::Autolock busyLock(mBusyMutex);
+ return mIsBusy;
+}
+
+bool JpegCompressor::isStreamInUse(uint32_t id) {
+ Mutex::Autolock lock(mBusyMutex);
+
+ if (mBuffers && mIsBusy) {
+ for (size_t i = 0; i < mBuffers->size(); i++) {
+ if ( (*mBuffers)[i].streamId == (int)id ) return true;
+ }
+ }
+ return false;
+}
+
+bool JpegCompressor::waitForDone(nsecs_t timeout) {
+ Mutex::Autolock lock(mBusyMutex);
+ while (mIsBusy) {
+ status_t res = mDone.waitRelative(mBusyMutex, timeout);
+ if (res != OK) return false;
+ }
+ return true;
+}
+
+bool JpegCompressor::checkError(const char *msg) {
+ if (mJpegErrorInfo) {
+ char errBuffer[JMSG_LENGTH_MAX];
+ mJpegErrorInfo->err->format_message(mJpegErrorInfo, errBuffer);
+ ALOGE("%s: %s: %s",
+ __FUNCTION__, msg, errBuffer);
+ mJpegErrorInfo = NULL;
+ return true;
+ }
+ return false;
+}
+
+void JpegCompressor::cleanUp() {
+ status_t res;
+ jpeg_destroy_compress(&mCInfo);
+ Mutex::Autolock lock(mBusyMutex);
+
+ if (mFoundAux) {
+ if (mAuxBuffer.streamId == 0) {
+ delete[] mAuxBuffer.img;
+ } else if (!mSynchronous) {
+ mListener->onJpegInputDone(mAuxBuffer);
+ }
+ }
+ if (!mSynchronous) {
+ delete mBuffers;
+ }
+
+ mBuffers = NULL;
+
+ mIsBusy = false;
+ mDone.signal();
+}
+
+void JpegCompressor::jpegErrorHandler(j_common_ptr cinfo) {
+ JpegError *error = static_cast<JpegError*>(cinfo->err);
+ error->parent->mJpegErrorInfo = cinfo;
+}
+
+void JpegCompressor::jpegInitDestination(j_compress_ptr cinfo) {
+ JpegDestination *dest= static_cast<JpegDestination*>(cinfo->dest);
+ ALOGV("%s: Setting destination to %p, size %zu",
+ __FUNCTION__, dest->parent->mJpegBuffer.img, kMaxJpegSize);
+ dest->next_output_byte = (JOCTET*)(dest->parent->mJpegBuffer.img);
+ dest->free_in_buffer = kMaxJpegSize;
+}
+
+boolean JpegCompressor::jpegEmptyOutputBuffer(j_compress_ptr cinfo) {
+ ALOGE("%s: JPEG destination buffer overflow!",
+ __FUNCTION__);
+ return true;
+}
+
+void JpegCompressor::jpegTermDestination(j_compress_ptr cinfo) {
+ ALOGV("%s: Done writing JPEG data. %zu bytes left in buffer",
+ __FUNCTION__, cinfo->dest->free_in_buffer);
+}
+
+JpegCompressor::JpegListener::~JpegListener() {
+}
+
+} // namespace android
diff --git a/guest/hals/camera/fake-pipeline2/JpegCompressor.h b/guest/hals/camera/fake-pipeline2/JpegCompressor.h
new file mode 100644
index 0000000..597cbdf
--- /dev/null
+++ b/guest/hals/camera/fake-pipeline2/JpegCompressor.h
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+
+/**
+ * This class simulates a hardware JPEG compressor. It receives image buffers
+ * in RGBA_8888 format, processes them in a worker thread, and then pushes them
+ * out to their destination stream.
+ */
+
+#ifndef HW_EMULATOR_CAMERA2_JPEG_H
+#define HW_EMULATOR_CAMERA2_JPEG_H
+
+#include "utils/Thread.h"
+#include "utils/Mutex.h"
+#include "utils/Timers.h"
+
+#include "Base.h"
+
+#include <stdio.h>
+
+extern "C" {
+#include <jpeglib.h>
+}
+
+namespace android {
+
+class JpegCompressor: private Thread, public virtual RefBase {
+ public:
+
+ JpegCompressor();
+ ~JpegCompressor();
+
+ struct JpegListener {
+ // Called when JPEG compression has finished, or encountered an error
+ virtual void onJpegDone(const StreamBuffer &jpegBuffer,
+ bool success) = 0;
+ // Called when the input buffer for JPEG is not needed any more,
+ // if the buffer came from the framework.
+ virtual void onJpegInputDone(const StreamBuffer &inputBuffer) = 0;
+ virtual ~JpegListener();
+ };
+
+ // Start compressing COMPRESSED format buffers; JpegCompressor takes
+ // ownership of the Buffers vector.
+ // Reserve() must be called first.
+ status_t start(Buffers *buffers, JpegListener *listener);
+
+ // Compress and block until buffer is complete.
+ status_t compressSynchronous(Buffers *buffers);
+
+ status_t cancel();
+
+ bool isBusy();
+ bool isStreamInUse(uint32_t id);
+
+ bool waitForDone(nsecs_t timeout);
+
+ // Reserve the compressor for a later start() call.
+ status_t reserve();
+
+ // TODO: Measure this
+ static const size_t kMaxJpegSize = 300000;
+
+ private:
+ Mutex mBusyMutex;
+ bool mIsBusy;
+ Condition mDone;
+ bool mSynchronous;
+
+ Mutex mMutex;
+
+ Buffers *mBuffers;
+ JpegListener *mListener;
+
+ StreamBuffer mJpegBuffer, mAuxBuffer;
+ bool mFoundJpeg, mFoundAux;
+
+ jpeg_compress_struct mCInfo;
+
+ struct JpegError : public jpeg_error_mgr {
+ JpegCompressor *parent;
+ };
+ j_common_ptr mJpegErrorInfo;
+
+ struct JpegDestination : public jpeg_destination_mgr {
+ JpegCompressor *parent;
+ };
+
+ static void jpegErrorHandler(j_common_ptr cinfo);
+
+ static void jpegInitDestination(j_compress_ptr cinfo);
+ static boolean jpegEmptyOutputBuffer(j_compress_ptr cinfo);
+ static void jpegTermDestination(j_compress_ptr cinfo);
+
+ bool checkError(const char *msg);
+ status_t compress();
+
+ void cleanUp();
+
+ /**
+ * Inherited Thread virtual overrides
+ */
+ private:
+ virtual status_t readyToRun();
+ virtual bool threadLoop();
+};
+
+} // namespace android
+
+#endif
diff --git a/guest/hals/camera/fake-pipeline2/Scene.cpp b/guest/hals/camera/fake-pipeline2/Scene.cpp
new file mode 100644
index 0000000..48296d2
--- /dev/null
+++ b/guest/hals/camera/fake-pipeline2/Scene.cpp
@@ -0,0 +1,478 @@
+/*
+ * Copyright (C) 2012 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_NDEBUG 0
+#define LOG_TAG "EmulatedCamera_Scene"
+#include <utils/Log.h>
+#include <stdlib.h>
+#include <cmath>
+#include "Scene.h"
+
+// TODO: This should probably be done host-side in OpenGL for speed and better
+// quality
+
+namespace android {
+
+// Define single-letter shortcuts for scene definition, for directly indexing
+// mCurrentColors
+#define G (Scene::GRASS * Scene::NUM_CHANNELS)
+#define S (Scene::GRASS_SHADOW * Scene::NUM_CHANNELS)
+#define H (Scene::HILL * Scene::NUM_CHANNELS)
+#define W (Scene::WALL * Scene::NUM_CHANNELS)
+#define R (Scene::ROOF * Scene::NUM_CHANNELS)
+#define D (Scene::DOOR * Scene::NUM_CHANNELS)
+#define C (Scene::CHIMNEY * Scene::NUM_CHANNELS)
+#define I (Scene::WINDOW * Scene::NUM_CHANNELS)
+#define U (Scene::SUN * Scene::NUM_CHANNELS)
+#define K (Scene::SKY * Scene::NUM_CHANNELS)
+#define M (Scene::MOON * Scene::NUM_CHANNELS)
+
+const int Scene::kSceneWidth = 20;
+const int Scene::kSceneHeight = 20;
+
+const uint8_t Scene::kScene[Scene::kSceneWidth * Scene::kSceneHeight] = {
+ // 5 10 15 20
+ K,K,K,K,K,K,K,K,K,K,K,K,K,K,K,K,K,K,K,K,
+ K,K,K,K,K,K,K,K,K,K,K,K,K,K,K,K,K,K,K,K,
+ K,K,K,K,K,K,K,K,K,K,K,K,K,K,K,K,K,K,K,K,
+ K,K,K,K,K,K,K,K,K,K,K,K,K,K,K,K,K,K,K,K,
+ K,K,K,K,K,K,K,K,K,K,K,K,K,K,K,K,K,K,K,K, // 5
+ K,K,K,K,K,K,K,K,K,K,K,K,K,K,K,K,K,K,K,K,
+ K,K,K,K,K,K,K,K,H,H,H,H,H,H,H,H,H,H,H,H,
+ K,K,K,K,K,K,K,K,H,H,H,H,H,H,H,C,C,H,H,H,
+ K,K,K,K,K,K,H,H,H,H,H,H,H,H,H,C,C,H,H,H,
+ H,K,K,K,K,K,H,R,R,R,R,R,R,R,R,R,R,R,R,H, // 10
+ H,K,K,K,K,H,H,R,R,R,R,R,R,R,R,R,R,R,R,H,
+ H,H,H,K,K,H,H,R,R,R,R,R,R,R,R,R,R,R,R,H,
+ H,H,H,K,K,H,H,H,W,W,W,W,W,W,W,W,W,W,H,H,
+ S,S,S,G,G,S,S,S,W,W,W,W,W,W,W,W,W,W,S,S,
+ S,G,G,G,G,S,S,S,W,I,I,W,D,D,W,I,I,W,S,S, // 15
+ G,G,G,G,G,G,S,S,W,I,I,W,D,D,W,I,I,W,S,S,
+ G,G,G,G,G,G,G,G,W,W,W,W,D,D,W,W,W,W,G,G,
+ G,G,G,G,G,G,G,G,W,W,W,W,D,D,W,W,W,W,G,G,
+ G,G,G,G,G,G,G,G,S,S,S,S,S,S,S,S,S,S,G,G,
+ G,G,G,G,G,G,G,G,S,S,S,S,S,S,S,S,S,S,G,G, // 20
+ // 5 10 15 20
+};
+
+#undef G
+#undef S
+#undef H
+#undef W
+#undef R
+#undef D
+#undef C
+#undef I
+#undef U
+#undef K
+#undef M
+
+Scene::Scene(
+ int sensorWidthPx,
+ int sensorHeightPx,
+ float sensorSensitivity):
+ mSensorWidth(sensorWidthPx),
+ mSensorHeight(sensorHeightPx),
+ mHour(12),
+ mExposureDuration(0.033f),
+ mSensorSensitivity(sensorSensitivity)
+{
+ // Map scene to sensor pixels
+ if (mSensorWidth > mSensorHeight) {
+ mMapDiv = (mSensorWidth / (kSceneWidth + 1) ) + 1;
+ } else {
+ mMapDiv = (mSensorHeight / (kSceneHeight + 1) ) + 1;
+ }
+ mOffsetX = (kSceneWidth * mMapDiv - mSensorWidth) / 2;
+ mOffsetY = (kSceneHeight * mMapDiv - mSensorHeight) / 2;
+
+ // Assume that sensor filters are sRGB primaries to start
+ mFilterR[0] = 3.2406f; mFilterR[1] = -1.5372f; mFilterR[2] = -0.4986f;
+ mFilterGr[0] = -0.9689f; mFilterGr[1] = 1.8758f; mFilterGr[2] = 0.0415f;
+ mFilterGb[0] = -0.9689f; mFilterGb[1] = 1.8758f; mFilterGb[2] = 0.0415f;
+ mFilterB[0] = 0.0557f; mFilterB[1] = -0.2040f; mFilterB[2] = 1.0570f;
+
+
+}
+
+Scene::~Scene() {
+}
+
+void Scene::setColorFilterXYZ(
+ float rX, float rY, float rZ,
+ float grX, float grY, float grZ,
+ float gbX, float gbY, float gbZ,
+ float bX, float bY, float bZ) {
+ mFilterR[0] = rX; mFilterR[1] = rY; mFilterR[2] = rZ;
+ mFilterGr[0] = grX; mFilterGr[1] = grY; mFilterGr[2] = grZ;
+ mFilterGb[0] = gbX; mFilterGb[1] = gbY; mFilterGb[2] = gbZ;
+ mFilterB[0] = bX; mFilterB[1] = bY; mFilterB[2] = bZ;
+}
+
+void Scene::setHour(int hour) {
+ ALOGV("Hour set to: %d", hour);
+ mHour = hour % 24;
+}
+
+int Scene::getHour() {
+ return mHour;
+}
+
+void Scene::setExposureDuration(float seconds) {
+ mExposureDuration = seconds;
+}
+
+void Scene::calculateScene(nsecs_t time) {
+ // Calculate time fractions for interpolation
+ int timeIdx = mHour / kTimeStep;
+ int nextTimeIdx = (timeIdx + 1) % (24 / kTimeStep);
+ const nsecs_t kOneHourInNsec = 1e9 * 60 * 60;
+ nsecs_t timeSinceIdx = (mHour - timeIdx * kTimeStep) * kOneHourInNsec + time;
+ float timeFrac = timeSinceIdx / (float)(kOneHourInNsec * kTimeStep);
+
+ // Determine overall sunlight levels
+ float sunLux =
+ kSunlight[timeIdx] * (1 - timeFrac) +
+ kSunlight[nextTimeIdx] * timeFrac;
+ ALOGV("Sun lux: %f", sunLux);
+
+ float sunShadeLux = sunLux * (kDaylightShadeIllum / kDirectSunIllum);
+
+ // Determine sun/shade illumination chromaticity
+ float currentSunXY[2];
+ float currentShadeXY[2];
+
+ const float *prevSunXY, *nextSunXY;
+ const float *prevShadeXY, *nextShadeXY;
+ if (kSunlight[timeIdx] == kSunsetIllum ||
+ kSunlight[timeIdx] == kTwilightIllum) {
+ prevSunXY = kSunsetXY;
+ prevShadeXY = kSunsetXY;
+ } else {
+ prevSunXY = kDirectSunlightXY;
+ prevShadeXY = kDaylightXY;
+ }
+ if (kSunlight[nextTimeIdx] == kSunsetIllum ||
+ kSunlight[nextTimeIdx] == kTwilightIllum) {
+ nextSunXY = kSunsetXY;
+ nextShadeXY = kSunsetXY;
+ } else {
+ nextSunXY = kDirectSunlightXY;
+ nextShadeXY = kDaylightXY;
+ }
+ currentSunXY[0] = prevSunXY[0] * (1 - timeFrac) +
+ nextSunXY[0] * timeFrac;
+ currentSunXY[1] = prevSunXY[1] * (1 - timeFrac) +
+ nextSunXY[1] * timeFrac;
+
+ currentShadeXY[0] = prevShadeXY[0] * (1 - timeFrac) +
+ nextShadeXY[0] * timeFrac;
+ currentShadeXY[1] = prevShadeXY[1] * (1 - timeFrac) +
+ nextShadeXY[1] * timeFrac;
+
+ ALOGV("Sun XY: %f, %f, Shade XY: %f, %f",
+ currentSunXY[0], currentSunXY[1],
+ currentShadeXY[0], currentShadeXY[1]);
+
+ // Converting for xyY to XYZ:
+ // X = Y / y * x
+ // Y = Y
+ // Z = Y / y * (1 - x - y);
+ float sunXYZ[3] = {
+ sunLux / currentSunXY[1] * currentSunXY[0],
+ sunLux,
+ sunLux / currentSunXY[1] *
+ (1 - currentSunXY[0] - currentSunXY[1])
+ };
+ float sunShadeXYZ[3] = {
+ sunShadeLux / currentShadeXY[1] * currentShadeXY[0],
+ sunShadeLux,
+ sunShadeLux / currentShadeXY[1] *
+ (1 - currentShadeXY[0] - currentShadeXY[1])
+ };
+ ALOGV("Sun XYZ: %f, %f, %f",
+ sunXYZ[0], sunXYZ[1], sunXYZ[2]);
+ ALOGV("Sun shade XYZ: %f, %f, %f",
+ sunShadeXYZ[0], sunShadeXYZ[1], sunShadeXYZ[2]);
+
+ // Determine moonlight levels
+ float moonLux =
+ kMoonlight[timeIdx] * (1 - timeFrac) +
+ kMoonlight[nextTimeIdx] * timeFrac;
+ float moonShadeLux = moonLux * (kDaylightShadeIllum / kDirectSunIllum);
+
+ float moonXYZ[3] = {
+ moonLux / kMoonlightXY[1] * kMoonlightXY[0],
+ moonLux,
+ moonLux / kMoonlightXY[1] *
+ (1 - kMoonlightXY[0] - kMoonlightXY[1])
+ };
+ float moonShadeXYZ[3] = {
+ moonShadeLux / kMoonlightXY[1] * kMoonlightXY[0],
+ moonShadeLux,
+ moonShadeLux / kMoonlightXY[1] *
+ (1 - kMoonlightXY[0] - kMoonlightXY[1])
+ };
+
+ // Determine starlight level
+ const float kClearNightXYZ[3] = {
+ kClearNightIllum / kMoonlightXY[1] * kMoonlightXY[0],
+ kClearNightIllum,
+ kClearNightIllum / kMoonlightXY[1] *
+ (1 - kMoonlightXY[0] - kMoonlightXY[1])
+ };
+
+ // Calculate direct and shaded light
+ float directIllumXYZ[3] = {
+ sunXYZ[0] + moonXYZ[0] + kClearNightXYZ[0],
+ sunXYZ[1] + moonXYZ[1] + kClearNightXYZ[1],
+ sunXYZ[2] + moonXYZ[2] + kClearNightXYZ[2],
+ };
+
+ float shadeIllumXYZ[3] = {
+ kClearNightXYZ[0],
+ kClearNightXYZ[1],
+ kClearNightXYZ[2]
+ };
+
+ shadeIllumXYZ[0] += (mHour < kSunOverhead) ? sunXYZ[0] : sunShadeXYZ[0];
+ shadeIllumXYZ[1] += (mHour < kSunOverhead) ? sunXYZ[1] : sunShadeXYZ[1];
+ shadeIllumXYZ[2] += (mHour < kSunOverhead) ? sunXYZ[2] : sunShadeXYZ[2];
+
+ // Moon up period covers 23->0 transition, shift for simplicity
+ int adjHour = (mHour + 12) % 24;
+ int adjMoonOverhead = (kMoonOverhead + 12 ) % 24;
+ shadeIllumXYZ[0] += (adjHour < adjMoonOverhead) ?
+ moonXYZ[0] : moonShadeXYZ[0];
+ shadeIllumXYZ[1] += (adjHour < adjMoonOverhead) ?
+ moonXYZ[1] : moonShadeXYZ[1];
+ shadeIllumXYZ[2] += (adjHour < adjMoonOverhead) ?
+ moonXYZ[2] : moonShadeXYZ[2];
+
+ ALOGV("Direct XYZ: %f, %f, %f",
+ directIllumXYZ[0],directIllumXYZ[1],directIllumXYZ[2]);
+ ALOGV("Shade XYZ: %f, %f, %f",
+ shadeIllumXYZ[0], shadeIllumXYZ[1], shadeIllumXYZ[2]);
+
+ for (int i = 0; i < NUM_MATERIALS; i++) {
+ // Converting for xyY to XYZ:
+ // X = Y / y * x
+ // Y = Y
+ // Z = Y / y * (1 - x - y);
+ float matXYZ[3] = {
+ kMaterials_xyY[i][2] / kMaterials_xyY[i][1] *
+ kMaterials_xyY[i][0],
+ kMaterials_xyY[i][2],
+ kMaterials_xyY[i][2] / kMaterials_xyY[i][1] *
+ (1 - kMaterials_xyY[i][0] - kMaterials_xyY[i][1])
+ };
+
+ if (kMaterialsFlags[i] == 0 || kMaterialsFlags[i] & kSky) {
+ matXYZ[0] *= directIllumXYZ[0];
+ matXYZ[1] *= directIllumXYZ[1];
+ matXYZ[2] *= directIllumXYZ[2];
+ } else if (kMaterialsFlags[i] & kShadowed) {
+ matXYZ[0] *= shadeIllumXYZ[0];
+ matXYZ[1] *= shadeIllumXYZ[1];
+ matXYZ[2] *= shadeIllumXYZ[2];
+ } // else if (kMaterialsFlags[i] * kSelfLit), do nothing
+
+ ALOGV("Mat %d XYZ: %f, %f, %f", i, matXYZ[0], matXYZ[1], matXYZ[2]);
+ float luxToElectrons = mSensorSensitivity * mExposureDuration /
+ (kAperture * kAperture);
+ mCurrentColors[i*NUM_CHANNELS + 0] =
+ (mFilterR[0] * matXYZ[0] +
+ mFilterR[1] * matXYZ[1] +
+ mFilterR[2] * matXYZ[2])
+ * luxToElectrons;
+ mCurrentColors[i*NUM_CHANNELS + 1] =
+ (mFilterGr[0] * matXYZ[0] +
+ mFilterGr[1] * matXYZ[1] +
+ mFilterGr[2] * matXYZ[2])
+ * luxToElectrons;
+ mCurrentColors[i*NUM_CHANNELS + 2] =
+ (mFilterGb[0] * matXYZ[0] +
+ mFilterGb[1] * matXYZ[1] +
+ mFilterGb[2] * matXYZ[2])
+ * luxToElectrons;
+ mCurrentColors[i*NUM_CHANNELS + 3] =
+ (mFilterB[0] * matXYZ[0] +
+ mFilterB[1] * matXYZ[1] +
+ mFilterB[2] * matXYZ[2])
+ * luxToElectrons;
+
+ ALOGV("Color %d RGGB: %d, %d, %d, %d", i,
+ mCurrentColors[i*NUM_CHANNELS + 0],
+ mCurrentColors[i*NUM_CHANNELS + 1],
+ mCurrentColors[i*NUM_CHANNELS + 2],
+ mCurrentColors[i*NUM_CHANNELS + 3]);
+ }
+ // Shake viewpoint; horizontal and vertical sinusoids at roughly
+ // human handshake frequencies
+ mHandshakeX =
+ ( kFreq1Magnitude * std::sin(kHorizShakeFreq1 * timeSinceIdx) +
+ kFreq2Magnitude * std::sin(kHorizShakeFreq2 * timeSinceIdx) ) *
+ mMapDiv * kShakeFraction;
+
+ mHandshakeY =
+ ( kFreq1Magnitude * std::sin(kVertShakeFreq1 * timeSinceIdx) +
+ kFreq2Magnitude * std::sin(kVertShakeFreq2 * timeSinceIdx) ) *
+ mMapDiv * kShakeFraction;
+
+ // Set starting pixel
+ setReadoutPixel(0,0);
+}
+
+void Scene::setReadoutPixel(int x, int y) {
+ mCurrentX = x;
+ mCurrentY = y;
+ mSubX = (x + mOffsetX + mHandshakeX) % mMapDiv;
+ mSubY = (y + mOffsetY + mHandshakeY) % mMapDiv;
+ mSceneX = (x + mOffsetX + mHandshakeX) / mMapDiv;
+ mSceneY = (y + mOffsetY + mHandshakeY) / mMapDiv;
+ mSceneIdx = mSceneY * kSceneWidth + mSceneX;
+ mCurrentSceneMaterial = &(mCurrentColors[kScene[mSceneIdx]]);
+}
+
+const uint32_t* Scene::getPixelElectrons() {
+ const uint32_t *pixel = mCurrentSceneMaterial;
+ mCurrentX++;
+ mSubX++;
+ if (mCurrentX >= mSensorWidth) {
+ mCurrentX = 0;
+ mCurrentY++;
+ if (mCurrentY >= mSensorHeight) mCurrentY = 0;
+ setReadoutPixel(mCurrentX, mCurrentY);
+ } else if (mSubX > mMapDiv) {
+ mSceneIdx++;
+ mSceneX++;
+ mCurrentSceneMaterial = &(mCurrentColors[kScene[mSceneIdx]]);
+ mSubX = 0;
+ }
+ return pixel;
+}
+
+// Handshake model constants.
+// Frequencies measured in a nanosecond timebase
+const float Scene::kHorizShakeFreq1 = 2 * M_PI * 2 / 1e9; // 2 Hz
+const float Scene::kHorizShakeFreq2 = 2 * M_PI * 13 / 1e9; // 13 Hz
+const float Scene::kVertShakeFreq1 = 2 * M_PI * 3 / 1e9; // 3 Hz
+const float Scene::kVertShakeFreq2 = 2 * M_PI * 11 / 1e9; // 1 Hz
+const float Scene::kFreq1Magnitude = 5;
+const float Scene::kFreq2Magnitude = 1;
+const float Scene::kShakeFraction = 0.03; // As a fraction of a scene tile
+
+// RGB->YUV, Jpeg standard
+const float Scene::kRgb2Yuv[12] = {
+ 0.299f, 0.587f, 0.114f, 0.f,
+ -0.16874f, -0.33126f, 0.5f, -128.f,
+ 0.5f, -0.41869f, -0.08131f, -128.f,
+};
+
+// Aperture of imaging lens
+const float Scene::kAperture = 2.8;
+
+// Sun illumination levels through the day
+const float Scene::kSunlight[24/kTimeStep] =
+{
+ 0, // 00:00
+ 0,
+ 0,
+ kTwilightIllum, // 06:00
+ kDirectSunIllum,
+ kDirectSunIllum,
+ kDirectSunIllum, // 12:00
+ kDirectSunIllum,
+ kDirectSunIllum,
+ kSunsetIllum, // 18:00
+ kTwilightIllum,
+ 0
+};
+
+// Moon illumination levels through the day
+const float Scene::kMoonlight[24/kTimeStep] =
+{
+ kFullMoonIllum, // 00:00
+ kFullMoonIllum,
+ 0,
+ 0, // 06:00
+ 0,
+ 0,
+ 0, // 12:00
+ 0,
+ 0,
+ 0, // 18:00
+ 0,
+ kFullMoonIllum
+};
+
+const int Scene::kSunOverhead = 12;
+const int Scene::kMoonOverhead = 0;
+
+// Used for sun illumination levels
+const float Scene::kDirectSunIllum = 100000;
+const float Scene::kSunsetIllum = 400;
+const float Scene::kTwilightIllum = 4;
+// Used for moon illumination levels
+const float Scene::kFullMoonIllum = 1;
+// Other illumination levels
+const float Scene::kDaylightShadeIllum = 20000;
+const float Scene::kClearNightIllum = 2e-3;
+const float Scene::kStarIllum = 2e-6;
+const float Scene::kLivingRoomIllum = 50;
+
+const float Scene::kIncandescentXY[2] = { 0.44757f, 0.40745f};
+const float Scene::kDirectSunlightXY[2] = { 0.34842f, 0.35161f};
+const float Scene::kDaylightXY[2] = { 0.31271f, 0.32902f};
+const float Scene::kNoonSkyXY[2] = { 0.346f, 0.359f};
+const float Scene::kMoonlightXY[2] = { 0.34842f, 0.35161f};
+const float Scene::kSunsetXY[2] = { 0.527f, 0.413f};
+
+const uint8_t Scene::kSelfLit = 0x01;
+const uint8_t Scene::kShadowed = 0x02;
+const uint8_t Scene::kSky = 0x04;
+
+// For non-self-lit materials, the Y component is normalized with 1=full
+// reflectance; for self-lit materials, it's the constant illuminance in lux.
+const float Scene::kMaterials_xyY[Scene::NUM_MATERIALS][3] = {
+ { 0.3688f, 0.4501f, .1329f }, // GRASS
+ { 0.3688f, 0.4501f, .1329f }, // GRASS_SHADOW
+ { 0.3986f, 0.5002f, .4440f }, // HILL
+ { 0.3262f, 0.5040f, .2297f }, // WALL
+ { 0.4336f, 0.3787f, .1029f }, // ROOF
+ { 0.3316f, 0.2544f, .0639f }, // DOOR
+ { 0.3425f, 0.3577f, .0887f }, // CHIMNEY
+ { kIncandescentXY[0], kIncandescentXY[1], kLivingRoomIllum }, // WINDOW
+ { kDirectSunlightXY[0], kDirectSunlightXY[1], kDirectSunIllum }, // SUN
+ { kNoonSkyXY[0], kNoonSkyXY[1], kDaylightShadeIllum / kDirectSunIllum }, // SKY
+ { kMoonlightXY[0], kMoonlightXY[1], kFullMoonIllum } // MOON
+};
+
+const uint8_t Scene::kMaterialsFlags[Scene::NUM_MATERIALS] = {
+ 0,
+ kShadowed,
+ kShadowed,
+ kShadowed,
+ kShadowed,
+ kShadowed,
+ kShadowed,
+ kSelfLit,
+ kSelfLit,
+ kSky,
+ kSelfLit,
+};
+
+} // namespace android
diff --git a/guest/hals/camera/fake-pipeline2/Scene.h b/guest/hals/camera/fake-pipeline2/Scene.h
new file mode 100644
index 0000000..66d1a69
--- /dev/null
+++ b/guest/hals/camera/fake-pipeline2/Scene.h
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+/**
+ * The Scene class implements a simple physical simulation of a scene, using the
+ * CIE 1931 colorspace to represent light in physical units (lux).
+ *
+ * It's fairly approximate, but does provide a scene with realistic widely
+ * variable illumination levels and colors over time.
+ *
+ */
+
+#ifndef HW_EMULATOR_CAMERA2_SCENE_H
+#define HW_EMULATOR_CAMERA2_SCENE_H
+
+#include "utils/Timers.h"
+
+namespace android {
+
+class Scene {
+ public:
+ Scene(int sensorWidthPx,
+ int sensorHeightPx,
+ float sensorSensitivity);
+ ~Scene();
+
+ // Set the filter coefficients for the red, green, and blue filters on the
+ // sensor. Used as an optimization to pre-calculate various illuminance
+ // values. Two different green filters can be provided, to account for
+ // possible cross-talk on a Bayer sensor. Must be called before
+ // calculateScene.
+ void setColorFilterXYZ(
+ float rX, float rY, float rZ,
+ float grX, float grY, float grZ,
+ float gbX, float gbY, float gbZ,
+ float bX, float bY, float bZ);
+
+ // Set time of day (24-hour clock). This controls the general light levels
+ // in the scene. Must be called before calculateScene
+ void setHour(int hour);
+ // Get current hour
+ int getHour();
+
+ // Set the duration of exposure for determining luminous exposure.
+ // Must be called before calculateScene
+ void setExposureDuration(float seconds);
+
+ // Calculate scene information for current hour and the time offset since
+ // the hour. Must be called at least once before calling getLuminousExposure.
+ // Resets pixel readout location to 0,0
+ void calculateScene(nsecs_t time);
+
+ // Set sensor pixel readout location.
+ void setReadoutPixel(int x, int y);
+
+ // Get sensor response in physical units (electrons) for light hitting the
+ // current readout pixel, after passing through color filters. The readout
+ // pixel will be auto-incremented. The returned array can be indexed with
+ // ColorChannels.
+ const uint32_t* getPixelElectrons();
+
+ enum ColorChannels {
+ R = 0,
+ Gr,
+ Gb,
+ B,
+ Y,
+ Cb,
+ Cr,
+ NUM_CHANNELS
+ };
+
+ private:
+ // Sensor color filtering coefficients in XYZ
+ float mFilterR[3];
+ float mFilterGr[3];
+ float mFilterGb[3];
+ float mFilterB[3];
+
+ int mOffsetX, mOffsetY;
+ int mMapDiv;
+
+ int mHandshakeX, mHandshakeY;
+
+ int mSensorWidth;
+ int mSensorHeight;
+ int mCurrentX;
+ int mCurrentY;
+ int mSubX;
+ int mSubY;
+ int mSceneX;
+ int mSceneY;
+ int mSceneIdx;
+ uint32_t *mCurrentSceneMaterial;
+
+ int mHour;
+ float mExposureDuration;
+ float mSensorSensitivity;
+
+ enum Materials {
+ GRASS = 0,
+ GRASS_SHADOW,
+ HILL,
+ WALL,
+ ROOF,
+ DOOR,
+ CHIMNEY,
+ WINDOW,
+ SUN,
+ SKY,
+ MOON,
+ NUM_MATERIALS
+ };
+
+ uint32_t mCurrentColors[NUM_MATERIALS*NUM_CHANNELS];
+
+ /**
+ * Constants for scene definition. These are various degrees of approximate.
+ */
+
+ // Fake handshake parameters. Two shake frequencies per axis, plus magnitude
+ // as a fraction of a scene tile, and relative magnitudes for the frequencies
+ static const float kHorizShakeFreq1;
+ static const float kHorizShakeFreq2;
+ static const float kVertShakeFreq1;
+ static const float kVertShakeFreq2;
+ static const float kFreq1Magnitude;
+ static const float kFreq2Magnitude;
+
+ static const float kShakeFraction;
+
+ // RGB->YUV conversion
+ static const float kRgb2Yuv[12];
+
+ // Aperture of imaging lens
+ static const float kAperture;
+
+ // Sun, moon illuminance levels in 2-hour increments. These don't match any
+ // real day anywhere.
+ static const uint32_t kTimeStep = 2;
+ static const float kSunlight[];
+ static const float kMoonlight[];
+ static const int kSunOverhead;
+ static const int kMoonOverhead;
+
+ // Illumination levels for various conditions, in lux
+ static const float kDirectSunIllum;
+ static const float kDaylightShadeIllum;
+ static const float kSunsetIllum;
+ static const float kTwilightIllum;
+ static const float kFullMoonIllum;
+ static const float kClearNightIllum;
+ static const float kStarIllum;
+ static const float kLivingRoomIllum;
+
+ // Chromaticity of various illumination sources
+ static const float kIncandescentXY[2];
+ static const float kDirectSunlightXY[2];
+ static const float kDaylightXY[2];
+ static const float kNoonSkyXY[2];
+ static const float kMoonlightXY[2];
+ static const float kSunsetXY[2];
+
+ static const uint8_t kSelfLit;
+ static const uint8_t kShadowed;
+ static const uint8_t kSky;
+
+ static const float kMaterials_xyY[NUM_MATERIALS][3];
+ static const uint8_t kMaterialsFlags[NUM_MATERIALS];
+
+ static const int kSceneWidth;
+ static const int kSceneHeight;
+ static const uint8_t kScene[];
+};
+
+}
+
+#endif // HW_EMULATOR_CAMERA2_SCENE_H
diff --git a/guest/hals/camera/fake-pipeline2/Sensor.cpp b/guest/hals/camera/fake-pipeline2/Sensor.cpp
new file mode 100644
index 0000000..108e336
--- /dev/null
+++ b/guest/hals/camera/fake-pipeline2/Sensor.cpp
@@ -0,0 +1,615 @@
+/*
+ * Copyright (C) 2012 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_NDEBUG 0
+//#define LOG_NNDEBUG 0
+#define LOG_TAG "EmulatedCamera2_Sensor"
+
+#ifdef LOG_NNDEBUG
+#define ALOGVV(...) ALOGV(__VA_ARGS__)
+#else
+#define ALOGVV(...) ((void)0)
+#endif
+
+#include <utils/Log.h>
+
+#include "../EmulatedFakeCamera2.h"
+#include "Sensor.h"
+#include <cmath>
+#include <cstdlib>
+#include "guest/libs/platform_support/api_level_fixes.h"
+#include "system/camera_metadata.h"
+
+namespace android {
+
+//const nsecs_t Sensor::kExposureTimeRange[2] =
+// {1000L, 30000000000L} ; // 1 us - 30 sec
+//const nsecs_t Sensor::kFrameDurationRange[2] =
+// {33331760L, 30000000000L}; // ~1/30 s - 30 sec
+const nsecs_t Sensor::kExposureTimeRange[2] =
+ {1000L, 300000000L} ; // 1 us - 0.3 sec
+const nsecs_t Sensor::kFrameDurationRange[2] =
+ {33331760L, 300000000L}; // ~1/30 s - 0.3 sec
+
+const nsecs_t Sensor::kMinVerticalBlank = 10000L;
+
+const uint8_t Sensor::kColorFilterArrangement =
+ ANDROID_SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_RGGB;
+
+// Output image data characteristics
+const uint32_t Sensor::kMaxRawValue = 4000;
+const uint32_t Sensor::kBlackLevel = 1000;
+
+// Sensor sensitivity
+const float Sensor::kSaturationVoltage = 0.520f;
+const uint32_t Sensor::kSaturationElectrons = 2000;
+const float Sensor::kVoltsPerLuxSecond = 0.100f;
+
+const float Sensor::kElectronsPerLuxSecond =
+ Sensor::kSaturationElectrons / Sensor::kSaturationVoltage
+ * Sensor::kVoltsPerLuxSecond;
+
+const float Sensor::kBaseGainFactor = (float)Sensor::kMaxRawValue /
+ Sensor::kSaturationElectrons;
+
+const float Sensor::kReadNoiseStddevBeforeGain = 1.177; // in electrons
+const float Sensor::kReadNoiseStddevAfterGain = 2.100; // in digital counts
+const float Sensor::kReadNoiseVarBeforeGain =
+ Sensor::kReadNoiseStddevBeforeGain *
+ Sensor::kReadNoiseStddevBeforeGain;
+const float Sensor::kReadNoiseVarAfterGain =
+ Sensor::kReadNoiseStddevAfterGain *
+ Sensor::kReadNoiseStddevAfterGain;
+
+const int32_t Sensor::kSensitivityRange[2] = {100, 1600};
+const uint32_t Sensor::kDefaultSensitivity = 100;
+
+/** A few utility functions for math, normal distributions */
+
+// Take advantage of IEEE floating-point format to calculate an approximate
+// square root. Accurate to within +-3.6%
+float sqrtf_approx(float r) {
+ // Modifier is based on IEEE floating-point representation; the
+ // manipulations boil down to finding approximate log2, dividing by two, and
+ // then inverting the log2. A bias is added to make the relative error
+ // symmetric about the real answer.
+ const int32_t modifier = 0x1FBB4000;
+
+ int32_t r_i = *(int32_t*)(&r);
+ r_i = (r_i >> 1) + modifier;
+
+ return *(float*)(&r_i);
+}
+
+
+
+Sensor::Sensor(uint32_t width, uint32_t height):
+ Thread(false),
+ mResolution{width, height},
+ mActiveArray{0, 0, width, height},
+ mRowReadoutTime(kFrameDurationRange[0] / height),
+ mGotVSync(false),
+ mExposureTime(kFrameDurationRange[0]-kMinVerticalBlank),
+ mFrameDuration(kFrameDurationRange[0]),
+ mGainFactor(kDefaultSensitivity),
+ mNextBuffers(NULL),
+ mFrameNumber(0),
+ mCapturedBuffers(NULL),
+ mListener(NULL),
+ mScene(width, height, kElectronsPerLuxSecond)
+{
+ ALOGV("Sensor created with pixel array %d x %d", width, height);
+}
+
+Sensor::~Sensor() {
+ shutDown();
+}
+
+status_t Sensor::startUp() {
+ ALOGV("%s: E", __FUNCTION__);
+
+ int res;
+ mCapturedBuffers = NULL;
+ res = run("EmulatedFakeCamera2::Sensor",
+ ANDROID_PRIORITY_URGENT_DISPLAY);
+
+ if (res != OK) {
+ ALOGE("Unable to start up sensor capture thread: %d", res);
+ }
+ return res;
+}
+
+status_t Sensor::shutDown() {
+ ALOGV("%s: E", __FUNCTION__);
+
+ int res;
+ res = requestExitAndWait();
+ if (res != OK) {
+ ALOGE("Unable to shut down sensor capture thread: %d", res);
+ }
+ return res;
+}
+
+Scene &Sensor::getScene() {
+ return mScene;
+}
+
+void Sensor::setExposureTime(uint64_t ns) {
+ Mutex::Autolock lock(mControlMutex);
+ ALOGVV("Exposure set to %f", ns/1000000.f);
+ mExposureTime = ns;
+}
+
+void Sensor::setFrameDuration(uint64_t ns) {
+ Mutex::Autolock lock(mControlMutex);
+ ALOGVV("Frame duration set to %f", ns/1000000.f);
+ mFrameDuration = ns;
+}
+
+void Sensor::setSensitivity(uint32_t gain) {
+ Mutex::Autolock lock(mControlMutex);
+ ALOGVV("Gain set to %d", gain);
+ mGainFactor = gain;
+}
+
+void Sensor::setDestinationBuffers(Buffers *buffers) {
+ Mutex::Autolock lock(mControlMutex);
+ mNextBuffers = buffers;
+}
+
+void Sensor::setFrameNumber(uint32_t frameNumber) {
+ Mutex::Autolock lock(mControlMutex);
+ mFrameNumber = frameNumber;
+}
+
+bool Sensor::waitForVSync(nsecs_t reltime) {
+ int res;
+ Mutex::Autolock lock(mControlMutex);
+
+ mGotVSync = false;
+ res = mVSync.waitRelative(mControlMutex, reltime);
+ if (res != OK && res != TIMED_OUT) {
+ ALOGE("%s: Error waiting for VSync signal: %d", __FUNCTION__, res);
+ return false;
+ }
+ return mGotVSync;
+}
+
+bool Sensor::waitForNewFrame(nsecs_t reltime,
+ nsecs_t *captureTime) {
+ Mutex::Autolock lock(mReadoutMutex);
+ uint8_t *ret;
+ if (mCapturedBuffers == NULL) {
+ int res;
+ res = mReadoutAvailable.waitRelative(mReadoutMutex, reltime);
+ if (res == TIMED_OUT) {
+ return false;
+ } else if (res != OK || mCapturedBuffers == NULL) {
+ ALOGE("Error waiting for sensor readout signal: %d", res);
+ return false;
+ }
+ }
+ mReadoutComplete.signal();
+
+ *captureTime = mCaptureTime;
+ mCapturedBuffers = NULL;
+ return true;
+}
+
+Sensor::SensorListener::~SensorListener() {
+}
+
+void Sensor::setSensorListener(SensorListener *listener) {
+ Mutex::Autolock lock(mControlMutex);
+ mListener = listener;
+}
+
+status_t Sensor::readyToRun() {
+ ALOGV("Starting up sensor thread");
+ mStartupTime = systemTime();
+ mNextCaptureTime = 0;
+ mNextCapturedBuffers = NULL;
+ return OK;
+}
+
+bool Sensor::threadLoop() {
+ /**
+ * Sensor capture operation main loop.
+ *
+ * Stages are out-of-order relative to a single frame's processing, but
+ * in-order in time.
+ */
+
+ /**
+ * Stage 1: Read in latest control parameters
+ */
+ uint64_t exposureDuration;
+ uint64_t frameDuration;
+ uint32_t gain;
+ Buffers *nextBuffers;
+ uint32_t frameNumber;
+ SensorListener *listener = NULL;
+ {
+ Mutex::Autolock lock(mControlMutex);
+ exposureDuration = mExposureTime;
+ frameDuration = mFrameDuration;
+ gain = mGainFactor;
+ nextBuffers = mNextBuffers;
+ frameNumber = mFrameNumber;
+ listener = mListener;
+ // Don't reuse a buffer set
+ mNextBuffers = NULL;
+
+ // Signal VSync for start of readout
+ ALOGVV("Sensor VSync");
+ mGotVSync = true;
+ mVSync.signal();
+ }
+
+ /**
+ * Stage 3: Read out latest captured image
+ */
+
+ Buffers *capturedBuffers = NULL;
+ nsecs_t captureTime = 0;
+
+ nsecs_t startRealTime = systemTime();
+ // Stagefright cares about system time for timestamps, so base simulated
+ // time on that.
+ nsecs_t simulatedTime = startRealTime;
+ nsecs_t frameEndRealTime = startRealTime + frameDuration;
+ nsecs_t frameReadoutEndRealTime = startRealTime +
+ mRowReadoutTime * mResolution[1];
+
+ if (mNextCapturedBuffers != NULL) {
+ ALOGVV("Sensor starting readout");
+ // Pretend we're doing readout now; will signal once enough time has elapsed
+ capturedBuffers = mNextCapturedBuffers;
+ captureTime = mNextCaptureTime;
+ }
+ simulatedTime += mRowReadoutTime + kMinVerticalBlank;
+
+ // TODO: Move this signal to another thread to simulate readout
+ // time properly
+ if (capturedBuffers != NULL) {
+ ALOGVV("Sensor readout complete");
+ Mutex::Autolock lock(mReadoutMutex);
+ if (mCapturedBuffers != NULL) {
+ ALOGV("Waiting for readout thread to catch up!");
+ mReadoutComplete.wait(mReadoutMutex);
+ }
+
+ mCapturedBuffers = capturedBuffers;
+ mCaptureTime = captureTime;
+ mReadoutAvailable.signal();
+ capturedBuffers = NULL;
+ }
+
+ /**
+ * Stage 2: Capture new image
+ */
+ mNextCaptureTime = simulatedTime;
+ mNextCapturedBuffers = nextBuffers;
+
+ if (mNextCapturedBuffers != NULL) {
+ if (listener != NULL) {
+ listener->onSensorEvent(frameNumber, SensorListener::EXPOSURE_START,
+ mNextCaptureTime);
+ }
+ ALOGVV("Starting next capture: Exposure: %f ms, gain: %d",
+ (float)exposureDuration/1e6, gain);
+ mScene.setExposureDuration((float)exposureDuration/1e9);
+ mScene.calculateScene(mNextCaptureTime);
+
+ // Might be adding more buffers, so size isn't constant
+ for (size_t i = 0; i < mNextCapturedBuffers->size(); i++) {
+ const StreamBuffer &b = (*mNextCapturedBuffers)[i];
+ ALOGVV("Sensor capturing buffer %d: stream %d,"
+ " %d x %d, format %x, stride %d, buf %p, img %p",
+ i, b.streamId, b.width, b.height, b.format, b.stride,
+ b.buffer, b.img);
+ switch(b.format) {
+#if VSOC_PLATFORM_SDK_AFTER(K)
+ case HAL_PIXEL_FORMAT_RAW16:
+ captureRaw(b.img, gain, b.stride);
+ break;
+#endif
+ case HAL_PIXEL_FORMAT_RGB_888:
+ captureRGB(b.img, gain, b.stride);
+ break;
+ case HAL_PIXEL_FORMAT_RGBA_8888:
+ captureRGBA(b.img, gain, b.stride);
+ break;
+ case HAL_PIXEL_FORMAT_BLOB:
+#if defined HAL_DATASPACE_DEPTH
+ if (b.dataSpace != HAL_DATASPACE_DEPTH) {
+#endif
+ // Add auxillary buffer of the right size
+ // Assumes only one BLOB (JPEG) buffer in
+ // mNextCapturedBuffers
+ StreamBuffer bAux;
+ bAux.streamId = 0;
+ bAux.width = b.width;
+ bAux.height = b.height;
+ bAux.format = HAL_PIXEL_FORMAT_RGB_888;
+ bAux.stride = b.width;
+ bAux.buffer = NULL;
+ // TODO: Reuse these
+ bAux.img = new uint8_t[b.width * b.height * 3];
+ mNextCapturedBuffers->push_back(bAux);
+#if defined HAL_DATASPACE_DEPTH
+ } else {
+ captureDepthCloud(b.img);
+ }
+#endif
+ break;
+ case HAL_PIXEL_FORMAT_YCrCb_420_SP:
+ case HAL_PIXEL_FORMAT_YCbCr_420_888:
+ captureNV21(b.img, gain, b.stride);
+ break;
+ case HAL_PIXEL_FORMAT_YV12:
+ // TODO:
+ ALOGE("%s: Format %x is TODO", __FUNCTION__, b.format);
+ break;
+ case HAL_PIXEL_FORMAT_Y16:
+ captureDepth(b.img, gain, b.stride);
+ break;
+ default:
+ ALOGE("%s: Unknown format %x, no output", __FUNCTION__,
+ b.format);
+ break;
+ }
+ }
+ }
+
+ ALOGVV("Sensor vertical blanking interval");
+ nsecs_t workDoneRealTime = systemTime();
+ const nsecs_t timeAccuracy = 2e6; // 2 ms of imprecision is ok
+ if (workDoneRealTime < frameEndRealTime - timeAccuracy) {
+ timespec t;
+ t.tv_sec = (frameEndRealTime - workDoneRealTime) / 1000000000L;
+ t.tv_nsec = (frameEndRealTime - workDoneRealTime) % 1000000000L;
+
+ int ret;
+ do {
+ ret = nanosleep(&t, &t);
+ } while (ret != 0);
+ }
+ nsecs_t endRealTime = systemTime();
+ ALOGVV("Frame cycle took %d ms, target %d ms",
+ (int)((endRealTime - startRealTime)/1000000),
+ (int)(frameDuration / 1000000));
+ return true;
+};
+
+void Sensor::captureRaw(uint8_t *img, uint32_t gain, uint32_t stride) {
+ float totalGain = gain/100.0 * kBaseGainFactor;
+ float noiseVarGain = totalGain * totalGain;
+ float readNoiseVar = kReadNoiseVarBeforeGain * noiseVarGain
+ + kReadNoiseVarAfterGain;
+
+ int bayerSelect[4] = {Scene::R, Scene::Gr, Scene::Gb, Scene::B}; // RGGB
+ mScene.setReadoutPixel(0,0);
+ for (unsigned int y = 0; y < mResolution[1]; y++ ) {
+ int *bayerRow = bayerSelect + (y & 0x1) * 2;
+ uint16_t *px = (uint16_t*)img + y * stride;
+ for (unsigned int x = 0; x < mResolution[0]; x++) {
+ uint32_t electronCount;
+ electronCount = mScene.getPixelElectrons()[bayerRow[x & 0x1]];
+
+ // TODO: Better pixel saturation curve?
+ electronCount = (electronCount < kSaturationElectrons) ?
+ electronCount : kSaturationElectrons;
+
+ // TODO: Better A/D saturation curve?
+ uint16_t rawCount = electronCount * totalGain;
+ rawCount = (rawCount < kMaxRawValue) ? rawCount : kMaxRawValue;
+
+ // Calculate noise value
+ // TODO: Use more-correct Gaussian instead of uniform noise
+ float photonNoiseVar = electronCount * noiseVarGain;
+ float noiseStddev = sqrtf_approx(readNoiseVar + photonNoiseVar);
+ // Scaled to roughly match gaussian/uniform noise stddev
+ float noiseSample = std::rand() * (2.5 / (1.0 + RAND_MAX)) - 1.25;
+
+ rawCount += kBlackLevel;
+ rawCount += noiseStddev * noiseSample;
+
+ *px++ = rawCount;
+ }
+ // TODO: Handle this better
+ //simulatedTime += mRowReadoutTime;
+ }
+ ALOGVV("Raw sensor image captured");
+}
+
+void Sensor::captureRGBA(uint8_t *img, uint32_t gain, uint32_t stride) {
+ float totalGain = gain/100.0 * kBaseGainFactor;
+ // In fixed-point math, calculate total scaling from electrons to 8bpp
+ int scale64x = 64 * totalGain * 255 / kMaxRawValue;
+ uint32_t inc = ceil( (float) mResolution[0] / stride);
+
+ for (unsigned int y = 0, outY = 0; y < mResolution[1]; y+=inc, outY++ ) {
+ uint8_t *px = img + outY * stride * 4;
+ mScene.setReadoutPixel(0, y);
+ for (unsigned int x = 0; x < mResolution[0]; x+=inc) {
+ uint32_t rCount, gCount, bCount;
+ // TODO: Perfect demosaicing is a cheat
+ const uint32_t *pixel = mScene.getPixelElectrons();
+ rCount = pixel[Scene::R] * scale64x;
+ gCount = pixel[Scene::Gr] * scale64x;
+ bCount = pixel[Scene::B] * scale64x;
+
+ *px++ = rCount < 255*64 ? rCount / 64 : 255;
+ *px++ = gCount < 255*64 ? gCount / 64 : 255;
+ *px++ = bCount < 255*64 ? bCount / 64 : 255;
+ *px++ = 255;
+ for (unsigned int j = 1; j < inc; j++)
+ mScene.getPixelElectrons();
+ }
+ // TODO: Handle this better
+ //simulatedTime += mRowReadoutTime;
+ }
+ ALOGVV("RGBA sensor image captured");
+}
+
+void Sensor::captureRGB(uint8_t *img, uint32_t gain, uint32_t stride) {
+ float totalGain = gain/100.0 * kBaseGainFactor;
+ // In fixed-point math, calculate total scaling from electrons to 8bpp
+ int scale64x = 64 * totalGain * 255 / kMaxRawValue;
+ uint32_t inc = ceil( (float) mResolution[0] / stride);
+
+ for (unsigned int y = 0, outY = 0; y < mResolution[1]; y += inc, outY++ ) {
+ mScene.setReadoutPixel(0, y);
+ uint8_t *px = img + outY * stride * 3;
+ for (unsigned int x = 0; x < mResolution[0]; x += inc) {
+ uint32_t rCount, gCount, bCount;
+ // TODO: Perfect demosaicing is a cheat
+ const uint32_t *pixel = mScene.getPixelElectrons();
+ rCount = pixel[Scene::R] * scale64x;
+ gCount = pixel[Scene::Gr] * scale64x;
+ bCount = pixel[Scene::B] * scale64x;
+
+ *px++ = rCount < 255*64 ? rCount / 64 : 255;
+ *px++ = gCount < 255*64 ? gCount / 64 : 255;
+ *px++ = bCount < 255*64 ? bCount / 64 : 255;
+ for (unsigned int j = 1; j < inc; j++)
+ mScene.getPixelElectrons();
+ }
+ // TODO: Handle this better
+ //simulatedTime += mRowReadoutTime;
+ }
+ ALOGVV("RGB sensor image captured");
+}
+
+void Sensor::captureNV21(uint8_t *img, uint32_t gain, uint32_t stride) {
+ float totalGain = gain/100.0 * kBaseGainFactor;
+ // Using fixed-point math with 6 bits of fractional precision.
+ // In fixed-point math, calculate total scaling from electrons to 8bpp
+ const int scale64x = 64 * totalGain * 255 / kMaxRawValue;
+ // In fixed-point math, saturation point of sensor after gain
+ const int saturationPoint = 64 * 255;
+ // Fixed-point coefficients for RGB-YUV transform
+ // Based on JFIF RGB->YUV transform.
+ // Cb/Cr offset scaled by 64x twice since they're applied post-multiply
+ const int rgbToY[] = {19, 37, 7};
+ const int rgbToCb[] = {-10,-21, 32, 524288};
+ const int rgbToCr[] = {32,-26, -5, 524288};
+ // Scale back to 8bpp non-fixed-point
+ const int scaleOut = 64;
+ const int scaleOutSq = scaleOut * scaleOut; // after multiplies
+
+ // inc = how many pixels to skip while reading every next pixel
+ // horizontally.
+ uint32_t inc = ceil( (float) mResolution[0] / stride);
+ // outH = projected vertical resolution based on stride.
+ uint32_t outH = mResolution[1] / inc;
+ for (unsigned int y = 0, outY = 0;
+ y < mResolution[1]; y+=inc, outY++) {
+ uint8_t *pxY = img + outY * stride;
+ uint8_t *pxVU = img + (outH + outY / 2) * stride;
+ mScene.setReadoutPixel(0,y);
+ for (unsigned int outX = 0; outX < stride; outX++) {
+ int32_t rCount, gCount, bCount;
+ // TODO: Perfect demosaicing is a cheat
+ const uint32_t *pixel = mScene.getPixelElectrons();
+ rCount = pixel[Scene::R] * scale64x;
+ rCount = rCount < saturationPoint ? rCount : saturationPoint;
+ gCount = pixel[Scene::Gr] * scale64x;
+ gCount = gCount < saturationPoint ? gCount : saturationPoint;
+ bCount = pixel[Scene::B] * scale64x;
+ bCount = bCount < saturationPoint ? bCount : saturationPoint;
+
+ *pxY++ = (rgbToY[0] * rCount +
+ rgbToY[1] * gCount +
+ rgbToY[2] * bCount) / scaleOutSq;
+ if (outY % 2 == 0 && outX % 2 == 0) {
+ *pxVU++ = (rgbToCb[0] * rCount +
+ rgbToCb[1] * gCount +
+ rgbToCb[2] * bCount +
+ rgbToCb[3]) / scaleOutSq;
+ *pxVU++ = (rgbToCr[0] * rCount +
+ rgbToCr[1] * gCount +
+ rgbToCr[2] * bCount +
+ rgbToCr[3]) / scaleOutSq;
+ }
+
+ // Skip unprocessed pixels from sensor.
+ for (unsigned int j = 1; j < inc; j++)
+ mScene.getPixelElectrons();
+ }
+ }
+ ALOGVV("NV21 sensor image captured");
+}
+
+void Sensor::captureDepth(uint8_t *img, uint32_t gain, uint32_t stride) {
+ float totalGain = gain/100.0 * kBaseGainFactor;
+ // In fixed-point math, calculate scaling factor to 13bpp millimeters
+ int scale64x = 64 * totalGain * 8191 / kMaxRawValue;
+ uint32_t inc = ceil( (float) mResolution[0] / stride);
+
+ for (unsigned int y = 0, outY = 0; y < mResolution[1]; y += inc, outY++ ) {
+ mScene.setReadoutPixel(0, y);
+ uint16_t *px = ((uint16_t*)img) + outY * stride;
+ for (unsigned int x = 0; x < mResolution[0]; x += inc) {
+ uint32_t depthCount;
+ // TODO: Make up real depth scene instead of using green channel
+ // as depth
+ const uint32_t *pixel = mScene.getPixelElectrons();
+ depthCount = pixel[Scene::Gr] * scale64x;
+
+ *px++ = depthCount < 8191*64 ? depthCount / 64 : 0;
+ for (unsigned int j = 1; j < inc; j++)
+ mScene.getPixelElectrons();
+ }
+ // TODO: Handle this better
+ //simulatedTime += mRowReadoutTime;
+ }
+ ALOGVV("Depth sensor image captured");
+}
+
+void Sensor::captureDepthCloud(uint8_t *img) {
+#if defined HAL_DATASPACE_DEPTH
+ android_depth_points *cloud = reinterpret_cast<android_depth_points*>(img);
+
+ cloud->num_points = 16;
+
+ // TODO: Create point cloud values that match RGB scene
+ const int FLOATS_PER_POINT = 4;
+ const float JITTER_STDDEV = 0.1f;
+ for (size_t y = 0, i = 0; y < 4; y++) {
+ for (size_t x = 0; x < 4; x++, i++) {
+ float randSampleX = std::rand() * (2.5f / (1.0f + RAND_MAX)) - 1.25f;
+ randSampleX *= JITTER_STDDEV;
+
+ float randSampleY = std::rand() * (2.5f / (1.0f + RAND_MAX)) - 1.25f;
+ randSampleY *= JITTER_STDDEV;
+
+ float randSampleZ = std::rand() * (2.5f / (1.0f + RAND_MAX)) - 1.25f;
+ randSampleZ *= JITTER_STDDEV;
+
+ cloud->xyzc_points[i * FLOATS_PER_POINT + 0] = x - 1.5f + randSampleX;
+ cloud->xyzc_points[i * FLOATS_PER_POINT + 1] = y - 1.5f + randSampleY;
+ cloud->xyzc_points[i * FLOATS_PER_POINT + 2] = 3.f + randSampleZ;
+ cloud->xyzc_points[i * FLOATS_PER_POINT + 3] = 0.8f;
+ }
+ }
+
+ ALOGVV("Depth point cloud captured");
+#endif
+}
+
+} // namespace android
diff --git a/guest/hals/camera/fake-pipeline2/Sensor.h b/guest/hals/camera/fake-pipeline2/Sensor.h
new file mode 100644
index 0000000..cdf1e97
--- /dev/null
+++ b/guest/hals/camera/fake-pipeline2/Sensor.h
@@ -0,0 +1,247 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+/**
+ * This class is a simple simulation of a typical CMOS cellphone imager chip,
+ * which outputs 12-bit Bayer-mosaic raw images.
+ *
+ * Unlike most real image sensors, this one's native color space is linear sRGB.
+ *
+ * The sensor is abstracted as operating as a pipeline 3 stages deep;
+ * conceptually, each frame to be captured goes through these three stages. The
+ * processing step for the sensor is marked off by vertical sync signals, which
+ * indicate the start of readout of the oldest frame. The interval between
+ * processing steps depends on the frame duration of the frame currently being
+ * captured. The stages are 1) configure, 2) capture, and 3) readout. During
+ * configuration, the sensor's registers for settings such as exposure time,
+ * frame duration, and gain are set for the next frame to be captured. In stage
+ * 2, the image data for the frame is actually captured by the sensor. Finally,
+ * in stage 3, the just-captured data is read out and sent to the rest of the
+ * system.
+ *
+ * The sensor is assumed to be rolling-shutter, so low-numbered rows of the
+ * sensor are exposed earlier in time than larger-numbered rows, with the time
+ * offset between each row being equal to the row readout time.
+ *
+ * The characteristics of this sensor don't correspond to any actual sensor,
+ * but are not far off typical sensors.
+ *
+ * Example timing diagram, with three frames:
+ * Frame 0-1: Frame duration 50 ms, exposure time 20 ms.
+ * Frame 2: Frame duration 75 ms, exposure time 65 ms.
+ * Legend:
+ * C = update sensor registers for frame
+ * v = row in reset (vertical blanking interval)
+ * E = row capturing image data
+ * R = row being read out
+ * | = vertical sync signal
+ *time(ms)| 0 55 105 155 230 270
+ * Frame 0| :configure : capture : readout : : :
+ * Row # | ..|CCCC______|_________|_________| : :
+ * 0 | :\ \vvvvvEEEER \ : :
+ * 500 | : \ \vvvvvEEEER \ : :
+ * 1000 | : \ \vvvvvEEEER \ : :
+ * 1500 | : \ \vvvvvEEEER \ : :
+ * 2000 | : \__________\vvvvvEEEER_________\ : :
+ * Frame 1| : configure capture readout : :
+ * Row # | : |CCCC_____|_________|______________| :
+ * 0 | : :\ \vvvvvEEEER \ :
+ * 500 | : : \ \vvvvvEEEER \ :
+ * 1000 | : : \ \vvvvvEEEER \ :
+ * 1500 | : : \ \vvvvvEEEER \ :
+ * 2000 | : : \_________\vvvvvEEEER______________\ :
+ * Frame 2| : : configure capture readout:
+ * Row # | : : |CCCC_____|______________|_______|...
+ * 0 | : : :\ \vEEEEEEEEEEEEER \
+ * 500 | : : : \ \vEEEEEEEEEEEEER \
+ * 1000 | : : : \ \vEEEEEEEEEEEEER \
+ * 1500 | : : : \ \vEEEEEEEEEEEEER \
+ * 2000 | : : : \_________\vEEEEEEEEEEEEER_______\
+ */
+
+#ifndef HW_EMULATOR_CAMERA2_SENSOR_H
+#define HW_EMULATOR_CAMERA2_SENSOR_H
+
+#include "utils/Thread.h"
+#include "utils/Mutex.h"
+#include "utils/Timers.h"
+
+#include "Scene.h"
+#include "Base.h"
+
+namespace android {
+
+class EmulatedFakeCamera2;
+
+class Sensor: private Thread, public virtual RefBase {
+ public:
+
+ // width: Width of pixel array
+ // height: Height of pixel array
+ Sensor(uint32_t width, uint32_t height);
+ ~Sensor();
+
+ /*
+ * Power control
+ */
+
+ status_t startUp();
+ status_t shutDown();
+
+ /*
+ * Access to scene
+ */
+ Scene &getScene();
+
+ /*
+ * Controls that can be updated every frame
+ */
+
+ void setExposureTime(uint64_t ns);
+ void setFrameDuration(uint64_t ns);
+ void setSensitivity(uint32_t gain);
+ // Buffer must be at least stride*height*2 bytes in size
+ void setDestinationBuffers(Buffers *buffers);
+ // To simplify tracking sensor's current frame
+ void setFrameNumber(uint32_t frameNumber);
+
+ /*
+ * Controls that cause reconfiguration delay
+ */
+
+ void setBinning(int horizontalFactor, int verticalFactor);
+
+ /*
+ * Synchronizing with sensor operation (vertical sync)
+ */
+
+ // Wait until the sensor outputs its next vertical sync signal, meaning it
+ // is starting readout of its latest frame of data. Returns true if vertical
+ // sync is signaled, false if the wait timed out.
+ bool waitForVSync(nsecs_t reltime);
+
+ // Wait until a new frame has been read out, and then return the time
+ // capture started. May return immediately if a new frame has been pushed
+ // since the last wait for a new frame. Returns true if new frame is
+ // returned, false if timed out.
+ bool waitForNewFrame(nsecs_t reltime,
+ nsecs_t *captureTime);
+
+ /*
+ * Interrupt event servicing from the sensor. Only triggers for sensor
+ * cycles that have valid buffers to write to.
+ */
+ struct SensorListener {
+ enum Event {
+ EXPOSURE_START, // Start of exposure
+ };
+
+ virtual void onSensorEvent(uint32_t frameNumber, Event e,
+ nsecs_t timestamp) = 0;
+ virtual ~SensorListener();
+ };
+
+ void setSensorListener(SensorListener *listener);
+
+ /**
+ * Static sensor characteristics
+ */
+ const uint32_t mResolution[2];
+ const uint32_t mActiveArray[4];
+
+ static const nsecs_t kExposureTimeRange[2];
+ static const nsecs_t kFrameDurationRange[2];
+ static const nsecs_t kMinVerticalBlank;
+
+ static const uint8_t kColorFilterArrangement;
+
+ // Output image data characteristics
+ static const uint32_t kMaxRawValue;
+ static const uint32_t kBlackLevel;
+ // Sensor sensitivity, approximate
+
+ static const float kSaturationVoltage;
+ static const uint32_t kSaturationElectrons;
+ static const float kVoltsPerLuxSecond;
+ static const float kElectronsPerLuxSecond;
+
+ static const float kBaseGainFactor;
+
+ static const float kReadNoiseStddevBeforeGain; // In electrons
+ static const float kReadNoiseStddevAfterGain; // In raw digital units
+ static const float kReadNoiseVarBeforeGain;
+ static const float kReadNoiseVarAfterGain;
+
+ // While each row has to read out, reset, and then expose, the (reset +
+ // expose) sequence can be overlapped by other row readouts, so the final
+ // minimum frame duration is purely a function of row readout time, at least
+ // if there's a reasonable number of rows.
+ const nsecs_t mRowReadoutTime;
+
+ static const int32_t kSensitivityRange[2];
+ static const uint32_t kDefaultSensitivity;
+
+ private:
+ Mutex mControlMutex; // Lock before accessing control parameters
+ // Start of control parameters
+ Condition mVSync;
+ bool mGotVSync;
+ uint64_t mExposureTime;
+ uint64_t mFrameDuration;
+ uint32_t mGainFactor;
+ Buffers *mNextBuffers;
+ uint32_t mFrameNumber;
+
+ // End of control parameters
+
+ Mutex mReadoutMutex; // Lock before accessing readout variables
+ // Start of readout variables
+ Condition mReadoutAvailable;
+ Condition mReadoutComplete;
+ Buffers *mCapturedBuffers;
+ nsecs_t mCaptureTime;
+ SensorListener *mListener;
+ // End of readout variables
+
+ // Time of sensor startup, used for simulation zero-time point
+ nsecs_t mStartupTime;
+
+ /**
+ * Inherited Thread virtual overrides, and members only used by the
+ * processing thread
+ */
+ private:
+ virtual status_t readyToRun();
+
+ virtual bool threadLoop();
+
+ nsecs_t mNextCaptureTime;
+ Buffers *mNextCapturedBuffers;
+
+ Scene mScene;
+
+ void captureRaw(uint8_t *img, uint32_t gain, uint32_t stride);
+ void captureRGBA(uint8_t *img, uint32_t gain, uint32_t stride);
+ void captureRGB(uint8_t *img, uint32_t gain, uint32_t stride);
+ void captureNV21(uint8_t *img, uint32_t gain, uint32_t stride);
+ void captureDepth(uint8_t *img, uint32_t gain, uint32_t stride);
+ void captureDepthCloud(uint8_t *img);
+
+};
+
+}
+
+#endif // HW_EMULATOR_CAMERA2_SENSOR_H
diff --git a/guest/hals/camera/media_codecs.xml b/guest/hals/camera/media_codecs.xml
new file mode 100644
index 0000000..87d11f2
--- /dev/null
+++ b/guest/hals/camera/media_codecs.xml
@@ -0,0 +1,84 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<!-- Copyright (C) 2012 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.
+-->
+
+<!--
+<!DOCTYPE MediaCodecs [
+<!ELEMENT Include EMPTY>
+<!ATTLIST Include href CDATA #REQUIRED>
+<!ELEMENT MediaCodecs (Decoders|Encoders|Include)*>
+<!ELEMENT Decoders (MediaCodec|Include)*>
+<!ELEMENT Encoders (MediaCodec|Include)*>
+<!ELEMENT MediaCodec (Type|Quirk|Include)*>
+<!ATTLIST MediaCodec name CDATA #REQUIRED>
+<!ATTLIST MediaCodec type CDATA>
+<!ELEMENT Type EMPTY>
+<!ATTLIST Type name CDATA #REQUIRED>
+<!ELEMENT Quirk EMPTY>
+<!ATTLIST Quirk name CDATA #REQUIRED>
+]>
+
+There's a simple and a complex syntax to declare the availability of a
+media codec:
+
+A codec that properly follows the OpenMax spec and therefore doesn't have any
+quirks and that only supports a single content type can be declared like so:
+
+ <MediaCodec name="OMX.foo.bar" type="something/interesting" />
+
+If a codec has quirks OR supports multiple content types, the following syntax
+can be used:
+
+ <MediaCodec name="OMX.foo.bar" >
+ <Type name="something/interesting" />
+ <Type name="something/else" />
+ ...
+ <Quirk name="requires-allocate-on-input-ports" />
+ <Quirk name="requires-allocate-on-output-ports" />
+ <Quirk name="output-buffers-are-unreadable" />
+ </MediaCodec>
+
+Only the three quirks included above are recognized at this point:
+
+"requires-allocate-on-input-ports"
+ must be advertised if the component does not properly support specification
+ of input buffers using the OMX_UseBuffer(...) API but instead requires
+ OMX_AllocateBuffer to be used.
+
+"requires-allocate-on-output-ports"
+ must be advertised if the component does not properly support specification
+ of output buffers using the OMX_UseBuffer(...) API but instead requires
+ OMX_AllocateBuffer to be used.
+
+"output-buffers-are-unreadable"
+ must be advertised if the emitted output buffers of a decoder component
+ are not readable, i.e. use a custom format even though abusing one of
+ the official OMX colorspace constants.
+ Clients of such decoders will not be able to access the decoded data,
+ naturally making the component much less useful. The only use for
+ a component with this quirk is to render the output to the screen.
+ Audio decoders MUST NOT advertise this quirk.
+ Video decoders that advertise this quirk must be accompanied by a
+ corresponding color space converter for thumbnail extraction,
+ matching surfaceflinger support that can render the custom format to
+ a texture and possibly other code, so just DON'T USE THIS QUIRK.
+
+-->
+
+<MediaCodecs>
+ <Include href="media_codecs_google_audio.xml" />
+ <Include href="media_codecs_google_telephony.xml" />
+ <Include href="media_codecs_google_video.xml" />
+</MediaCodecs>
diff --git a/guest/hals/camera/media_codecs_performance.xml b/guest/hals/camera/media_codecs_performance.xml
new file mode 100644
index 0000000..f3eeb12
--- /dev/null
+++ b/guest/hals/camera/media_codecs_performance.xml
@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<!-- Copyright 2016 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.
+-->
+
+<!-- The 'range' values below are based on tests run 2016-04-13 on Ubuntu 14.04
+ x86-64, Xeon 2.8 GHz x 10
+
+ The range values are set to (meas / sqrt(tolerance)) and
+ (meas * sqrt(tolerance)).
+ These values maximize the 'success' window for the tests performed in
+ cts/libs/deviceutil/src/android/cts/util/MediaUtils.java.
+ That file defines 'tolerance' as sqrt(12.1).
+
+ Where multiple results were obtained, the geometric mean was used.
+
+ OMX.google.h264.encoder video/avc 320x 240 measured 1294.2
+ OMX.google.h264.decoder video/avc 320x 240 measured 7204.1, 9151.4
+
+ OMX.google.h263.encoder video/3gpp 176x 144 measured 2127.0
+ OMX.google.h263.decoder video/3gpp 176x 144 measured 7574.0
+
+ OMX.google.mpeg4.encoder video/mp4v-es 176x 144 measured 2783.8
+ OMX.google.mpeg4.decoder video/mp4v-es 176x 144 measured 6954.2
+
+ OMX.google.vp8.encoder video/x-vnd.on2.vp8 1280x 720 measured 195.0
+ OMX.google.vp8.encoder video/x-vnd.on2.vp8 1920x1080 measured 93.3, 91.1
+ OMX.google.vp8.decoder video/x-vnd.on2.vp8 1280x 720 measured 1196.1, 1211.3
+ OMX.google.vp8.decoder video/x-vnd.on2.vp8 1920x1080 measured 483.7, 497.6
+-->
+
+<MediaCodecs>
+ <Encoders>
+ <MediaCodec name="OMX.google.h264.encoder" type="video/avc" update="true">
+ <Limit name="measured-frame-rate-320x240" range="694-2414" />
+ </MediaCodec>
+ <MediaCodec name="OMX.google.h263.encoder" type="video/3gpp" update="true">
+ <Limit name="measured-frame-rate-176x144" range="1140-3967" />
+ </MediaCodec>
+ <MediaCodec name="OMX.google.mpeg4.encoder" type="video/mp4v-es" update="true">
+ <Limit name="measured-frame-rate-176x144" range="1493-5192" />
+ </MediaCodec>
+ <MediaCodec name="OMX.google.vp8.encoder" type="video/x-vnd.on2.vp8" update="true">
+ <Limit name="measured-frame-rate-1280x720" range="105-364" />
+ <Limit name="measured-frame-rate-1920x1080" range="49-172" />
+ </MediaCodec>
+ </Encoders>
+ <Decoders>
+ <MediaCodec name="OMX.google.h264.decoder" type="video/avc" update="true">
+ <Limit name="measured-frame-rate-320x240" range="4353-15114" />
+ </MediaCodec>
+ <MediaCodec name="OMX.google.h263.decoder" type="video/3gpp" update="true">
+ <Limit name="measured-frame-rate-176x144" range="4061-14126" />
+ </MediaCodec>
+ <MediaCodec name="OMX.google.hevc.decoder" type="video/hevc" update="true">
+ <Limit name="measured-frame-rate-352x288" range="1000-4000" />
+ <Limit name="measured-frame-rate-720x480" range="500-2000" />
+ <Limit name="measured-frame-rate-1280x720" range="100-1000" />
+ <Limit name="measured-frame-rate-1920x1080" range="50-700" />
+ </MediaCodec>
+ <MediaCodec name="OMX.google.mpeg4.decoder" type="video/mp4v-es" update="true">
+ <Limit name="measured-frame-rate-176x144" range="3729-12970" />
+ </MediaCodec>
+ <MediaCodec name="OMX.google.vp8.decoder" type="video/x-vnd.on2.vp8" update="true">
+ <Limit name="measured-frame-rate-1280x720" range="645-2245" />
+ <Limit name="measured-frame-rate-1920x1080" range="263-915" />
+ </MediaCodec>
+ <MediaCodec name="OMX.google.vp9.decoder" type="video/x-vnd.on2.vp9" update="true">
+ <Limit name="measured-frame-rate-320x240" range="645-2245" />
+ <Limit name="measured-frame-rate-640x360" range="500-3000" />
+ <Limit name="measured-frame-rate-1280x720" range="350-1500" />
+ <Limit name="measured-frame-rate-1920x1080" range="150-1000" />
+ </MediaCodec>
+ </Decoders>
+</MediaCodecs>
diff --git a/guest/hals/camera/media_profiles.xml b/guest/hals/camera/media_profiles.xml
new file mode 100644
index 0000000..cd99857
--- /dev/null
+++ b/guest/hals/camera/media_profiles.xml
@@ -0,0 +1,368 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<!DOCTYPE MediaSettings [
+<!ELEMENT MediaSettings (CamcorderProfiles,
+ EncoderOutputFileFormat+,
+ VideoEncoderCap+,
+ AudioEncoderCap+,
+ VideoDecoderCap,
+ AudioDecoderCap)>
+<!ELEMENT CamcorderProfiles (EncoderProfile+, ImageEncoding+, ImageDecoding, Camera)>
+<!ELEMENT EncoderProfile (Video, Audio)>
+<!ATTLIST EncoderProfile quality (high|low) #REQUIRED>
+<!ATTLIST EncoderProfile fileFormat (mp4|3gp) #REQUIRED>
+<!ATTLIST EncoderProfile duration (30|60) #REQUIRED>
+<!ATTLIST EncoderProfile cameraId (0|1) #REQUIRED>
+<!ELEMENT Video EMPTY>
+<!ATTLIST Video codec (h264|h263|m4v) #REQUIRED>
+<!ATTLIST Video bitRate CDATA #REQUIRED>
+<!ATTLIST Video width CDATA #REQUIRED>
+<!ATTLIST Video height CDATA #REQUIRED>
+<!ATTLIST Video frameRate CDATA #REQUIRED>
+<!ELEMENT Audio EMPTY>
+<!ATTLIST Audio codec (amrnb|amrwb|aac) #REQUIRED>
+<!ATTLIST Audio bitRate CDATA #REQUIRED>
+<!ATTLIST Audio sampleRate CDATA #REQUIRED>
+<!ATTLIST Audio channels (1|2) #REQUIRED>
+<!ELEMENT ImageEncoding EMPTY>
+<!ATTLIST ImageEncoding quality (90|80|70|60|50|40) #REQUIRED>
+<!ELEMENT ImageDecoding EMPTY>
+<!ATTLIST ImageDecoding memCap CDATA #REQUIRED>
+<!ELEMENT Camera EMPTY>
+<!ELEMENT EncoderOutputFileFormat EMPTY>
+<!ATTLIST EncoderOutputFileFormat name (mp4|3gp) #REQUIRED>
+<!ELEMENT VideoEncoderCap EMPTY>
+<!ATTLIST VideoEncoderCap name (h264|h263|m4v|wmv) #REQUIRED>
+<!ATTLIST VideoEncoderCap enabled (true|false) #REQUIRED>
+<!ATTLIST VideoEncoderCap minBitRate CDATA #REQUIRED>
+<!ATTLIST VideoEncoderCap maxBitRate CDATA #REQUIRED>
+<!ATTLIST VideoEncoderCap minFrameWidth CDATA #REQUIRED>
+<!ATTLIST VideoEncoderCap maxFrameWidth CDATA #REQUIRED>
+<!ATTLIST VideoEncoderCap minFrameHeight CDATA #REQUIRED>
+<!ATTLIST VideoEncoderCap maxFrameHeight CDATA #REQUIRED>
+<!ATTLIST VideoEncoderCap minFrameRate CDATA #REQUIRED>
+<!ATTLIST VideoEncoderCap maxFrameRate CDATA #REQUIRED>
+<!ELEMENT AudioEncoderCap EMPTY>
+<!ATTLIST AudioEncoderCap name (amrnb|amrwb|aac|wma) #REQUIRED>
+<!ATTLIST AudioEncoderCap enabled (true|false) #REQUIRED>
+<!ATTLIST AudioEncoderCap minBitRate CDATA #REQUIRED>
+<!ATTLIST AudioEncoderCap maxBitRate CDATA #REQUIRED>
+<!ATTLIST AudioEncoderCap minSampleRate CDATA #REQUIRED>
+<!ATTLIST AudioEncoderCap maxSampleRate CDATA #REQUIRED>
+<!ATTLIST AudioEncoderCap minChannels (1|2) #REQUIRED>
+<!ATTLIST AudioEncoderCap maxChannels (1|2) #REQUIRED>
+<!ELEMENT VideoDecoderCap EMPTY>
+<!ATTLIST VideoDecoderCap name (wmv) #REQUIRED>
+<!ATTLIST VideoDecoderCap enabled (true|false) #REQUIRED>
+<!ELEMENT AudioDecoderCap EMPTY>
+<!ATTLIST AudioDecoderCap name (wma) #REQUIRED>
+<!ATTLIST AudioDecoderCap enabled (true|false) #REQUIRED>
+]>
+<!--
+ This file is used to declare the multimedia profiles and capabilities
+ on an android-powered device.
+-->
+<MediaSettings>
+ <!-- Each camcorder profile defines a set of predefined configuration parameters -->
+ <CamcorderProfiles cameraId="0">
+
+ <EncoderProfile quality="qvga" fileFormat="mp4" duration="60">
+ <Video codec="m4v"
+ bitRate="128000"
+ width="320"
+ height="240"
+ frameRate="15" />
+ <Audio codec="amrnb"
+ bitRate="12200"
+ sampleRate="8000"
+ channels="1" />
+ </EncoderProfile>
+
+ <EncoderProfile quality="timelapseqcif" fileFormat="mp4" duration="30">
+ <Video codec="h264"
+ bitRate="192000"
+ width="176"
+ height="144"
+ frameRate="30" />
+ <!-- audio setting is ignored -->
+ <Audio codec="amrnb"
+ bitRate="12200"
+ sampleRate="8000"
+ channels="1" />
+ </EncoderProfile>
+
+ <ImageEncoding quality="95" />
+ <ImageEncoding quality="80" />
+ <ImageEncoding quality="70" />
+ <ImageDecoding memCap="20000000" />
+
+ </CamcorderProfiles>
+
+ <CamcorderProfiles cameraId="1">
+
+ <EncoderProfile quality="qvga" fileFormat="mp4" duration="60">
+ <Video codec="m4v"
+ bitRate="128000"
+ width="320"
+ height="240"
+ frameRate="15" />
+ <Audio codec="amrnb"
+ bitRate="12200"
+ sampleRate="8000"
+ channels="1" />
+ </EncoderProfile>
+
+ <EncoderProfile quality="timelapseqcif" fileFormat="mp4" duration="30">
+ <Video codec="h264"
+ bitRate="192000"
+ width="176"
+ height="144"
+ frameRate="30" />
+ <!-- audio setting is ignored -->
+ <Audio codec="amrnb"
+ bitRate="12200"
+ sampleRate="8000"
+ channels="1" />
+ </EncoderProfile>
+
+ <ImageEncoding quality="95" />
+ <ImageEncoding quality="80" />
+ <ImageEncoding quality="70" />
+ <ImageDecoding memCap="20000000" />
+
+ </CamcorderProfiles>
+
+ <CamcorderProfiles cameraId="2">
+
+ <EncoderProfile quality="qvga" fileFormat="mp4" duration="60">
+ <Video codec="m4v"
+ bitRate="128000"
+ width="320"
+ height="240"
+ frameRate="15" />
+ <Audio codec="amrnb"
+ bitRate="12200"
+ sampleRate="8000"
+ channels="1" />
+ </EncoderProfile>
+
+ <EncoderProfile quality="timelapseqcif" fileFormat="mp4" duration="30">
+ <Video codec="h264"
+ bitRate="192000"
+ width="176"
+ height="144"
+ frameRate="30" />
+ <!-- audio setting is ignored -->
+ <Audio codec="amrnb"
+ bitRate="12200"
+ sampleRate="8000"
+ channels="1" />
+ </EncoderProfile>
+
+ <ImageEncoding quality="95" />
+ <ImageEncoding quality="80" />
+ <ImageEncoding quality="70" />
+ <ImageDecoding memCap="20000000" />
+
+ </CamcorderProfiles>
+
+ <CamcorderProfiles cameraId="3">
+
+ <EncoderProfile quality="qvga" fileFormat="mp4" duration="60">
+ <Video codec="m4v"
+ bitRate="128000"
+ width="320"
+ height="240"
+ frameRate="15" />
+ <Audio codec="amrnb"
+ bitRate="12200"
+ sampleRate="8000"
+ channels="1" />
+ </EncoderProfile>
+
+ <EncoderProfile quality="timelapseqcif" fileFormat="mp4" duration="30">
+ <Video codec="h264"
+ bitRate="192000"
+ width="176"
+ height="144"
+ frameRate="30" />
+ <!-- audio setting is ignored -->
+ <Audio codec="amrnb"
+ bitRate="12200"
+ sampleRate="8000"
+ channels="1" />
+ </EncoderProfile>
+
+ <ImageEncoding quality="95" />
+ <ImageEncoding quality="80" />
+ <ImageEncoding quality="70" />
+ <ImageDecoding memCap="20000000" />
+
+ </CamcorderProfiles>
+
+ <CamcorderProfiles cameraId="4">
+
+ <EncoderProfile quality="qvga" fileFormat="mp4" duration="60">
+ <Video codec="m4v"
+ bitRate="128000"
+ width="320"
+ height="240"
+ frameRate="15" />
+ <Audio codec="amrnb"
+ bitRate="12200"
+ sampleRate="8000"
+ channels="1" />
+ </EncoderProfile>
+
+ <EncoderProfile quality="timelapseqcif" fileFormat="mp4" duration="30">
+ <Video codec="h264"
+ bitRate="192000"
+ width="176"
+ height="144"
+ frameRate="30" />
+ <!-- audio setting is ignored -->
+ <Audio codec="amrnb"
+ bitRate="12200"
+ sampleRate="8000"
+ channels="1" />
+ </EncoderProfile>
+
+ <ImageEncoding quality="95" />
+ <ImageEncoding quality="80" />
+ <ImageEncoding quality="70" />
+ <ImageDecoding memCap="20000000" />
+
+ </CamcorderProfiles>
+
+ <CamcorderProfiles cameraId="5">
+
+ <EncoderProfile quality="qvga" fileFormat="mp4" duration="60">
+ <Video codec="m4v"
+ bitRate="128000"
+ width="320"
+ height="240"
+ frameRate="15" />
+ <Audio codec="amrnb"
+ bitRate="12200"
+ sampleRate="8000"
+ channels="1" />
+ </EncoderProfile>
+
+ <EncoderProfile quality="timelapseqcif" fileFormat="mp4" duration="30">
+ <Video codec="h264"
+ bitRate="192000"
+ width="176"
+ height="144"
+ frameRate="30" />
+ <!-- audio setting is ignored -->
+ <Audio codec="amrnb"
+ bitRate="12200"
+ sampleRate="8000"
+ channels="1" />
+ </EncoderProfile>
+
+ <ImageEncoding quality="95" />
+ <ImageEncoding quality="80" />
+ <ImageEncoding quality="70" />
+ <ImageDecoding memCap="20000000" />
+
+ </CamcorderProfiles>
+
+ <CamcorderProfiles cameraId="6">
+
+ <EncoderProfile quality="qvga" fileFormat="mp4" duration="60">
+ <Video codec="m4v"
+ bitRate="128000"
+ width="320"
+ height="240"
+ frameRate="15" />
+ <Audio codec="amrnb"
+ bitRate="12200"
+ sampleRate="8000"
+ channels="1" />
+ </EncoderProfile>
+
+ <EncoderProfile quality="timelapseqcif" fileFormat="mp4" duration="30">
+ <Video codec="h264"
+ bitRate="192000"
+ width="176"
+ height="144"
+ frameRate="30" />
+ <!-- audio setting is ignored -->
+ <Audio codec="amrnb"
+ bitRate="12200"
+ sampleRate="8000"
+ channels="1" />
+ </EncoderProfile>
+
+ <ImageEncoding quality="95" />
+ <ImageEncoding quality="80" />
+ <ImageEncoding quality="70" />
+ <ImageDecoding memCap="20000000" />
+
+ </CamcorderProfiles>
+
+ <EncoderOutputFileFormat name="3gp" />
+ <EncoderOutputFileFormat name="mp4" />
+
+ <!--
+ If a codec is not enabled, it is invisible to the applications
+ In other words, the applications won't be able to use the codec
+ or query the capabilities of the codec at all if it is disabled
+ -->
+ <VideoEncoderCap name="h264" enabled="true"
+ minBitRate="64000" maxBitRate="192000"
+ minFrameWidth="176" maxFrameWidth="320"
+ minFrameHeight="144" maxFrameHeight="240"
+ minFrameRate="15" maxFrameRate="30" />
+
+ <VideoEncoderCap name="h263" enabled="true"
+ minBitRate="64000" maxBitRate="192000"
+ minFrameWidth="176" maxFrameWidth="320"
+ minFrameHeight="144" maxFrameHeight="240"
+ minFrameRate="15" maxFrameRate="30" />
+
+ <VideoEncoderCap name="m4v" enabled="true"
+ minBitRate="64000" maxBitRate="192000"
+ minFrameWidth="176" maxFrameWidth="320"
+ minFrameHeight="144" maxFrameHeight="240"
+ minFrameRate="15" maxFrameRate="30" />
+
+ <AudioEncoderCap name="aac" enabled="true"
+ minBitRate="8000" maxBitRate="96000"
+ minSampleRate="8000" maxSampleRate="48000"
+ minChannels="1" maxChannels="1" />
+
+ <AudioEncoderCap name="amrwb" enabled="true"
+ minBitRate="6600" maxBitRate="23050"
+ minSampleRate="16000" maxSampleRate="16000"
+ minChannels="1" maxChannels="1" />
+
+ <AudioEncoderCap name="amrnb" enabled="true"
+ minBitRate="5525" maxBitRate="12200"
+ minSampleRate="8000" maxSampleRate="8000"
+ minChannels="1" maxChannels="1" />
+
+ <!--
+ FIXME:
+ We do not check decoder capabilities at present
+ At present, we only check whether windows media is visible
+ for TEST applications. For other applications, we do
+ not perform any checks at all.
+ -->
+ <VideoDecoderCap name="wmv" enabled="false"/>
+ <AudioDecoderCap name="wma" enabled="false"/>
+</MediaSettings>
diff --git a/guest/hals/gps/Android.mk b/guest/hals/gps/Android.mk
new file mode 100644
index 0000000..46c2d30
--- /dev/null
+++ b/guest/hals/gps/Android.mk
@@ -0,0 +1,41 @@
+# Copyright (C) 2017 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.
+
+LOCAL_PATH := $(call my-dir)
+
+# HAL module implemenation, not prelinked and stored in
+# hw/<LIGHTS_HARDWARE_MODULE_ID>.<ro.hardware>.so
+include $(CLEAR_VARS)
+
+ifeq (0, $(shell test $(PLATFORM_SDK_VERSION) -ge 21; echo $$?))
+LOCAL_MODULE_RELATIVE_PATH := hw
+else
+LOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)/hw
+endif
+LOCAL_MULTILIB := first
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SHARED_LIBRARIES := liblog libcutils
+LOCAL_SRC_FILES := gps_vsoc.cpp gps_thread.cpp
+LOCAL_MODULE := gps.vsoc
+LOCAL_C_INCLUDES := device/google/cuttlefish_common
+
+LOCAL_CFLAGS := \
+ -Wall -Werror -Wno-missing-field-initializers \
+ -DLOG_TAG=\"VSoCGPS\" \
+ $(VSOC_VERSION_CFLAGS)
+LOCAL_VENDOR_MODULE := true
+
+include $(BUILD_SHARED_LIBRARY)
+
diff --git a/guest/hals/gps/gps_thread.cpp b/guest/hals/gps/gps_thread.cpp
new file mode 100644
index 0000000..c745d45
--- /dev/null
+++ b/guest/hals/gps/gps_thread.cpp
@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#include "guest/hals/gps/gps_thread.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <math.h>
+#include <pthread.h>
+#include <sys/epoll.h>
+#include <time.h>
+
+#include <cutils/log.h>
+#include <cutils/sockets.h>
+#include <hardware/gps.h>
+
+// Calls an callback function to pass received and parsed GPS data to Android.
+static void reader_call_callback(GpsDataReader* r) {
+ if (!r) {
+ ALOGW("%s: called with r=NULL", __FUNCTION__);
+ return;
+ }
+ if (!r->callback) {
+ ALOGW("%s: no callback registered; keeping the data to send later",
+ __FUNCTION__);
+ return;
+ }
+ if (!r->fix.flags) {
+ ALOGW("%s: no GPS fix", __FUNCTION__);
+ return;
+ }
+ // Always uses current time converted to UTC time in milliseconds.
+ time_t secs = time(NULL); // seconds from 01/01/1970.
+ r->fix.timestamp = (long long)secs * 1000;
+
+#if GPS_DEBUG
+ D("* Parsed GPS Data");
+ if (r->fix.flags & GPS_LOCATION_HAS_LAT_LONG) {
+ D(" - latitude = %g", r->fix.latitude);
+ D(" - longitude = %g", r->fix.longitude);
+ }
+ if (r->fix.flags & GPS_LOCATION_HAS_ALTITUDE)
+ D(" - altitude = %g", r->fix.altitude);
+ if (r->fix.flags & GPS_LOCATION_HAS_SPEED) D(" - speed = %g", r->fix.speed);
+ if (r->fix.flags & GPS_LOCATION_HAS_BEARING)
+ D(" - bearing = %g", r->fix.bearing);
+ if (r->fix.flags & GPS_LOCATION_HAS_ACCURACY)
+ D(" - accuracy = %g", r->fix.accuracy);
+ long long utc_secs = r->fix.timestamp / 1000;
+ struct tm utc;
+ gmtime_r((time_t*)&utc_secs, &utc);
+ D(" - time = %s", asctime(&utc));
+#endif
+
+ D("Sending fix to callback %p", r->callback);
+ r->callback(&r->fix);
+}
+
+// Parses data received so far and calls reader_call_callback().
+static void reader_parse_message(GpsDataReader* r) {
+ D("Received: '%s'", r->buffer);
+
+ int num_read = sscanf(r->buffer, "%lf,%lf,%lf,%f,%f,%f", &r->fix.longitude,
+ &r->fix.latitude, &r->fix.altitude, &r->fix.bearing,
+ &r->fix.speed, &r->fix.accuracy);
+ if (num_read != 6) {
+ ALOGE("Couldn't find 6 values from the received message %s.", r->buffer);
+ return;
+ }
+ r->fix.flags = DEFAULT_GPS_LOCATION_FLAG;
+ reader_call_callback(r);
+}
+
+// Accepts a newly received string & calls reader_parse_message if '\n' is seen.
+static void reader_accept_string(GpsDataReader* r, char* const str,
+ const int len) {
+ int index;
+ for (index = 0; index < len; index++) {
+ if (r->index >= (int)sizeof(r->buffer) - 1) {
+ if (str[index] == '\n') {
+ ALOGW("Message longer than buffer; new byte (%d) skipped.", str[index]);
+ r->index = 0;
+ }
+ } else {
+ r->buffer[r->index++] = str[index];
+ if (str[index] == '\n') {
+ r->buffer[r->index] = '\0';
+ reader_parse_message(r);
+ r->index = 0;
+ }
+ }
+ }
+}
+
+// GPS state threads which communicates with control and data sockets.
+void gps_state_thread(void* arg) {
+ GpsState* state = (GpsState*)arg;
+ GpsDataReader reader;
+ int epoll_fd = epoll_create(2);
+ int started = -1;
+ int gps_fd = state->fd;
+ int control_fd = state->control[1];
+
+ memset(&reader, 0, sizeof(reader));
+ reader.fix.size = sizeof(reader.fix);
+
+ epoll_register(epoll_fd, control_fd);
+ epoll_register(epoll_fd, gps_fd);
+
+ while (1) {
+ struct epoll_event events[2];
+ int nevents, event_index;
+
+ nevents = epoll_wait(epoll_fd, events, 2, 500);
+ D("Thread received %d events", nevents);
+ if (nevents < 0) {
+ if (errno != EINTR)
+ ALOGE("epoll_wait() unexpected error: %s", strerror(errno));
+ continue;
+ } else if (nevents == 0) {
+ if (started == 1) {
+ reader_call_callback(&reader);
+ }
+ continue;
+ }
+
+ for (event_index = 0; event_index < nevents; event_index++) {
+ if ((events[event_index].events & (EPOLLERR | EPOLLHUP)) != 0) {
+ ALOGE("EPOLLERR or EPOLLHUP after epoll_wait() !?");
+ goto Exit;
+ }
+
+ if ((events[event_index].events & EPOLLIN) != 0) {
+ int fd = events[event_index].data.fd;
+ if (fd == control_fd) {
+ unsigned char cmd = 255;
+ int ret;
+ do {
+ ret = read(fd, &cmd, 1);
+ } while (ret < 0 && errno == EINTR);
+
+ if (cmd == CMD_STOP || cmd == CMD_QUIT) {
+ if (started == 1) {
+ D("Thread stopping");
+ started = 0;
+ reader.callback = NULL;
+ }
+ if (cmd == CMD_QUIT) {
+ D("Thread quitting");
+ goto Exit;
+ }
+ } else if (cmd == CMD_START) {
+ if (started != 1) {
+ reader.callback = state->callbacks.location_cb;
+ D("Thread starting callback=%p", reader.callback);
+ reader_call_callback(&reader);
+ started = 1;
+ }
+ } else {
+ ALOGE("unknown control command %d", cmd);
+ }
+ } else if (fd == gps_fd) {
+ char buff[256];
+ int ret;
+ for (;;) {
+ ret = read(fd, buff, sizeof(buff));
+ if (ret < 0) {
+ if (errno == EINTR) continue;
+ if (errno != EWOULDBLOCK)
+ ALOGE("error while reading from gps daemon socket: %s:",
+ strerror(errno));
+ break;
+ }
+ D("Thread received %d bytes: %.*s", ret, ret, buff);
+ reader_accept_string(&reader, buff, ret);
+ }
+ } else {
+ ALOGE("epoll_wait() returned unknown fd %d.", fd);
+ }
+ }
+ }
+ }
+
+Exit:
+ epoll_deregister(epoll_fd, control_fd);
+ epoll_deregister(epoll_fd, gps_fd);
+}
diff --git a/guest/hals/gps/gps_thread.h b/guest/hals/gps/gps_thread.h
new file mode 100644
index 0000000..7cd3c9e
--- /dev/null
+++ b/guest/hals/gps/gps_thread.h
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#pragma once
+
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/epoll.h>
+
+#include <hardware/gps.h>
+
+#define GPS_DEBUG 0
+#define GPS_DATA_BUFFER_MAX_SIZE 256
+
+#define DEFAULT_GPS_LOCATION_FLAG \
+ (GPS_LOCATION_HAS_LAT_LONG | GPS_LOCATION_HAS_ALTITUDE | \
+ GPS_LOCATION_HAS_BEARING | GPS_LOCATION_HAS_SPEED | \
+ GPS_LOCATION_HAS_ACCURACY)
+
+#if GPS_DEBUG
+#define D(...) ALOGD(__VA_ARGS__)
+#else
+#define D(...) ((void)0)
+#endif
+
+// Control commands to GPS thread
+enum { CMD_QUIT = 0, CMD_START = 1, CMD_STOP = 2 };
+
+// GPS HAL's state
+typedef struct {
+ int init;
+ int fd;
+ int control[2];
+ pthread_t thread;
+ GpsCallbacks callbacks;
+} GpsState;
+
+typedef struct {
+ GpsLocation fix;
+ gps_location_callback callback;
+ char buffer[GPS_DATA_BUFFER_MAX_SIZE + 1];
+ int index;
+} GpsDataReader;
+
+void gps_state_thread(void* arg);
+
+static inline int epoll_register(int epoll_fd, int fd) {
+ fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK);
+
+ struct epoll_event ev;
+ ev.events = EPOLLIN;
+ ev.data.fd = fd;
+
+ int ret;
+ do {
+ ret = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ev);
+ } while (ret < 0 && errno == EINTR);
+ return ret;
+}
+
+static inline int epoll_deregister(int epoll_fd, int fd) {
+ int ret;
+ do {
+ ret = epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fd, NULL);
+ } while (ret < 0 && errno == EINTR);
+ return ret;
+}
diff --git a/guest/hals/gps/gps_vsoc.cpp b/guest/hals/gps/gps_vsoc.cpp
new file mode 100644
index 0000000..fde0e12
--- /dev/null
+++ b/guest/hals/gps/gps_vsoc.cpp
@@ -0,0 +1,257 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+/* This implements a GPS hardware HAL library for cuttlefish.
+ * A produced shared library is placed in /system/lib/hw/gps.gce.so, and
+ * loaded by hardware/libhardware/hardware.c code which is called from
+ * android_location_GpsLocationProvider.cpp
+ */
+
+#include <errno.h>
+#include <pthread.h>
+#include <stdint.h>
+
+#include <cutils/log.h>
+#include <cutils/sockets.h>
+#include <hardware/gps.h>
+
+#include "guest/hals/gps/gps_thread.h"
+#include "guest/libs/platform_support/api_level_fixes.h"
+
+static GpsState _gps_state;
+
+// Cleans up GpsState data structure.
+static void gps_state_cleanup(GpsState* s) {
+ char cmd = CMD_QUIT;
+
+ write(s->control[0], &cmd, 1);
+ if (s->thread > 0) {
+ pthread_join(s->thread, NULL);
+ }
+
+ close(s->control[0]);
+ close(s->control[1]);
+ close(s->fd);
+
+ s->thread = 0;
+ s->control[0] = -1;
+ s->control[1] = -1;
+ s->fd = -1;
+ s->init = 0;
+}
+
+static int gce_gps_init(GpsCallbacks* callbacks) {
+ D("%s: called", __FUNCTION__);
+ // Stop if the framework does not fulfill its interface contract.
+ // We don't want to return an error and continue to ensure that we
+ // catch framework breaks ASAP and to give a tombstone to track down the
+ // offending code.
+ LOG_ALWAYS_FATAL_IF(!callbacks->location_cb);
+ LOG_ALWAYS_FATAL_IF(!callbacks->status_cb);
+ LOG_ALWAYS_FATAL_IF(!callbacks->sv_status_cb);
+ LOG_ALWAYS_FATAL_IF(!callbacks->nmea_cb);
+ LOG_ALWAYS_FATAL_IF(!callbacks->set_capabilities_cb);
+ LOG_ALWAYS_FATAL_IF(!callbacks->acquire_wakelock_cb);
+ LOG_ALWAYS_FATAL_IF(!callbacks->release_wakelock_cb);
+ LOG_ALWAYS_FATAL_IF(!callbacks->create_thread_cb);
+ LOG_ALWAYS_FATAL_IF(!callbacks->request_utc_time_cb);
+ if (!_gps_state.init) {
+ _gps_state.init = 1;
+ _gps_state.control[0] = -1;
+ _gps_state.control[1] = -1;
+ _gps_state.thread = 0;
+
+ _gps_state.fd = socket_local_client(
+ "gps_broadcasts", ANDROID_SOCKET_NAMESPACE_ABSTRACT, SOCK_STREAM);
+ if (_gps_state.fd < 0) {
+ ALOGE("no GPS emulation detected.");
+ goto Fail;
+ }
+ D("GPS HAL will receive data from remoter via gps_broadcasts channel.");
+
+ if (socketpair(AF_LOCAL, SOCK_STREAM, 0, _gps_state.control) < 0) {
+ ALOGE("could not create thread control socket pair: %s", strerror(errno));
+ goto Fail;
+ }
+
+ _gps_state.callbacks = *callbacks;
+ ALOGE("Starting thread callback=%p", callbacks->location_cb);
+ _gps_state.thread = callbacks->create_thread_cb(
+ "gps_state_thread", gps_state_thread, &_gps_state);
+ if (!_gps_state.thread) {
+ ALOGE("could not create GPS thread: %s", strerror(errno));
+ goto Fail;
+ }
+ }
+
+ if (_gps_state.fd < 0) return -1;
+ return 0;
+
+Fail:
+ gps_state_cleanup(&_gps_state);
+ return -1;
+}
+
+static void gce_gps_cleanup() {
+ D("%s: called", __FUNCTION__);
+ if (_gps_state.init) gps_state_cleanup(&_gps_state);
+}
+
+static int gce_gps_start() {
+ if (!_gps_state.init) {
+ ALOGE("%s: called with uninitialized gps_state!", __FUNCTION__);
+ return -1;
+ }
+
+ char cmd = CMD_START;
+ int ret;
+ do {
+ ret = write(_gps_state.control[0], &cmd, 1);
+ } while (ret < 0 && errno == EINTR);
+
+ if (ret != 1) {
+ D("%s: could not send CMD_START command: ret=%d: %s", __FUNCTION__, ret,
+ strerror(errno));
+ return -1;
+ }
+
+ return 0;
+}
+
+static int gce_gps_stop() {
+ D("%s: called", __FUNCTION__);
+ if (!_gps_state.init) {
+ ALOGE("%s: called with uninitialized gps_state!", __FUNCTION__);
+ return -1;
+ }
+
+ char cmd = CMD_STOP;
+ int ret;
+
+ do {
+ ret = write(_gps_state.control[0], &cmd, 1);
+ } while (ret < 0 && errno == EINTR);
+
+ if (ret != 1) {
+ ALOGE("%s: could not send CMD_STOP command: ret=%d: %s", __FUNCTION__, ret,
+ strerror(errno));
+ return -1;
+ }
+ return 0;
+}
+
+static int gce_gps_inject_time(GpsUtcTime /*time*/, int64_t /*time_ref*/,
+ int /*uncertainty*/) {
+ D("%s: called", __FUNCTION__);
+ if (!_gps_state.init) {
+ ALOGE("%s: called with uninitialized gps_state!", __FUNCTION__);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int gce_gps_inject_location(double /*latitude*/, double /*longitude*/,
+ float /*accuracy*/) {
+ D("%s: called", __FUNCTION__);
+ if (!_gps_state.init) {
+ ALOGE("%s: called with uninitialized gps_state!", __FUNCTION__);
+ return -1;
+ }
+
+ return 0;
+}
+
+static void gce_gps_delete_aiding_data(GpsAidingData /*flags*/) {
+ D("%s: called", __FUNCTION__);
+ if (!_gps_state.init) {
+ ALOGE("%s: called with uninitialized gps_state!", __FUNCTION__);
+ return;
+ }
+}
+
+static int gce_gps_set_position_mode(GpsPositionMode mode,
+ GpsPositionRecurrence recurrence,
+ uint32_t min_interval,
+ uint32_t preferred_accuracy,
+ uint32_t preferred_time) {
+ D("%s: called", __FUNCTION__);
+ if (!_gps_state.init) {
+ ALOGE("%s: called with uninitialized gps_state!", __FUNCTION__);
+ return -1;
+ }
+ ALOGE("%s(mode=%d, recurrence=%d, min_interval=%" PRIu32
+ ", "
+ "preferred_accuracy=%" PRIu32 ", preferred_time=%" PRIu32
+ ") unimplemented",
+ __FUNCTION__, mode, recurrence, min_interval, preferred_accuracy,
+ preferred_time);
+ return 0;
+}
+
+static const void* gce_gps_get_extension(const char* name) {
+ D("%s: called", __FUNCTION__);
+ // It is normal for this to be called before init.
+ ALOGE("%s(%s): called but not implemented.", __FUNCTION__,
+ name ? name : "NULL");
+ return NULL;
+}
+
+static const GpsInterface gceGpsInterface = {
+ sizeof(GpsInterface),
+ gce_gps_init,
+ gce_gps_start,
+ gce_gps_stop,
+ gce_gps_cleanup,
+ gce_gps_inject_time,
+ gce_gps_inject_location,
+ gce_gps_delete_aiding_data,
+ gce_gps_set_position_mode,
+ gce_gps_get_extension,
+};
+
+const GpsInterface* gps_get_gps_interface(struct gps_device_t* /*dev*/) {
+ return &gceGpsInterface;
+}
+
+static int open_gps(const struct hw_module_t* module, char const* /*name*/,
+ struct hw_device_t** device) {
+ struct gps_device_t* dev =
+ (struct gps_device_t*)malloc(sizeof(struct gps_device_t));
+ LOG_FATAL_IF(!dev, "%s: malloc returned NULL.", __FUNCTION__);
+ memset(dev, 0, sizeof(*dev));
+
+ dev->common.tag = HARDWARE_DEVICE_TAG;
+ dev->common.version = 0;
+ dev->common.module = (struct hw_module_t*)module;
+ dev->get_gps_interface = gps_get_gps_interface;
+
+ *device = (struct hw_device_t*)dev;
+ return 0;
+}
+
+static struct hw_module_methods_t gps_module_methods = {
+ VSOC_STATIC_INITIALIZER(open) open_gps};
+
+struct hw_module_t HAL_MODULE_INFO_SYM = {
+ VSOC_STATIC_INITIALIZER(tag) HARDWARE_MODULE_TAG,
+ VSOC_STATIC_INITIALIZER(version_major) 1,
+ VSOC_STATIC_INITIALIZER(version_minor) 0,
+ VSOC_STATIC_INITIALIZER(id) GPS_HARDWARE_MODULE_ID,
+ VSOC_STATIC_INITIALIZER(name) "GCE GPS Module",
+ VSOC_STATIC_INITIALIZER(author) "The Android Open Source Project",
+ VSOC_STATIC_INITIALIZER(methods) & gps_module_methods,
+};
diff --git a/guest/hals/gralloc/Android.mk b/guest/hals/gralloc/Android.mk
new file mode 100644
index 0000000..5e4a648
--- /dev/null
+++ b/guest/hals/gralloc/Android.mk
@@ -0,0 +1,59 @@
+# Copyright (C) 2017 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.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := gralloc.vsoc-future
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := \
+ gralloc.cpp \
+ mapper.cpp
+
+LOCAL_C_INCLUDES += \
+ device/google/cuttlefish_common \
+ device/google/cuttlefish_kernel \
+ hardware/libhardware/include \
+ system/core/base/include
+
+LOCAL_CFLAGS := \
+ -DLOG_TAG=\"gralloc_vsoc\" \
+ -Wno-missing-field-initializers \
+ -Wall -Werror
+
+LOCAL_SHARED_LIBRARIES := \
+ libbase \
+ libcutils \
+ cuttlefish_auto_resources \
+ libcuttlefish_fs \
+ liblog \
+ vsoc_lib
+
+ifeq (0, $(shell test $(PLATFORM_SDK_VERSION) -ge 21; echo $$?))
+LOCAL_MODULE_RELATIVE_PATH := hw
+else
+LOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)/hw
+endif
+
+LOCAL_VENDOR_MODULE := true
+
+# See b/67109557
+ifeq (true, $(TARGET_TRANSLATE_2ND_ARCH))
+LOCAL_MULTILIB := first
+endif
+include $(BUILD_SHARED_LIBRARY)
+
+include $(call first-makefiles-under,$(LOCAL_PATH))
diff --git a/guest/hals/gralloc/gralloc.cpp b/guest/hals/gralloc/gralloc.cpp
new file mode 100644
index 0000000..32bcc07
--- /dev/null
+++ b/guest/hals/gralloc/gralloc.cpp
@@ -0,0 +1,310 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include "gralloc_vsoc_priv.h"
+#include <hardware/gralloc.h>
+#include <hardware/hardware.h>
+#include <log/log.h>
+#include <stdlib.h>
+
+namespace {
+
+static const int kSwiftShaderPadding = 4;
+
+inline void formatToYcbcr(
+ int format, int width, int height, void* base_v, android_ycbcr* ycbcr) {
+ uintptr_t it = reinterpret_cast<uintptr_t>(base_v);
+ // Clear reserved fields;
+ memset(ycbcr, 0, sizeof(*ycbcr));
+ switch (format) {
+ case HAL_PIXEL_FORMAT_YV12:
+ case HAL_PIXEL_FORMAT_YCbCr_420_888:
+ ycbcr->ystride = align(width, 16);
+ ycbcr->cstride = align(ycbcr->ystride / 2, 16);
+ ycbcr->chroma_step = 1;
+ ycbcr->y = reinterpret_cast<void*>(it);
+ it += ycbcr->ystride * height;
+ ycbcr->cr = reinterpret_cast<void*>(it);
+ it += ycbcr->cstride * height / 2;
+ ycbcr->cb = reinterpret_cast<void*>(it);
+ break;
+ default:
+ ALOGE("%s: can't deal with format=0x%x", __FUNCTION__, format);
+ }
+}
+
+inline int formatToBytesPerPixel(int format) {
+ switch (format) {
+ case HAL_PIXEL_FORMAT_RGBA_8888:
+ case HAL_PIXEL_FORMAT_RGBX_8888:
+ case HAL_PIXEL_FORMAT_BGRA_8888:
+ // The camera 3.0 implementation assumes that IMPLEMENTATION_DEFINED
+ // means HAL_PIXEL_FORMAT_RGBA_8888
+ case HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED:
+ return 4;
+ case HAL_PIXEL_FORMAT_RGB_888:
+ return 3;
+ case HAL_PIXEL_FORMAT_RGB_565:
+ case HAL_PIXEL_FORMAT_YV12:
+ case HAL_PIXEL_FORMAT_YCbCr_420_888:
+ return 2;
+ case HAL_PIXEL_FORMAT_BLOB:
+ return 1;
+ default:
+ ALOGE("%s: unknown format=%d", __FUNCTION__, format);
+ return 4;
+ }
+}
+
+inline int formatToBytesPerFrame(int format, int w, int h) {
+ int bytes_per_pixel = formatToBytesPerPixel(format);
+ int w16, h16;
+ int y_size, c_size;
+
+ switch (format) {
+ // BLOB is used to allocate buffers for JPEG formatted data. Bytes per pixel
+ // is 1, the desired buffer size is in w, and h should be 1. We refrain from
+ // adding additional padding, although the caller is likely to round
+ // up to a page size.
+ case HAL_PIXEL_FORMAT_BLOB:
+ return bytes_per_pixel * w * h;
+ case HAL_PIXEL_FORMAT_YV12:
+ case HAL_PIXEL_FORMAT_YCbCr_420_888:
+ android_ycbcr strides;
+ formatToYcbcr(format, w, h, NULL, &strides);
+ y_size = strides.ystride * h;
+ c_size = strides.cstride * h / 2;
+ return (y_size + 2 * c_size + kSwiftShaderPadding);
+ /*case HAL_PIXEL_FORMAT_RGBA_8888:
+ case HAL_PIXEL_FORMAT_RGBX_8888:
+ case HAL_PIXEL_FORMAT_BGRA_8888:
+ case HAL_PIXEL_FORMAT_RGB_888:
+ case HAL_PIXEL_FORMAT_RGB_565:*/
+ default:
+ w16 = align(w, 16);
+ h16 = align(h, 16);
+ return bytes_per_pixel * w16 * h16 + kSwiftShaderPadding;
+ }
+}
+
+}
+
+/******************************************************************************/
+
+void dump(struct alloc_device_t */*dev*/, char */*buff*/, int /*buff_len*/) {}
+
+/******************************************************************************/
+
+int lock(struct gralloc_module_t const* /*module*/,
+ buffer_handle_t handle,
+ int /*usage*/,
+ int /*l*/,
+ int /*t*/,
+ int /*w*/,
+ int /*h*/,
+ void** vaddr) {
+ if (!vaddr || vsoc_buffer_handle_t::validate(handle)) {
+ return -EINVAL;
+ }
+ // TODO(jemoreira): Check allocation usage flags against requested usage.
+ const vsoc_buffer_handle_t* hnd =
+ reinterpret_cast<const vsoc_buffer_handle_t*>(handle);
+ void* mapped = reference_buffer(hnd);
+ if (mapped == NULL) {
+ ALOGE("Unable to reference buffer, %s", __FUNCTION__);
+ return -1;
+ }
+ *vaddr = mapped;
+ return 0;
+}
+
+int unlock(struct gralloc_module_t const* /*module*/, buffer_handle_t handle) {
+ if (vsoc_buffer_handle_t::validate(handle)) {
+ return -EINVAL;
+ }
+ return unreference_buffer(
+ reinterpret_cast<const vsoc_buffer_handle_t*>(handle));
+}
+
+int lock_ycbcr(struct gralloc_module_t const* module,
+ buffer_handle_t handle,
+ int usage,
+ int l,
+ int t,
+ int w,
+ int h,
+ struct android_ycbcr* ycbcr) {
+ void* mapped;
+ int retval = lock(module, handle, usage, l, t, w, h, &mapped);
+ if (retval) {
+ return retval;
+ }
+ const vsoc_buffer_handle_t* hnd =
+ reinterpret_cast<const vsoc_buffer_handle_t*>(handle);
+ formatToYcbcr(hnd->format, w, h, mapped, ycbcr);
+ return 0;
+}
+
+/******************************************************************************/
+
+static int gralloc_alloc(
+ alloc_device_t* dev, int w, int h, int format, int /*usage*/,
+ buffer_handle_t* pHandle, int* pStrideInPixels) {
+ int fd = -1;
+
+ int bytes_per_pixel = formatToBytesPerPixel(format);
+ int bytes_per_line;
+ int stride_in_pixels;
+ int size = 0;
+ uint32_t offset = 0;
+ // SwiftShader can't handle RGB_888, so fail fast and hard if we try to create
+ // a gralloc buffer in this format.
+ ALOG_ASSERT(format != HAL_PIXEL_FORMAT_RGB_888);
+ if (format == HAL_PIXEL_FORMAT_YV12) {
+ bytes_per_line = align(bytes_per_pixel * w, 16);
+ } else {
+ bytes_per_line = align(bytes_per_pixel * w, 8);
+ }
+ size = align(size + formatToBytesPerFrame(format, w, h), PAGE_SIZE);
+ size += PAGE_SIZE;
+ fd = reinterpret_cast<vsoc_alloc_device_t*>(dev)
+ ->gralloc_region->AllocateBuffer(size, &offset);
+ if (fd < 0) {
+ ALOGE("Unable to allocate buffer (%s)", strerror(-fd));
+ return fd;
+ }
+
+ stride_in_pixels = bytes_per_line / bytes_per_pixel;
+ vsoc_buffer_handle_t* hnd = new vsoc_buffer_handle_t(fd,
+ offset,
+ size,
+ format,
+ w, h,
+ stride_in_pixels);
+ void* addr =
+ reference_buffer(reinterpret_cast<const vsoc_buffer_handle_t*>(hnd));
+ if (!addr) {
+ ALOGE("Unable to reference buffer, %s", __FUNCTION__);
+ return -EIO;
+ }
+
+ *pHandle = hnd;
+ *pStrideInPixels = stride_in_pixels;
+
+ return 0;
+}
+
+static int gralloc_free(alloc_device_t* /*dev*/, buffer_handle_t handle) {
+ // No need to do anything else, the buffer will be atomatically deallocated
+ // when the handle is closed.
+ return unreference_buffer(
+ reinterpret_cast<const vsoc_buffer_handle_t*>(handle));
+}
+
+static int register_buffer(struct gralloc_module_t const* /*module*/,
+ buffer_handle_t handle) {
+ if (vsoc_buffer_handle_t::validate(handle)) {
+ return -EINVAL;
+ }
+ void* addr =
+ reference_buffer(reinterpret_cast<const vsoc_buffer_handle_t*>(handle));
+ if (!addr) {
+ ALOGE("Unable to reference buffer, %s", __FUNCTION__);
+ return -EIO;
+ }
+ return 0;
+}
+
+int unregister_buffer(struct gralloc_module_t const* /*module*/,
+ buffer_handle_t handle) {
+ if (vsoc_buffer_handle_t::validate(handle)) {
+ return -EINVAL;
+ }
+ return unreference_buffer(
+ reinterpret_cast<const vsoc_buffer_handle_t*>(handle));
+}
+
+/******************************************************************************/
+
+static int gralloc_device_close(struct hw_device_t *dev) {
+ vsoc_alloc_device_t* pdev = reinterpret_cast<vsoc_alloc_device_t*>(dev);
+ if (pdev) {
+ free(pdev);
+ }
+ return 0;
+}
+
+static int gralloc_device_open(
+ const hw_module_t* module, const char* name, hw_device_t** device) {
+ int status = -EINVAL;
+ if (!strcmp(name, GRALLOC_HARDWARE_GPU0)) {
+ vsoc_alloc_device_t *dev;
+ dev = (vsoc_alloc_device_t*) malloc(sizeof(*dev));
+ LOG_FATAL_IF(!dev, "%s: malloc returned NULL.", __FUNCTION__);
+
+ /* initialize our state here */
+ memset(dev, 0, sizeof(*dev));
+
+ /* initialize the procs */
+ dev->device.common.tag = HARDWARE_DEVICE_TAG;
+ dev->device.common.version = 0; // TODO(jemoreira): Bump to 0_2 when stable
+ dev->device.common.module = const_cast<hw_module_t*>(module);
+ dev->device.common.close = gralloc_device_close;
+
+ dev->device.alloc = gralloc_alloc;
+ dev->device.free = gralloc_free;
+
+ dev->gralloc_region = vsoc::gralloc::GrallocRegionView::GetInstance();
+ if (!dev->gralloc_region) {
+ LOG_FATAL("Unable to instantiate the gralloc region");
+ free(dev);
+ return -EIO;
+ }
+
+ *device = &dev->device.common;
+ status = 0;
+ }
+ // TODO(jemoreira): Consider opening other type of devices (framebuffer)
+ return status;
+}
+
+/******************************************************************************/
+
+static struct hw_module_methods_t gralloc_module_methods = {
+ .open = gralloc_device_open
+};
+
+struct vsoc_gralloc_module_t HAL_MODULE_INFO_SYM = {
+ .base = {
+ .common = {
+ .tag = HARDWARE_MODULE_TAG,
+ .version_major = GRALLOC_MODULE_API_VERSION_0_2,
+ .version_minor = 0,
+ .id = GRALLOC_HARDWARE_MODULE_ID,
+ .name = "VSoC X86 Graphics Memory Allocator Module",
+ .author = "The Android Open Source Project",
+ .methods = &gralloc_module_methods,
+ .dso = NULL,
+ .reserved = {0},
+ },
+ .registerBuffer = register_buffer,
+ .unregisterBuffer = unregister_buffer,
+ .lock = lock,
+ .unlock = unlock,
+ .lock_ycbcr = lock_ycbcr,
+ .perform = NULL,
+ },
+};
diff --git a/guest/hals/gralloc/gralloc_vsoc_priv.h b/guest/hals/gralloc/gralloc_vsoc_priv.h
new file mode 100644
index 0000000..2f4f287
--- /dev/null
+++ b/guest/hals/gralloc/gralloc_vsoc_priv.h
@@ -0,0 +1,102 @@
+#pragma once
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include <cutils/native_handle.h>
+#include <hardware/gralloc.h>
+#include <log/log.h>
+
+#include "guest/vsoc/lib/gralloc_region_view.h"
+
+struct vsoc_alloc_device_t {
+ alloc_device_t device;
+ vsoc::gralloc::GrallocRegionView* gralloc_region;
+};
+
+struct vsoc_gralloc_module_t {
+ gralloc_module_t base;
+};
+
+static_assert(sizeof(int) >= 4, "At least 4 bytes are needed for offsets");
+
+struct vsoc_buffer_handle_t : public native_handle {
+ // File descriptors
+ int fd;
+ // ints
+ int magic;
+ int format;
+ int x_res;
+ int y_res;
+ int stride_in_pixels;
+ int size;
+ // buffer offset in bytes divided by PAGE_SIZE
+ int offset;
+
+ static inline int sNumInts() {
+ return ((sizeof(vsoc_buffer_handle_t) - sizeof(native_handle_t)) /
+ sizeof(int) -
+ sNumFds);
+ }
+ static const int sNumFds = 1;
+ static const int sMagic = 0xc63752f4;
+
+ vsoc_buffer_handle_t(int fd,
+ int offset,
+ int size,
+ int format,
+ int x_res,
+ int y_res,
+ int stride_in_pixels)
+ : fd(fd),
+ magic(sMagic),
+ format(format),
+ x_res(x_res),
+ y_res(y_res),
+ stride_in_pixels(stride_in_pixels),
+ size(size),
+ offset(offset) {
+ version = sizeof(native_handle);
+ numInts = sNumInts();
+ numFds = sNumFds;
+ }
+
+ ~vsoc_buffer_handle_t() {
+ magic = 0;
+ }
+
+ static int validate(const native_handle* handle) {
+ const vsoc_buffer_handle_t* hnd =
+ reinterpret_cast<const vsoc_buffer_handle_t*>(handle);
+ if (!hnd || hnd->version != sizeof(native_handle) ||
+ hnd->numInts != sNumInts() || hnd->numFds != sNumFds ||
+ hnd->magic != sMagic) {
+ ALOGE("Invalid gralloc handle (at %p)", handle);
+ return -EINVAL;
+ }
+ return 0;
+ }
+};
+
+// These functions are to be used to map/unmap gralloc buffers. They are thread
+// safe and ensure that the same buffer never gets mapped twice.
+void* reference_buffer(const vsoc_buffer_handle_t* hnd);
+int unreference_buffer(const vsoc_buffer_handle_t* hnd);
+
+// TODO(jemoreira): Move this to a place where it can be used by the gralloc
+// region as well.
+inline int align(int input, int alignment) {
+ return (input + alignment - 1) & -alignment;
+}
diff --git a/guest/hals/gralloc/legacy/Android.mk b/guest/hals/gralloc/legacy/Android.mk
new file mode 100644
index 0000000..96ac256
--- /dev/null
+++ b/guest/hals/gralloc/legacy/Android.mk
@@ -0,0 +1,52 @@
+# Copyright (C) 2016 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.
+
+# Temporary, should be removed once vsoc hals are in usable state
+
+LOCAL_PATH := $(call my-dir)
+
+VSOC_GRALLOC_COMMON_SRC_FILES := \
+ gralloc.cpp \
+ framebuffer.cpp \
+ mapper.cpp
+
+VSOC_GRALLOC_COMMON_CFLAGS:= \
+ -DLOG_TAG=\"gralloc_vsoc_legacy\" \
+ -Wno-missing-field-initializers \
+ -Wall -Werror \
+ $(VSOC_VERSION_CFLAGS)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := gralloc.vsoc
+LOCAL_MODULE_RELATIVE_PATH := hw
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := $(VSOC_GRALLOC_COMMON_SRC_FILES)
+
+LOCAL_CFLAGS := $(VSOC_GRALLOC_COMMON_CFLAGS)
+LOCAL_C_INCLUDES := device/google/cuttlefish_common
+LOCAL_SHARED_LIBRARIES := \
+ liblog \
+ libutils \
+ libcutils \
+ libvsocframebuffer
+
+LOCAL_VENDOR_MODULE := true
+
+# See b/67109557
+ifeq (true, $(TARGET_TRANSLATE_2ND_ARCH))
+LOCAL_MULTILIB := first
+endif
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/guest/hals/gralloc/legacy/framebuffer.cpp b/guest/hals/gralloc/legacy/framebuffer.cpp
new file mode 100644
index 0000000..4251433
--- /dev/null
+++ b/guest/hals/gralloc/legacy/framebuffer.cpp
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+#include <sys/mman.h>
+
+#include <dlfcn.h>
+
+#include <cutils/ashmem.h>
+#include <cutils/log.h>
+#include <cutils/properties.h>
+
+#include <sys/system_properties.h>
+
+#include <hardware/hardware.h>
+#include <hardware/gralloc.h>
+
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/ioctl.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include <cutils/atomic.h>
+#include <private/android_filesystem_config.h>
+
+#if defined(__ANDROID__)
+#include <linux/fb.h>
+#endif
+
+#include "gralloc_vsoc_priv.h"
+
+#include <guest/libs/legacy_framebuffer/vsoc_framebuffer.h>
+#include <guest/libs/legacy_framebuffer/vsoc_framebuffer_control.h>
+#include <guest/libs/legacy_framebuffer/RegionRegistry.h>
+#include "common/libs/auto_resources/auto_resources.h"
+#include "common/libs/threads/cuttlefish_thread.h"
+
+/*****************************************************************************/
+
+struct fb_context_t {
+ framebuffer_device_t device;
+};
+
+/*****************************************************************************/
+
+static int fb_setSwapInterval(struct framebuffer_device_t* dev, int interval) {
+ if (interval < dev->minSwapInterval || interval > dev->maxSwapInterval) {
+ return -EINVAL;
+ }
+ // FIXME: implement fb_setSwapInterval
+ return 0;
+}
+
+/*
+ * These functions (and probably the entire framebuffer device) are most likely
+ * not used when the hardware composer device is present, however is hard to be
+ * 100% sure.
+ */
+static int fb_setUpdateRect(
+ struct framebuffer_device_t* dev __unused, int l, int t, int w, int h) {
+ if (((w|h) <= 0) || ((l|t)<0)) {
+ return -EINVAL;
+ }
+ // TODO(jemoreira): Find a way to broadcast this with the framebuffer control.
+ return 0;
+}
+
+static int fb_post(struct framebuffer_device_t* dev __unused, buffer_handle_t buffer) {
+ const int yoffset = YOffsetFromHandle(buffer);
+ if (yoffset >= 0) {
+ int retval =
+ VSoCFrameBufferControl::getInstance().BroadcastFrameBufferChanged(
+ yoffset);
+ if (retval) ALOGI("Failed to post framebuffer");
+
+ return retval;
+ }
+ return -1;
+}
+
+/*****************************************************************************/
+
+int initUserspaceFrameBuffer(struct private_module_t* module) {
+ cvd::LockGuard<pthread_mutex_t> guard(module->lock);
+ if (module->framebuffer) {
+ return 0;
+ }
+
+ int fd;
+ if (!VSoCFrameBuffer::OpenFrameBuffer(&fd)) {
+ return -errno;
+ }
+
+ const VSoCFrameBuffer& config = VSoCFrameBuffer::getInstance();
+
+ /*
+ * map the framebuffer
+ */
+ module->framebuffer =
+ new private_handle_t(fd,
+ config.total_buffer_size(),
+ config.hal_format(),
+ config.x_res(),
+ config.y_res(),
+ config.line_length() / (config.bits_per_pixel() / 8),
+ private_handle_t::PRIV_FLAGS_FRAMEBUFFER);
+ reference_region("framebuffer_init", module->framebuffer);
+
+ return 0;
+}
+
+
+/*****************************************************************************/
+
+static int fb_close(struct hw_device_t *dev) {
+ fb_context_t* ctx = (fb_context_t*)dev;
+ if (ctx) {
+ free(ctx);
+ }
+ return 0;
+}
+
+int fb_device_open(
+ hw_module_t const* module, const char* name, hw_device_t** device) {
+ int status = -EINVAL;
+ if (!strcmp(name, GRALLOC_HARDWARE_FB0)) {
+ /* initialize our state here */
+ fb_context_t* dev = (fb_context_t*) malloc(sizeof(*dev));
+ LOG_FATAL_IF(!dev, "%s: malloc returned NULL.", __FUNCTION__);
+ memset(dev, 0, sizeof(*dev));
+
+ /* initialize the procs */
+ dev->device.common.tag = HARDWARE_DEVICE_TAG;
+ dev->device.common.version = 0;
+ dev->device.common.module = const_cast<hw_module_t*>(module);
+ dev->device.common.close = fb_close;
+ dev->device.setSwapInterval = fb_setSwapInterval;
+ dev->device.post = fb_post;
+ dev->device.setUpdateRect = fb_setUpdateRect;
+
+ private_module_t* m = (private_module_t*)module;
+
+ status = initUserspaceFrameBuffer(m);
+
+ const VSoCFrameBuffer& config = VSoCFrameBuffer::getInstance();
+
+ if (status >= 0) {
+ int stride = config.line_length() / (config.bits_per_pixel() / 8);
+ int format = config.hal_format();
+ const_cast<uint32_t&>(dev->device.flags) = 0;
+ const_cast<uint32_t&>(dev->device.width) = config.x_res();
+ const_cast<uint32_t&>(dev->device.height) = config.y_res();
+ const_cast<int&>(dev->device.stride) = stride;
+ const_cast<int&>(dev->device.format) = format;
+ const_cast<float&>(dev->device.xdpi) = config.dpi();
+ const_cast<float&>(dev->device.ydpi) = config.dpi();
+ // TODO (jemoreira): DRY!! Managed by the vsync thread in the hwcomposer
+ const_cast<float&>(dev->device.fps) = (60 * 1000) / 1000.0f;
+ const_cast<int&>(dev->device.minSwapInterval) = 1;
+ const_cast<int&>(dev->device.maxSwapInterval) = 1;
+ *device = &dev->device.common;
+ }
+ }
+ return status;
+}
diff --git a/guest/hals/gralloc/legacy/gralloc.cpp b/guest/hals/gralloc/legacy/gralloc.cpp
new file mode 100644
index 0000000..e2c4104
--- /dev/null
+++ b/guest/hals/gralloc/legacy/gralloc.cpp
@@ -0,0 +1,304 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+#include <limits.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <pthread.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/ioctl.h>
+
+#include <cutils/ashmem.h>
+#include <cutils/log.h>
+#include <cutils/atomic.h>
+#include <utils/String8.h>
+
+#include <hardware/hardware.h>
+#include <hardware/gralloc.h>
+
+#include <guest/libs/platform_support/api_level_fixes.h>
+
+#include "guest/libs/legacy_framebuffer/vsoc_framebuffer.h"
+#include "guest/libs/legacy_framebuffer/vsoc_framebuffer_control.h"
+#include "guest/libs/legacy_framebuffer/RegionRegistry.h"
+#include "gralloc_vsoc_priv.h"
+#include "common/libs/auto_resources/auto_resources.h"
+
+/*****************************************************************************/
+
+static int gralloc_alloc_buffer(
+ alloc_device_t* /*dev*/, int format, int w, int h,
+ buffer_handle_t* pHandle, int* pStrideInPixels) {
+ int err = 0;
+ int fd = -1;
+ static int sequence = 0;
+
+ int bytes_per_pixel = formatToBytesPerPixel(format);
+ int bytes_per_line;
+ int stride_in_pixels;
+ int size = 0;
+ // SwiftShader can't handle RGB_888, so fail fast and hard if we try to create
+ // a gralloc buffer in this format.
+ ALOG_ASSERT(format != HAL_PIXEL_FORMAT_RGB_888);
+ if (format == HAL_PIXEL_FORMAT_YV12) {
+ bytes_per_line = VSoCFrameBuffer::align(bytes_per_pixel * w, 16);
+ } else {
+ bytes_per_line = VSoCFrameBuffer::align(bytes_per_pixel * w);
+ }
+ size = roundUpToPageSize(size + formatToBytesPerFrame(format, w, h));
+ size += PAGE_SIZE;
+ fd = ashmem_create_region(
+ android::String8::format(
+ "gralloc-%d.%d", getpid(), sequence++).string(),
+ size);
+ if (fd < 0) {
+ ALOGE("couldn't create ashmem (%s)", strerror(-errno));
+ err = -errno;
+ }
+
+ if (err == 0) {
+ stride_in_pixels = bytes_per_line / bytes_per_pixel;
+ private_handle_t* hnd =
+ new private_handle_t(fd, size, format, w, h, stride_in_pixels, 0);
+ void* base = reference_region(__FUNCTION__, hnd);
+ if (base) {
+ *pHandle = hnd;
+ *pStrideInPixels = stride_in_pixels;
+ } else {
+ err = -EIO;
+ }
+ }
+
+ ALOGE_IF(err, "gralloc failed err=%s", strerror(-err));
+
+ return err;
+}
+
+/*****************************************************************************/
+
+static int ensure_framebuffer_allocated(private_module_t* m) {
+ // allocate the framebuffer
+ if (m->framebuffer == NULL) {
+ // The framebuffer is mapped once and forever.
+ int err = initUserspaceFrameBuffer(m);
+ if (err < 0) {
+ ALOGE("Failed to map framebuffer (%d)", errno);
+ return err;
+ }
+ }
+ return 0;
+}
+
+static int gralloc_free_framebuffer(private_handle_t const * hnd) {
+ static const VSoCFrameBuffer& config = VSoCFrameBuffer::getInstance();
+ static VSoCFrameBufferControl& fb_control =
+ VSoCFrameBufferControl::getInstance();
+
+ // free this buffer
+ const size_t bufferSize = config.line_length() * config.y_res();
+ int index = hnd->frame_offset / bufferSize;
+ return fb_control.UnsetBufferBits(1LU << index);
+}
+
+// Allocates a framebuffer taken from the set of buffers given by buffer_mask.
+static int gralloc_alloc_framebuffer(alloc_device_t* dev,
+ buffer_handle_t* pHandle,
+ int* pStrideInPixels,
+ uint32_t buffer_mask) {
+ static const VSoCFrameBuffer& config = VSoCFrameBuffer::getInstance();
+ static VSoCFrameBufferControl& fb_control =
+ VSoCFrameBufferControl::getInstance();
+
+ private_module_t* m = reinterpret_cast<private_module_t*>(
+ dev->common.module);
+
+ ensure_framebuffer_allocated(m);
+
+ const uint32_t numBuffers = VSoCFrameBuffer::kNumBuffers;
+ const size_t bufferSize = config.bufferSize();
+
+ // Paranoia: Force the mask to be valid
+ buffer_mask &= (1LU << numBuffers) - 1;
+
+ uint32_t bit = fb_control.GetAndSetNextAvailableBufferBit(buffer_mask);
+ if (!bit) {
+ // All buffers in the mask have been allocated already or there was another
+ // error
+ return -ENOMEM;
+ }
+
+ int frame_offset = 0;
+ while (bit != 1LU) {
+ bit >>= 1;
+ frame_offset += bufferSize;
+ }
+
+ int stride_in_pixels = config.line_length() / (config.bits_per_pixel() / 8);
+
+ // create a "fake" handle for it
+ private_handle_t* hnd = new private_handle_t(
+ dup(m->framebuffer->fd), config.total_buffer_size(),
+ config.hal_format(), config.x_res(), config.y_res(),
+ stride_in_pixels, private_handle_t::PRIV_FLAGS_FRAMEBUFFER, frame_offset);
+
+ *pHandle = hnd;
+ if (pStrideInPixels) {
+ *pStrideInPixels = stride_in_pixels;
+ }
+
+ return 0;
+}
+
+static int gralloc_alloc_sf_framebuffer(alloc_device_t* dev,
+ buffer_handle_t* pHandle,
+ int* pStrideInPixels) {
+ uint32_t mask = (1LU << VSoCFrameBuffer::kNumSfBuffers) - 1LU;
+ // Skip the first buffers since those are for HWC usage
+ mask <<= VSoCFrameBuffer::kNumHwcBuffers;
+ return gralloc_alloc_framebuffer(dev, pHandle, pStrideInPixels, mask);
+}
+
+static int gralloc_alloc_hwc_framebuffer(alloc_device_t* dev,
+ buffer_handle_t* pHandle) {
+ // Use the first kNumHwcBuffers for hwcomposer
+ uint32_t mask = (1LU << VSoCFrameBuffer::kNumHwcBuffers) - 1LU;
+ return gralloc_alloc_framebuffer(dev, pHandle, NULL, mask);
+}
+
+/*****************************************************************************/
+
+static int gralloc_alloc(
+ alloc_device_t* dev, int w, int h, int format, int usage,
+ buffer_handle_t* pHandle, int* pStrideInPixels) {
+ if (!pHandle || !pStrideInPixels)
+ return -EINVAL;
+
+ int err;
+ if (usage & GRALLOC_USAGE_HW_FB) {
+ err = gralloc_alloc_sf_framebuffer(dev, pHandle, pStrideInPixels);
+ } else {
+ err = gralloc_alloc_buffer(dev, format, w, h, pHandle, pStrideInPixels);
+ }
+
+ if (err < 0) {
+ return err;
+ }
+ return 0;
+}
+
+static int gralloc_free(alloc_device_t* /*dev*/, buffer_handle_t handle) {
+ if (private_handle_t::validate(handle) < 0) {
+ return -EINVAL;
+ }
+
+ int retval = 0;
+
+ private_handle_t const* hnd = reinterpret_cast<private_handle_t const*>(
+ handle);
+ if (hnd->flags & private_handle_t::PRIV_FLAGS_FRAMEBUFFER) {
+ retval = gralloc_free_framebuffer(hnd);
+ } else {
+ retval = unreference_region(__FUNCTION__, hnd);
+ }
+
+ close(hnd->fd);
+ delete hnd;
+ return retval;
+}
+
+/*****************************************************************************/
+
+static int gralloc_close(struct hw_device_t *dev) {
+ priv_alloc_device_t* ctx = reinterpret_cast<priv_alloc_device_t*>(dev);
+ if (ctx) {
+ /* TODO: keep a list of all buffer_handle_t created, and free them
+ * all here.
+ */
+ free(ctx);
+ }
+ return 0;
+}
+
+static int gralloc_device_open(
+ const hw_module_t* module, const char* name, hw_device_t** device) {
+ int status = -EINVAL;
+ if (!strcmp(name, GRALLOC_HARDWARE_GPU0)) {
+ priv_alloc_device_t *dev;
+ dev = (priv_alloc_device_t*) malloc(sizeof(*dev));
+ LOG_FATAL_IF(!dev, "%s: malloc returned NULL.", __FUNCTION__);
+
+ /* initialize our state here */
+ memset(dev, 0, sizeof(*dev));
+
+ /* initialize the procs */
+ dev->device.common.tag = HARDWARE_DEVICE_TAG;
+ dev->device.common.version = 0;
+ dev->device.common.module = const_cast<hw_module_t*>(module);
+ dev->device.common.close = gralloc_close;
+
+ dev->device.alloc = gralloc_alloc;
+ dev->device.free = gralloc_free;
+ dev->alloc_hwc_framebuffer = gralloc_alloc_hwc_framebuffer;
+
+
+ *device = &dev->device.common;
+ status = 0;
+ } else {
+ status = fb_device_open(module, name, device);
+ }
+ return status;
+}
+
+/*****************************************************************************/
+
+static struct hw_module_methods_t gralloc_module_methods = {
+ VSOC_STATIC_INITIALIZER(open) gralloc_device_open
+};
+
+struct private_module_t HAL_MODULE_INFO_SYM = {
+ VSOC_STATIC_INITIALIZER(base) {
+ VSOC_STATIC_INITIALIZER(common) {
+ VSOC_STATIC_INITIALIZER(tag) HARDWARE_MODULE_TAG,
+#ifdef GRALLOC_MODULE_API_VERSION_0_2
+ VSOC_STATIC_INITIALIZER(version_major) GRALLOC_MODULE_API_VERSION_0_2,
+#else
+ VSOC_STATIC_INITIALIZER(version_major) 1,
+#endif
+ VSOC_STATIC_INITIALIZER(version_minor) 0,
+ VSOC_STATIC_INITIALIZER(id) GRALLOC_HARDWARE_MODULE_ID,
+ VSOC_STATIC_INITIALIZER(name) "VSOC X86 Graphics Memory Allocator Module",
+ VSOC_STATIC_INITIALIZER(author) "The Android Open Source Project",
+ VSOC_STATIC_INITIALIZER(methods) &gralloc_module_methods,
+ VSOC_STATIC_INITIALIZER(dso) NULL,
+ VSOC_STATIC_INITIALIZER(reserved) {0},
+ },
+ VSOC_STATIC_INITIALIZER(registerBuffer) gralloc_register_buffer,
+ VSOC_STATIC_INITIALIZER(unregisterBuffer) gralloc_unregister_buffer,
+ VSOC_STATIC_INITIALIZER(lock) gralloc_lock,
+ VSOC_STATIC_INITIALIZER(unlock) gralloc_unlock,
+#ifdef GRALLOC_MODULE_API_VERSION_0_2
+ VSOC_STATIC_INITIALIZER(perform) NULL,
+ VSOC_STATIC_INITIALIZER(lock_ycbcr) gralloc_lock_ycbcr,
+#endif
+ },
+ VSOC_STATIC_INITIALIZER(framebuffer) 0,
+ VSOC_STATIC_INITIALIZER(lock) PTHREAD_MUTEX_INITIALIZER,
+};
diff --git a/guest/hals/gralloc/legacy/gralloc_vsoc_priv.h b/guest/hals/gralloc/legacy/gralloc_vsoc_priv.h
new file mode 100644
index 0000000..0fc2fd8
--- /dev/null
+++ b/guest/hals/gralloc/legacy/gralloc_vsoc_priv.h
@@ -0,0 +1,264 @@
+#pragma once
+/*
+ * Copyright (C) 2016 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.
+ */
+#include <stdint.h>
+#include <limits.h>
+#include <sys/cdefs.h>
+#include <sys/mman.h>
+#include <hardware/gralloc.h>
+#include <errno.h>
+#include <unistd.h>
+
+#include <cutils/native_handle.h>
+#include <cutils/log.h>
+
+#include <linux/fb.h>
+
+#include "guest/libs/legacy_framebuffer/vsoc_framebuffer.h"
+#include "guest/libs/platform_support/api_level_fixes.h"
+
+#ifndef GRALLOC_MODULE_API_VERSION_0_2
+// This structure will be defined in later releases of Android. Declare it
+// here to allow us to structure the code well.
+struct android_ycbcr {
+ void* y;
+ void* cb;
+ void* cr;
+ size_t ystride;
+ size_t cstride;
+ size_t chroma_step;
+ uint32_t reserved[8];
+};
+#endif
+
+/*****************************************************************************/
+
+struct private_handle_t;
+
+struct private_module_t {
+ gralloc_module_t base;
+
+ private_handle_t* framebuffer;
+ pthread_mutex_t lock;
+};
+
+int initUserspaceFrameBuffer(struct private_module_t* module);
+
+/*****************************************************************************/
+
+struct priv_alloc_device_t {
+ alloc_device_t device;
+ // Creates handles for the hwcomposer-specific framebuffers
+ int (*alloc_hwc_framebuffer)(alloc_device_t* m,
+ buffer_handle_t* handle);
+};
+
+/*****************************************************************************/
+
+struct private_handle_t : public native_handle {
+ enum {
+ PRIV_FLAGS_FRAMEBUFFER = 0x00000001
+ };
+
+ // file-descriptors
+ int fd;
+ // ints
+ int magic;
+ int flags;
+ int format;
+ int x_res;
+ int y_res;
+ int stride_in_pixels;
+ // Use to indicate which frame we're using.
+ int frame_offset;
+ int total_size;
+ int lock_level;
+
+ static inline int sNumInts() {
+ return (((sizeof(private_handle_t) - sizeof(native_handle_t))/sizeof(int)) - sNumFds);
+ }
+ static const int sNumFds = 1;
+ static const int sMagic = 0x3141592;
+
+ private_handle_t(int fd, int size, int format, int x_res, int y_res,
+ int stride_in_pixels, int flags, int frame_offset = 0)
+ : fd(fd),
+ magic(sMagic),
+ flags(flags),
+ format(format),
+ x_res(x_res),
+ y_res(y_res),
+ stride_in_pixels(stride_in_pixels),
+ frame_offset(frame_offset),
+ total_size(size),
+ lock_level(0) {
+ version = sizeof(native_handle);
+ numInts = sNumInts();
+ numFds = sNumFds;
+ }
+
+ ~private_handle_t() {
+ magic = 0;
+ }
+
+ static int validate(const native_handle* h) {
+ const private_handle_t* hnd = (const private_handle_t*)h;
+ if (!h || h->version != sizeof(native_handle) ||
+ h->numInts != sNumInts() || h->numFds != sNumFds ||
+ hnd->magic != sMagic) {
+ ALOGE("invalid gralloc handle (at %p)", h);
+ return -EINVAL;
+ }
+ return 0;
+ }
+};
+
+
+static inline int formatToBytesPerPixel(int format) {
+ switch (format) {
+ case HAL_PIXEL_FORMAT_RGBA_8888:
+ case HAL_PIXEL_FORMAT_RGBX_8888:
+ case HAL_PIXEL_FORMAT_BGRA_8888:
+#if VSOC_PLATFORM_SDK_AFTER(J)
+ // The camera 3.0 implementation assumes that IMPLEMENTATION_DEFINED
+ // means HAL_PIXEL_FORMAT_RGBA_8888
+ case HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED:
+#endif
+ return 4;
+ case HAL_PIXEL_FORMAT_RGB_888:
+ return 3;
+ case HAL_PIXEL_FORMAT_RGB_565:
+ case HAL_PIXEL_FORMAT_YV12:
+#ifdef GRALLOC_MODULE_API_VERSION_0_2
+ case HAL_PIXEL_FORMAT_YCbCr_420_888:
+#endif
+ return 2;
+#if VSOC_PLATFORM_SDK_AFTER(J)
+ case HAL_PIXEL_FORMAT_BLOB:
+ return 1;
+#endif
+ default:
+ ALOGE("%s: unknown format=%d", __FUNCTION__, format);
+ return 4;
+ }
+}
+
+static inline void formatToYcbcr(
+ int format, int width, int height, void* base_v, android_ycbcr* out) {
+ char* it = static_cast<char*>(base_v);
+ // Clear reserved fields;
+ memset(out, 0, sizeof(*out));
+ switch (format) {
+ case HAL_PIXEL_FORMAT_YV12:
+#ifdef GRALLOC_MODULE_API_VERSION_0_2
+ case HAL_PIXEL_FORMAT_YCbCr_420_888:
+#endif
+ out->ystride = VSoCFrameBuffer::align(width, 16);
+ out->cstride = VSoCFrameBuffer::align(out->ystride / 2, 16);
+ out->chroma_step = 1;
+ out->y = it;
+ it += out->ystride * height;
+ out->cr = it;
+ it += out->cstride * height / 2;
+ out->cb = it;
+ break;
+ default:
+ ALOGE("%s: can't deal with format=0x%x (%s)",
+ __FUNCTION__, format, pixel_format_to_string(format));
+ }
+}
+
+static inline int formatToBytesPerFrame(int format, int w, int h) {
+ int bytes_per_pixel = formatToBytesPerPixel(format);
+ int w16, h16;
+ int y_size, c_size;
+
+ switch (format) {
+#if VSOC_PLATFORM_SDK_AFTER(J)
+ // BLOB is used to allocate buffers for JPEG formatted data. Bytes per pixel
+ // is 1, the desired buffer size is in w, and h should be 1. We refrain from
+ // adding additional padding, although the caller is likely to round
+ // up to a page size.
+ case HAL_PIXEL_FORMAT_BLOB:
+ return bytes_per_pixel * w * h;
+#endif
+ case HAL_PIXEL_FORMAT_YV12:
+#ifdef GRALLOC_MODULE_API_VERSION_0_2
+ case HAL_PIXEL_FORMAT_YCbCr_420_888:
+#endif
+ android_ycbcr strides;
+ formatToYcbcr(format, w, h, NULL, &strides);
+ y_size = strides.ystride * h;
+ c_size = strides.cstride * h / 2;
+ return (y_size + 2 * c_size + VSoCFrameBuffer::kSwiftShaderPadding);
+ /*case HAL_PIXEL_FORMAT_RGBA_8888:
+ case HAL_PIXEL_FORMAT_RGBX_8888:
+ case HAL_PIXEL_FORMAT_BGRA_8888:
+ case HAL_PIXEL_FORMAT_RGB_888:
+ case HAL_PIXEL_FORMAT_RGB_565:*/
+ default:
+ w16 = VSoCFrameBuffer::align(w, 16);
+ h16 = VSoCFrameBuffer::align(h, 16);
+ return bytes_per_pixel * w16 * h16 + VSoCFrameBuffer::kSwiftShaderPadding;
+ }
+}
+
+// Calculates the yoffset from a framebuffer handle. I checks the given handle
+// for errors first. Returns the yoffset (non negative integer) or -1 if there
+// is an error.
+static inline int YOffsetFromHandle(buffer_handle_t buffer_hnd) {
+ if (!buffer_hnd) {
+ ALOGE("Attempt to post null buffer");
+ return -1;
+ }
+ if (private_handle_t::validate(buffer_hnd) < 0) {
+ ALOGE("Attempt to post non-vsoc handle");
+ return -1;
+ }
+ const private_handle_t* hnd =
+ reinterpret_cast<private_handle_t const*>(buffer_hnd);
+ if (!(hnd->flags & private_handle_t::PRIV_FLAGS_FRAMEBUFFER)) {
+ ALOGE("Attempt to post non-framebuffer");
+ return -1;
+ }
+
+ const VSoCFrameBuffer& config = VSoCFrameBuffer::getInstance();
+ return hnd->frame_offset / config.line_length();
+}
+
+int fb_device_open(
+ const hw_module_t* module, const char* name, hw_device_t** device);
+
+int gralloc_lock(
+ gralloc_module_t const* module,
+ buffer_handle_t handle, int usage,
+ int l, int t, int w, int h,
+ void** vaddr);
+
+int gralloc_unlock(
+ gralloc_module_t const* module, buffer_handle_t handle);
+
+int gralloc_register_buffer(
+ gralloc_module_t const* module, buffer_handle_t handle);
+
+int gralloc_unregister_buffer(
+ gralloc_module_t const* module, buffer_handle_t handle);
+
+int gralloc_lock_ycbcr(
+ struct gralloc_module_t const* module,
+ buffer_handle_t handle, int usage,
+ int l, int t, int w, int h,
+ struct android_ycbcr *ycbcr);
diff --git a/guest/hals/gralloc/legacy/mapper.cpp b/guest/hals/gralloc/legacy/mapper.cpp
new file mode 100644
index 0000000..8339df9
--- /dev/null
+++ b/guest/hals/gralloc/legacy/mapper.cpp
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+#include <limits.h>
+#include <errno.h>
+#include <pthread.h>
+#include <unistd.h>
+#include <string.h>
+
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <cutils/hashmap.h>
+#include <cutils/log.h>
+#include <cutils/atomic.h>
+
+#include <hardware/hardware.h>
+#include <hardware/gralloc.h>
+#include <system/graphics.h>
+
+#include "guest/libs/legacy_framebuffer/RegionRegistry.h"
+
+#include "gralloc_vsoc_priv.h"
+#include "guest/libs/remoter/remoter_framework_pkt.h"
+
+#define DEBUG_REFERENCES 1
+#define DEBUG_MAX_LOCK_LEVEL 20
+
+/*****************************************************************************/
+
+int gralloc_register_buffer(gralloc_module_t const* /*module*/,
+ buffer_handle_t handle) {
+ if (private_handle_t::validate(handle) < 0) {
+ return -EINVAL;
+ }
+
+ private_handle_t* hnd = (private_handle_t*)handle;
+ if (reference_region(__FUNCTION__, hnd)) {
+ return 0;
+ } else {
+ return -EIO;
+ }
+}
+
+int gralloc_unregister_buffer(gralloc_module_t const* /*module*/,
+ buffer_handle_t handle) {
+ if (private_handle_t::validate(handle) < 0) {
+ return -EINVAL;
+ }
+ private_handle_t* hnd = (private_handle_t*)handle;
+ return unreference_region("gralloc_unregister_buffer", hnd);
+}
+
+int gralloc_lock(
+ gralloc_module_t const* /*module*/, buffer_handle_t handle, int /*usage*/,
+ int /*l*/, int /*t*/, int /*w*/, int /*h*/,
+ void** vaddr) {
+ if (private_handle_t::validate(handle) < 0) {
+ return -EINVAL;
+ }
+ if (!vaddr) {
+ return -EINVAL;
+ }
+ private_handle_t* hnd = (private_handle_t*)handle;
+#if DEBUG_REFERENCES
+ if (hnd->lock_level > DEBUG_MAX_LOCK_LEVEL) {
+ LOG_FATAL("%s: unbalanced lock detected. lock level = %d",
+ __FUNCTION__, hnd->lock_level);
+ }
+ ++hnd->lock_level;
+#endif
+ void* base = reference_region("gralloc_lock", hnd);
+ *vaddr = reinterpret_cast<unsigned char*>(base)
+ + hnd->frame_offset;
+ return 0;
+}
+
+int gralloc_unlock(
+ gralloc_module_t const* /*module*/, buffer_handle_t handle) {
+ if (private_handle_t::validate(handle) < 0) {
+ return -EINVAL;
+ }
+ private_handle_t* hnd = (private_handle_t*) handle;
+#if DEBUG_REFERENCES
+ if (hnd->lock_level <= 0) {
+ LOG_FATAL("%s unbalanced unlock detected. lock level = %d",
+ __FUNCTION__, hnd->lock_level);
+ }
+ --hnd->lock_level;
+#endif
+ unreference_region("gralloc_unlock", hnd);
+ return 0;
+}
+
+int gralloc_lock_ycbcr(
+ gralloc_module_t const* /*module*/, buffer_handle_t handle, int /*usage*/,
+ int /*l*/, int /*t*/, int /*w*/, int /*h*/,
+ struct android_ycbcr* ycbcr) {
+ if (private_handle_t::validate(handle) < 0) {
+ return -EINVAL;
+ }
+ private_handle_t* hnd = (private_handle_t*)handle;
+#if DEBUG_REFERENCES
+ if (hnd->lock_level > DEBUG_MAX_LOCK_LEVEL) {
+ LOG_FATAL("%s: unbalanced lock detected. lock level = %d",
+ __FUNCTION__, hnd->lock_level);
+ }
+ ++hnd->lock_level;
+#endif
+ void* base = reference_region("gralloc_lock_ycbcr", hnd);
+ formatToYcbcr(hnd->format, hnd->x_res, hnd->y_res, base, ycbcr);
+ return 0;
+}
diff --git a/guest/hals/gralloc/mapper.cpp b/guest/hals/gralloc/mapper.cpp
new file mode 100644
index 0000000..f8a06fb
--- /dev/null
+++ b/guest/hals/gralloc/mapper.cpp
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include "gralloc_vsoc_priv.h"
+
+#include <cutils/hashmap.h>
+#include <hardware/gralloc.h>
+#include <hardware/hardware.h>
+#include <log/log.h>
+#include <string.h>
+#include <sys/mman.h>
+
+namespace {
+
+struct HmLockGuard {
+ HmLockGuard(Hashmap* map) : map_(map) {
+ hashmapLock(map_);
+ }
+ ~HmLockGuard() {
+ hashmapUnlock(map_);
+ }
+ private:
+ Hashmap* map_;
+};
+
+int offset_hash(void* key) {
+ return *reinterpret_cast<int*>(key);
+}
+
+bool offset_equals(void* key1, void* key2) {
+ return *reinterpret_cast<int*>(key1) ==
+ *reinterpret_cast<int*>(key2);
+}
+
+// Keeps track of how many times a buffer is locked in the current process.
+struct GrallocBuffer {
+ void* vaddr;
+ int ref_count;
+ GrallocBuffer() : vaddr(NULL), ref_count(0) {}
+
+ static Hashmap* mapped_buffers() {
+ static Hashmap* mapped_buffers =
+ hashmapCreate(19, offset_hash, offset_equals);
+ return mapped_buffers;
+ }
+};
+
+}
+
+void* reference_buffer(const vsoc_buffer_handle_t* hnd) {
+ Hashmap* map = GrallocBuffer::mapped_buffers();
+ HmLockGuard lock_guard(map);
+ GrallocBuffer* buffer = reinterpret_cast<GrallocBuffer*>(
+ hashmapGet(map, const_cast<int*>(&hnd->offset)));
+ if (!buffer) {
+ buffer = new GrallocBuffer();
+ hashmapPut(map, const_cast<int*>(&hnd->offset), buffer);
+ }
+
+ if (!buffer->vaddr) {
+ void* mapped =
+ mmap(NULL, hnd->size, PROT_READ | PROT_WRITE, MAP_SHARED, hnd->fd, 0);
+ if (mapped == MAP_FAILED) {
+ ALOGE("Unable to map buffer (offset: %d, size: %d): %s",
+ hnd->offset,
+ hnd->size,
+ strerror(errno));
+ return NULL;
+ }
+ // Set up the guard pages. The last page is always a guard
+ uintptr_t base = uintptr_t(mapped);
+ uintptr_t addr = base + hnd->size - PAGE_SIZE;
+ if (mprotect((void*)addr, PAGE_SIZE, PROT_NONE) == -1) {
+ ALOGW("Unable to protect last page of buffer (offset: %d, size: %d): %s",
+ hnd->offset,
+ hnd->size,
+ strerror(errno));
+ }
+ buffer->vaddr = mapped;
+ }
+ buffer->ref_count++;
+ return buffer->vaddr;
+}
+
+int unreference_buffer(const vsoc_buffer_handle_t* hnd) {
+ int result = 0;
+ Hashmap* map = GrallocBuffer::mapped_buffers();
+ HmLockGuard lock_guard(map);
+ GrallocBuffer* buffer = reinterpret_cast<GrallocBuffer*>(
+ hashmapGet(map, const_cast<int*>(&hnd->offset)));
+ if (!buffer) {
+ ALOGE("Unreferencing an unknown buffer (offset: %d, size: %d)",
+ hnd->offset,
+ hnd->size);
+ return -EINVAL;
+ }
+ if (buffer->ref_count == 0) {
+ ALOGE("Unbalanced reference/unreference on buffer (offset: %d, size: %d)",
+ hnd->offset,
+ hnd->size);
+ return -EINVAL;
+ }
+ buffer->ref_count--;
+ if (buffer->ref_count == 0) {
+ result = munmap(buffer->vaddr, hnd->size);
+ if (result) {
+ ALOGE("Unable to unmap buffer (offset: %d, size: %d): %s",
+ hnd->offset,
+ hnd->size,
+ strerror(errno));
+ }
+ buffer->vaddr = NULL;
+ }
+ return result;
+}
diff --git a/guest/hals/hwcomposer/Android.mk b/guest/hals/hwcomposer/Android.mk
new file mode 100644
index 0000000..e1312f1
--- /dev/null
+++ b/guest/hals/hwcomposer/Android.mk
@@ -0,0 +1,55 @@
+# Copyright (C) 2017 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.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := hwcomposer.vsoc-future
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := \
+ hwcomposer.cpp
+
+LOCAL_C_INCLUDES += \
+ device/google/cuttlefish_kernel \
+ device/google/cuttlefish_common \
+ device/google/cuttlefish_common/guest/hals/hwcomposer \
+ hardware/libhardware/include \
+ system/core/base/include
+
+LOCAL_CFLAGS := \
+ -DLOG_TAG=\"hwcomposer_vsoc\" \
+ -Wno-missing-field-initializers \
+ -Wall -Werror
+
+LOCAL_SHARED_LIBRARIES := \
+ libbase \
+ liblog \
+ vsoc_lib
+
+ifeq (0, $(shell test $(PLATFORM_SDK_VERSION) -ge 21; echo $$?))
+LOCAL_MODULE_RELATIVE_PATH := hw
+else
+LOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)/hw
+endif
+
+LOCAL_VENDOR_MODULE := true
+# See b/67109557
+ifeq (true, $(TARGET_TRANSLATE_2ND_ARCH))
+LOCAL_MULTILIB := first
+endif
+include $(BUILD_SHARED_LIBRARY)
+
+include $(call first-makefiles-under,$(LOCAL_PATH))
diff --git a/guest/hals/hwcomposer/hwcomposer.cpp b/guest/hals/hwcomposer/hwcomposer.cpp
new file mode 100644
index 0000000..125b510
--- /dev/null
+++ b/guest/hals/hwcomposer/hwcomposer.cpp
@@ -0,0 +1,378 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include <pthread.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+
+#include <hardware/hwcomposer.h>
+#include <hardware/hwcomposer_defs.h>
+#include <log/log.h>
+
+#include "common/vsoc/lib/fb_bcast_region_view.h"
+#include "guest/hals/gralloc/gralloc_vsoc_priv.h"
+
+// This file contains just a skeleton hwcomposer, the first step in the
+// multisided vsoc hwcomposer for cuttlefish.
+
+using vsoc::framebuffer::FBBroadcastRegionView;
+
+// TODO(jemoreira): FBBroadcastRegionView may belong in the HWC region
+
+FBBroadcastRegionView* GetFBBroadcastRegionView() {
+ static FBBroadcastRegionView instance;
+ return &instance;
+}
+
+namespace {
+
+// Ensures that the layer does not include any inconsistencies
+int SanityCheckLayer(const hwc_layer_1& layer) {
+ // Check displayFrame
+ if (layer.displayFrame.left > layer.displayFrame.right ||
+ layer.displayFrame.top > layer.displayFrame.bottom) {
+ ALOGE(
+ "%s: Malformed rectangle (displayFrame): [left = %d, right = %d, top = "
+ "%d, bottom = %d]",
+ __FUNCTION__,
+ layer.displayFrame.left,
+ layer.displayFrame.right,
+ layer.displayFrame.top,
+ layer.displayFrame.bottom);
+ return -EINVAL;
+ }
+ // Check sourceCrop
+ if (layer.sourceCrop.left > layer.sourceCrop.right ||
+ layer.sourceCrop.top > layer.sourceCrop.bottom) {
+ ALOGE(
+ "%s: Malformed rectangle (sourceCrop): [left = %d, right = %d, top = "
+ "%d, bottom = %d]",
+ __FUNCTION__,
+ layer.sourceCrop.left,
+ layer.sourceCrop.right,
+ layer.sourceCrop.top,
+ layer.sourceCrop.bottom);
+ return -EINVAL;
+ }
+ const vsoc_buffer_handle_t* p_handle =
+ reinterpret_cast<const vsoc_buffer_handle_t*>(layer.handle);
+ if (!p_handle) {
+ ALOGE("Layer has a NULL buffer handle");
+ return -EINVAL;
+ }
+ if (layer.sourceCrop.left < 0 || layer.sourceCrop.top < 0 ||
+ layer.sourceCrop.right > p_handle->x_res ||
+ layer.sourceCrop.bottom > p_handle->y_res) {
+ ALOGE(
+ "%s: Invalid sourceCrop for buffer handle: sourceCrop = [left = %d, "
+ "right = %d, top = %d, bottom = %d], handle = [width = %d, height = "
+ "%d]",
+ __FUNCTION__,
+ layer.sourceCrop.left,
+ layer.sourceCrop.right,
+ layer.sourceCrop.top,
+ layer.sourceCrop.bottom,
+ p_handle->x_res,
+ p_handle->y_res);
+ return -EINVAL;
+ }
+ return 0;
+}
+
+struct vsoc_hwc_device {
+ hwc_composer_device_1_t base;
+ const hwc_procs_t* procs;
+ pthread_t vsync_thread;
+ int64_t vsync_base_timestamp;
+ int32_t vsync_period_ns;
+ FBBroadcastRegionView* fb_broadcast;
+ uint32_t frame_num;
+};
+
+void* vsync_thread(void* arg) {
+ vsoc_hwc_device* pdev = reinterpret_cast<vsoc_hwc_device*>(arg);
+ setpriority(PRIO_PROCESS, 0, HAL_PRIORITY_URGENT_DISPLAY);
+
+ int64_t base_timestamp = pdev->vsync_base_timestamp;
+ int64_t last_logged = base_timestamp / 1e9;
+ int sent = 0;
+ int last_sent = 0;
+ static const int log_interval = 60;
+ while (true) {
+ struct timespec rt;
+ if (clock_gettime(CLOCK_MONOTONIC, &rt) == -1) {
+ ALOGE("%s:%d error in vsync thread clock_gettime: %s", __FILE__, __LINE__,
+ strerror(errno));
+ }
+ int64_t timestamp = int64_t(rt.tv_sec) * 1e9 + rt.tv_nsec;
+ // Given now's timestamp calculate the time of the next vsync.
+ timestamp += pdev->vsync_period_ns -
+ (timestamp - base_timestamp) % pdev->vsync_period_ns;
+
+ rt.tv_sec = timestamp / 1e9;
+ rt.tv_nsec = timestamp % static_cast<int32_t>(1e9);
+ int err = clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &rt, NULL);
+ if ( err == -1) {
+ ALOGE("error in vsync thread: %s", strerror(errno));
+ if (errno == EINTR) {
+ continue;
+ }
+ }
+
+ pdev->procs->vsync(const_cast<hwc_procs_t*>(pdev->procs), 0, timestamp);
+ if (rt.tv_sec - last_logged > log_interval) {
+ ALOGI("Sent %d syncs in %ds", sent - last_sent, log_interval);
+ last_logged = rt.tv_sec;
+ last_sent = sent;
+ }
+ ++sent;
+ }
+
+ return NULL;
+}
+
+int hwc_prepare(struct hwc_composer_device_1* /*dev*/,
+ size_t numDisplays,
+ hwc_display_contents_1_t** displays) {
+ if (!numDisplays || !displays) return 0;
+ hwc_display_contents_1_t* list = displays[HWC_DISPLAY_PRIMARY];
+ if (!list) return 0;
+
+ for (size_t i = 0; i < list->numHwLayers; i++) {
+ if (list->hwLayers[i].compositionType == HWC_FRAMEBUFFER_TARGET) {
+ continue;
+ }
+ list->hwLayers[i].compositionType = HWC_FRAMEBUFFER;
+ }
+
+ return 0;
+}
+
+int hwc_set(struct hwc_composer_device_1* dev,
+ size_t numDisplays,
+ hwc_display_contents_1_t** displays) {
+ if (!numDisplays || !displays) return 0;
+ hwc_display_contents_1_t* list = displays[HWC_DISPLAY_PRIMARY];
+ if (!list) return 0;
+ if (!dev) {
+ ALOGE("%s: dev is NULL", __FUNCTION__);
+ return -EINVAL;
+ }
+
+ for (size_t i = 0; i < list->numHwLayers; i++) {
+ if (vsoc_buffer_handle_t::validate(list->hwLayers[i].handle)) {
+ return -EINVAL;
+ }
+ if (list->hwLayers[i].compositionType == HWC_FRAMEBUFFER_TARGET) {
+ if (SanityCheckLayer(list->hwLayers[i])) {
+ ALOGW("Skipping layer %zu due to failed sanity check", i);
+ continue;
+ }
+ vsoc_hwc_device* pdev = reinterpret_cast<vsoc_hwc_device*>(dev);
+ const vsoc_buffer_handle_t* fb_handle =
+ reinterpret_cast<const vsoc_buffer_handle_t*>(
+ list->hwLayers[i].handle);
+ pdev->fb_broadcast->BroadcastNewFrame(pdev->frame_num++,
+ fb_handle->offset);
+ break;
+ }
+ }
+
+ return 0;
+}
+
+int hwc_eventControl(struct hwc_composer_device_1* /*dev*/,
+ int disp,
+ int event,
+ int /*enabled*/) {
+ if (event == HWC_EVENT_VSYNC || disp != HWC_DISPLAY_PRIMARY) {
+ return 0;
+ }
+ return -EINVAL;
+}
+
+int hwc_blank(struct hwc_composer_device_1* /*dev*/, int disp, int /*blank*/) {
+ if (disp != HWC_DISPLAY_PRIMARY) {
+ return -EINVAL;
+ }
+ return 0;
+}
+
+int hwc_query(struct hwc_composer_device_1* dev, int what, int* value) {
+ vsoc_hwc_device* pdev = reinterpret_cast<vsoc_hwc_device*>(dev);
+ switch (what) {
+ case HWC_BACKGROUND_LAYER_SUPPORTED:
+ // we don't support the background layer
+ *value = 0;
+ break;
+ case HWC_VSYNC_PERIOD:
+ *value = pdev->vsync_period_ns;
+ break;
+ case HWC_DISPLAY_TYPES_SUPPORTED:
+ // We only support the primary display
+ *value = HWC_DISPLAY_PRIMARY_BIT;
+ break;
+ default:
+ // unsupported query
+ ALOGE("%s badness unsupported query what=%d", __FUNCTION__, what);
+ return -EINVAL;
+ }
+ return 0;
+}
+
+void hwc_registerProcs(struct hwc_composer_device_1* dev,
+ hwc_procs_t const* procs) {
+ reinterpret_cast<vsoc_hwc_device*>(dev)->procs = procs;
+}
+
+void hwc_dump(struct hwc_composer_device_1* /*dev*/,
+ char* /*buff*/, int /*buff_len*/) {}
+
+int hwc_getDisplayConfigs(struct hwc_composer_device_1* /*dev*/,
+ int disp,
+ uint32_t* configs,
+ size_t* numConfigs) {
+ if (*numConfigs == 0) return 0;
+
+ if (disp == HWC_DISPLAY_PRIMARY) {
+ configs[0] = 0;
+ *numConfigs = 1;
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+int32_t vsoc_hwc_attribute(vsoc_hwc_device* pdev, uint32_t attribute) {
+ switch (attribute) {
+ case HWC_DISPLAY_VSYNC_PERIOD:
+ return 1000000000/pdev->fb_broadcast->refresh_rate_hz();
+ case HWC_DISPLAY_WIDTH:
+ return pdev->fb_broadcast->x_res();
+ case HWC_DISPLAY_HEIGHT:
+ return pdev->fb_broadcast->y_res();
+ case HWC_DISPLAY_DPI_X:
+ case HWC_DISPLAY_DPI_Y:
+ // The number of pixels per thousand inches
+ return pdev->fb_broadcast->dpi() * 1000;
+ case HWC_DISPLAY_COLOR_TRANSFORM:
+ // TODO(jemoreira): Add the other color transformations
+ return HAL_COLOR_TRANSFORM_IDENTITY;
+ default:
+ ALOGE("unknown display attribute %u", attribute);
+ return -EINVAL;
+ }
+}
+
+int hwc_getDisplayAttributes(struct hwc_composer_device_1* dev,
+ int disp,
+ uint32_t /*config*/,
+ const uint32_t* attributes,
+ int32_t* values) {
+ vsoc_hwc_device* pdev = reinterpret_cast<vsoc_hwc_device*>(dev);
+
+ if (disp != HWC_DISPLAY_PRIMARY) {
+ ALOGE("Unknown display type %u", disp);
+ return -EINVAL;
+ }
+
+ for (int i = 0; attributes[i] != HWC_DISPLAY_NO_ATTRIBUTE; i++) {
+ values[i] = vsoc_hwc_attribute(pdev, attributes[i]);
+ }
+
+ return 0;
+}
+
+int hwc_close(hw_device_t* device) {
+ vsoc_hwc_device* dev = reinterpret_cast<vsoc_hwc_device*>(device);
+ pthread_kill(dev->vsync_thread, SIGTERM);
+ pthread_join(dev->vsync_thread, NULL);
+ delete dev;
+ return 0;
+}
+
+int hwc_open(const struct hw_module_t* module, const char* name,
+ struct hw_device_t** device) {
+ ALOGI("Opening vsoc hwcomposer device: %s", __FUNCTION__);
+ if (strcmp(name, HWC_HARDWARE_COMPOSER)) {
+ ALOGE("%s called with bad name %s", __FUNCTION__, name);
+ return -EINVAL;
+ }
+
+ vsoc_hwc_device* dev = new vsoc_hwc_device();
+ if (!dev) {
+ ALOGE("%s failed to allocate dev", __FUNCTION__);
+ return -ENOMEM;
+ }
+ memset(dev, 0, sizeof(*dev));
+
+ int refreshRate = 60;
+ dev->vsync_period_ns = 1000000000 / refreshRate;
+ struct timespec rt;
+ if (clock_gettime(CLOCK_MONOTONIC, &rt) == -1) {
+ ALOGE("%s:%d error in clock_gettime: %s",
+ __FILE__,
+ __LINE__,
+ strerror(errno));
+ }
+ dev->vsync_base_timestamp = int64_t(rt.tv_sec) * 1e9 + rt.tv_nsec;
+
+ dev->base.common.tag = HARDWARE_DEVICE_TAG;
+ dev->base.common.version = HWC_DEVICE_API_VERSION_1_1;
+ dev->base.common.module = const_cast<hw_module_t*>(module);
+ dev->base.common.close = hwc_close;
+
+ dev->base.prepare = hwc_prepare;
+ dev->base.set = hwc_set;
+ dev->base.query = hwc_query;
+ dev->base.registerProcs = hwc_registerProcs;
+ dev->base.dump = hwc_dump;
+ dev->base.blank = hwc_blank;
+ dev->base.eventControl = hwc_eventControl;
+ dev->base.getDisplayConfigs = hwc_getDisplayConfigs;
+ dev->base.getDisplayAttributes = hwc_getDisplayAttributes;
+
+ dev->fb_broadcast = GetFBBroadcastRegionView();
+ if (!dev->fb_broadcast->Open()) {
+ ALOGE("Unable to open framebuffer broadcaster (%s)", __FUNCTION__);
+ delete dev;
+ return -1;
+ }
+
+ int ret = pthread_create(&dev->vsync_thread, NULL, vsync_thread, dev);
+ if (ret) {
+ ALOGE("failed to start vsync thread: %s", strerror(ret));
+ delete dev;
+ } else {
+ *device = &dev->base.common;
+ }
+
+ return -ret;
+}
+
+struct hw_module_methods_t hwc_module_methods = { hwc_open };
+
+} // namespace
+
+hwc_module_t HAL_MODULE_INFO_SYM = {{HARDWARE_MODULE_TAG,
+ HWC_MODULE_API_VERSION_0_1,
+ HARDWARE_HAL_API_VERSION,
+ HWC_HARDWARE_MODULE_ID,
+ "Cuttlefish hwcomposer module",
+ "Google",
+ &hwc_module_methods,
+ NULL,
+ {0}}};
diff --git a/guest/hals/hwcomposer/legacy/Android.mk b/guest/hals/hwcomposer/legacy/Android.mk
new file mode 100644
index 0000000..2de1251
--- /dev/null
+++ b/guest/hals/hwcomposer/legacy/Android.mk
@@ -0,0 +1,82 @@
+# Copyright (C) 2016 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.
+
+LOCAL_PATH := $(call my-dir)
+
+# HAL module implemenation stored in
+# hw/<OVERLAY_HARDWARE_MODULE_ID>.<ro.product.board>.so
+
+# Old hwcomposer, relies on GLES composition
+include $(CLEAR_VARS)
+include $(LOCAL_PATH)/hwcomposer.mk
+LOCAL_CFLAGS += -DUSE_OLD_HWCOMPOSER
+LOCAL_MODULE := hwcomposer.vsoc-deprecated
+
+# See b/67109557
+ifeq (true, $(TARGET_TRANSLATE_2ND_ARCH))
+LOCAL_MULTILIB := first
+endif
+
+include $(BUILD_SHARED_LIBRARY)
+
+# New hwcomposer, performs software composition
+include $(CLEAR_VARS)
+include $(LOCAL_PATH)/hwcomposer.mk
+LOCAL_MODULE := hwcomposer.vsoc
+LOCAL_VENDOR_MODULE := true
+
+# See b/67109557
+ifeq (true, $(TARGET_TRANSLATE_2ND_ARCH))
+LOCAL_MULTILIB := first
+endif
+
+include $(BUILD_SHARED_LIBRARY)
+
+# An executable to run some tests
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := hwc_tests.vsoc
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SHARED_LIBRARIES := \
+ libvsocframebuffer \
+ liblog \
+ libbase \
+ libcutils \
+ libutils \
+ libsync \
+ libhardware \
+ libjpeg \
+ $(VSOC_STLPORT_LIBS)
+
+LOCAL_STATIC_LIBRARIES := \
+ libyuv_static
+
+LOCAL_SRC_FILES := \
+ hwc_tests.cpp \
+ vsoc_composer.cpp \
+ base_composer.cpp \
+ geometry_utils.cpp
+
+
+LOCAL_CFLAGS += \
+ -DLOG_TAG=\"hwc_tests\" \
+ $(VSOC_VERSION_CFLAGS)
+
+LOCAL_C_INCLUDES := \
+ device/google/cuttlefish_common \
+ bionic \
+ $(VSOC_STLPORT_INCLUDES)
+
+include $(BUILD_EXECUTABLE)
diff --git a/guest/hals/hwcomposer/legacy/base_composer.cpp b/guest/hals/hwcomposer/legacy/base_composer.cpp
new file mode 100644
index 0000000..561ce6f
--- /dev/null
+++ b/guest/hals/hwcomposer/legacy/base_composer.cpp
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#include "base_composer.h"
+
+#include <cutils/log.h>
+#include "guest/libs/legacy_framebuffer/vsoc_framebuffer_control.h"
+#include "guest/hals/gralloc//legacy/gralloc_vsoc_priv.h"
+
+namespace cvd {
+
+namespace {
+
+int BroadcastFrameBufferChanged (int yoffset) {
+ return VSoCFrameBufferControl::getInstance().BroadcastFrameBufferChanged(
+ yoffset);
+}
+
+}
+
+BaseComposer::BaseComposer(int64_t vsync_base_timestamp, int32_t vsync_period_ns)
+ : fb_broadcaster_(BroadcastFrameBufferChanged),
+ vsync_base_timestamp_(vsync_base_timestamp),
+ vsync_period_ns_(vsync_period_ns) {
+ const gralloc_module_t* gralloc_module;
+ hw_get_module(GRALLOC_HARDWARE_MODULE_ID,
+ reinterpret_cast<const hw_module_t**>(&gralloc_module));
+}
+
+BaseComposer::~BaseComposer() {}
+
+FbBroadcaster BaseComposer::ReplaceFbBroadcaster(
+ FbBroadcaster fb_broadcaster) {
+ FbBroadcaster tmp = fb_broadcaster_;
+ fb_broadcaster_ = fb_broadcaster;
+ return tmp;
+}
+
+void BaseComposer::Dump(char* buff __unused, int buff_len __unused) {}
+
+
+int BaseComposer::PostFrameBuffer(buffer_handle_t buffer) {
+ const int yoffset = YOffsetFromHandle(buffer);
+ // If the broadcaster is NULL or could not get a good yoffset just ignore it.
+ if (fb_broadcaster_ && yoffset >= 0) {
+ int retval = fb_broadcaster_(yoffset);
+ if (retval){
+ ALOGI("Failed to post framebuffer");
+ return -1;
+ }
+ }
+
+ return yoffset;
+}
+
+int BaseComposer::PrepareLayers(size_t num_layers, vsoc_hwc_layer* layers) {
+ // find unsupported overlays
+ for (size_t i = 0; i < num_layers; i++) {
+ if (IS_TARGET_FRAMEBUFFER(layers[i].compositionType)) {
+ continue;
+ }
+ layers[i].compositionType = HWC_FRAMEBUFFER;
+ }
+ return 0;
+}
+
+int BaseComposer::SetLayers(size_t num_layers, vsoc_hwc_layer* layers) {
+ for (size_t idx = 0; idx < num_layers; idx++) {
+ if (IS_TARGET_FRAMEBUFFER(layers[idx].compositionType)) {
+ return PostFrameBuffer(layers[idx].handle);
+ }
+ }
+ return -1;
+}
+
+} // namespace cvd
diff --git a/guest/hals/hwcomposer/legacy/base_composer.h b/guest/hals/hwcomposer/legacy/base_composer.h
new file mode 100644
index 0000000..5a084fd
--- /dev/null
+++ b/guest/hals/hwcomposer/legacy/base_composer.h
@@ -0,0 +1,51 @@
+#pragma once
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#include <hardware/gralloc.h>
+#include "hwcomposer_common.h"
+
+namespace cvd {
+
+typedef int (*FbBroadcaster)(int);
+
+class BaseComposer {
+ public:
+ BaseComposer(int64_t vsync_base_timestamp, int32_t vsync_period_ns);
+ ~BaseComposer();
+
+ // Sets the composition type of each layer and returns the number of layers
+ // to be composited by the hwcomposer.
+ int PrepareLayers(size_t num_layers, vsoc_hwc_layer* layers);
+ // Returns the yoffset that was broadcasted or a negative number if there was
+ // an error.
+ int SetLayers(size_t num_layers, vsoc_hwc_layer* layers);
+ // Returns yoffset of the handle or negative on error.
+ int PostFrameBuffer(buffer_handle_t handle);
+ // Changes the broadcaster, gives the ability to report more than just the
+ // yoffset by using a wrapper like the StatsKeepingComposer. Returns the old
+ // broadcaster. Passing a NULL pointer will cause the composer to not
+ // broadcast at all.
+ FbBroadcaster ReplaceFbBroadcaster(FbBroadcaster);
+ void Dump(char* buff, int buff_len);
+ protected:
+ int64_t vsync_base_timestamp_;
+ int32_t vsync_period_ns_;
+ private:
+ FbBroadcaster fb_broadcaster_;
+};
+
+} // namespace cvd
diff --git a/guest/hals/hwcomposer/legacy/geometry_utils.cpp b/guest/hals/hwcomposer/legacy/geometry_utils.cpp
new file mode 100644
index 0000000..75b7a61
--- /dev/null
+++ b/guest/hals/hwcomposer/legacy/geometry_utils.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#include "geometry_utils.h"
+#include <algorithm>
+#include <utility>
+
+namespace cvd {
+
+bool LayersOverlap(const vsoc_hwc_layer& layer1, const vsoc_hwc_layer& layer2) {
+ int left1 = layer1.displayFrame.left;
+ int right1 = layer1.displayFrame.right;
+ int top1 = layer1.displayFrame.top;
+ int bottom1 = layer1.displayFrame.bottom;
+
+ int left2 = layer2.displayFrame.left;
+ int right2 = layer2.displayFrame.right;
+ int top2 = layer2.displayFrame.top;
+ int bottom2 = layer2.displayFrame.bottom;
+
+ bool overlap_x = left1 < right2 && left2 < right1;
+ bool overlap_y = top1 < bottom2 && top2 < bottom1;
+
+ return overlap_x && overlap_y;
+}
+
+} // namespace cvd
diff --git a/guest/hals/hwcomposer/legacy/geometry_utils.h b/guest/hals/hwcomposer/legacy/geometry_utils.h
new file mode 100644
index 0000000..b6a037b
--- /dev/null
+++ b/guest/hals/hwcomposer/legacy/geometry_utils.h
@@ -0,0 +1,24 @@
+#pragma once
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#include "hwcomposer_common.h"
+
+namespace cvd {
+
+bool LayersOverlap(const vsoc_hwc_layer& layer1, const vsoc_hwc_layer& layer2);
+
+} // namespace cvd
diff --git a/guest/hals/hwcomposer/legacy/hwc_tests.cpp b/guest/hals/hwcomposer/legacy/hwc_tests.cpp
new file mode 100644
index 0000000..5cfe797
--- /dev/null
+++ b/guest/hals/hwcomposer/legacy/hwc_tests.cpp
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+#include "vsoc_composer.h"
+#include <stdio.h>
+
+// This executable is only intended to perform simple tests on the hwcomposer
+// functionality. It should not be part of the images, but rather be included
+// (via scp) when needed to test specific scenarios that are hard to reproduce
+// in the normal operation of the device.
+
+class HWC_Tester : public cvd::VSoCComposer {
+ public:
+ HWC_Tester() : cvd::VSoCComposer(int64_t(0), int32_t(16000000)) {}
+ int RunTest() {
+ // Allocate two buffers (1x1 and 800x1280)
+ buffer_handle_t src_handle;
+ int src_stride;
+ int res = gralloc_dev_->device.alloc(&gralloc_dev_->device,
+ 1,
+ 1,
+ HAL_PIXEL_FORMAT_RGBA_8888,
+ GRALLOC_USAGE_SW_READ_OFTEN,
+ &src_handle,
+ &src_stride);
+ if (res) {
+ fprintf(stderr, "Error allocating source buffer, see logs for details\n");
+ return -1;
+ }
+ buffer_handle_t dst_handle;
+ int dst_stride;
+ res = gralloc_dev_->device.alloc(&gralloc_dev_->device,
+ 800,
+ 1280,
+ HAL_PIXEL_FORMAT_RGBA_8888,
+ GRALLOC_USAGE_SW_WRITE_OFTEN,
+ &dst_handle,
+ &dst_stride);
+ if (res) {
+ fprintf(stderr,
+ "Error allocating destination buffer, see logs for details\n");
+ return -1;
+ }
+ // Create a mock layer requesting a sinple copy of the pixels so that DoCopy gets called
+ vsoc_hwc_layer src_layer;
+ src_layer.compositionType = HWC_OVERLAY;
+ src_layer.hints = 0;
+ src_layer.flags = 0;
+ src_layer.handle = src_handle;
+
+ // No transformation, just a copy
+ src_layer.transform = 0;
+ src_layer.blending = HWC_BLENDING_NONE;
+
+ src_layer.sourceCrop.top = 0;
+ src_layer.sourceCrop.left = 0;
+ src_layer.sourceCrop.bottom = 1;
+ src_layer.sourceCrop.right = 1;
+
+ src_layer.displayFrame.top = 0;
+ src_layer.displayFrame.left = 0;
+ src_layer.displayFrame.bottom = 1;
+ src_layer.displayFrame.right = 1;
+
+ src_layer.visibleRegionScreen.numRects = 0;
+ src_layer.visibleRegionScreen.rects = NULL;
+
+ src_layer.acquireFenceFd = -1;
+ src_layer.releaseFenceFd = -1;
+ // Call CompositeLayer
+ CompositeLayer(&src_layer, dst_handle);
+ // If we got this far without a SEGFAULT we call it success
+ printf("OK\n");
+ gralloc_dev_->device.free(&gralloc_dev_->device, src_handle);
+ gralloc_dev_->device.free(&gralloc_dev_->device, dst_handle);
+ return 0;
+ }
+};
+
+int main() {
+ HWC_Tester t;
+ return t.RunTest();
+}
diff --git a/guest/hals/hwcomposer/legacy/hwcomposer.cpp b/guest/hals/hwcomposer/legacy/hwcomposer.cpp
new file mode 100644
index 0000000..82b0aac
--- /dev/null
+++ b/guest/hals/hwcomposer/legacy/hwcomposer.cpp
@@ -0,0 +1,388 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+// Versions of hwcomposer we implement:
+// JB: 0.3
+// JB-MR1 to N : 1.1
+// N-MR1 to ... : We report 1.1 but SurfaceFlinger has the option to use an
+// adapter to treat our 1.1 hwcomposer as a 2.0. If SF stops using that adapter
+// to support 1.1 implementations it can be copied into cuttlefish from
+// frameworks/native/services/surfaceflinger/DisplayHardware/HWC2On1Adapter.*
+
+#include <guest/libs/platform_support/api_level_fixes.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <math.h>
+#include <poll.h>
+#include <pthread.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sys/resource.h>
+#include <sys/time.h>
+
+#define HWC_REMOVE_DEPRECATED_VERSIONS 1
+
+#include <cutils/compiler.h>
+#include <cutils/log.h>
+#include <cutils/properties.h>
+#include <hardware/gralloc.h>
+#include <hardware/hardware.h>
+#include <hardware/hwcomposer.h>
+#include <hardware/hwcomposer_defs.h>
+#include <utils/String8.h>
+#include <utils/Vector.h>
+
+#include <guest/libs/legacy_framebuffer/vsoc_framebuffer.h>
+#include <guest/libs/legacy_framebuffer/vsoc_framebuffer_control.h>
+#include <guest/hals/gralloc/legacy/gralloc_vsoc_priv.h>
+#include <guest/libs/remoter/remoter_framework_pkt.h>
+#include <sync/sync.h>
+
+#include "vsoc_composer.h"
+#include "geometry_utils.h"
+#include "hwcomposer_common.h"
+#include "base_composer.h"
+#include "stats_keeper.h"
+
+
+#ifdef USE_OLD_HWCOMPOSER
+typedef cvd::BaseComposer InnerComposerType;
+#else
+typedef cvd::VSoCComposer InnerComposerType;
+#endif
+
+
+#ifdef GATHER_STATS
+typedef cvd::StatsKeepingComposer<InnerComposerType> ComposerType;
+#else
+typedef InnerComposerType ComposerType;
+#endif
+
+struct vsoc_hwc_composer_device_1_t {
+ vsoc_hwc_device base;
+ const hwc_procs_t* procs;
+ pthread_t vsync_thread;
+ int64_t vsync_base_timestamp;
+ int32_t vsync_period_ns;
+ ComposerType* composer;
+};
+
+static void dump_layer(vsoc_hwc_layer const* l) {
+ ALOGI(
+ "\ttype=%d, flags=%08x, handle=%p, tr=%02x, blend=%04x, "
+ "{%d,%d,%d,%d}, {%d,%d,%d,%d}",
+ l->compositionType, l->flags, l->handle, l->transform, l->blending,
+ l->sourceCrop.left, l->sourceCrop.top, l->sourceCrop.right,
+ l->sourceCrop.bottom, l->displayFrame.left, l->displayFrame.top,
+ l->displayFrame.right, l->displayFrame.bottom);
+}
+
+#if VSOC_PLATFORM_SDK_BEFORE(J_MR1)
+static int vsoc_hwc_prepare(vsoc_hwc_device* dev, hwc_layer_list_t* list) {
+#else
+static int vsoc_hwc_prepare(vsoc_hwc_device* dev, size_t numDisplays,
+ hwc_display_contents_1_t** displays) {
+ if (!numDisplays || !displays) return 0;
+
+ hwc_display_contents_1_t* list = displays[HWC_DISPLAY_PRIMARY];
+
+ if (!list) return 0;
+#endif
+ int composited_layers_count =
+ reinterpret_cast<vsoc_hwc_composer_device_1_t*>(dev)
+ ->composer->PrepareLayers(list->numHwLayers, &list->hwLayers[0]);
+ return 0;
+}
+
+#if VSOC_PLATFORM_SDK_BEFORE(J_MR1)
+int vsoc_hwc_set(struct hwc_composer_device* dev, hwc_display_t dpy,
+ hwc_surface_t sur, hwc_layer_list_t* list) {
+ reinterpret_cast<vsoc_hwc_composer_device_1_t*>(dev)->composer->SetLayers(
+ list->numHwLayers, &list->hwLayers[0]);
+ return 0;
+}
+#else
+static int vsoc_hwc_set(vsoc_hwc_device* dev, size_t numDisplays,
+ hwc_display_contents_1_t** displays) {
+ if (!numDisplays || !displays) return 0;
+
+ hwc_display_contents_1_t* contents = displays[HWC_DISPLAY_PRIMARY];
+ if (!contents) return 0;
+
+ vsoc_hwc_layer* layers = &contents->hwLayers[0];
+ reinterpret_cast<vsoc_hwc_composer_device_1_t*>(dev)->composer->SetLayers(
+ contents->numHwLayers, layers);
+
+ int closedFds = 0;
+ for (size_t index = 0; index < contents->numHwLayers; ++index) {
+ if (layers[index].acquireFenceFd != -1) {
+ close(layers[index].acquireFenceFd);
+ layers[index].acquireFenceFd = -1;
+ ++closedFds;
+ }
+ }
+ if (closedFds) {
+ ALOGI("Saw %d layers, closed=%d", contents->numHwLayers, closedFds);
+ }
+
+ // TODO(ghartman): This should be set before returning. On the next set it
+ // should be signalled when we load the new frame.
+ contents->retireFenceFd = -1;
+ return 0;
+}
+#endif
+
+static void vsoc_hwc_register_procs(vsoc_hwc_device* dev,
+ const hwc_procs_t* procs) {
+ struct vsoc_hwc_composer_device_1_t* pdev =
+ (struct vsoc_hwc_composer_device_1_t*)dev;
+ pdev->procs = procs;
+}
+
+static int vsoc_hwc_query(vsoc_hwc_device* dev, int what, int* value) {
+ struct vsoc_hwc_composer_device_1_t* pdev =
+ (struct vsoc_hwc_composer_device_1_t*)dev;
+
+ switch (what) {
+ case HWC_BACKGROUND_LAYER_SUPPORTED:
+ // we support the background layer
+ value[0] = 0;
+ break;
+ case HWC_VSYNC_PERIOD:
+ value[0] = pdev->vsync_period_ns;
+ break;
+ default:
+ // unsupported query
+ ALOGE("%s badness unsupported query what=%d", __FUNCTION__, what);
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int vsoc_hwc_event_control(
+#if VSOC_PLATFORM_SDK_BEFORE(J_MR1)
+ vsoc_hwc_device* dev, int event, int /*enabled*/) {
+#else
+ vsoc_hwc_device* dev, int /*dpy*/, int event, int /*enabled*/) {
+#endif
+ struct vsoc_hwc_composer_device_1_t* pdev =
+ (struct vsoc_hwc_composer_device_1_t*)dev;
+
+ if (event == HWC_EVENT_VSYNC) {
+ return 0;
+ }
+ return -EINVAL;
+}
+
+static void* hwc_vsync_thread(void* data) {
+ struct vsoc_hwc_composer_device_1_t* pdev =
+ (struct vsoc_hwc_composer_device_1_t*)data;
+ setpriority(PRIO_PROCESS, 0, HAL_PRIORITY_URGENT_DISPLAY);
+
+ int64_t base_timestamp = pdev->vsync_base_timestamp;
+ int64_t last_logged = base_timestamp / 1e9;
+ int sent = 0;
+ int last_sent = 0;
+ static const int log_interval = 60;
+ while (true) {
+ struct timespec rt;
+ if (clock_gettime(CLOCK_MONOTONIC, &rt) == -1) {
+ ALOGE("%s:%d error in vsync thread clock_gettime: %s", __FILE__, __LINE__,
+ strerror(errno));
+ }
+ int64_t timestamp = int64_t(rt.tv_sec) * 1e9 + rt.tv_nsec;
+ // Given now's timestamp calculate the time of the next timestamp.
+ timestamp += pdev->vsync_period_ns -
+ (timestamp - base_timestamp) % pdev->vsync_period_ns;
+
+ rt.tv_sec = timestamp / 1e9;
+ rt.tv_nsec = timestamp % static_cast<int32_t>(1e9);
+ int err = clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &rt, NULL);
+ if ( err == -1) {
+ ALOGE("error in vsync thread: %s", strerror(errno));
+ if (errno == EINTR) {
+ continue;
+ }
+ }
+
+ pdev->procs->vsync(const_cast<hwc_procs_t*>(pdev->procs), 0, timestamp);
+ if (rt.tv_sec - last_logged > log_interval) {
+ ALOGI("Sent %d syncs in %ds", sent - last_sent, log_interval);
+ last_logged = rt.tv_sec;
+ last_sent = sent;
+ }
+ ++sent;
+ }
+
+ return NULL;
+}
+
+static int vsoc_hwc_blank(vsoc_hwc_device* dev, int disp, int /*blank*/) {
+ struct vsoc_hwc_composer_device_1_t* pdev =
+ (struct vsoc_hwc_composer_device_1_t*)dev;
+ if (!IS_PRIMARY_DISPLAY(disp)) return -EINVAL;
+ return 0;
+}
+
+static void vsoc_hwc_dump(vsoc_hwc_device* dev, char* buff, int buff_len) {
+ reinterpret_cast<vsoc_hwc_composer_device_1_t*>(dev)->composer->Dump(buff,
+ buff_len);
+}
+
+static int vsoc_hwc_get_display_configs(vsoc_hwc_device* dev, int disp,
+ uint32_t* configs, size_t* numConfigs) {
+ struct vsoc_hwc_composer_device_1_t* pdev =
+ (struct vsoc_hwc_composer_device_1_t*)dev;
+
+ if (*numConfigs == 0) return 0;
+
+ if (IS_PRIMARY_DISPLAY(disp)) {
+ configs[0] = 0;
+ *numConfigs = 1;
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+#if VSOC_PLATFORM_SDK_AFTER(J)
+static int32_t vsoc_hwc_attribute(struct vsoc_hwc_composer_device_1_t* pdev,
+ const uint32_t attribute) {
+ const VSoCFrameBuffer& config = VSoCFrameBuffer::getInstance();
+ switch (attribute) {
+ case HWC_DISPLAY_VSYNC_PERIOD:
+ return pdev->vsync_period_ns;
+ case HWC_DISPLAY_WIDTH:
+ return config.x_res();
+ case HWC_DISPLAY_HEIGHT:
+ return config.y_res();
+ case HWC_DISPLAY_DPI_X:
+ ALOGI("Reporting DPI_X of %d", config.dpi());
+ return config.dpi() * 1000; // The number of pixels per thousand inches
+ case HWC_DISPLAY_DPI_Y:
+ ALOGI("Reporting DPI_Y of %d", config.dpi());
+ return config.dpi() * 1000; // The number of pixels per thousand inches
+ default:
+ ALOGE("unknown display attribute %u", attribute);
+ return -EINVAL;
+ }
+}
+
+static int vsoc_hwc_get_display_attributes(vsoc_hwc_device* dev, int disp,
+ uint32_t config __unused,
+ const uint32_t* attributes,
+ int32_t* values) {
+ struct vsoc_hwc_composer_device_1_t* pdev =
+ (struct vsoc_hwc_composer_device_1_t*)dev;
+
+ if (!IS_PRIMARY_DISPLAY(disp)) {
+ ALOGE("unknown display type %u", disp);
+ return -EINVAL;
+ }
+
+ for (int i = 0; attributes[i] != HWC_DISPLAY_NO_ATTRIBUTE; i++) {
+ values[i] = vsoc_hwc_attribute(pdev, attributes[i]);
+ }
+
+ return 0;
+}
+#endif
+
+static int vsoc_hwc_close(hw_device_t* device) {
+ struct vsoc_hwc_composer_device_1_t* dev =
+ (struct vsoc_hwc_composer_device_1_t*)device;
+ ALOGE("vsoc_hwc_close");
+ pthread_kill(dev->vsync_thread, SIGTERM);
+ pthread_join(dev->vsync_thread, NULL);
+ delete dev->composer;
+ delete dev;
+ return 0;
+}
+
+static int vsoc_hwc_open(const struct hw_module_t* module, const char* name,
+ struct hw_device_t** device) {
+ ALOGI("%s", __FUNCTION__);
+ if (strcmp(name, HWC_HARDWARE_COMPOSER)) {
+ ALOGE("%s called with bad name %s", __FUNCTION__, name);
+ return -EINVAL;
+ }
+
+ vsoc_hwc_composer_device_1_t* dev = new vsoc_hwc_composer_device_1_t();
+ if (!dev) {
+ ALOGE("%s failed to allocate dev", __FUNCTION__);
+ return -ENOMEM;
+ }
+
+ int refreshRate = 60;
+ dev->vsync_period_ns = 1000000000 / refreshRate;
+ struct timespec rt;
+ if (clock_gettime(CLOCK_MONOTONIC, &rt) == -1) {
+ ALOGE("%s:%d error in vsync thread clock_gettime: %s", __FILE__, __LINE__,
+ strerror(errno));
+ }
+ dev->vsync_base_timestamp = int64_t(rt.tv_sec) * 1e9 + rt.tv_nsec;
+
+ dev->base.common.tag = HARDWARE_DEVICE_TAG;
+ dev->base.common.version = VSOC_HWC_DEVICE_API_VERSION;
+ dev->base.common.module = const_cast<hw_module_t*>(module);
+ dev->base.common.close = vsoc_hwc_close;
+
+ dev->base.prepare = vsoc_hwc_prepare;
+ dev->base.set = vsoc_hwc_set;
+ dev->base.query = vsoc_hwc_query;
+ dev->base.registerProcs = vsoc_hwc_register_procs;
+ dev->base.dump = vsoc_hwc_dump;
+#if VSOC_PLATFORM_SDK_BEFORE(J_MR1)
+ static hwc_methods_t hwc_methods = {vsoc_hwc_event_control};
+ dev->base.methods = &hwc_methods;
+#else
+ dev->base.blank = vsoc_hwc_blank;
+ dev->base.eventControl = vsoc_hwc_event_control;
+ dev->base.getDisplayConfigs = vsoc_hwc_get_display_configs;
+ dev->base.getDisplayAttributes = vsoc_hwc_get_display_attributes;
+#endif
+ dev->composer =
+ new ComposerType(dev->vsync_base_timestamp, dev->vsync_period_ns);
+
+ int ret = pthread_create(&dev->vsync_thread, NULL, hwc_vsync_thread, dev);
+ if (ret) {
+ ALOGE("failed to start vsync thread: %s", strerror(ret));
+ ret = -ret;
+ delete dev;
+ } else {
+ *device = &dev->base.common;
+ }
+
+ return ret;
+}
+
+static struct hw_module_methods_t vsoc_hwc_module_methods = {
+ vsoc_hwc_open,
+};
+
+hwc_module_t HAL_MODULE_INFO_SYM = {{HARDWARE_MODULE_TAG,
+ HWC_MODULE_API_VERSION_0_1,
+ HARDWARE_HAL_API_VERSION,
+ HWC_HARDWARE_MODULE_ID,
+ "VSOC hwcomposer module",
+ "Google",
+ &vsoc_hwc_module_methods,
+ NULL,
+ {0}}};
diff --git a/guest/hals/hwcomposer/legacy/hwcomposer.mk b/guest/hals/hwcomposer/legacy/hwcomposer.mk
new file mode 100644
index 0000000..7383ae0
--- /dev/null
+++ b/guest/hals/hwcomposer/legacy/hwcomposer.mk
@@ -0,0 +1,49 @@
+# Copyright (C) 2016 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.
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)/hw
+LOCAL_MULTILIB := first
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SHARED_LIBRARIES := \
+ libvsocframebuffer \
+ libbase \
+ liblog \
+ libcutils \
+ libutils \
+ libsync \
+ libhardware \
+ libjpeg \
+ $(VSOC_STLPORT_LIBS)
+
+LOCAL_STATIC_LIBRARIES := \
+ libyuv_static
+
+LOCAL_SRC_FILES := \
+ geometry_utils.cpp \
+ hwcomposer.cpp \
+ vsoc_composer.cpp \
+ stats_keeper.cpp \
+ base_composer.cpp
+
+LOCAL_CFLAGS += \
+ -DLOG_TAG=\"hwcomposer_legacy\" \
+ -DGATHER_STATS \
+ $(VSOC_VERSION_CFLAGS)
+
+LOCAL_C_INCLUDES := \
+ device/google/cuttlefish_common \
+ external/libyuv/files/include \
+ bionic \
+ $(VSOC_STLPORT_INCLUDES)
diff --git a/guest/hals/hwcomposer/legacy/hwcomposer_common.h b/guest/hals/hwcomposer/legacy/hwcomposer_common.h
new file mode 100644
index 0000000..a6b8121
--- /dev/null
+++ b/guest/hals/hwcomposer/legacy/hwcomposer_common.h
@@ -0,0 +1,35 @@
+#pragma once
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#include <guest/libs/platform_support/api_level_fixes.h>
+
+#include <hardware/hwcomposer.h>
+#include <hardware/hwcomposer_defs.h>
+
+#if VSOC_PLATFORM_SDK_BEFORE(J_MR1)
+typedef hwc_composer_device_t vsoc_hwc_device;
+typedef hwc_layer_t vsoc_hwc_layer;
+#define IS_TARGET_FRAMEBUFFER(x) false
+#define IS_PRIMARY_DISPLAY(x) true
+#define VSOC_HWC_DEVICE_API_VERSION HWC_DEVICE_API_VERSION_0_3
+#else
+typedef hwc_composer_device_1_t vsoc_hwc_device;
+typedef hwc_layer_1_t vsoc_hwc_layer;
+#define IS_TARGET_FRAMEBUFFER(x) ((x) == HWC_FRAMEBUFFER_TARGET)
+#define IS_PRIMARY_DISPLAY(x) ((x) == HWC_DISPLAY_PRIMARY)
+#define VSOC_HWC_DEVICE_API_VERSION HWC_DEVICE_API_VERSION_1_1
+#endif
diff --git a/guest/hals/hwcomposer/legacy/stats_keeper.cpp b/guest/hals/hwcomposer/legacy/stats_keeper.cpp
new file mode 100644
index 0000000..86b786b
--- /dev/null
+++ b/guest/hals/hwcomposer/legacy/stats_keeper.cpp
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#include <cutils/log.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <algorithm>
+#include <utility>
+#include <vector>
+
+#include "geometry_utils.h"
+#include "stats_keeper.h"
+
+using cvd::time::TimeDifference;
+using cvd::time::Nanoseconds;
+using cvd::time::Microseconds;
+using cvd::time::Seconds;
+using cvd::time::MonotonicTimePoint;
+using cvd::Mutex;
+using cvd::LockGuard;
+
+namespace cvd {
+
+namespace {
+
+// These functions assume that there is at least one suitable element inside
+// the multiset.
+template <class T>
+void MultisetDeleteOne(std::multiset<T>* mset, const T& key) {
+ mset->erase(mset->find(key));
+}
+template <class T>
+const T& MultisetMin(const std::multiset<T>& mset) {
+ return *mset.begin();
+}
+template <class T>
+const T& MultisetMax(const std::multiset<T>& mset) {
+ return *mset.rbegin();
+}
+
+} // namespace
+
+StatsKeeper::StatsKeeper(TimeDifference timespan,
+ int64_t vsync_base,
+ int32_t vsync_period)
+ : period_length_(timespan, 1),
+ num_layers_(0),
+ num_hwcomposited_layers_(0),
+ num_prepare_calls_(0),
+ num_set_calls_(0),
+ prepare_call_total_time_(0),
+ set_call_total_time_(0),
+ total_layers_area(0),
+ total_invisible_area(0),
+ vsync_base_(vsync_base),
+ vsync_period_(vsync_period) {
+ last_composition_stats_.num_prepare_calls = 0;
+}
+
+StatsKeeper::~StatsKeeper() {}
+
+void StatsKeeper::RecordPrepareStart(int num_layers) {
+ last_composition_stats_.num_layers = num_layers;
+ last_composition_stats_.num_prepare_calls++;
+ num_prepare_calls_++;
+ last_composition_stats_.prepare_start = MonotonicTimePoint::Now();
+ // Calculate the (expected) time of last VSYNC event. We can only make a guess about it because
+ // the vsync thread could run late or surfaceflinger could run late and call prepare from a
+ // previous vsync cycle.
+ int64_t last_vsync =
+ Nanoseconds(last_composition_stats_.set_start.SinceEpoch()).count();
+ last_vsync -= (last_vsync - vsync_base_) % vsync_period_;
+ last_composition_stats_.last_vsync = MonotonicTimePoint() + Nanoseconds(last_vsync);
+}
+
+void StatsKeeper::RecordPrepareEnd(int num_hwcomposited_layers) {
+ last_composition_stats_.prepare_end = MonotonicTimePoint::Now();
+ last_composition_stats_.num_hwc_layers = num_hwcomposited_layers;
+}
+
+void StatsKeeper::RecordSetStart() {
+ last_composition_stats_.set_start = MonotonicTimePoint::Now();
+}
+
+void StatsKeeper::RecordSetEnd() {
+ last_composition_stats_.set_end = MonotonicTimePoint::Now();
+ LockGuard<Mutex> lock(mutex_);
+ num_set_calls_++;
+ while (!raw_composition_data_.empty() &&
+ period_length_ < last_composition_stats_.set_end -
+ raw_composition_data_.front().time_point()) {
+ const CompositionData& front = raw_composition_data_.front();
+
+ num_prepare_calls_ -= front.num_prepare_calls();
+ --num_set_calls_;
+ num_layers_ -= front.num_layers();
+ num_hwcomposited_layers_ -= front.num_hwcomposited_layers();
+ prepare_call_total_time_ =
+ Nanoseconds(prepare_call_total_time_ - front.prepare_time());
+ set_call_total_time_ =
+ Nanoseconds(set_call_total_time_ - front.set_calls_time());
+
+ MultisetDeleteOne(&prepare_calls_per_set_calls_, front.num_prepare_calls());
+ MultisetDeleteOne(&layers_per_compositions_, front.num_layers());
+ MultisetDeleteOne(&prepare_call_times_, front.prepare_time());
+ MultisetDeleteOne(&set_call_times_, front.set_calls_time());
+ if (front.num_hwcomposited_layers() != 0) {
+ MultisetDeleteOne(
+ &set_call_times_per_hwcomposited_layer_ns_,
+ front.set_calls_time().count() / front.num_hwcomposited_layers());
+ }
+
+ raw_composition_data_.pop_front();
+ }
+ Nanoseconds last_prepare_call_time_(last_composition_stats_.prepare_end -
+ last_composition_stats_.prepare_start);
+ Nanoseconds last_set_call_total_time_(last_composition_stats_.set_end -
+ last_composition_stats_.set_start);
+ raw_composition_data_.push_back(CompositionData(
+ last_composition_stats_.set_end, last_composition_stats_.num_prepare_calls,
+ last_composition_stats_.num_layers, last_composition_stats_.num_hwc_layers, last_prepare_call_time_,
+ last_set_call_total_time_));
+
+ const CompositionData& back = raw_composition_data_.back();
+
+ // There may be several calls to prepare before a call to set, but the only
+ // valid call is the last one, so we need to compute these here:
+ num_layers_ += last_composition_stats_.num_layers;
+ num_hwcomposited_layers_ += last_composition_stats_.num_hwc_layers;
+ prepare_call_total_time_ =
+ Nanoseconds(prepare_call_total_time_ + last_prepare_call_time_);
+ set_call_total_time_ =
+ Nanoseconds(set_call_total_time_ + last_set_call_total_time_);
+ prepare_calls_per_set_calls_.insert(last_composition_stats_.num_prepare_calls);
+ layers_per_compositions_.insert(last_composition_stats_.num_layers);
+ prepare_call_times_.insert(last_prepare_call_time_);
+ set_call_times_.insert(last_set_call_total_time_);
+ if (last_composition_stats_.num_hwc_layers != 0) {
+ set_call_times_per_hwcomposited_layer_ns_.insert(
+ last_set_call_total_time_.count() / last_composition_stats_.num_hwc_layers);
+ }
+
+ // Reset the counter
+ last_composition_stats_.num_prepare_calls = 0;
+}
+
+void StatsKeeper::SynchronizedDump(char* buffer,
+ int buffer_size) const {
+ LockGuard<Mutex> lock(mutex_);
+ int chars_written = 0;
+// Make sure there is enough space to write the next line
+#define bprintf(...) \
+ (chars_written += (chars_written < buffer_size) \
+ ? (snprintf(&buffer[chars_written], \
+ buffer_size - chars_written, __VA_ARGS__)) \
+ : 0)
+
+ bprintf(
+ "HWComposer stats from the %lld seconds just before the last call to "
+ "set() (which happended %lld seconds ago):\n",
+ Seconds(period_length_).count(),
+ Seconds(MonotonicTimePoint::Now() - last_composition_stats_.set_end).count());
+ bprintf(" Layer count: %d\n", num_layers_);
+
+ if (num_layers_ == 0 || num_prepare_calls_ == 0 || num_set_calls_ == 0) {
+ return;
+ }
+
+ bprintf(" Layers composited by hwcomposer: %d (%d%%)\n",
+ num_hwcomposited_layers_,
+ 100 * num_hwcomposited_layers_ / num_layers_);
+ bprintf(" Number of calls to prepare(): %d\n", num_prepare_calls_);
+ bprintf(" Number of calls to set(): %d\n", num_set_calls_);
+ if (num_set_calls_ > 0) {
+ bprintf(
+ " Maximum number of calls to prepare() before a single call to set(): "
+ "%d\n",
+ MultisetMax(prepare_calls_per_set_calls_));
+ }
+ bprintf(
+ " Time spent on prepare() (in microseconds):\n max: %lld\n "
+ "average: %lld\n min: %lld\n total: %lld\n",
+ Microseconds(MultisetMax(prepare_call_times_)).count(),
+ Microseconds(prepare_call_total_time_).count() / num_prepare_calls_,
+ Microseconds(MultisetMin(prepare_call_times_)).count(),
+ Microseconds(prepare_call_total_time_).count());
+ bprintf(
+ " Time spent on set() (in microseconds):\n max: %lld\n average: "
+ "%lld\n min: %lld\n total: %lld\n",
+ Microseconds(MultisetMax(set_call_times_)).count(),
+ Microseconds(set_call_total_time_).count() / num_set_calls_,
+ Microseconds(MultisetMin(set_call_times_)).count(),
+ Microseconds(set_call_total_time_).count());
+ if (num_hwcomposited_layers_ > 0) {
+ bprintf(
+ " Per layer compostition time:\n max: %lld\n average: %lld\n "
+ "min: %lld\n",
+ Microseconds(MultisetMax(set_call_times_per_hwcomposited_layer_ns_))
+ .count(),
+ Microseconds(set_call_total_time_).count() / num_hwcomposited_layers_,
+ Microseconds(MultisetMin(set_call_times_per_hwcomposited_layer_ns_))
+ .count());
+ }
+ bprintf("Statistics from last 100 compositions:\n");
+ bprintf(" Total area: %lld square pixels\n", total_layers_area);
+ if (total_layers_area != 0) {
+ bprintf(" Total invisible area: %lld square pixels, %lld%%\n",
+ total_invisible_area,
+ 100 * total_invisible_area / total_layers_area);
+ }
+#undef bprintf
+}
+
+} // namespace cvd
diff --git a/guest/hals/hwcomposer/legacy/stats_keeper.h b/guest/hals/hwcomposer/legacy/stats_keeper.h
new file mode 100644
index 0000000..8612cd1
--- /dev/null
+++ b/guest/hals/hwcomposer/legacy/stats_keeper.h
@@ -0,0 +1,187 @@
+#pragma once
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#include <guest/libs/legacy_framebuffer/vsoc_framebuffer_control.h>
+#include <common/libs/time/monotonic_time.h>
+#include <common/libs/threads/cuttlefish_thread.h>
+#include <android-base/thread_annotations.h>
+#include <deque>
+#include <set>
+
+#include "hwcomposer_common.h"
+
+namespace cvd {
+
+class CompositionData {
+ public:
+ CompositionData(cvd::time::MonotonicTimePoint time_point,
+ int num_prepares, int num_layers, int num_hwcomposited_layers,
+ cvd::time::Nanoseconds prepare_time,
+ cvd::time::Nanoseconds set_calls_time)
+ : time_point_(time_point),
+ num_prepare_calls_(num_prepares),
+ num_layers_(num_layers),
+ num_hwcomposited_layers_(num_hwcomposited_layers),
+ prepare_time_(prepare_time),
+ set_calls_time_(set_calls_time) {}
+
+ cvd::time::MonotonicTimePoint time_point() const {
+ return time_point_;
+ }
+
+ int num_prepare_calls() const { return num_prepare_calls_; }
+
+ int num_layers() const { return num_layers_; }
+
+ int num_hwcomposited_layers() const { return num_hwcomposited_layers_; }
+
+ cvd::time::Nanoseconds prepare_time() const {
+ return prepare_time_;
+ }
+
+ cvd::time::Nanoseconds set_calls_time() const {
+ return set_calls_time_;
+ }
+
+ private:
+ cvd::time::MonotonicTimePoint time_point_;
+ int num_prepare_calls_;
+ int num_layers_;
+ int num_hwcomposited_layers_;
+ cvd::time::Nanoseconds prepare_time_;
+ cvd::time::Nanoseconds set_calls_time_;
+};
+
+class StatsKeeper {
+ public:
+ // The timespan parameter indicates for how long we keep stats about the past
+ // compositions.
+ StatsKeeper(cvd::time::TimeDifference timespan,
+ int64_t vsync_base,
+ int32_t vsync_period);
+ StatsKeeper();
+ ~StatsKeeper();
+
+ // Record the time at which a call to prepare was made, takes the number of
+ // layers received (excluding the framebuffer) as a parameter.
+ void RecordPrepareStart(int num_layers);
+ // Record the time at which a call to prepare (was about to) returned, takes
+ // the number of layers marked for hardware composition as a parameter.
+ void RecordPrepareEnd(int num_hwcomposited_layers);
+ void RecordSetStart();
+ void RecordSetEnd() EXCLUDES(mutex_);
+
+ const CompositionStats& last_composition_stats() { return last_composition_stats_; }
+
+ // Calls to this function are synchronized with calls to 'RecordSetEnd' with a
+ // mutex. The other Record* functions do not need such synchronization because
+ // they access last_* variables only, which are not read by 'Dump'.
+ void SynchronizedDump(char* buffer, int buffer_size) const EXCLUDES(mutex_);
+
+ private:
+
+ cvd::time::TimeDifference period_length_;
+
+ // Base and period of the VSYNC signal, allows to accurately calculate the
+ // time of the last vsync broadcast.
+ int64_t vsync_base_;
+ int32_t vsync_period_;
+ // Data collected about ongoing composition. These variables are not accessed
+ // from Dump(), so they don't need to be guarded by a mutex.
+ CompositionStats last_composition_stats_;
+
+ // Aggregated performance data collected from past compositions. These
+ // variables are modified when a composition is completed and when old
+ // compositions need to be discarded in RecordSetEnd(), and is accessed from
+ // Dump(). Non-aggregated data is kept in the raw_composition_data_ deque to
+ // be able to discard old values from the aggregated data.
+ int num_layers_ GUARDED_BY(mutex_);
+ int num_hwcomposited_layers_ GUARDED_BY(mutex_);
+ int num_prepare_calls_ GUARDED_BY(mutex_);
+ int num_set_calls_ GUARDED_BY(mutex_);
+ cvd::time::Nanoseconds prepare_call_total_time_ GUARDED_BY(mutex_);
+ cvd::time::Nanoseconds set_call_total_time_ GUARDED_BY(mutex_);
+ // These are kept in multisets to be able to calculate mins and maxs of
+ // changing sets of (not necessarily different) values.
+ std::multiset<int> prepare_calls_per_set_calls_ GUARDED_BY(mutex_);
+ std::multiset<int> layers_per_compositions_ GUARDED_BY(mutex_);
+ std::multiset<cvd::time::Nanoseconds> prepare_call_times_
+ GUARDED_BY(mutex_);
+ std::multiset<cvd::time::Nanoseconds> set_call_times_
+ GUARDED_BY(mutex_);
+ std::multiset<int64_t> set_call_times_per_hwcomposited_layer_ns_
+ GUARDED_BY(mutex_);
+
+ // Time-ordered list of compositions, used to update the global aggregated
+ // performance data when old compositions fall out of the period of interest.
+ std::deque<CompositionData> raw_composition_data_ GUARDED_BY(mutex_);
+
+ // TODO(jemoreira): Add min/max/average composition times per layer area units
+
+ std::deque<std::pair<int64_t, int64_t> > composition_areas GUARDED_BY(mutex_);
+ int64_t total_layers_area GUARDED_BY(mutex_);
+ int64_t total_invisible_area GUARDED_BY(mutex_);
+
+ // Controls access to data from past compositions.
+ mutable cvd::Mutex mutex_;
+};
+
+template <class Composer>
+class StatsKeepingComposer {
+ public:
+ // Keep stats from the last 10 seconds.
+ StatsKeepingComposer(int64_t vsync_base_timestamp, int32_t vsync_period_ns)
+ : composer_(vsync_base_timestamp, vsync_period_ns),
+ stats_keeper_(cvd::time::TimeDifference(cvd::time::Seconds(10), 1),
+ vsync_base_timestamp,
+ vsync_period_ns) {
+ // Don't let the composer broadcast by itself, allow it to return to collect
+ // the timings and broadcast then.
+ composer_.ReplaceFbBroadcaster(NULL);
+ }
+ ~StatsKeepingComposer() {}
+
+ int PrepareLayers(size_t num_layers, vsoc_hwc_layer* layers) {
+ stats_keeper_.RecordPrepareStart(num_layers);
+ int num_hwc_layers = composer_.PrepareLayers(num_layers, layers);
+ stats_keeper_.RecordPrepareEnd(num_hwc_layers);
+ return num_hwc_layers;
+ }
+
+ int SetLayers(size_t num_layers, vsoc_hwc_layer* layers) {
+ stats_keeper_.RecordSetStart();
+ int yoffset = composer_.SetLayers(num_layers, layers);
+ stats_keeper_.RecordSetEnd();
+ if (yoffset >= 0) {
+ VSoCFrameBufferControl::getInstance().BroadcastFrameBufferChanged(
+ yoffset, &stats_keeper_.last_composition_stats());
+ } else {
+ ALOGE("%s: Error on SetLayers(), yoffset: %d", __FUNCTION__, yoffset);
+ }
+ return yoffset;
+ }
+
+ void Dump(char* buff, int buff_len) {
+ stats_keeper_.SynchronizedDump(buff, buff_len);
+ }
+
+ private:
+ StatsKeeper stats_keeper_;
+ Composer composer_;
+};
+
+} // namespace cvd
diff --git a/guest/hals/hwcomposer/legacy/vsoc_composer.cpp b/guest/hals/hwcomposer/legacy/vsoc_composer.cpp
new file mode 100644
index 0000000..5fcaccb
--- /dev/null
+++ b/guest/hals/hwcomposer/legacy/vsoc_composer.cpp
@@ -0,0 +1,704 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#include "vsoc_composer.h"
+#include <guest/libs/legacy_framebuffer/vsoc_framebuffer.h>
+#include <guest/libs/legacy_framebuffer/vsoc_framebuffer_control.h>
+#include <cutils/log.h>
+#include <hardware/hwcomposer.h>
+#include <hardware/hwcomposer_defs.h>
+#include <libyuv.h>
+#include <system/graphics.h>
+#include <algorithm>
+#include <cstdlib>
+#include <utility>
+#include <vector>
+#include "geometry_utils.h"
+#include "hwcomposer_common.h"
+
+namespace cvd {
+
+namespace {
+
+// Ensures that the layer does not include any inconsistencies
+int SanityCheckLayer(const vsoc_hwc_layer& layer) {
+ // Check displayFrame
+ if (layer.displayFrame.left > layer.displayFrame.right ||
+ layer.displayFrame.top > layer.displayFrame.bottom) {
+ ALOGE(
+ "%s: Malformed rectangle (displayFrame): [left = %d, right = %d, top = "
+ "%d, bottom = %d]",
+ __FUNCTION__,
+ layer.displayFrame.left,
+ layer.displayFrame.right,
+ layer.displayFrame.top,
+ layer.displayFrame.bottom);
+ return -EINVAL;
+ }
+ // Check sourceCrop
+ if (layer.sourceCrop.left > layer.sourceCrop.right ||
+ layer.sourceCrop.top > layer.sourceCrop.bottom) {
+ ALOGE(
+ "%s: Malformed rectangle (sourceCrop): [left = %d, right = %d, top = "
+ "%d, bottom = %d]",
+ __FUNCTION__,
+ layer.sourceCrop.left,
+ layer.sourceCrop.right,
+ layer.sourceCrop.top,
+ layer.sourceCrop.bottom);
+ return -EINVAL;
+ }
+ const private_handle_t* p_handle =
+ reinterpret_cast<const private_handle_t*>(layer.handle);
+ if (!p_handle) {
+ ALOGE("Layer has a NULL buffer handle");
+ return -EINVAL;
+ }
+ if (layer.sourceCrop.left < 0 || layer.sourceCrop.top < 0 ||
+ layer.sourceCrop.right > p_handle->x_res ||
+ layer.sourceCrop.bottom > p_handle->y_res) {
+ ALOGE(
+ "%s: Invalid sourceCrop for buffer handle: sourceCrop = [left = %d, "
+ "right = %d, top = %d, bottom = %d], handle = [width = %d, height = "
+ "%d]",
+ __FUNCTION__,
+ layer.sourceCrop.left,
+ layer.sourceCrop.right,
+ layer.sourceCrop.top,
+ layer.sourceCrop.bottom,
+ p_handle->x_res,
+ p_handle->y_res);
+ return -EINVAL;
+ }
+ return 0;
+}
+
+bool LayerNeedsScaling(const vsoc_hwc_layer& layer) {
+ int from_w = layer.sourceCrop.right - layer.sourceCrop.left;
+ int from_h = layer.sourceCrop.bottom - layer.sourceCrop.top;
+ int to_w = layer.displayFrame.right - layer.displayFrame.left;
+ int to_h = layer.displayFrame.bottom - layer.displayFrame.top;
+
+ bool not_rot_scale = from_w != to_w || from_h != to_h;
+ bool rot_scale = from_w != to_h || from_h != to_w;
+
+ bool needs_rot = layer.transform & HAL_TRANSFORM_ROT_90;
+
+ return needs_rot ? rot_scale : not_rot_scale;
+}
+
+bool LayerNeedsBlending(const vsoc_hwc_layer& layer) {
+ return layer.blending != HWC_BLENDING_NONE;
+}
+
+bool LayerNeedsAttenuation(const vsoc_hwc_layer& layer) {
+ return layer.blending == HWC_BLENDING_COVERAGE;
+}
+
+struct BufferSpec;
+typedef int (*ConverterFunction)(const BufferSpec& src, const BufferSpec& dst,
+ bool v_flip);
+int DoCopy(const BufferSpec& src, const BufferSpec& dst, bool v_flip);
+int ConvertFromYV12(const BufferSpec& src, const BufferSpec& dst, bool v_flip);
+ConverterFunction GetConverter(uint32_t format) {
+ switch (format) {
+ case HAL_PIXEL_FORMAT_RGBA_8888:
+ case HAL_PIXEL_FORMAT_RGBX_8888:
+ case HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED:
+ return &DoCopy;
+
+ case HAL_PIXEL_FORMAT_YV12:
+ return &ConvertFromYV12;
+
+ // Unsupported formats
+ // TODO(jemoreira): Conversion from these formats should be implemented as
+ // we find evidence of its usage.
+ // case HAL_PIXEL_FORMAT_BGRA_8888:
+
+ // case HAL_PIXEL_FORMAT_RGB_888:
+ // case HAL_PIXEL_FORMAT_RGB_565:
+
+ // case HAL_PIXEL_FORMAT_sRGB_A_8888:
+ // case HAL_PIXEL_FORMAT_sRGB_X_8888:
+
+ // case HAL_PIXEL_FORMAT_Y8:
+ // case HAL_PIXEL_FORMAT_Y16:
+
+ // case HAL_PIXEL_FORMAT_RAW_SENSOR:
+ // case HAL_PIXEL_FORMAT_BLOB:
+
+ // case HAL_PIXEL_FORMAT_YCbCr_420_888:
+ // case HAL_PIXEL_FORMAT_YCbCr_422_SP:
+ // case HAL_PIXEL_FORMAT_YCrCb_420_SP:
+ // case HAL_PIXEL_FORMAT_YCbCr_422_I:
+ default:
+ ALOGW("Unsupported format: 0x%04x, returning null converter function",
+ format);
+ }
+ return NULL;
+}
+
+// Whether we support a given format
+bool IsFormatSupported(uint32_t format) {
+ return GetConverter(format) != NULL;
+}
+
+bool CanCompositeLayer(const vsoc_hwc_layer& layer) {
+ if (layer.handle == NULL) {
+ ALOGW("%s received a layer with a null handler", __FUNCTION__);
+ return false;
+ }
+ int format = reinterpret_cast<const private_handle_t*>(layer.handle)->format;
+ if (!IsFormatSupported(format)) {
+ ALOGD("Unsupported pixel format: 0x%x, doing software composition instead",
+ format);
+ return false;
+ }
+ return true;
+}
+
+/*******************************************************************************
+Libyuv's convert functions only allow the combination of any rotation (multiple
+of 90 degrees) and a vertical flip, but not horizontal flips.
+Surfaceflinger's transformations are expressed in terms of a vertical flip, a
+horizontal flip and/or a single 90 degrees clockwise rotation (see
+NATIVE_WINDOW_TRANSFORM_HINT documentation on system/window.h for more insight).
+The following code allows to turn a horizontal flip into a 180 degrees rotation
+and a vertical flip.
+*******************************************************************************/
+libyuv::RotationMode GetRotationFromTransform(uint32_t transform) {
+ uint32_t rotation =
+ (transform & HAL_TRANSFORM_ROT_90) ? 1 : 0; // 1 * ROT90 bit
+ rotation += (transform & HAL_TRANSFORM_FLIP_H) ? 2 : 0; // 2 * VFLIP bit
+ return static_cast<libyuv::RotationMode>(90 * rotation);
+}
+
+bool GetVFlipFromTransform(uint32_t transform) {
+ // vertical flip xor horizontal flip
+ return ((transform & HAL_TRANSFORM_FLIP_V) >> 1) ^
+ (transform & HAL_TRANSFORM_FLIP_H);
+}
+
+struct BufferSpec {
+ uint8_t* buffer;
+ size_t size;
+ int width;
+ int height;
+ int stride;
+ int crop_x;
+ int crop_y;
+ int crop_width;
+ int crop_height;
+ uint32_t format;
+
+ BufferSpec(uint8_t* buffer, size_t size, int width, int height, int stride)
+ : buffer(buffer),
+ size(size),
+ width(width),
+ height(height),
+ stride(stride),
+ crop_x(0),
+ crop_y(0),
+ crop_width(width),
+ crop_height(height),
+ format(HAL_PIXEL_FORMAT_RGBA_8888) {}
+};
+
+int ConvertFromYV12(const BufferSpec& src, const BufferSpec& dst, bool v_flip) {
+ // use the stride in pixels as the source width
+ int stride_in_pixels = src.stride / formatToBytesPerPixel(src.format);
+
+ // The following calculation of plane offsets and alignments are based on
+ // swiftshader's Sampler::setTextureLevel() implementation
+ // (Renderer/Sampler.cpp:225)
+ uint8_t* src_y = src.buffer;
+ int stride_y = stride_in_pixels;
+ uint8_t* src_v = src_y + stride_y * src.height;
+ int stride_v = VSoCFrameBuffer::align(stride_y / 2, 16);
+ uint8_t* src_u = src_v + stride_v * src.height / 2;
+ int stride_u = VSoCFrameBuffer::align(stride_y / 2, 16);
+
+ // Adjust for crop
+ src_y += src.crop_y * stride_y + src.crop_x;
+ src_v += (src.crop_y / 2) * stride_v + (src.crop_x / 2);
+ src_u += (src.crop_y / 2) * stride_u + (src.crop_x / 2);
+ uint8_t* dst_buffer = dst.buffer + dst.crop_y * dst.stride +
+ dst.crop_x * formatToBytesPerPixel(dst.format);
+
+ // YV12 is the same as I420, with the U and V planes swapped
+ return libyuv::I420ToARGB(src_y, stride_y, src_v, stride_v, src_u, stride_u,
+ dst_buffer, dst.stride, dst.crop_width,
+ v_flip ? -dst.crop_height : dst.crop_height);
+}
+
+int DoConversion(const BufferSpec& src, const BufferSpec& dst, bool v_flip) {
+ return (*GetConverter(src.format))(src, dst, v_flip);
+}
+
+int DoCopy(const BufferSpec& src, const BufferSpec& dst, bool v_flip) {
+ // Point to the upper left corner of the crop rectangle
+ uint8_t* src_buffer = src.buffer + src.crop_y * src.stride +
+ src.crop_x * formatToBytesPerPixel(src.format);
+ uint8_t* dst_buffer = dst.buffer + dst.crop_y * dst.stride +
+ dst.crop_x * formatToBytesPerPixel(dst.format);
+ int width = src.crop_width;
+ int height = src.crop_height;
+
+ if (v_flip) {
+ height = -height;
+ }
+
+ // HAL formats are named based on the order of the pixel componets on the
+ // byte stream, while libyuv formats are named based on the order of those
+ // pixel components in an integer written from left to right. So
+ // libyuv::FOURCC_ARGB is equivalent to HAL_PIXEL_FORMAT_BGRA_8888.
+ return libyuv::ARGBCopy(src_buffer, src.stride, dst_buffer, dst.stride, width,
+ height);
+}
+
+int DoRotation(const BufferSpec& src, const BufferSpec& dst,
+ libyuv::RotationMode rotation, bool v_flip) {
+ // Point to the upper left corner of the crop rectangles
+ uint8_t* src_buffer = src.buffer + src.crop_y * src.stride +
+ src.crop_x * formatToBytesPerPixel(src.format);
+ uint8_t* dst_buffer = dst.buffer + dst.crop_y * dst.stride +
+ dst.crop_x * formatToBytesPerPixel(dst.format);
+ int width = src.crop_width;
+ int height = src.crop_height;
+
+ if (v_flip) {
+ height = -height;
+ }
+
+ return libyuv::ARGBRotate(src_buffer, src.stride, dst_buffer, dst.stride,
+ width, height, rotation);
+}
+
+int DoScaling(const BufferSpec& src, const BufferSpec& dst, bool v_flip) {
+ // Point to the upper left corner of the crop rectangles
+ uint8_t* src_buffer = src.buffer + src.crop_y * src.stride +
+ src.crop_x * formatToBytesPerPixel(src.format);
+ uint8_t* dst_buffer = dst.buffer + dst.crop_y * dst.stride +
+ dst.crop_x * formatToBytesPerPixel(dst.format);
+ int src_width = src.crop_width;
+ int src_height = src.crop_height;
+ int dst_width = dst.crop_width;
+ int dst_height = dst.crop_height;
+
+ if (v_flip) {
+ src_height = -src_height;
+ }
+
+ return libyuv::ARGBScale(src_buffer, src.stride, src_width, src_height,
+ dst_buffer, dst.stride, dst_width, dst_height,
+ libyuv::kFilterBilinear);
+}
+
+int DoAttenuation(const BufferSpec& src, const BufferSpec& dest, bool v_flip) {
+ // Point to the upper left corner of the crop rectangles
+ uint8_t* src_buffer = src.buffer + src.crop_y * src.stride +
+ src.crop_x * formatToBytesPerPixel(src.format);
+ uint8_t* dst_buffer = dest.buffer + dest.crop_y * dest.stride +
+ dest.crop_x * formatToBytesPerPixel(dest.format);
+ int width = dest.crop_width;
+ int height = dest.crop_height;
+
+ if (v_flip) {
+ height = -height;
+ }
+
+ return libyuv::ARGBAttenuate(src_buffer, src.stride, dst_buffer, dest.stride,
+ width, height);
+}
+
+int DoBlending(const BufferSpec& src, const BufferSpec& dest, bool v_flip) {
+ // Point to the upper left corner of the crop rectangles
+ uint8_t* src_buffer = src.buffer + src.crop_y * src.stride +
+ src.crop_x * formatToBytesPerPixel(src.format);
+ uint8_t* dst_buffer = dest.buffer + dest.crop_y * dest.stride +
+ dest.crop_x * formatToBytesPerPixel(dest.format);
+ int width = dest.crop_width;
+ int height = dest.crop_height;
+
+ if (v_flip) {
+ height = -height;
+ }
+
+ // libyuv's ARGB format is hwcomposer's BGRA format, since blending only cares
+ // for the position of alpha in the pixel and not the position of the colors
+ // this function is perfectly usable.
+ return libyuv::ARGBBlend(src_buffer, src.stride, dst_buffer, dest.stride,
+ dst_buffer, dest.stride, width, height);
+}
+
+} // namespace
+
+// Returns a handle to the appropriate framebuffer to use:
+// - the one provided by surfaceflinger if it is doing any GLES composition
+// - the next hwc-only framebuffer otherwise
+// Takes care of rotating the hwc-only framebuffers
+buffer_handle_t VSoCComposer::FindFrameBuffer(int num_layers,
+ vsoc_hwc_layer* layers) {
+ buffer_handle_t* fb_handle = NULL;
+ bool use_hwc_fb = true;
+ // The framebuffer target is usually the last layer in the list, so iterate in
+ // reverse
+ for (int idx = num_layers - 1; idx >= 0; --idx) {
+ if (IS_TARGET_FRAMEBUFFER(layers[idx].compositionType)) {
+ fb_handle = &layers[idx].handle;
+ } else if (layers[idx].compositionType != HWC_OVERLAY) {
+ use_hwc_fb = false;
+ // Just in case the FB target was not found yet
+ if (fb_handle) break;
+ }
+ }
+ if (use_hwc_fb && !hwc_framebuffers_.empty()) {
+ fb_handle = &hwc_framebuffers_[next_hwc_framebuffer_];
+ next_hwc_framebuffer_ =
+ (next_hwc_framebuffer_ + 1) % hwc_framebuffers_.size();
+ }
+
+ return *fb_handle;
+}
+
+void VSoCComposer::CompositeLayer(vsoc_hwc_layer* src_layer,
+ buffer_handle_t dst_handle) {
+ libyuv::RotationMode rotation =
+ GetRotationFromTransform(src_layer->transform);
+
+ const private_handle_t* src_priv_handle =
+ reinterpret_cast<const private_handle_t*>(src_layer->handle);
+ const private_handle_t* dst_priv_handle =
+ reinterpret_cast<const private_handle_t*>(dst_handle);
+
+ bool needs_conversion = src_priv_handle->format != dst_priv_handle->format;
+ bool needs_scaling = LayerNeedsScaling(*src_layer);
+ bool needs_rotation = rotation != libyuv::kRotate0;
+ bool needs_transpose = needs_rotation && rotation != libyuv::kRotate180;
+ bool needs_vflip = GetVFlipFromTransform(src_layer->transform);
+ bool needs_attenuation = LayerNeedsAttenuation(*src_layer);
+ bool needs_blending = LayerNeedsBlending(*src_layer);
+ bool needs_copy = !(needs_conversion || needs_scaling || needs_rotation ||
+ needs_vflip || needs_attenuation || needs_blending);
+
+ uint8_t* src_buffer;
+ uint8_t* dst_buffer;
+ int retval = gralloc_module_->lock(
+ gralloc_module_, src_layer->handle, GRALLOC_USAGE_SW_READ_OFTEN, 0, 0,
+ src_priv_handle->x_res, src_priv_handle->y_res,
+ reinterpret_cast<void**>(&src_buffer));
+ if (retval) {
+ ALOGE("Got error code %d from lock function", retval);
+ return;
+ }
+ retval = gralloc_module_->lock(gralloc_module_, dst_handle,
+ GRALLOC_USAGE_SW_WRITE_OFTEN, 0, 0,
+ dst_priv_handle->x_res, dst_priv_handle->y_res,
+ reinterpret_cast<void**>(&dst_buffer));
+ if (retval) {
+ ALOGE("Got error code %d from lock function", retval);
+ // TODO(jemoreira): Use a lock_guard-like object.
+ gralloc_module_->unlock(gralloc_module_, src_priv_handle);
+ return;
+ }
+
+ BufferSpec src_layer_spec(src_buffer, src_priv_handle->total_size,
+ src_priv_handle->x_res, src_priv_handle->y_res,
+ src_priv_handle->stride_in_pixels *
+ formatToBytesPerPixel(src_priv_handle->format));
+ src_layer_spec.crop_x = src_layer->sourceCrop.left;
+ src_layer_spec.crop_y = src_layer->sourceCrop.top;
+ src_layer_spec.crop_width =
+ src_layer->sourceCrop.right - src_layer->sourceCrop.left;
+ src_layer_spec.crop_height =
+ src_layer->sourceCrop.bottom - src_layer->sourceCrop.top;
+ src_layer_spec.format = src_priv_handle->format;
+
+ BufferSpec dst_layer_spec(dst_buffer, dst_priv_handle->total_size,
+ dst_priv_handle->x_res, dst_priv_handle->y_res,
+ dst_priv_handle->stride_in_pixels *
+ formatToBytesPerPixel(dst_priv_handle->format));
+ dst_layer_spec.crop_x = src_layer->displayFrame.left;
+ dst_layer_spec.crop_y = src_layer->displayFrame.top;
+ dst_layer_spec.crop_width =
+ src_layer->displayFrame.right - src_layer->displayFrame.left;
+ dst_layer_spec.crop_height =
+ src_layer->displayFrame.bottom - src_layer->displayFrame.top;
+ dst_layer_spec.format = dst_priv_handle->format;
+
+ // Add the destination layer to the bottom of the buffer stack
+ std::vector<BufferSpec> dest_buffer_stack(1, dst_layer_spec);
+
+ // If more than operation is to be performed, a temporary buffer is needed for
+ // each additional operation
+
+ // N operations need N destination buffers, the destination layer (the
+ // framebuffer) is one of them, so only N-1 temporary buffers are needed.
+ // Vertical flip is not taken into account because it can be done together
+ // with any other operation.
+ int needed_tmp_buffers = (needs_conversion ? 1 : 0) +
+ (needs_scaling ? 1 : 0) + (needs_rotation ? 1 : 0) +
+ (needs_attenuation ? 1 : 0) +
+ (needs_blending ? 1 : 0) + (needs_copy ? 1 : 0) - 1;
+
+ int x_res = src_layer->displayFrame.right - src_layer->displayFrame.left;
+ int y_res = src_layer->displayFrame.bottom - src_layer->displayFrame.top;
+ size_t output_frame_size =
+ x_res * y_res * formatToBytesPerPixel(dst_priv_handle->format);
+ while (needed_tmp_buffers > 0) {
+ BufferSpec tmp(
+ RotateTmpBuffer(needed_tmp_buffers), output_frame_size, x_res, y_res,
+ // There should be no danger of overflow aligning the stride because
+ // these sizes are taken from the displayFrame rectangle which is always
+ // smaller than the framebuffer, the framebuffer in turn has aligned
+ // stride and these buffers are the size of the framebuffer.
+ VSoCFrameBuffer::align(
+ x_res * formatToBytesPerPixel(dst_priv_handle->format), 16));
+ dest_buffer_stack.push_back(tmp);
+ needed_tmp_buffers--;
+ }
+
+ // Conversion and scaling should always be the first operations, so that every
+ // other operation works on equally sized frames (garanteed to fit in the tmp
+ // buffers)
+
+ // TODO(jemoreira): We are converting to ARGB as the first step under the
+ // assumption that scaling ARGB is faster than scaling I420 (the most common).
+ // This should be confirmed with testing.
+ if (needs_conversion) {
+ BufferSpec& dst_buffer_spec = dest_buffer_stack.back();
+ if (needs_scaling || needs_transpose) {
+ // If a rotation or a scaling operation are needed the dimensions at the
+ // top of the buffer stack are wrong (wrong sizes for scaling, swapped
+ // width and height for 90 and 270 rotations).
+ // Make width and height match the crop sizes on the source
+ int src_width = src_layer_spec.crop_width;
+ int src_height = src_layer_spec.crop_height;
+ int dst_stride = VSoCFrameBuffer::align(
+ src_width * formatToBytesPerPixel(dst_priv_handle->format), 16);
+ size_t needed_size = dst_stride * src_height;
+ dst_buffer_spec.width = src_width;
+ dst_buffer_spec.height = src_height;
+ // Ajust the stride accordingly
+ dst_buffer_spec.stride = dst_stride;
+ // Crop sizes also need to be adjusted
+ dst_buffer_spec.crop_width = src_width;
+ dst_buffer_spec.crop_height = src_height;
+ dst_buffer_spec.size = needed_size;
+ // crop_x and y are fine at 0, format is already set to match destination
+
+ // In case of a scale, the source frame may be bigger than the default tmp
+ // buffer size
+ if (needed_size > tmp_buffer_.size() / kNumTmpBufferPieces) {
+ dst_buffer_spec.buffer = GetSpecialTmpBuffer(needed_size);
+ }
+ }
+ retval = DoConversion(src_layer_spec, dst_buffer_spec, needs_vflip);
+ if (retval) {
+ ALOGE("Got error code %d from DoConversion function", retval);
+ }
+ needs_vflip = false;
+ src_layer_spec = dst_buffer_spec;
+ dest_buffer_stack.pop_back();
+ }
+
+ if (needs_scaling) {
+ BufferSpec& dst_buffer_spec = dest_buffer_stack.back();
+ if (needs_transpose) {
+ // If a rotation is needed, the temporary buffer has the correct size but
+ // needs to be transposed and have its stride updated accordingly. The
+ // crop sizes also needs to be transposed, but not the x and y since they
+ // are both zero in a temporary buffer (and it is a temporary buffer
+ // because a rotation will be performed next).
+ std::swap(dst_buffer_spec.width, dst_buffer_spec.height);
+ std::swap(dst_buffer_spec.crop_width, dst_buffer_spec.crop_height);
+ // TODO (jemoreira): Aligment (To align here may cause the needed size to
+ // be bigger than the buffer, so care should be taken)
+ dst_buffer_spec.stride = dst_buffer_spec.width *
+ formatToBytesPerPixel(dst_priv_handle->format);
+ }
+ retval = DoScaling(src_layer_spec, dst_buffer_spec, needs_vflip);
+ needs_vflip = false;
+ if (retval) {
+ ALOGE("Got error code %d from DoScaling function", retval);
+ }
+ src_layer_spec = dst_buffer_spec;
+ dest_buffer_stack.pop_back();
+ }
+
+ if (needs_rotation) {
+ retval = DoRotation(src_layer_spec, dest_buffer_stack.back(), rotation,
+ needs_vflip);
+ needs_vflip = false;
+ if (retval) {
+ ALOGE("Got error code %d from DoTransform function", retval);
+ }
+ src_layer_spec = dest_buffer_stack.back();
+ dest_buffer_stack.pop_back();
+ }
+
+ if (needs_attenuation) {
+ retval =
+ DoAttenuation(src_layer_spec, dest_buffer_stack.back(), needs_vflip);
+ needs_vflip = false;
+ if (retval) {
+ ALOGE("Got error code %d from DoBlending function", retval);
+ }
+ src_layer_spec = dest_buffer_stack.back();
+ dest_buffer_stack.pop_back();
+ }
+
+ if (needs_copy) {
+ retval = DoCopy(src_layer_spec, dest_buffer_stack.back(), needs_vflip);
+ needs_vflip = false;
+ if (retval) {
+ ALOGE("Got error code %d from DoBlending function", retval);
+ }
+ src_layer_spec = dest_buffer_stack.back();
+ dest_buffer_stack.pop_back();
+ }
+
+ // Blending (if needed) should always be the last operation, so that it reads
+ // and writes in the destination layer and not some temporary buffer.
+ if (needs_blending) {
+ retval = DoBlending(src_layer_spec, dest_buffer_stack.back(), needs_vflip);
+ needs_vflip = false;
+ if (retval) {
+ ALOGE("Got error code %d from DoBlending function", retval);
+ }
+ // Don't need to assign destination to source in the last one
+ dest_buffer_stack.pop_back();
+ }
+
+ gralloc_module_->unlock(gralloc_module_, src_priv_handle);
+ gralloc_module_->unlock(gralloc_module_, dst_priv_handle);
+}
+
+/* static */ const int VSoCComposer::kNumTmpBufferPieces = 2;
+
+VSoCComposer::VSoCComposer(int64_t vsync_base_timestamp, int32_t vsync_period_ns)
+ : BaseComposer(vsync_base_timestamp, vsync_period_ns),
+ tmp_buffer_(kNumTmpBufferPieces *
+ VSoCFrameBuffer::getInstance().bufferSize()),
+ next_hwc_framebuffer_(0) {
+ hw_get_module(GRALLOC_HARDWARE_MODULE_ID,
+ reinterpret_cast<const hw_module_t**>(&gralloc_module_));
+ gralloc_module_->common.methods->open(
+ reinterpret_cast<const hw_module_t*>(gralloc_module_),
+ GRALLOC_HARDWARE_GPU0,
+ reinterpret_cast<hw_device_t**>(&gralloc_dev_));
+ for (int i = 0; i < VSoCFrameBuffer::kNumHwcBuffers; ++i) {
+ buffer_handle_t tmp;
+ gralloc_dev_->alloc_hwc_framebuffer(
+ reinterpret_cast<alloc_device_t*>(gralloc_dev_), &tmp);
+ hwc_framebuffers_.push_back(tmp);
+ }
+}
+
+VSoCComposer::~VSoCComposer() {
+ // Free the hwc fb handles
+ for (int idx = 0; idx < hwc_framebuffers_.size(); ++idx) {
+ gralloc_dev_->device.free(reinterpret_cast<alloc_device_t*>(gralloc_dev_),
+ hwc_framebuffers_[idx]);
+ }
+
+ // close devices
+ gralloc_dev_->device.common.close(reinterpret_cast<hw_device_t*>(gralloc_dev_));
+}
+
+int VSoCComposer::PrepareLayers(size_t num_layers, vsoc_hwc_layer* layers) {
+ int composited_layers_count = 0;
+
+ // Loop over layers in inverse order of z-index
+ for (size_t layer_index = num_layers; layer_index > 0;) {
+ // Decrement here to be able to compare unsigned integer with 0 in the
+ // loop condition
+ --layer_index;
+ if (IS_TARGET_FRAMEBUFFER(layers[layer_index].compositionType)) {
+ continue;
+ }
+ if (layers[layer_index].flags & HWC_SKIP_LAYER) {
+ continue;
+ }
+ if (layers[layer_index].compositionType == HWC_BACKGROUND) {
+ layers[layer_index].compositionType = HWC_FRAMEBUFFER;
+ continue;
+ }
+ layers[layer_index].compositionType = HWC_OVERLAY;
+ // Hwcomposer cannot draw below software-composed layers, so we need
+ // to mark those HWC_FRAMEBUFFER as well.
+ for (size_t top_idx = layer_index + 1; top_idx < num_layers; ++top_idx) {
+ // layers marked as skip are in a state that makes them unreliable to
+ // read, so it's best to assume they cover the whole screen
+ if (layers[top_idx].flags & HWC_SKIP_LAYER ||
+ (layers[top_idx].compositionType == HWC_FRAMEBUFFER &&
+ LayersOverlap(layers[layer_index], layers[top_idx]))) {
+ layers[layer_index].compositionType = HWC_FRAMEBUFFER;
+ break;
+ }
+ }
+ if (layers[layer_index].compositionType == HWC_OVERLAY &&
+ !CanCompositeLayer(layers[layer_index])) {
+ layers[layer_index].compositionType = HWC_FRAMEBUFFER;
+ }
+ if (layers[layer_index].compositionType == HWC_OVERLAY) {
+ ++composited_layers_count;
+ }
+ }
+ return composited_layers_count;
+}
+
+int VSoCComposer::SetLayers(size_t num_layers, vsoc_hwc_layer* layers) {
+ int targetFbs = 0;
+ buffer_handle_t fb_handle = FindFrameBuffer(num_layers, layers);
+ if (!fb_handle) {
+ ALOGE("%s: framebuffer handle is null", __FUNCTION__);
+ return -1;
+ }
+ // TODO(jemoreira): Lock all HWC_OVERLAY layers and the framebuffer before
+ // this loop and unlock them after. The way it's done now causes the target
+ // framebuffer to be locked and unlocked many times, if regions are
+ // implemented it will also be true for every layer that covers more than one
+ // region.
+ for (size_t idx = 0; idx < num_layers; idx++) {
+ if (IS_TARGET_FRAMEBUFFER(layers[idx].compositionType)) {
+ ++targetFbs;
+ } else if (layers[idx].compositionType == HWC_OVERLAY &&
+ !(layers[idx].flags & HWC_SKIP_LAYER)) {
+ if (SanityCheckLayer(layers[idx])) {
+ ALOGE("Layer (%d) failed sanity check", idx);
+ return -EINVAL;
+ }
+ CompositeLayer(&layers[idx], fb_handle);
+ }
+ }
+ if (targetFbs != 1) {
+ ALOGW("Saw %d layers, posted=%d", num_layers, targetFbs);
+ }
+ return PostFrameBuffer(fb_handle);
+}
+
+uint8_t* VSoCComposer::RotateTmpBuffer(unsigned int order) {
+ return &tmp_buffer_[(order % kNumTmpBufferPieces) * tmp_buffer_.size() /
+ kNumTmpBufferPieces];
+}
+
+uint8_t* VSoCComposer::GetSpecialTmpBuffer(size_t needed_size) {
+ special_tmp_buffer_.resize(needed_size);
+ return &special_tmp_buffer_[0];
+}
+
+} // namespace cvd
diff --git a/guest/hals/hwcomposer/legacy/vsoc_composer.h b/guest/hals/hwcomposer/legacy/vsoc_composer.h
new file mode 100644
index 0000000..cd455a5
--- /dev/null
+++ b/guest/hals/hwcomposer/legacy/vsoc_composer.h
@@ -0,0 +1,51 @@
+#pragma once
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#include <hardware/gralloc.h>
+#include "guest/hals/gralloc/legacy/gralloc_vsoc_priv.h"
+#include "hwcomposer_common.h"
+#include "base_composer.h"
+
+#include <vector>
+
+namespace cvd {
+
+class VSoCComposer : public BaseComposer {
+ public:
+ VSoCComposer(int64_t vsync_base_timestamp, int32_t vsync_period_ns);
+ ~VSoCComposer();
+
+ // override
+ int PrepareLayers(size_t num_layers, vsoc_hwc_layer* layers);
+ // override
+ int SetLayers(size_t num_layers, vsoc_hwc_layer* layers);
+
+ protected:
+ static const int kNumTmpBufferPieces;
+ uint8_t* RotateTmpBuffer(unsigned int order);
+ uint8_t* GetSpecialTmpBuffer(size_t needed_size);
+ buffer_handle_t FindFrameBuffer(int num_layers, vsoc_hwc_layer* layers);
+ void CompositeLayer(vsoc_hwc_layer* src_layer, buffer_handle_t dst_layer);
+ std::vector<uint8_t> tmp_buffer_;
+ std::vector<uint8_t> special_tmp_buffer_;
+ const gralloc_module_t* gralloc_module_;
+ priv_alloc_device_t* gralloc_dev_;
+ std::vector<buffer_handle_t> hwc_framebuffers_;
+ int next_hwc_framebuffer_;
+};
+
+} // namespace cvd
diff --git a/guest/hals/lights/Android.mk b/guest/hals/lights/Android.mk
new file mode 100644
index 0000000..f5e2082
--- /dev/null
+++ b/guest/hals/lights/Android.mk
@@ -0,0 +1,35 @@
+# Copyright (C) 2017 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.
+
+LOCAL_PATH := $(call my-dir)
+
+# HAL module implemenation, not prelinked and stored in
+# hw/<LIGHTS_HARDWARE_MODULE_ID>.<ro.hardware>.so
+include $(CLEAR_VARS)
+ifeq (0, $(shell test $(PLATFORM_SDK_VERSION) -ge 21; echo $$?))
+LOCAL_MODULE_RELATIVE_PATH := hw
+else
+LOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)/hw
+endif
+LOCAL_MULTILIB := first
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_C_INCLUDES := device/google/cuttlefish_common
+LOCAL_SHARED_LIBRARIES := liblog libcutils
+LOCAL_SRC_FILES := lights_vsoc.c
+LOCAL_MODULE := lights.vsoc
+LOCAL_CFLAGS += -DLIGHT_BACKLIGHT -DLOG_TAG=\"VSoC-lights\" $(VSOC_VERSION_CFLAGS)
+LOCAL_VENDOR_MODULE := true
+include $(BUILD_SHARED_LIBRARY)
+
diff --git a/guest/hals/lights/lights_vsoc.c b/guest/hals/lights/lights_vsoc.c
new file mode 100644
index 0000000..d1c9c10
--- /dev/null
+++ b/guest/hals/lights/lights_vsoc.c
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#include <cutils/log.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <hardware/lights.h>
+#include <pthread.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "guest/libs/platform_support/api_level_fixes.h"
+
+static int set_light(struct light_device_t* dev,
+ struct light_state_t const* state) {
+ ALOGI("%s: dev %p state %p", __FUNCTION__, dev, state);
+ if (state) {
+ ALOGI("%s: state value %d\n", __FUNCTION__, state->color);
+ }
+ return 0;
+}
+
+static int close_lights(struct light_device_t* dev) {
+ free(dev);
+ return 0;
+}
+
+static int open_lights(const struct hw_module_t* module,
+ char const* name __unused, struct hw_device_t** device) {
+ struct light_device_t* dev = malloc(sizeof(struct light_device_t));
+ if (dev == NULL) {
+ return -EINVAL;
+ }
+ memset(dev, 0, sizeof(*dev));
+
+ dev->common.tag = HARDWARE_DEVICE_TAG;
+ dev->common.version = 0;
+ dev->common.module = (struct hw_module_t*)module;
+ dev->common.close = (int (*)(struct hw_device_t*))close_lights;
+ dev->set_light = set_light;
+ *device = (struct hw_device_t*)dev;
+ return 0;
+}
+
+static struct hw_module_methods_t lights_module_methods = {
+ VSOC_STATIC_INITIALIZER(open) open_lights,
+};
+
+struct hw_module_t HAL_MODULE_INFO_SYM = {
+ VSOC_STATIC_INITIALIZER(tag) HARDWARE_MODULE_TAG,
+ VSOC_STATIC_INITIALIZER(version_major) 1,
+ VSOC_STATIC_INITIALIZER(version_minor) 0,
+ VSOC_STATIC_INITIALIZER(id) LIGHTS_HARDWARE_MODULE_ID,
+ VSOC_STATIC_INITIALIZER(name) "Android GCE lights Module",
+ VSOC_STATIC_INITIALIZER(author) "Google",
+ VSOC_STATIC_INITIALIZER(methods) & lights_module_methods,
+};
diff --git a/guest/hals/power/Android.mk b/guest/hals/power/Android.mk
new file mode 100644
index 0000000..3a7cfdb
--- /dev/null
+++ b/guest/hals/power/Android.mk
@@ -0,0 +1,38 @@
+# Copyright (C) 2017 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.
+
+LOCAL_PATH := $(call my-dir)
+
+# HAL module implementation stored in
+# hw/<POWERS_HARDWARE_MODULE_ID>.<ro.hardware>.so
+include $(CLEAR_VARS)
+
+LOCAL_CFLAGS += $(VSOC_VERSION_CFLAGS)
+
+LOCAL_C_INCLUDES := device/google/cuttlefish_common
+
+ifeq (0, $(shell test $(PLATFORM_SDK_VERSION) -ge 21; echo $$?))
+LOCAL_MODULE_RELATIVE_PATH := hw
+else
+LOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)/hw
+endif
+LOCAL_MULTILIB := first
+
+LOCAL_SHARED_LIBRARIES := liblog libcutils
+LOCAL_SRC_FILES := power.c
+LOCAL_MODULE := power.vsoc
+LOCAL_MODULE_TAGS := optional
+LOCAL_VENDOR_MODULE := true
+include $(BUILD_SHARED_LIBRARY)
+
diff --git a/guest/hals/power/power.c b/guest/hals/power/power.c
new file mode 100644
index 0000000..5bd3b0a
--- /dev/null
+++ b/guest/hals/power/power.c
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2017 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.
+ *
+ * Based on the HiKeyPowerHAL
+ */
+
+#include <pthread.h>
+#include <semaphore.h>
+#include <cutils/properties.h>
+
+#define LOG_TAG "VSoCPowerHAL"
+#include <utils/Log.h>
+
+#include <hardware/hardware.h>
+#include <hardware/power.h>
+#include "guest/libs/platform_support/api_level_fixes.h"
+
+struct vsoc_power_module {
+ struct power_module base;
+ pthread_mutex_t lock;
+};
+
+
+#if VSOC_PLATFORM_SDK_AFTER(N_MR1)
+
+static void vsoc_power_set_feature(struct power_module __unused *module,
+ feature_t __unused hint,
+ int __unused state) {
+ return;
+}
+
+#elif VSOC_PLATFORM_SDK_AFTER(L)
+
+static void vsoc_power_set_feature(struct power_module __unused *module,
+ power_hint_t __unused hint,
+ int __unused state) {
+ return;
+}
+
+#endif
+
+static void vsoc_power_hint(struct power_module __unused *module,
+ power_hint_t __unused hint,
+ void __unused *data) {
+ return;
+}
+
+static void vsoc_power_set_interactive(struct power_module __unused *module,
+ int __unused on) {
+ return;
+}
+
+static void vsoc_power_init(struct power_module __unused *module) {
+ return;
+}
+
+
+/*
+ * The power module wasn't opened at all in versions prior to 'O'. The module
+ * pointer was reinterpretd as a device pointer. 'O' retains this behavior when
+ * open is set to NULL. This code is using that mode.
+ * For reference,
+ * 'O': hardware/interfaces/power/1.0/default/Power.cpp
+ * prior: frameworks/base/services/core/jni/com_android_server_power_PowerManagerService.cpp
+ */
+static struct hw_module_methods_t power_module_methods = {
+ VSOC_STATIC_INITIALIZER(open) NULL
+};
+
+
+struct vsoc_power_module HAL_MODULE_INFO_SYM = {
+ VSOC_STATIC_INITIALIZER(base) {
+ .common = {
+ VSOC_STATIC_INITIALIZER(tag) HARDWARE_MODULE_TAG,
+ VSOC_STATIC_INITIALIZER(module_api_version) POWER_MODULE_API_VERSION_0_2,
+ VSOC_STATIC_INITIALIZER(hal_api_version) HARDWARE_HAL_API_VERSION,
+ VSOC_STATIC_INITIALIZER(id) POWER_HARDWARE_MODULE_ID,
+ VSOC_STATIC_INITIALIZER(name) "VSoC Power HAL",
+ VSOC_STATIC_INITIALIZER(author) "The Android Open Source Project",
+ VSOC_STATIC_INITIALIZER(methods) &power_module_methods,
+ },
+ VSOC_STATIC_INITIALIZER(init) vsoc_power_init,
+ VSOC_STATIC_INITIALIZER(setInteractive) vsoc_power_set_interactive,
+ VSOC_STATIC_INITIALIZER(powerHint) vsoc_power_hint,
+ // Before L_MR1 we don't have setFeature
+#if VSOC_PLATFORM_SDK_AFTER(L)
+ VSOC_STATIC_INITIALIZER(setFeature) vsoc_power_set_feature,
+#endif
+ },
+
+ VSOC_STATIC_INITIALIZER(lock) PTHREAD_MUTEX_INITIALIZER,
+};
+
diff --git a/guest/hals/ril/Android.mk b/guest/hals/ril/Android.mk
new file mode 100644
index 0000000..de5e079
--- /dev/null
+++ b/guest/hals/ril/Android.mk
@@ -0,0 +1,42 @@
+# Copyright (C) 2017 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.
+
+# RIL (Radio Interface Layer) and basic modem emulator for GCE.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:= \
+ vsoc_ril.cpp
+
+LOCAL_SHARED_LIBRARIES := \
+ liblog \
+ libcutils \
+ libutils \
+ libril \
+ libcuttlefish_fs \
+ cuttlefish_net \
+ cuttlefish_auto_resources \
+ libnetutils \
+ libbase
+
+LOCAL_C_INCLUDES := bionic device/google/cuttlefish_common
+LOCAL_MODULE:= libvsoc-ril
+LOCAL_MODULE_TAGS := optional
+LOCAL_VENDOR_MODULE := true
+LOCAL_CFLAGS += $(VSOC_VERSION_CFLAGS)
+
+include $(BUILD_SHARED_LIBRARY)
+
diff --git a/guest/hals/ril/vsoc_ril.cpp b/guest/hals/ril/vsoc_ril.cpp
new file mode 100644
index 0000000..70c6633
--- /dev/null
+++ b/guest/hals/ril/vsoc_ril.cpp
@@ -0,0 +1,2532 @@
+/*
+** Copyright 2017, 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 ioogle/s 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.
+*/
+
+#include "guest/hals/ril/vsoc_ril.h"
+
+#include <cutils/properties.h>
+#include <netutils/ifc.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include <time.h>
+
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "common/libs/net/netlink_client.h"
+#include "common/libs/net/network_interface.h"
+#include "common/libs/net/network_interface_manager.h"
+#include "guest/libs/platform_support/api_level_fixes.h"
+
+#define GCE_RIL_VERSION_STRING "Android VSoC RIL 1.0"
+
+/* Modem Technology bits */
+#define MDM_GSM 0x01
+#define MDM_WCDMA 0x02
+#define MDM_CDMA 0x04
+#define MDM_EVDO 0x08
+#define MDM_LTE 0x10
+
+#if VSOC_PLATFORM_SDK_BEFORE(K)
+#define RADIO_TECH_3GPP 1
+#define RADIO_TECH_3GPP2 2
+#endif
+
+typedef enum {
+ SIM_ABSENT = 0,
+ SIM_NOT_READY = 1,
+ SIM_READY = 2, // SIM_READY means the radio state is RADIO_STATE_SIM_READY
+ SIM_PIN = 3,
+ SIM_PUK = 4,
+ SIM_NETWORK_PERSONALIZATION = 5,
+ RUIM_ABSENT = 6,
+ RUIM_NOT_READY = 7,
+ RUIM_READY = 8,
+ RUIM_PIN = 9,
+ RUIM_PUK = 10,
+ RUIM_NETWORK_PERSONALIZATION = 11
+} SIM_Status;
+
+static const struct RIL_Env* gce_ril_env;
+
+static const struct timeval TIMEVAL_SIMPOLL = {3, 0};
+
+static time_t gce_ril_start_time;
+
+static void pollSIMState(void* param);
+
+RIL_RadioState gRadioPowerState = RADIO_STATE_OFF;
+
+struct DataCall {
+ enum AllowedAuthenticationType { kNone = 0, kPap = 1, kChap = 2, kBoth = 3 };
+
+ enum ConnectionType {
+ kConnTypeIPv4,
+ kConnTypeIPv6,
+ kConnTypeIPv4v6,
+ kConnTypePPP
+ };
+
+ enum LinkState {
+ kLinkStateInactive = 0,
+ kLinkStateDown = 1,
+ kLinkStateUp = 2,
+ };
+
+ RIL_RadioTechnology technology_;
+ RIL_DataProfile profile_;
+ std::string access_point_;
+ std::string username_;
+ std::string password_;
+ AllowedAuthenticationType auth_type_;
+ ConnectionType connection_type_;
+ LinkState link_state_;
+ RIL_DataCallFailCause fail_cause_;
+ std::string other_properties_;
+};
+
+static std::string gSimPIN = "0000";
+static const std::string gSimPUK = "11223344";
+static int gSimPINAttempts = 0;
+static const int gSimPINAttemptsMax = 3;
+static SIM_Status gSimStatus = SIM_NOT_READY;
+
+namespace {
+// Holds the address and prefix from the latest DHCP query.
+char g_address_prefix[80];
+
+// Holds the dotted decimal IP address of the gateway from the latest DHCP
+// query.
+char g_gateway[80];
+
+// Holds the IP address of the DNS server.
+char g_dns1[80];
+}; // namespace
+
+// Oddly "part of the VNDK" doesn't translate into "has a header."
+extern "C" {
+int do_dhcp(char* iface);
+void get_dhcp_info(uint32_t* ipaddr, uint32_t* gateway, uint32_t* prefixLength,
+ uint32_t* dns1, uint32_t* dns2, uint32_t* server,
+ uint32_t* lease);
+};
+
+// SetUpNetworkInterface configures IP and Broadcast addresses on a RIL
+// controlled network interface by using dhcp code that is part of the VNDK.
+// This call returns true, if operation was successful.
+bool SetUpNetworkInterface(const char* interface_name) {
+ if (ifc_init()) {
+ ALOGE("%s disabled because ifc_init failed", interface_name);
+ return false;
+ }
+ // do_dhcp doesn't really write to this
+ if (do_dhcp(const_cast<char*>(interface_name))) {
+ ALOGE("%s disabled because DHCP failed", interface_name);
+ return false;
+ }
+ struct in_addr ipaddr, gateway, dns1;
+ uint32_t prefix_length, unused;
+ get_dhcp_info(&ipaddr.s_addr, &gateway.s_addr, &prefix_length, &dns1.s_addr,
+ &unused, &unused, &unused);
+ snprintf(g_address_prefix, sizeof(g_address_prefix), "%s/%d",
+ inet_ntoa(ipaddr), prefix_length);
+ // snprintf always null terminates, strncpy doesn't
+ snprintf(g_gateway, sizeof(g_gateway), "%s", inet_ntoa(gateway));
+ snprintf(g_dns1, sizeof(g_dns1), "%s", inet_ntoa(dns1));
+ return true;
+}
+
+namespace {
+// This gets cast to a char* because some of the interfaces are lazy about
+// const. Declaring it this way ensures that we'll crash if someone tries to
+// write to the interface name.
+const char g_ril_interface[] = "rmnet0";
+}; // namespace
+
+// TearDownNetworkInterface disables network interface.
+// This call returns true, if operation was successful.
+bool TearDownNetworkInterface() {
+ auto nm(cvd::NetworkInterfaceManager::New(nullptr));
+ auto ni(nm->Open("rmnet0"));
+
+ if (ni) {
+ ni->SetOperational(false);
+ bool res = nm->ApplyChanges(*ni);
+ if (!res) ALOGE("Could not disable %s", g_ril_interface);
+ return res;
+ }
+ return false;
+}
+
+static int gNextDataCallId = 8;
+static std::map<int, DataCall> gDataCalls;
+static bool gRilConnected = false;
+
+static int request_or_send_data_calllist(RIL_Token* t) {
+#if VSOC_PLATFORM_SDK_AFTER(N_MR1)
+ RIL_Data_Call_Response_v11* responses =
+ new RIL_Data_Call_Response_v11[gDataCalls.size()];
+#else
+ RIL_Data_Call_Response_v6* responses =
+ new RIL_Data_Call_Response_v6[gDataCalls.size()];
+#endif
+
+ int index = 0;
+
+ ALOGV("Query data call list: %zu data calls tracked.", gDataCalls.size());
+
+ for (std::map<int, DataCall>::iterator iter = gDataCalls.begin();
+ iter != gDataCalls.end(); ++iter, ++index) {
+ responses[index].status = iter->second.fail_cause_;
+ responses[index].suggestedRetryTime = -1;
+ responses[index].cid = iter->first;
+ responses[index].active = iter->second.link_state_;
+
+ switch (iter->second.connection_type_) {
+ case DataCall::kConnTypeIPv4:
+ responses[index].type = (char*)"IP";
+ break;
+ case DataCall::kConnTypeIPv6:
+ responses[index].type = (char*)"IPV6";
+ break;
+ case DataCall::kConnTypeIPv4v6:
+ responses[index].type = (char*)"IPV4V6";
+ break;
+ case DataCall::kConnTypePPP:
+ responses[index].type = (char*)"PPP";
+ break;
+ default:
+ responses[index].type = (char*)"IP";
+ break;
+ }
+
+ responses[index].ifname = const_cast<char*>(g_ril_interface);
+ responses[index].addresses = g_address_prefix;
+ responses[index].dnses = g_dns1;
+ responses[index].gateways = g_gateway;
+#if VSOC_PLATFORM_SDK_AFTER(N_MR1)
+ responses[index].pcscf = (char*)"";
+ responses[index].mtu = 1440;
+#endif
+ }
+
+ bool new_conn_state = (gDataCalls.size() > 0);
+
+ if (gRilConnected != new_conn_state) {
+ time_t curr_time;
+ time(&curr_time);
+ double diff_in_secs = difftime(curr_time, gce_ril_start_time);
+
+ gRilConnected = new_conn_state;
+
+ if (new_conn_state) {
+ ALOGV("MOBILE_DATA_CONNECTED %.2lf seconds", diff_in_secs);
+ } else {
+ ALOGV("MOBILE_DATA_DISCONNECTED %.2lf seconds", diff_in_secs);
+ }
+
+ if (property_set("ril.net_connected", new_conn_state ? "1" : "0")) {
+ ALOGE("Couldn't set a system property ril.net_connected.");
+ }
+ }
+
+ if (t != NULL) {
+ gce_ril_env->OnRequestComplete(*t, RIL_E_SUCCESS, responses,
+ gDataCalls.size() * sizeof(*responses));
+ } else {
+ gce_ril_env->OnUnsolicitedResponse(RIL_UNSOL_DATA_CALL_LIST_CHANGED,
+ responses,
+ gDataCalls.size() * sizeof(*responses));
+ }
+ delete[] responses;
+ return 0;
+}
+
+static void request_datacall_fail_cause(RIL_Token t) {
+ RIL_DataCallFailCause fail = PDP_FAIL_DATA_REGISTRATION_FAIL;
+
+ if (gDataCalls.size() > 0) {
+ fail = gDataCalls.rbegin()->second.fail_cause_;
+ }
+
+ ALOGV("Requesting last data call setup fail cause (%d)", fail);
+ gce_ril_env->OnRequestComplete(t, RIL_E_SUCCESS, &fail, sizeof(fail));
+};
+
+static void on_data_calllist_changed(void* /*param*/) {
+ request_or_send_data_calllist(NULL);
+}
+
+static void request_data_calllist(void* /*data*/, size_t /*datalen*/,
+ RIL_Token t) {
+ request_or_send_data_calllist(&t);
+}
+
+static void request_setup_data_call(void* data, size_t datalen, RIL_Token t) {
+ const char* apn;
+ char* cmd;
+ int err;
+ char** details = static_cast<char**>(data);
+ const size_t fields = datalen / sizeof(details[0]);
+
+ // There are two different versions of this interface, one providing 7 strings
+ // and the other providing 8. The code below will assume the presence of 7
+ // strings in all cases, so bail out here if things appear to be wrong. We
+ // protect the 8 string case below.
+ if (fields < 7) {
+ ALOGE("%s returning: called with small datalen %zu", __FUNCTION__, datalen);
+ return;
+ }
+ DataCall call;
+ int tech = atoi(details[0]);
+ switch (tech) {
+ case 0:
+ case 2 + RADIO_TECH_1xRTT:
+ call.technology_ = RADIO_TECH_1xRTT;
+ break;
+
+ case 1:
+ case 2 + RADIO_TECH_EDGE:
+ call.technology_ = RADIO_TECH_EDGE;
+ break;
+
+ default:
+ call.technology_ = RIL_RadioTechnology(tech - 2);
+ break;
+ }
+
+ int profile = atoi(details[1]);
+ call.profile_ = RIL_DataProfile(profile);
+
+ if (details[2]) call.access_point_ = details[2];
+ if (details[3]) call.username_ = details[3];
+ if (details[4]) call.password_ = details[4];
+
+ int auth_type = atoi(details[5]);
+ call.auth_type_ = DataCall::AllowedAuthenticationType(auth_type);
+
+ if (!strcmp("IP", details[6])) {
+ call.connection_type_ = DataCall::kConnTypeIPv4;
+ } else if (!strcmp("IPV6", details[6])) {
+ call.connection_type_ = DataCall::kConnTypeIPv6;
+ } else if (!strcmp("IPV4V6", details[6])) {
+ call.connection_type_ = DataCall::kConnTypeIPv4v6;
+ } else if (!strcmp("PPP", details[6])) {
+ call.connection_type_ = DataCall::kConnTypePPP;
+ } else {
+ ALOGW("Unknown / unsupported connection type %s. Falling back to IPv4",
+ details[6]);
+ call.connection_type_ = DataCall::kConnTypeIPv4;
+ }
+
+ if (call.connection_type_ != DataCall::kConnTypeIPv4) {
+ ALOGE("Non-IPv4 connections are not supported by GCE RIL.");
+ gce_ril_env->OnRequestComplete(t, RIL_E_GENERIC_FAILURE, NULL, 0);
+ return;
+ }
+
+ call.link_state_ = DataCall::kLinkStateUp;
+ call.fail_cause_ = PDP_FAIL_NONE;
+ if (fields > 7) {
+ if (details[7]) call.other_properties_ = details[7];
+ }
+
+ if (gDataCalls.empty()) {
+ SetUpNetworkInterface(g_ril_interface);
+ }
+
+ gDataCalls[gNextDataCallId] = call;
+ gNextDataCallId++;
+
+ ALOGV("Requesting data call setup to APN %s, technology %s, prof %s",
+ details[2], details[0], details[1]);
+
+ request_or_send_data_calllist(&t);
+
+ gRilConnected = (gDataCalls.size() > 0);
+}
+
+static void request_teardown_data_call(void* data, size_t /*datalen*/,
+ RIL_Token t) {
+ char** data_strs = (char**)data;
+ int call_id = atoi(data_strs[0]);
+ int reason = atoi(data_strs[1]);
+
+ ALOGV("Tearing down data call %d, reason: %d", call_id, reason);
+
+ gDataCalls.erase(call_id);
+ gRilConnected = (gDataCalls.size() > 0);
+
+ if (!gRilConnected) {
+ TearDownNetworkInterface();
+ }
+ gce_ril_env->OnRequestComplete(t, RIL_E_SUCCESS, NULL, 0);
+}
+
+static void set_radio_state(RIL_RadioState new_state, RIL_Token t) {
+ // From header:
+ // Toggle radio on and off (for "airplane" mode)
+ // If the radio is is turned off/on the radio modem subsystem
+ // is expected return to an initialized state. For instance,
+ // any voice and data calls will be terminated and all associated
+ // lists emptied.
+ gDataCalls.clear();
+
+ RIL_RadioState old_state;
+
+ old_state = gRadioPowerState;
+ gRadioPowerState = new_state;
+ gSimStatus = SIM_NOT_READY;
+ ALOGV("RIL_RadioState change %d to %d", old_state, new_state);
+
+ if (new_state == RADIO_STATE_OFF) {
+ TearDownNetworkInterface();
+ }
+
+ if (t != NULL) {
+ gce_ril_env->OnRequestComplete(t, RIL_E_SUCCESS, NULL, 0);
+ }
+
+ gce_ril_env->OnUnsolicitedResponse(RIL_UNSOL_RESPONSE_RADIO_STATE_CHANGED,
+ NULL, 0);
+
+ pollSIMState(NULL);
+}
+
+// returns 1 if on, 0 if off, and -1 on error
+static char is_radio_on() {
+ RIL_RadioState state;
+
+ state = gRadioPowerState;
+
+ return state == RADIO_STATE_ON;
+}
+
+static void request_radio_power(void* data, size_t /*datalen*/, RIL_Token t) {
+ int on = ((int*)data)[0];
+ set_radio_state(on ? RADIO_STATE_ON : RADIO_STATE_OFF, t);
+}
+
+static void send_call_state_changed(void* /*param*/) {
+ gce_ril_env->OnUnsolicitedResponse(RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED,
+ NULL, 0);
+}
+
+// TODO(ender): this should be a class member. Move where it belongs.
+struct CallState {
+ RIL_CallState state; // e.g. RIL_CALL_HOLDING;
+ bool isInternational;
+ bool isMobileTerminated;
+ bool isVoice;
+ bool isMultiParty;
+
+ std::string number;
+ std::string name;
+ std::string dtmf;
+
+ bool canPresentNumber;
+ bool canPresentName;
+
+ CallState()
+ : state(RIL_CallState(0)),
+ isInternational(false),
+ isMobileTerminated(true),
+ isVoice(true),
+ isMultiParty(false),
+ canPresentNumber(true),
+ canPresentName(true) {}
+
+ CallState(const std::string& number)
+ : state(RIL_CALL_INCOMING),
+ isInternational(false),
+ isMobileTerminated(true),
+ isVoice(true),
+ isMultiParty(false),
+ number(number),
+ name(number),
+ canPresentNumber(true),
+ canPresentName(true) {}
+
+ bool isBackground() { return state == RIL_CALL_HOLDING; }
+
+ bool isActive() { return state == RIL_CALL_ACTIVE; }
+
+ bool isDialing() { return state == RIL_CALL_DIALING; }
+
+ bool isIncoming() { return state == RIL_CALL_INCOMING; }
+
+ bool isWaiting() { return state == RIL_CALL_WAITING; }
+
+ void addDtmfDigit(char c) {
+ dtmf.push_back(c);
+ ALOGV("Call to %s: DTMF %s", number.c_str(), dtmf.c_str());
+ }
+
+ bool makeBackground() {
+ if (state == RIL_CALL_ACTIVE) {
+ state = RIL_CALL_HOLDING;
+ return true;
+ }
+
+ return false;
+ }
+
+ bool makeActive() {
+ if (state == RIL_CALL_INCOMING || state == RIL_CALL_WAITING ||
+ state == RIL_CALL_DIALING || state == RIL_CALL_HOLDING) {
+ state = RIL_CALL_ACTIVE;
+ return true;
+ }
+
+ return false;
+ }
+};
+
+static int gLastActiveCallIndex = 1;
+static int gMicrophoneMute = 0;
+static std::map<int, CallState> gActiveCalls;
+
+static void request_get_current_calls(void* /*data*/, size_t /*datalen*/,
+ RIL_Token t) {
+ const int countCalls = gActiveCalls.size();
+
+ RIL_Call** pp_calls = (RIL_Call**)alloca(countCalls * sizeof(RIL_Call*));
+ RIL_Call* p_calls = (RIL_Call*)alloca(countCalls * sizeof(RIL_Call));
+
+ memset(p_calls, 0, countCalls * sizeof(RIL_Call));
+
+ /* init the pointer array */
+ for (int i = 0; i < countCalls; i++) {
+ pp_calls[i] = &(p_calls[i]);
+ }
+
+ // TODO(ender): This should be built from calls requested via RequestDial.
+ for (std::map<int, CallState>::iterator iter = gActiveCalls.begin();
+ iter != gActiveCalls.end(); ++iter, ++p_calls) {
+ p_calls->state = iter->second.state;
+ p_calls->index = iter->first;
+ p_calls->toa = iter->second.isInternational ? 145 : 129;
+ p_calls->isMpty = iter->second.isMultiParty;
+ p_calls->isMT = iter->second.isMobileTerminated;
+ p_calls->als = iter->first;
+ p_calls->isVoice = iter->second.isVoice;
+ p_calls->isVoicePrivacy = 0;
+ p_calls->number = strdup(iter->second.number.c_str());
+ p_calls->numberPresentation = iter->second.canPresentNumber ? 0 : 1;
+ p_calls->name = strdup(iter->second.name.c_str());
+ p_calls->namePresentation = iter->second.canPresentName ? 0 : 1;
+ p_calls->uusInfo = NULL;
+
+ ALOGV("Call to %s (%s): voice=%d mt=%d type=%d state=%d index=%d",
+ p_calls->name, p_calls->number, p_calls->isVoice, p_calls->isMT,
+ p_calls->toa, p_calls->state, p_calls->index);
+ }
+
+ gce_ril_env->OnRequestComplete(t, RIL_E_SUCCESS, pp_calls,
+ countCalls * sizeof(RIL_Call*));
+
+ ALOGV("Get Current calls: %d calls found.\n", countCalls);
+}
+
+static void simulate_pending_calls_answered(void* /*ignore*/) {
+ ALOGV("Simulating outgoing call answered.");
+ // This also resumes held calls.
+ for (std::map<int, CallState>::iterator iter = gActiveCalls.begin();
+ iter != gActiveCalls.end(); ++iter) {
+ if (iter->second.isDialing()) {
+ iter->second.makeActive();
+ }
+ }
+
+ // Only unsolicited here.
+ gce_ril_env->OnUnsolicitedResponse(RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED,
+ NULL, 0);
+}
+
+static void request_dial(void* data, size_t /*datalen*/, RIL_Token t) {
+ RIL_Dial* p_dial = (RIL_Dial*)data;
+
+ ALOGV("Dialing %s, number presentation is %s.", p_dial->address,
+ (p_dial->clir == 0) ? "defined by operator"
+ : (p_dial->clir == 1) ? "allowed" : "restricted");
+
+ CallState state(p_dial->address);
+ state.isMobileTerminated = false;
+ state.state = RIL_CALL_DIALING;
+
+ switch (p_dial->clir) {
+ case 0: // default
+ case 1: // allow
+ state.canPresentNumber = true;
+ break;
+
+ case 2: // restrict
+ state.canPresentNumber = false;
+ break;
+ }
+
+ int call_index = gLastActiveCallIndex++;
+ gActiveCalls[call_index] = state;
+
+ static const struct timeval kAnswerTime = {5, 0};
+ gce_ril_env->RequestTimedCallback(simulate_pending_calls_answered, NULL,
+ &kAnswerTime);
+
+ // success or failure is ignored by the upper layer here.
+ // it will call GET_CURRENT_CALLS and determine success that way
+ gce_ril_env->OnRequestComplete(t, RIL_E_SUCCESS, NULL, 0);
+}
+
+void request_set_mute(void* data, size_t /*datalen*/, RIL_Token t) {
+ gMicrophoneMute = ((int*)data)[0] != 0;
+ gce_ril_env->OnRequestComplete(t, RIL_E_SUCCESS, NULL, 0);
+}
+
+void request_get_mute(RIL_Token t) {
+ gce_ril_env->OnRequestComplete(t, RIL_E_SUCCESS, &gMicrophoneMute,
+ sizeof(gMicrophoneMute));
+}
+
+// TODO(ender): this should be a class member. Move where it belongs.
+struct SmsMessage {
+ enum SmsStatus { kUnread = 0, kRead = 1, kUnsent = 2, kSent = 3 };
+
+ std::string message;
+ SmsStatus status;
+};
+
+static int gNextMessageId = 1;
+static std::map<int, SmsMessage> gMessagesOnSimCard;
+
+static void request_write_sms_to_sim(void* data, size_t /*datalen*/,
+ RIL_Token t) {
+ RIL_SMS_WriteArgs* p_args = (RIL_SMS_WriteArgs*)data;
+
+ SmsMessage message;
+ message.status = SmsMessage::SmsStatus(p_args->status);
+ message.message = p_args->pdu;
+
+ ALOGV("Storing SMS message: '%s' with state: %s.", message.message.c_str(),
+ (message.status < SmsMessage::kUnsent)
+ ? ((message.status == SmsMessage::kRead) ? "READ" : "UNREAD")
+ : ((message.status == SmsMessage::kSent) ? "SENT" : "UNSENT"));
+
+ // TODO(ender): simulate SIM FULL?
+ int index = gNextMessageId++;
+ gMessagesOnSimCard[index] = message;
+
+ gce_ril_env->OnRequestComplete(t, RIL_E_SUCCESS, &index, sizeof(index));
+}
+
+static void request_delete_sms_on_sim(void* data, size_t /*datalen*/,
+ RIL_Token t) {
+ int index = *(int*)data;
+
+ ALOGV("Delete SMS message %d", index);
+
+ if (gMessagesOnSimCard.erase(index) == 0) {
+ // No such message
+ gce_ril_env->OnRequestComplete(t, RIL_E_GENERIC_FAILURE, NULL, 0);
+ return;
+ }
+
+ gce_ril_env->OnRequestComplete(t, RIL_E_SUCCESS, NULL, 0);
+}
+
+static void request_hangup(void* data, size_t /*datalen*/, RIL_Token t) {
+ int* p_line = (int*)data;
+
+ ALOGV("Hanging up call %d.", *p_line);
+ std::map<int, CallState>::iterator iter = gActiveCalls.find(*p_line);
+
+ if (iter == gActiveCalls.end()) {
+ ALOGV("No such call: %d.", *p_line);
+ gce_ril_env->OnRequestComplete(t, RIL_E_GENERIC_FAILURE, NULL, 0);
+ } else {
+ gActiveCalls.erase(iter);
+ gce_ril_env->OnRequestComplete(t, RIL_E_SUCCESS, NULL, 0);
+ }
+}
+
+static void request_hangup_waiting(void* /*data*/, size_t /*datalen*/,
+ RIL_Token t) {
+ ALOGV("Hanging up background/held calls.");
+ for (std::map<int, CallState>::iterator iter = gActiveCalls.begin();
+ iter != gActiveCalls.end();) {
+ if (iter->second.isBackground()) {
+ // C++98 -- std::map::erase doesn't return iterator.
+ std::map<int, CallState>::iterator temp = iter++;
+ gActiveCalls.erase(temp);
+ } else {
+ ++iter;
+ }
+ }
+ gce_ril_env->OnRequestComplete(t, RIL_E_SUCCESS, NULL, 0);
+}
+
+static void request_hangup_current(RIL_Token t) {
+ ALOGV("Hanging up foreground/active calls.");
+ // This also resumes held calls.
+ for (std::map<int, CallState>::iterator iter = gActiveCalls.begin();
+ iter != gActiveCalls.end();) {
+ if (iter->second.isBackground()) {
+ iter->second.makeActive();
+ ++iter;
+ } else {
+ // C++98 -- std::map::erase doesn't return iterator.
+ std::map<int, CallState>::iterator temp = iter++;
+ gActiveCalls.erase(temp);
+ }
+ }
+ gce_ril_env->OnRequestComplete(t, RIL_E_SUCCESS, NULL, 0);
+}
+
+static void request_switch_current_and_waiting(RIL_Token t) {
+ ALOGV("Toggle foreground and background calls.");
+ // TODO(ender): fix all states. Max 2 calls.
+ // BEFORE AFTER
+ // Call 1 Call 2 Call 1 Call 2
+ // ACTIVE HOLDING HOLDING ACTIVE
+ // ACTIVE WAITING HOLDING ACTIVE
+ // HOLDING WAITING HOLDING ACTIVE
+ // ACTIVE IDLE HOLDING IDLE
+ // IDLE IDLE IDLE IDLE
+ for (std::map<int, CallState>::iterator iter = gActiveCalls.begin();
+ iter != gActiveCalls.end(); ++iter) {
+ // TODO(ender): call could also be waiting or dialing or...
+ if (iter->second.isBackground()) {
+ iter->second.makeActive();
+ } else {
+ iter->second.makeBackground();
+ }
+ }
+ gce_ril_env->OnRequestComplete(t, RIL_E_SUCCESS, NULL, 0);
+}
+
+static void request_answer_incoming(RIL_Token t) {
+ ALOGV("Answering incoming call.");
+
+ // There's two types of incoming calls:
+ // - incoming: we are receiving this call while nothing happens,
+ // - waiting: we are receiving this call while we're already talking.
+ // We only accept the incoming ones.
+ for (std::map<int, CallState>::iterator iter = gActiveCalls.begin();
+ iter != gActiveCalls.end(); ++iter) {
+ if (iter->second.isIncoming()) {
+ iter->second.makeActive();
+ }
+ }
+ gce_ril_env->OnRequestComplete(t, RIL_E_SUCCESS, NULL, 0);
+}
+
+static void request_combine_multiparty_call(void* /*data*/, size_t /*datalen*/,
+ RIL_Token t) {
+ ALOGW("Conference calls are not supported.");
+ gce_ril_env->OnRequestComplete(t, RIL_E_GENERIC_FAILURE, NULL, 0);
+}
+
+static void request_split_multiparty_call(void* /*data*/, size_t /*datalen*/,
+ RIL_Token t) {
+ ALOGW("Conference calls are not supported.");
+ gce_ril_env->OnRequestComplete(t, RIL_E_GENERIC_FAILURE, NULL, 0);
+}
+
+static void request_udub_on_incoming_calls(RIL_Token t) {
+ // UDUB = user determined user busy.
+ // We don't exactly do that. We simply drop these calls.
+ ALOGV("Reporting busy signal to incoming calls.");
+ for (std::map<int, CallState>::iterator iter = gActiveCalls.begin();
+ iter != gActiveCalls.end();) {
+ // If we have an incoming call, there should be no waiting call.
+ // If we have a waiting call, then previous incoming call has been answered.
+ if (iter->second.isIncoming() || iter->second.isWaiting()) {
+ // C++98 -- std::map::erase doesn't return iterator.
+ std::map<int, CallState>::iterator temp = iter++;
+ gActiveCalls.erase(temp);
+ } else {
+ ++iter;
+ }
+ }
+ gce_ril_env->OnRequestComplete(t, RIL_E_SUCCESS, NULL, 0);
+}
+
+static void request_send_dtmf(void* data, size_t /*datalen*/, RIL_Token t) {
+ char c = ((char*)data)[0];
+ ALOGV("Sending DTMF digit '%c'", c);
+
+ for (std::map<int, CallState>::iterator iter = gActiveCalls.begin();
+ iter != gActiveCalls.end(); ++iter) {
+ if (iter->second.isActive()) {
+ iter->second.addDtmfDigit(c);
+ break;
+ }
+ }
+
+ gce_ril_env->OnRequestComplete(t, RIL_E_SUCCESS, NULL, 0);
+}
+
+static void request_send_dtmf_stop(RIL_Token t) {
+ ALOGV("DTMF tone end.");
+
+ gce_ril_env->OnRequestComplete(t, RIL_E_SUCCESS, NULL, 0);
+}
+
+// Check SignalStrength.java file for more details on how these map to signal
+// strength bars.
+const int kGatewaySignalStrengthMin = 4;
+const int kGatewaySignalStrengthMax = 30;
+const int kCDMASignalStrengthMin = -110;
+const int kCDMASignalStrengthMax = -60;
+const int kEVDOSignalStrengthMin = -160;
+const int kEVDOSignalStrengthMax = -70;
+const int kLTESignalStrengthMin = 4;
+const int kLTESignalStrengthMax = 30;
+
+static int gGatewaySignalStrength = kGatewaySignalStrengthMax;
+static int gCDMASignalStrength = kCDMASignalStrengthMax;
+static int gEVDOSignalStrength = kEVDOSignalStrengthMax;
+static int gLTESignalStrength = kLTESignalStrengthMax;
+
+static void request_signal_strength(void* /*data*/, size_t /*datalen*/,
+ RIL_Token t) {
+ // TODO(ender): possible to support newer APIs here.
+#if VSOC_PLATFORM_SDK_AFTER(N_MR1)
+ RIL_SignalStrength_v10 strength;
+#else
+ RIL_SignalStrength_v6 strength;
+#endif
+
+ gGatewaySignalStrength += (rand() % 3 - 1);
+ gCDMASignalStrength += (rand() % 3 - 1);
+ gEVDOSignalStrength += (rand() % 3 - 1);
+ gLTESignalStrength += (rand() % 3 - 1);
+
+ if (gGatewaySignalStrength < kGatewaySignalStrengthMin)
+ gGatewaySignalStrength = kGatewaySignalStrengthMin;
+ if (gGatewaySignalStrength > kGatewaySignalStrengthMax)
+ gGatewaySignalStrength = kGatewaySignalStrengthMax;
+ if (gCDMASignalStrength < kCDMASignalStrengthMin)
+ gCDMASignalStrength = kCDMASignalStrengthMin;
+ if (gCDMASignalStrength > kCDMASignalStrengthMax)
+ gCDMASignalStrength = kCDMASignalStrengthMax;
+ if (gEVDOSignalStrength < kEVDOSignalStrengthMin)
+ gEVDOSignalStrength = kEVDOSignalStrengthMin;
+ if (gEVDOSignalStrength > kEVDOSignalStrengthMax)
+ gEVDOSignalStrength = kEVDOSignalStrengthMax;
+ if (gLTESignalStrength < kLTESignalStrengthMin)
+ gLTESignalStrength = kLTESignalStrengthMin;
+ if (gLTESignalStrength > kLTESignalStrengthMax)
+ gLTESignalStrength = kLTESignalStrengthMax;
+
+ strength.GW_SignalStrength.signalStrength = gGatewaySignalStrength;
+ strength.GW_SignalStrength.bitErrorRate = 0; // 0..7%
+
+ strength.CDMA_SignalStrength.dbm = gCDMASignalStrength;
+ strength.CDMA_SignalStrength.ecio = 0; // Ec/Io; keep high to use dbm.
+
+ strength.EVDO_SignalStrength.dbm = gEVDOSignalStrength;
+ strength.EVDO_SignalStrength.ecio = 0; // Ec/Io; keep high to use dbm.
+
+ strength.LTE_SignalStrength.signalStrength = gLTESignalStrength;
+ strength.LTE_SignalStrength.rsrp = INT_MAX; // Invalid = Use signalStrength.
+ strength.LTE_SignalStrength.rsrq = INT_MAX; // Invalid = Use signalStrength.
+ strength.LTE_SignalStrength.rssnr = INT_MAX; // Invalid = Use signalStrength.
+ strength.LTE_SignalStrength.cqi = INT_MAX; // Invalid = Use signalStrength.
+
+ ALOGV("Reporting signal strength: GW=%d CDMA=%d EVDO=%d LTE=%d",
+ gGatewaySignalStrength, gCDMASignalStrength, gEVDOSignalStrength,
+ gLTESignalStrength);
+
+ gce_ril_env->OnRequestComplete(t, RIL_E_SUCCESS, &strength, sizeof(strength));
+}
+
+static std::map<RIL_PreferredNetworkType, int> gModemSupportedNetworkTypes;
+
+static void init_modem_supported_network_types() {
+ gModemSupportedNetworkTypes[PREF_NET_TYPE_GSM_WCDMA] = MDM_GSM | MDM_WCDMA;
+ gModemSupportedNetworkTypes[PREF_NET_TYPE_GSM_ONLY] = MDM_GSM;
+ gModemSupportedNetworkTypes[PREF_NET_TYPE_WCDMA] = MDM_WCDMA;
+ gModemSupportedNetworkTypes[PREF_NET_TYPE_GSM_WCDMA_AUTO] =
+ MDM_GSM | MDM_WCDMA;
+ gModemSupportedNetworkTypes[PREF_NET_TYPE_CDMA_EVDO_AUTO] =
+ MDM_CDMA | MDM_EVDO;
+ gModemSupportedNetworkTypes[PREF_NET_TYPE_CDMA_ONLY] = MDM_CDMA;
+ gModemSupportedNetworkTypes[PREF_NET_TYPE_EVDO_ONLY] = MDM_EVDO;
+ gModemSupportedNetworkTypes[PREF_NET_TYPE_GSM_WCDMA_CDMA_EVDO_AUTO] =
+ MDM_GSM | MDM_WCDMA | MDM_CDMA | MDM_EVDO;
+ gModemSupportedNetworkTypes[PREF_NET_TYPE_LTE_CDMA_EVDO] =
+ MDM_LTE | MDM_CDMA | MDM_EVDO;
+ gModemSupportedNetworkTypes[PREF_NET_TYPE_LTE_GSM_WCDMA] =
+ MDM_LTE | MDM_GSM | MDM_WCDMA;
+ gModemSupportedNetworkTypes[PREF_NET_TYPE_LTE_CMDA_EVDO_GSM_WCDMA] =
+ MDM_LTE | MDM_CDMA | MDM_EVDO | MDM_GSM | MDM_WCDMA;
+ gModemSupportedNetworkTypes[PREF_NET_TYPE_LTE_ONLY] = MDM_LTE;
+}
+
+static std::map<RIL_PreferredNetworkType, int> gModemTechnologies;
+
+RIL_RadioTechnology gDataTechnologiesPreferenceOrder[] = {
+ RADIO_TECH_LTE, RADIO_TECH_EHRPD, RADIO_TECH_HSPAP, RADIO_TECH_HSPA,
+ RADIO_TECH_HSDPA, RADIO_TECH_HSUPA, RADIO_TECH_EVDO_B, RADIO_TECH_EVDO_A,
+ RADIO_TECH_EVDO_0, RADIO_TECH_1xRTT, RADIO_TECH_UMTS, RADIO_TECH_EDGE,
+ RADIO_TECH_GPRS};
+
+RIL_RadioTechnology gVoiceTechnologiesPreferenceOrder[] = {
+ RADIO_TECH_LTE, RADIO_TECH_EHRPD, RADIO_TECH_EVDO_B, RADIO_TECH_EVDO_A,
+ RADIO_TECH_EVDO_0, RADIO_TECH_1xRTT, RADIO_TECH_IS95B, RADIO_TECH_IS95A,
+ RADIO_TECH_UMTS, RADIO_TECH_GSM};
+
+static void init_modem_technologies() {
+ gModemTechnologies[PREF_NET_TYPE_GSM_WCDMA] =
+ (1 << RADIO_TECH_GSM) | (1 << RADIO_TECH_GPRS) | (1 << RADIO_TECH_EDGE) |
+ (1 << RADIO_TECH_UMTS);
+ gModemTechnologies[PREF_NET_TYPE_GSM_ONLY] =
+ (1 << RADIO_TECH_GSM) | (1 << RADIO_TECH_GPRS) | (1 << RADIO_TECH_EDGE);
+ gModemTechnologies[PREF_NET_TYPE_WCDMA] =
+ (1 << RADIO_TECH_EDGE) | (1 << RADIO_TECH_UMTS);
+ gModemTechnologies[PREF_NET_TYPE_GSM_WCDMA_AUTO] =
+ (1 << RADIO_TECH_GSM) | (1 << RADIO_TECH_GPRS) | (1 << RADIO_TECH_EDGE) |
+ (1 << RADIO_TECH_UMTS);
+ gModemTechnologies[PREF_NET_TYPE_CDMA_EVDO_AUTO] =
+ (1 << RADIO_TECH_IS95A) | (1 << RADIO_TECH_IS95B) |
+ (1 << RADIO_TECH_1xRTT) | (1 << RADIO_TECH_EVDO_0) |
+ (1 << RADIO_TECH_EVDO_A) | (1 << RADIO_TECH_HSDPA) |
+ (1 << RADIO_TECH_HSUPA) | (1 << RADIO_TECH_HSPA) |
+ (1 << RADIO_TECH_EVDO_B);
+ gModemTechnologies[PREF_NET_TYPE_CDMA_ONLY] = (1 << RADIO_TECH_IS95A) |
+ (1 << RADIO_TECH_IS95B) |
+ (1 << RADIO_TECH_1xRTT);
+ gModemTechnologies[PREF_NET_TYPE_EVDO_ONLY] =
+ (1 << RADIO_TECH_EVDO_0) | (1 << RADIO_TECH_EVDO_A) |
+ (1 << RADIO_TECH_EVDO_A) | (1 << RADIO_TECH_HSDPA) |
+ (1 << RADIO_TECH_HSUPA) | (1 << RADIO_TECH_HSPA) |
+ (1 << RADIO_TECH_EVDO_B);
+ gModemTechnologies[PREF_NET_TYPE_GSM_WCDMA_CDMA_EVDO_AUTO] =
+ (1 << RADIO_TECH_GSM) | (1 << RADIO_TECH_GPRS) | (1 << RADIO_TECH_EDGE) |
+ (1 << RADIO_TECH_UMTS) | (1 << RADIO_TECH_IS95A) |
+ (1 << RADIO_TECH_IS95B) | (1 << RADIO_TECH_1xRTT) |
+ (1 << RADIO_TECH_EVDO_0) | (1 << RADIO_TECH_EVDO_A) |
+ (1 << RADIO_TECH_HSDPA) | (1 << RADIO_TECH_HSUPA) |
+ (1 << RADIO_TECH_HSPA) | (1 << RADIO_TECH_EVDO_B);
+ gModemTechnologies[PREF_NET_TYPE_LTE_CDMA_EVDO] =
+ (1 << RADIO_TECH_HSPAP) | (1 << RADIO_TECH_LTE) |
+ (1 << RADIO_TECH_EHRPD) | (1 << RADIO_TECH_IS95A) |
+ (1 << RADIO_TECH_IS95B) | (1 << RADIO_TECH_1xRTT) |
+ (1 << RADIO_TECH_EVDO_0) | (1 << RADIO_TECH_EVDO_A) |
+ (1 << RADIO_TECH_HSDPA) | (1 << RADIO_TECH_HSUPA) |
+ (1 << RADIO_TECH_HSPA) | (1 << RADIO_TECH_EVDO_B);
+ gModemTechnologies[PREF_NET_TYPE_LTE_GSM_WCDMA] =
+ (1 << RADIO_TECH_HSPAP) | (1 << RADIO_TECH_LTE) |
+ (1 << RADIO_TECH_EHRPD) | (1 << RADIO_TECH_GSM) | (1 << RADIO_TECH_GPRS) |
+ (1 << RADIO_TECH_EDGE) | (1 << RADIO_TECH_UMTS);
+
+ gModemTechnologies[PREF_NET_TYPE_LTE_CMDA_EVDO_GSM_WCDMA] =
+ (1 << RADIO_TECH_HSPAP) | (1 << RADIO_TECH_LTE) |
+ (1 << RADIO_TECH_EHRPD) | (1 << RADIO_TECH_IS95A) |
+ (1 << RADIO_TECH_IS95B) | (1 << RADIO_TECH_1xRTT) |
+ (1 << RADIO_TECH_EVDO_0) | (1 << RADIO_TECH_EVDO_A) |
+ (1 << RADIO_TECH_HSDPA) | (1 << RADIO_TECH_HSUPA) |
+ (1 << RADIO_TECH_HSPA) | (1 << RADIO_TECH_EVDO_B) |
+ (1 << RADIO_TECH_GSM) | (1 << RADIO_TECH_GPRS) | (1 << RADIO_TECH_EDGE) |
+ (1 << RADIO_TECH_UMTS);
+ gModemTechnologies[PREF_NET_TYPE_LTE_ONLY] =
+ (1 << RADIO_TECH_HSPAP) | (1 << RADIO_TECH_LTE) | (1 << RADIO_TECH_EHRPD);
+}
+
+static const RIL_PreferredNetworkType gModemDefaultType =
+ PREF_NET_TYPE_LTE_GSM_WCDMA;
+static RIL_PreferredNetworkType gModemCurrentType = gModemDefaultType;
+static RIL_RadioTechnology gModemTechnology = RADIO_TECH_LTE;
+static RIL_RadioTechnology gModemVoiceTechnology = RADIO_TECH_LTE;
+
+// Report technology change.
+// Select best technology from the list of supported techs.
+// Demotes RADIO_TECH_GSM as it's voice-only.
+static RIL_RadioTechnology getBestDataTechnology(
+ RIL_PreferredNetworkType network_type) {
+ RIL_RadioTechnology technology = RADIO_TECH_GPRS;
+
+ std::map<RIL_PreferredNetworkType, int>::iterator iter =
+ gModemTechnologies.find(network_type);
+
+ ALOGV("Searching for best data technology for network type %d...",
+ network_type);
+
+ // Find which technology bits are lit. Pick the top most.
+ for (size_t tech_index = 0;
+ tech_index < sizeof(gDataTechnologiesPreferenceOrder) /
+ sizeof(gDataTechnologiesPreferenceOrder[0]);
+ ++tech_index) {
+ if (iter->second & (1 << gDataTechnologiesPreferenceOrder[tech_index])) {
+ technology = gDataTechnologiesPreferenceOrder[tech_index];
+ break;
+ }
+ }
+
+ ALOGV("Best data technology: %d.", technology);
+ return technology;
+}
+
+static RIL_RadioTechnology getBestVoiceTechnology(
+ RIL_PreferredNetworkType network_type) {
+ RIL_RadioTechnology technology = RADIO_TECH_GSM;
+
+ std::map<RIL_PreferredNetworkType, int>::iterator iter =
+ gModemTechnologies.find(network_type);
+
+ ALOGV("Searching for best voice technology for network type %d...",
+ network_type);
+
+ // Find which technology bits are lit. Pick the top most.
+ for (size_t tech_index = 0;
+ tech_index < sizeof(gVoiceTechnologiesPreferenceOrder) /
+ sizeof(gVoiceTechnologiesPreferenceOrder[0]);
+ ++tech_index) {
+ if (iter->second & (1 << gVoiceTechnologiesPreferenceOrder[tech_index])) {
+ technology = gVoiceTechnologiesPreferenceOrder[tech_index];
+ break;
+ }
+ }
+
+ ALOGV("Best voice technology: %d.", technology);
+ return technology;
+}
+
+static void setRadioTechnology(RIL_PreferredNetworkType network_type) {
+ RIL_RadioTechnology technology = getBestVoiceTechnology(network_type);
+
+ if (technology != gModemVoiceTechnology) {
+ gModemVoiceTechnology = technology;
+ gce_ril_env->OnUnsolicitedResponse(RIL_UNSOL_VOICE_RADIO_TECH_CHANGED,
+ &gModemVoiceTechnology,
+ sizeof(gModemVoiceTechnology));
+ }
+}
+
+#if VSOC_PLATFORM_SDK_AFTER(L)
+static void request_get_radio_capability(RIL_Token t) {
+ ALOGV("Requesting radio capability.");
+ RIL_RadioCapability rc;
+ rc.version = RIL_RADIO_CAPABILITY_VERSION;
+ rc.session = 1;
+ rc.phase = RC_PHASE_CONFIGURED;
+ rc.rat = RAF_HSPAP;
+ strncpy(rc.logicalModemUuid, "com.google.cvdgce1.modem", MAX_UUID_LENGTH);
+ rc.status = RC_STATUS_SUCCESS;
+ gce_ril_env->OnRequestComplete(t, RIL_E_SUCCESS, &rc, sizeof(rc));
+}
+
+static void request_set_radio_capability(void* data, size_t datalen,
+ RIL_Token t) {
+ RIL_RadioCapability* rc = (RIL_RadioCapability*)data;
+ ALOGV(
+ "RadioCapability version %d session %d phase %d rat %d "
+ "logicalModemUuid %s status %d",
+ rc->version, rc->session, rc->phase, rc->rat, rc->logicalModemUuid,
+ rc->status);
+ // TODO(ender): do something about these numbers.
+ gce_ril_env->OnRequestComplete(t, RIL_E_SUCCESS, rc, datalen);
+}
+#endif
+
+static void request_set_preferred_network_type(int /*request*/, void* data,
+ size_t /*datalen*/,
+ RIL_Token t) {
+ RIL_PreferredNetworkType desired_type = *(RIL_PreferredNetworkType*)(data);
+
+ // TODO(ender): telephony still believes this phone is GSM only.
+ ALOGV("Requesting modem technology change -> %d", desired_type);
+
+ if (gModemSupportedNetworkTypes.find(desired_type) ==
+ gModemSupportedNetworkTypes.end()) {
+ desired_type = gModemSupportedNetworkTypes.begin()->first;
+ }
+
+ if (gModemCurrentType == desired_type) {
+ ALOGV("Modem technology already set to %d.", desired_type);
+ gce_ril_env->OnRequestComplete(t, RIL_E_SUCCESS, NULL, 0);
+ return;
+ }
+
+ int supported_technologies = gModemSupportedNetworkTypes[gModemDefaultType];
+ int desired_technologies = gModemSupportedNetworkTypes[desired_type];
+
+ ALOGV("Requesting modem technology change %d -> %d", gModemCurrentType,
+ desired_type);
+
+ // Check if we support this technology.
+ if ((supported_technologies & desired_technologies) != desired_technologies) {
+ ALOGV("Desired technology is not supported.");
+ gce_ril_env->OnRequestComplete(t, RIL_E_MODE_NOT_SUPPORTED, NULL, 0);
+ return;
+ }
+
+ gModemCurrentType = desired_type;
+ setRadioTechnology(desired_type);
+ ALOGV("Technology change successful.");
+ gce_ril_env->OnRequestComplete(t, RIL_E_SUCCESS, NULL, 0);
+}
+
+static void request_get_preferred_network_type(int /*request*/, void* /*data*/,
+ size_t /*datalen*/,
+ RIL_Token t) {
+ gce_ril_env->OnRequestComplete(
+ t, RIL_E_SUCCESS,
+ const_cast<RIL_PreferredNetworkType*>(&gModemDefaultType),
+ sizeof(gModemDefaultType));
+}
+
+enum RegistrationState {
+ kUnregistered = 0,
+ kRegisteredInHomeNetwork = 1,
+ kSearchingForOperators = 2,
+ kRegistrationDenied = 3,
+ kUnknown = 4,
+ kRegisteredInRoamingMode = 5,
+
+ kUnregistered_EmergencyCallsOnly = 10,
+ kSearchingForOperators_EmergencyCallsOnly = 12,
+ kRegistrationDenied_EmergencyCallsOnly = 13,
+ kUnknown_EmergencyCallsOnly = 14
+};
+
+static const char kCdmaMobileDeviceNumber[] = "5551234567";
+static const char kCdmaSID[] = "123";
+static const char kCdmaNID[] = "65535"; // special: indicates free roaming.
+
+static void request_registration_state(int request, void* /*data*/,
+ size_t /*datalen*/, RIL_Token t) {
+ char** responseStr = NULL;
+ int numElements = 0;
+
+ // See RIL_REQUEST_VOICE_REGISTRATION_STATE and
+ // RIL_REQUEST_DATA_REGISTRATION_STATE.
+ numElements = (request == RIL_REQUEST_VOICE_REGISTRATION_STATE) ? 15 : 6;
+ responseStr = (char**)malloc(numElements * sizeof(char*));
+
+ asprintf(&responseStr[0], "%d", kRegisteredInHomeNetwork);
+ responseStr[1] = NULL; // LAC - needed for GSM / WCDMA only.
+ responseStr[2] = NULL; // CID - needed for GSM / WCDMA only.
+
+ // This is (and always has been) a huge memory leak.
+ if (request == RIL_REQUEST_VOICE_REGISTRATION_STATE) {
+ ALOGV("Requesting voice registration state.");
+ asprintf(&responseStr[3], "%d", getBestVoiceTechnology(gModemCurrentType));
+ responseStr[4] = strdup("1"); // BSID
+ responseStr[5] = strdup("123"); // Latitude
+ responseStr[6] = strdup("222"); // Longitude
+ responseStr[7] = strdup("0"); // CSS Indicator
+ responseStr[8] = strdup(kCdmaSID); // SID
+ responseStr[9] = strdup(kCdmaNID); // NID
+ responseStr[10] = strdup("0"); // Roaming indicator
+ responseStr[11] = strdup("1"); // System is in PRL
+ responseStr[12] = strdup("0"); // Default Roaming indicator
+ responseStr[13] = strdup("0"); // Reason for denial
+ responseStr[14] = strdup("0"); // Primary Scrambling Code of Current
+ } else if (request == RIL_REQUEST_DATA_REGISTRATION_STATE) {
+ ALOGV("Requesting data registration state.");
+ asprintf(&responseStr[3], "%d", getBestDataTechnology(gModemCurrentType));
+ responseStr[4] = strdup(""); // DataServiceDenyReason
+ responseStr[5] = strdup("1"); // Max simultaneous data calls.
+ } else {
+ ALOGV("Unexpected request type: %d", request);
+ return;
+ }
+
+ gce_ril_env->OnRequestComplete(t, RIL_E_SUCCESS, responseStr,
+ numElements * sizeof(responseStr));
+}
+
+static void request_baseband_version(RIL_Token t) {
+ const char* response_str = "CVD_R1.0.0";
+
+ ALOGV("Requested phone baseband version.");
+
+ gce_ril_env->OnRequestComplete(t, RIL_E_SUCCESS, strdup(response_str),
+ sizeof(response_str));
+}
+
+// Returns true, if modem is CDMA capable.
+static bool isCDMA() {
+ switch (gModemCurrentType) {
+ case PREF_NET_TYPE_GSM_WCDMA:
+ case PREF_NET_TYPE_GSM_ONLY:
+ case PREF_NET_TYPE_WCDMA:
+ case PREF_NET_TYPE_GSM_WCDMA_AUTO:
+ case PREF_NET_TYPE_LTE_GSM_WCDMA:
+ case PREF_NET_TYPE_LTE_ONLY:
+ return false;
+
+ case PREF_NET_TYPE_CDMA_EVDO_AUTO:
+ case PREF_NET_TYPE_CDMA_ONLY:
+ case PREF_NET_TYPE_EVDO_ONLY:
+ case PREF_NET_TYPE_LTE_CDMA_EVDO:
+ case PREF_NET_TYPE_LTE_CMDA_EVDO_GSM_WCDMA:
+ case PREF_NET_TYPE_GSM_WCDMA_CDMA_EVDO_AUTO:
+ return true;
+ default:
+ break;
+ }
+
+ ALOGE("INVALID MODEM TYPE: %d", gModemCurrentType);
+ return false;
+}
+
+// Returns true, if modem is GSM capable.
+// Note, this is not same as !isCDMA().
+static bool isGSM() {
+ switch (gModemCurrentType) {
+ case PREF_NET_TYPE_GSM_WCDMA:
+ case PREF_NET_TYPE_GSM_ONLY:
+ case PREF_NET_TYPE_WCDMA:
+ case PREF_NET_TYPE_GSM_WCDMA_AUTO:
+ case PREF_NET_TYPE_LTE_GSM_WCDMA:
+ case PREF_NET_TYPE_LTE_ONLY:
+ case PREF_NET_TYPE_GSM_WCDMA_CDMA_EVDO_AUTO:
+ return true;
+
+ case PREF_NET_TYPE_CDMA_EVDO_AUTO:
+ case PREF_NET_TYPE_CDMA_ONLY:
+ case PREF_NET_TYPE_EVDO_ONLY:
+ case PREF_NET_TYPE_LTE_CDMA_EVDO:
+ case PREF_NET_TYPE_LTE_CMDA_EVDO_GSM_WCDMA:
+ return false;
+ default:
+ break;
+ }
+
+ ALOGE("INVALID MODEM TYPE: %d", gModemCurrentType);
+ return false;
+}
+
+static const char gIdentityGsmImei[] = "12345678902468"; // Luhn cksum = 0.
+static const char gIdentityGsmImeiSv[] = "01"; // Arbitrary version.
+static const char gIdentityCdmaEsn[] = "A0123456"; // 8 digits, ^[A-F].*
+static const char gIdentityCdmaMeid[] =
+ "A0123456789012"; // 14 digits, ^[A-F].*
+
+static void request_get_imei(RIL_Token t) {
+ ALOGV("Requesting IMEI");
+ gce_ril_env->OnRequestComplete(t, RIL_E_SUCCESS,
+ const_cast<char*>(gIdentityGsmImei),
+ strlen(gIdentityGsmImei));
+}
+
+static void request_get_imei_sv(RIL_Token t) {
+ ALOGV("Requesting IMEI SV");
+ gce_ril_env->OnRequestComplete(t, RIL_E_SUCCESS,
+ const_cast<char*>(gIdentityGsmImeiSv),
+ strlen(gIdentityGsmImeiSv));
+}
+
+static void request_device_identity(int /*request*/, void* /*data*/,
+ size_t /*datalen*/, RIL_Token t) {
+ char* response[4] = {NULL};
+
+ ALOGV("Requesting device identity...");
+
+ if (isCDMA()) {
+ response[2] = strdup(&gIdentityCdmaEsn[0]);
+ response[3] = strdup(&gIdentityCdmaMeid[0]);
+ }
+
+ if (isGSM()) {
+ response[0] = strdup(&gIdentityGsmImei[0]);
+ response[1] = strdup(&gIdentityGsmImeiSv[0]);
+ }
+
+ gce_ril_env->OnRequestComplete(t, RIL_E_SUCCESS, &response, sizeof(response));
+
+ free(response[0]);
+ free(response[1]);
+}
+
+// Let's pretend we have SIM for CDMA (by default).
+static bool gCdmaHasSim = true;
+static RIL_CdmaSubscriptionSource gCdmaSubscriptionType =
+ CDMA_SUBSCRIPTION_SOURCE_RUIM_SIM;
+
+static void request_cdma_get_subscription_source(int /*request*/,
+ void* /*data*/,
+ size_t /*datalen*/,
+ RIL_Token t) {
+ ALOGV("Requesting CDMA Subscription source.");
+
+ if (!isCDMA()) {
+ // No such radio.
+ gce_ril_env->OnRequestComplete(t, RIL_E_RADIO_NOT_AVAILABLE, NULL, 0);
+ return;
+ }
+
+ gce_ril_env->OnRequestComplete(t, RIL_E_SUCCESS, &gCdmaSubscriptionType,
+ sizeof(gCdmaSubscriptionType));
+}
+
+static void request_cdma_set_subscription_source(int /*request*/, void* data,
+ size_t /*datalen*/,
+ RIL_Token t) {
+ ALOGV("Setting CDMA Subscription source.");
+
+ if (!isCDMA()) {
+ // No such radio.
+ gce_ril_env->OnRequestComplete(t, RIL_E_RADIO_NOT_AVAILABLE, NULL, 0);
+ return;
+ }
+
+ RIL_CdmaSubscriptionSource new_source = *(RIL_CdmaSubscriptionSource*)(data);
+
+ if (new_source == CDMA_SUBSCRIPTION_SOURCE_RUIM_SIM && !gCdmaHasSim) {
+ // No such radio.
+ gce_ril_env->OnRequestComplete(t, RIL_E_SIM_ABSENT, NULL, 0);
+ return;
+ }
+
+ ALOGV("Changed CDMA subscription type from %d to %d", gCdmaSubscriptionType,
+ new_source);
+ gCdmaSubscriptionType = new_source;
+
+ gce_ril_env->OnRequestComplete(t, RIL_E_SUCCESS, NULL, 0);
+ gce_ril_env->OnUnsolicitedResponse(RIL_UNSOL_CDMA_SUBSCRIPTION_SOURCE_CHANGED,
+ &gCdmaSubscriptionType,
+ sizeof(gCdmaSubscriptionType));
+}
+
+static void request_cdma_subscription(int /*request*/, void* /*data*/,
+ size_t /*datalen*/, RIL_Token t) {
+ ALOGV("Requesting CDMA Subscription.");
+
+ if (!isCDMA()) {
+ // No such radio.
+ gce_ril_env->OnRequestComplete(t, RIL_E_RADIO_NOT_AVAILABLE, NULL, 0);
+ return;
+ }
+
+ char* responseStr[5] = {NULL};
+ responseStr[0] = strdup(&kCdmaMobileDeviceNumber[0]); // MDN
+ responseStr[1] = strdup(&kCdmaSID[0]); // SID
+ responseStr[2] = strdup(&kCdmaNID[0]); // NID
+ responseStr[3] = strdup(&kCdmaMobileDeviceNumber[0]); // MIN
+ responseStr[4] = strdup("1"); // PRL Version
+ gce_ril_env->OnRequestComplete(t, RIL_E_SUCCESS, responseStr,
+ sizeof(responseStr));
+}
+
+static const int gMaxConcurrentVoiceCalls = 4;
+static const int gMaxConcurrentDataCalls = 4;
+static const int gMaxConcurrentStandbyConnections = 4;
+
+#if VSOC_PLATFORM_SDK_AFTER(K)
+static void request_hardware_config(RIL_Token t) {
+ RIL_HardwareConfig hw_cfg[2];
+
+ ALOGV("Requesting hardware configuration.");
+
+ strncpy(hw_cfg[0].uuid, "com.google.cvdgce1.modem", sizeof(hw_cfg[0].uuid));
+ strncpy(hw_cfg[1].uuid, "com.google.cvdgce1.sim", sizeof(hw_cfg[1].uuid));
+
+ int technologies = 0; // = unknown.
+ std::map<RIL_PreferredNetworkType, int>::iterator iter =
+ gModemTechnologies.find(gModemDefaultType);
+ if (iter != gModemTechnologies.end()) {
+ technologies = iter->second;
+ }
+
+ hw_cfg[0].type = RIL_HARDWARE_CONFIG_MODEM;
+ hw_cfg[0].state = RIL_HARDWARE_CONFIG_STATE_ENABLED;
+ hw_cfg[0].cfg.modem.rilModel = 0;
+ hw_cfg[0].cfg.modem.rat = technologies;
+ hw_cfg[0].cfg.modem.maxVoice = gMaxConcurrentVoiceCalls;
+ hw_cfg[0].cfg.modem.maxData = gMaxConcurrentDataCalls;
+ hw_cfg[0].cfg.modem.maxStandby = gMaxConcurrentStandbyConnections;
+
+ hw_cfg[1].type = RIL_HARDWARE_CONFIG_SIM;
+ hw_cfg[1].state = RIL_HARDWARE_CONFIG_STATE_ENABLED;
+ memcpy(hw_cfg[1].cfg.sim.modemUuid, hw_cfg[0].uuid,
+ sizeof(hw_cfg[1].cfg.sim.modemUuid));
+
+ gce_ril_env->OnRequestComplete(t, RIL_E_SUCCESS, &hw_cfg, sizeof(hw_cfg));
+}
+#endif
+
+// 0 = Home network only, 1 = preferred networks only, 2 = all networks.
+static int gCdmaRoamingPreference = 2;
+
+static void request_cdma_get_roaming_preference(int /*request*/, void* /*data*/,
+ size_t /*datalen*/,
+ RIL_Token t) {
+ if (!isCDMA()) {
+ // No such radio.
+ gce_ril_env->OnRequestComplete(t, RIL_E_RADIO_NOT_AVAILABLE, NULL, 0);
+ return;
+ }
+
+ ALOGV("Requesting CDMA Roaming preference");
+
+ gce_ril_env->OnRequestComplete(t, RIL_E_SUCCESS, &gCdmaRoamingPreference,
+ sizeof(gCdmaRoamingPreference));
+}
+
+static void request_cdma_set_roaming_preference(int /*request*/, void* data,
+ size_t /*datalen*/,
+ RIL_Token t) {
+ if (!isCDMA()) {
+ // No such radio.
+ gce_ril_env->OnRequestComplete(t, RIL_E_RADIO_NOT_AVAILABLE, NULL, 0);
+ return;
+ }
+
+ int pref = *(int*)data;
+ ALOGV("Changing CDMA roaming preference: %d -> %d", gCdmaRoamingPreference,
+ pref);
+
+ if ((pref < 0) || (pref > 2)) {
+ ALOGV("Unsupported roaming preference: %d", pref);
+ gce_ril_env->OnRequestComplete(t, RIL_E_GENERIC_FAILURE, NULL, 0);
+ return;
+ }
+
+ gCdmaRoamingPreference = pref;
+ gce_ril_env->OnRequestComplete(t, RIL_E_SUCCESS, NULL, 0);
+}
+
+static void request_send_ussd(void* /*data*/, size_t /*datalen*/, RIL_Token t) {
+ ALOGV("Sending USSD code is currently not supported");
+ // TODO(ender): support this feature
+ gce_ril_env->OnRequestComplete(t, RIL_E_REQUEST_NOT_SUPPORTED, NULL, 0);
+}
+
+static void request_cancel_ussd(RIL_Token t) {
+ ALOGV("Cancelling USSD code is currently not supported");
+ // TODO(ender): support this feature
+ gce_ril_env->OnRequestComplete(t, RIL_E_REQUEST_NOT_SUPPORTED, NULL, 0);
+}
+
+static void request_exit_emergency_mode(void* /*data*/, size_t /*datalen*/,
+ RIL_Token t) {
+ ALOGV("Exiting emergency callback mode.");
+
+ gce_ril_env->OnRequestComplete(t, RIL_E_SUCCESS, NULL, 0);
+}
+
+static RIL_RadioState gce_ril_current_state() {
+ ALOGV("Reporting radio state %d", gRadioPowerState);
+ return gRadioPowerState;
+}
+
+static int gce_ril_on_supports(int requestCode) {
+ ALOGE("%s: Request code %d not implemented", __FUNCTION__, requestCode);
+ return 1;
+}
+
+static void gce_ril_on_cancel(RIL_Token /*t*/) {
+ ALOGE("Cancel operation not implemented");
+}
+
+static const char* gce_ril_get_version(void) {
+ ALOGV("Reporting GCE version " GCE_RIL_VERSION_STRING);
+ return GCE_RIL_VERSION_STRING;
+}
+
+static int s_cell_info_rate_ms = INT_MAX;
+static int s_mcc = 0;
+static int s_mnc = 0;
+static int s_lac = 0;
+static int s_cid = 0;
+
+std::vector<RIL_NeighboringCell> gGSMNeighboringCells;
+
+static void request_get_neighboring_cell_ids(void* /*data*/, size_t /*datalen*/,
+ RIL_Token t) {
+ ALOGV("Requesting GSM neighboring cell ids");
+
+ if (!isGSM() || gGSMNeighboringCells.empty()) {
+ gce_ril_env->OnRequestComplete(t, RIL_E_GENERIC_FAILURE, NULL, 0);
+ return;
+ }
+
+ RIL_NeighboringCell** cells =
+ new RIL_NeighboringCell*[gGSMNeighboringCells.size()];
+
+ for (size_t index = 0; index < gGSMNeighboringCells.size(); ++index) {
+ cells[index] = &gGSMNeighboringCells[index];
+ }
+
+ gce_ril_env->OnRequestComplete(t, RIL_E_SUCCESS, cells,
+ sizeof(RIL_NeighboringCell*));
+ delete[] cells;
+}
+
+#if VSOC_PLATFORM_SDK_AFTER(J_MR1)
+static void request_get_cell_info_list(void* /*data*/, size_t /*datalen*/,
+ RIL_Token t) {
+ struct timespec now;
+ uint64_t curTime;
+
+ ALOGV("Requesting Cell Info List");
+
+ clock_gettime(CLOCK_MONOTONIC, &now);
+ curTime = now.tv_sec * 1000000000LL + now.tv_nsec;
+
+#if VSOC_PLATFORM_SDK_AFTER(N_MR1)
+ RIL_CellInfo_v12 ci;
+#else
+ RIL_CellInfo ci;
+#endif
+
+ if (isGSM()) {
+ ci.cellInfoType = RIL_CELL_INFO_TYPE_GSM;
+ ci.registered = 1;
+ ci.timeStampType = RIL_TIMESTAMP_TYPE_ANTENNA; // Our own timestamp.
+ ci.timeStamp = curTime - 1000; // Fake time in the past.
+ ci.CellInfo.gsm.cellIdentityGsm.mcc = s_mcc;
+ ci.CellInfo.gsm.cellIdentityGsm.mnc = s_mnc;
+ ci.CellInfo.gsm.cellIdentityGsm.lac = s_lac;
+ ci.CellInfo.gsm.cellIdentityGsm.cid = s_cid;
+ ci.CellInfo.gsm.signalStrengthGsm.signalStrength = 10;
+ ci.CellInfo.gsm.signalStrengthGsm.bitErrorRate = 0;
+
+ gce_ril_env->OnRequestComplete(t, RIL_E_SUCCESS, &ci, sizeof(ci));
+ } else if (isCDMA()) {
+ // TODO(ender): CDMA cell support. And LTE.
+ gce_ril_env->OnRequestComplete(t, RIL_E_RADIO_NOT_AVAILABLE, NULL, 0);
+ } else {
+ gce_ril_env->OnRequestComplete(t, RIL_E_GENERIC_FAILURE, NULL, 0);
+ }
+}
+#endif
+
+struct NetworkOperator {
+ std::string long_name;
+ std::string short_name;
+ bool is_accessible;
+
+ NetworkOperator() {}
+
+ NetworkOperator(const std::string& long_name, const std::string& short_name,
+ bool is_accessible)
+ : long_name(long_name),
+ short_name(short_name),
+ is_accessible(is_accessible) {}
+};
+
+static std::map<std::string, NetworkOperator> gNetworkOperators;
+static std::string gCurrentNetworkOperator = "";
+
+enum OperatorSelectionMethod {
+ kOperatorAutomatic = 0,
+ kOperatorManual = 1,
+ kOperatorDeregistered = 2,
+ kOperatorManualThenAutomatic = 4
+};
+
+static void init_virtual_network() {
+ gGSMNeighboringCells.resize(1);
+ gGSMNeighboringCells[0].cid = (char*)"0000";
+ gGSMNeighboringCells[0].rssi = 75;
+ gNetworkOperators["310260"] =
+ NetworkOperator("Android Virtual Operator", "Android", true);
+ gNetworkOperators["310300"] =
+ NetworkOperator("Alternative Operator", "Alternative", true);
+ gNetworkOperators["310400"] =
+ NetworkOperator("Hermetic Network Operator", "Hermetic", false);
+}
+
+static OperatorSelectionMethod gOperatorSelectionMethod = kOperatorDeregistered;
+
+static void request_query_network_selection_mode(void* /*data*/,
+ size_t /*datalen*/,
+ RIL_Token t) {
+ ALOGV("Query operator selection mode (%d)", gOperatorSelectionMethod);
+ gce_ril_env->OnRequestComplete(t, RIL_E_SUCCESS, &gOperatorSelectionMethod,
+ sizeof(gOperatorSelectionMethod));
+}
+
+static void request_operator(void* /*data*/, size_t /*datalen*/, RIL_Token t) {
+ std::map<std::string, NetworkOperator>::iterator iter =
+ gNetworkOperators.find(gCurrentNetworkOperator);
+
+ ALOGV("Requesting current operator info");
+
+ if (iter == gNetworkOperators.end()) {
+ gce_ril_env->OnRequestComplete(t, RIL_E_RADIO_NOT_AVAILABLE, NULL, 0);
+ return;
+ }
+
+ const char* response[] = {iter->second.long_name.c_str(),
+ iter->second.short_name.c_str(),
+ iter->first.c_str()};
+
+ gce_ril_env->OnRequestComplete(t, RIL_E_SUCCESS, response, sizeof(response));
+}
+
+static void request_query_available_networks(void* /*data*/, size_t /*datalen*/,
+ RIL_Token t) {
+ const char** available_networks =
+ new const char*[gNetworkOperators.size() * 4];
+
+ ALOGV("Querying available networks.");
+
+ // TODO(ender): this should only respond once operator is selected and
+ // registered.
+ int index = 0;
+ for (std::map<std::string, NetworkOperator>::iterator iter =
+ gNetworkOperators.begin();
+ iter != gNetworkOperators.end(); ++iter) {
+ // TODO(ender): wrap in a neat structure maybe?
+ available_networks[index++] = iter->second.long_name.c_str();
+ available_networks[index++] = iter->second.short_name.c_str();
+ available_networks[index++] = iter->first.c_str();
+ if (!iter->second.is_accessible) {
+ available_networks[index++] = "forbidden";
+ } else if (iter->first == gCurrentNetworkOperator) {
+ available_networks[index++] = "current";
+ } else {
+ available_networks[index++] = "available";
+ }
+ }
+
+ gce_ril_env->OnRequestComplete(t, RIL_E_SUCCESS, &available_networks,
+ 4 * gNetworkOperators.size());
+ delete[] available_networks;
+}
+
+static void request_set_automatic_network_selection(RIL_Token t) {
+ ALOGV("Requesting automatic operator selection");
+ gCurrentNetworkOperator = gNetworkOperators.begin()->first;
+ gOperatorSelectionMethod = kOperatorAutomatic;
+ gce_ril_env->OnRequestComplete(t, RIL_E_SUCCESS, NULL, 0);
+}
+
+static void request_set_manual_network_selection(void* data, size_t /*datalen*/,
+ RIL_Token t) {
+ char* mccmnc = (char*)data;
+
+ ALOGV("Requesting manual operator selection: %s", mccmnc);
+
+ std::map<std::string, NetworkOperator>::iterator iter =
+ gNetworkOperators.find(mccmnc);
+
+ if (iter == gNetworkOperators.end() || iter->second.is_accessible) {
+ gce_ril_env->OnRequestComplete(t, RIL_E_ILLEGAL_SIM_OR_ME, NULL, 0);
+ return;
+ }
+
+ gCurrentNetworkOperator = mccmnc;
+ gOperatorSelectionMethod = kOperatorManual;
+
+ gce_ril_env->OnRequestComplete(t, RIL_E_SUCCESS, NULL, 0);
+}
+
+static const char kDefaultSMSC[] = "00";
+static int gNextSmsMessageId = 1;
+
+static void request_cdma_send_SMS(void* /*data*/, RIL_Token t) {
+ RIL_SMS_Response response = {0, 0, 0};
+ // RIL_CDMA_SMS_Message* rcsm = (RIL_CDMA_SMS_Message*) data;
+
+ ALOGW("CDMA SMS Send is currently not implemented.");
+
+ // Cdma Send SMS implementation will go here:
+ // But it is not implemented yet.
+ memset(&response, 0, sizeof(response));
+ response.messageRef = -1; // This must be BearerData MessageId.
+ gce_ril_env->OnRequestComplete(t, RIL_E_SMS_SEND_FAIL_RETRY, &response,
+ sizeof(response));
+}
+
+static void request_send_SMS(void* data, RIL_Token t) {
+ RIL_SMS_Response response = {0, 0, 0};
+
+ ALOGV("Send GSM SMS Message");
+
+ // SMSC is an address of SMS center or NULL for default.
+ const char* smsc = ((const char**)data)[0];
+ if (smsc == NULL) smsc = &kDefaultSMSC[0];
+
+ // PDU in hex-encoded string.
+ const char* pdu = ((const char**)data)[1];
+ int pdu_length = strlen(pdu) / 2;
+
+ response.messageRef = gNextSmsMessageId++;
+ response.ackPDU = NULL;
+ response.errorCode = 0;
+
+ gce_ril_env->OnRequestComplete(t, RIL_E_SUCCESS, &response, sizeof(response));
+
+ // response.messageRef = -1;
+ // gce_ril_env->OnRequestComplete(t, RIL_E_SMS_SEND_FAIL_RETRY, &response,
+ // sizeof(response));
+}
+
+#if VSOC_PLATFORM_SDK_AFTER(J_MR1)
+static void request_set_cell_info_list_rate(void* data, size_t /*datalen*/,
+ RIL_Token t) {
+ // For now we'll save the rate but no RIL_UNSOL_CELL_INFO_LIST messages
+ // will be sent.
+ ALOGV("Setting cell info list rate.");
+ s_cell_info_rate_ms = ((int*)data)[0];
+ gce_ril_env->OnRequestComplete(t, RIL_E_SUCCESS, NULL, 0);
+}
+#endif
+#if VSOC_PLATFORM_SDK_AFTER(J_MR2)
+static void request_ims_send_SMS(void* data, size_t /*datalen*/, RIL_Token t) {
+ RIL_IMS_SMS_Message* args = (RIL_IMS_SMS_Message*)data;
+ RIL_SMS_Response response{};
+
+ ALOGV("Send IMS SMS Message");
+
+ switch (args->tech) {
+ case RADIO_TECH_3GPP:
+ return request_send_SMS(args->message.gsmMessage, t);
+
+ case RADIO_TECH_3GPP2:
+ return request_cdma_send_SMS(args->message.gsmMessage, t);
+
+ default:
+ ALOGE("Invalid SMS format value: %d", args->tech);
+ response.messageRef = -2;
+ gce_ril_env->OnRequestComplete(t, RIL_E_GENERIC_FAILURE, &response,
+ sizeof(response));
+ }
+}
+#endif
+
+static void request_SMS_acknowledge(void* data, size_t /*datalen*/,
+ RIL_Token t) {
+ int* ack = (int*)data;
+
+ // TODO(ender): we should retain "incoming" sms for later reception.
+ ALOGV("SMS receipt %ssuccessful (reason %d).", ack[0] ? "" : "un", ack[1]);
+
+ gce_ril_env->OnRequestComplete(t, RIL_E_SUCCESS, NULL, 0);
+}
+
+struct SimFileCommand {
+ uint8_t command;
+ uint16_t efid;
+ uint8_t param1;
+ uint8_t param2;
+ uint8_t param3;
+
+ bool operator<(const SimFileCommand& other) const {
+ uint64_t sum1, sum2;
+ sum1 = (command * 1ull << 40) | (efid * 1ull << 24) | (param1 << 16) |
+ (param2 << 8) | (param3);
+ sum2 = (other.command * 1ull << 40) | (other.efid * 1ull << 24) |
+ (other.param1 << 16) | (other.param2 << 8) | (other.param3);
+ return sum1 < sum2;
+ }
+
+ SimFileCommand(uint8_t cmd, uint16_t efid, uint8_t p1, uint8_t p2, uint8_t p3)
+ : command(cmd), efid(efid), param1(p1), param2(p2), param3(p3) {}
+};
+
+struct SimFileResponse {
+ uint8_t sw1;
+ uint8_t sw2;
+ const char* data;
+
+ SimFileResponse() : sw1(0), sw2(0), data(NULL) {}
+
+ SimFileResponse(uint8_t sw1, uint8_t sw2, const char* data)
+ : sw1(sw1), sw2(sw2), data(data) {}
+};
+
+// TODO(ender): Double check & rewrite these.
+std::map<SimFileCommand, SimFileResponse> gSimFileSystem;
+
+static void init_sim_file_system() {
+ gSimFileSystem[SimFileCommand(192, 28436, 0, 0, 15)] =
+ SimFileResponse(144, 0, "000000146f1404001aa0aa01020000");
+ gSimFileSystem[SimFileCommand(176, 28436, 0, 0, 20)] =
+ SimFileResponse(144, 0, "416e64726f6964ffffffffffffffffffffffffff");
+ gSimFileSystem[SimFileCommand(192, 28433, 0, 0, 15)] =
+ SimFileResponse(144, 0, "000000016f11040011a0aa01020000");
+ gSimFileSystem[SimFileCommand(176, 28433, 0, 0, 1)] =
+ SimFileResponse(144, 0, "55");
+ gSimFileSystem[SimFileCommand(192, 12258, 0, 0, 15)] =
+ SimFileResponse(144, 0, "0000000a2fe204000fa0aa01020000");
+ gSimFileSystem[SimFileCommand(176, 12258, 0, 0, 10)] =
+ SimFileResponse(144, 0, "98101430121181157002");
+ gSimFileSystem[SimFileCommand(192, 28435, 0, 0, 15)] =
+ SimFileResponse(144, 0, "000000016f13040011a0aa01020000");
+ gSimFileSystem[SimFileCommand(176, 28435, 0, 0, 1)] =
+ SimFileResponse(144, 0, "55");
+ gSimFileSystem[SimFileCommand(192, 28472, 0, 0, 15)] =
+ SimFileResponse(144, 0, "0000000f6f3804001aa0aa01020000");
+ gSimFileSystem[SimFileCommand(176, 28472, 0, 0, 15)] =
+ SimFileResponse(144, 0, "ff30ffff3c003c03000c0000f03f00");
+ gSimFileSystem[SimFileCommand(192, 28617, 0, 0, 15)] =
+ SimFileResponse(144, 0, "000000086fc9040011a0aa01020104");
+ gSimFileSystem[SimFileCommand(178, 28617, 1, 4, 4)] =
+ SimFileResponse(144, 0, "01000000");
+ gSimFileSystem[SimFileCommand(192, 28618, 0, 0, 15)] =
+ SimFileResponse(144, 0, "0000000a6fca040011a0aa01020105");
+ gSimFileSystem[SimFileCommand(178, 28618, 1, 4, 5)] =
+ SimFileResponse(144, 0, "0000000000");
+ gSimFileSystem[SimFileCommand(192, 28589, 0, 0, 15)] =
+ SimFileResponse(144, 0, "000000046fad04000aa0aa01020000");
+ gSimFileSystem[SimFileCommand(176, 28589, 0, 0, 4)] =
+ SimFileResponse(144, 0, "00000003");
+ gSimFileSystem[SimFileCommand(192, 28438, 0, 0, 15)] =
+ SimFileResponse(144, 0, "000000026f1604001aa0aa01020000");
+ gSimFileSystem[SimFileCommand(176, 28438, 0, 0, 2)] =
+ SimFileResponse(144, 0, "0233");
+ gSimFileSystem[SimFileCommand(192, 28486, 0, 0, 15)] =
+ SimFileResponse(148, 4, NULL);
+ gSimFileSystem[SimFileCommand(192, 28621, 0, 0, 15)] =
+ SimFileResponse(148, 4, NULL);
+ gSimFileSystem[SimFileCommand(192, 28613, 0, 0, 15)] =
+ SimFileResponse(144, 0, "000000f06fc504000aa0aa01020118");
+ gSimFileSystem[SimFileCommand(178, 28613, 1, 4, 24)] = SimFileResponse(
+ 144, 0, "43058441aa890affffffffffffffffffffffffffffffffff");
+ gSimFileSystem[SimFileCommand(192, 28480, 0, 0, 15)] =
+ SimFileResponse(144, 0, "000000806f40040011a0aa01020120");
+ // Primary phone number encapsulated
+ // [51][55][21][43][65][f7] = 1 555 1234 567$
+ gSimFileSystem[SimFileCommand(178, 28480, 1, 4, 32)] = SimFileResponse(
+ 144, 0,
+ "ffffffffffffffffffffffffffffffffffff07915155214365f7ffffffffffff");
+ gSimFileSystem[SimFileCommand(192, 28615, 0, 0, 15)] =
+ SimFileResponse(144, 0, "000000406fc7040011a0aa01020120");
+ // Voice mail number encapsulated
+ // [56][6f][69][63][65][6d][61][69][6c] = 'Voicemail'
+ // [51][55][67][45][23][f1] = 1 555 7654 321$
+ gSimFileSystem[SimFileCommand(178, 28615, 1, 4, 32)] = SimFileResponse(
+ 144, 0,
+ "566f6963656d61696cffffffffffffffffff07915155674523f1ffffffffffff");
+ gSimFileSystem[SimFileCommand(192, 12037, 0, 0, 15)] =
+ SimFileResponse(148, 4, NULL);
+ gSimFileSystem[SimFileCommand(192, 28437, 0, 0, 15)] =
+ SimFileResponse(148, 4, NULL);
+ gSimFileSystem[SimFileCommand(192, 28478, 0, 0, 15)] =
+ SimFileResponse(148, 4, NULL);
+ gSimFileSystem[SimFileCommand(192, 28450, 0, 0, 15)] =
+ SimFileResponse(148, 4, NULL);
+ gSimFileSystem[SimFileCommand(192, 28456, 0, 0, 15)] =
+ SimFileResponse(148, 4, NULL);
+ gSimFileSystem[SimFileCommand(192, 28474, 0, 0, 15)] =
+ SimFileResponse(148, 4, NULL);
+ gSimFileSystem[SimFileCommand(192, 28481, 0, 0, 15)] =
+ SimFileResponse(148, 4, NULL);
+ gSimFileSystem[SimFileCommand(192, 28484, 0, 0, 15)] =
+ SimFileResponse(148, 4, NULL);
+ gSimFileSystem[SimFileCommand(192, 28493, 0, 0, 15)] =
+ SimFileResponse(148, 4, NULL);
+ gSimFileSystem[SimFileCommand(192, 28619, 0, 0, 15)] =
+ SimFileResponse(148, 4, NULL);
+ gSimFileSystem[SimFileCommand(176, 28506, 0, 0, 4)] =
+ SimFileResponse(144, 0, "00000013");
+}
+
+static void request_SIM_IO(void* data, size_t /*datalen*/, RIL_Token t) {
+ const RIL_SIM_IO_v6& args = *(RIL_SIM_IO_v6*)data;
+ RIL_SIM_IO_Response sr = {0, 0, 0};
+
+ ALOGV(
+ "Requesting SIM File IO: %d EFID %x, Params: %d, %d, %d, path: %s, "
+ "data %s PIN: %s AID: %s",
+ args.command, args.fileid, args.p1, args.p2, args.p3, args.path,
+ args.data, args.pin2, args.aidPtr);
+
+ SimFileCommand cmd(args.command, args.fileid, args.p1, args.p2, args.p3);
+
+ std::map<SimFileCommand, SimFileResponse>::iterator resp =
+ gSimFileSystem.find(cmd);
+
+ if (resp != gSimFileSystem.end()) {
+ sr.sw1 = resp->second.sw1;
+ sr.sw2 = resp->second.sw2;
+ if (resp->second.data) sr.simResponse = strdup(resp->second.data);
+ gce_ril_env->OnRequestComplete(t, RIL_E_SUCCESS, &sr, sizeof(sr));
+ return;
+ }
+
+ ALOGW("Unsupported SIM File IO command.");
+ gce_ril_env->OnRequestComplete(t, RIL_E_GENERIC_FAILURE, NULL, 0);
+}
+
+static void request_enter_sim_pin(void* data, size_t /*datalen*/, RIL_Token t) {
+ const char** pin_aid = (const char**)data;
+
+ ALOGV("Entering PIN: %s / %s", pin_aid[0], pin_aid[1]);
+
+ ++gSimPINAttempts;
+ int remaining_attempts = gSimPINAttemptsMax - gSimPINAttempts;
+
+ bool is_valid = false;
+
+ if (gSimStatus == SIM_PIN) {
+ is_valid = (gSimPIN == pin_aid[0]);
+ } else if (gSimStatus == SIM_PUK) {
+ is_valid = (gSimPUK == pin_aid[0]);
+ } else {
+ ALOGV("Unexpected SIM status for unlock: %d", gSimStatus);
+ gce_ril_env->OnRequestComplete(t, RIL_E_GENERIC_FAILURE, NULL, 0);
+ return;
+ }
+
+ if (!is_valid) {
+ if (gSimPINAttempts == gSimPINAttemptsMax) {
+ if (gSimStatus == SIM_PIN) {
+ gSimStatus = SIM_PUK;
+ gSimPINAttempts = 0;
+ } else {
+ ALOGV("PIN and PUK verification failed; locking SIM card.");
+ gSimStatus = SIM_NOT_READY;
+ gce_ril_env->OnRequestComplete(t, RIL_E_GENERIC_FAILURE, NULL, 0);
+ return;
+ }
+ }
+
+ gce_ril_env->OnRequestComplete(t, RIL_E_PASSWORD_INCORRECT,
+ &remaining_attempts,
+ sizeof(remaining_attempts));
+ } else {
+ if (gSimStatus == SIM_PUK) {
+ ALOGV("Resetting SIM PIN to %s", pin_aid[1]);
+ gSimPIN = pin_aid[1];
+ }
+
+ gSimPINAttempts = 0;
+ gSimStatus = SIM_READY;
+ gce_ril_env->OnRequestComplete(t, RIL_E_SUCCESS, &remaining_attempts,
+ sizeof(remaining_attempts));
+ }
+
+ pollSIMState(NULL);
+}
+
+/**
+ * No longer POLL.
+ */
+static void pollSIMState(void* /*param*/) {
+ // TODO(ender): check radio state?
+
+ ALOGV("Polling SIM Status.");
+
+ switch (gSimStatus) {
+ case SIM_ABSENT:
+ case SIM_PIN:
+ case SIM_PUK:
+ case SIM_NETWORK_PERSONALIZATION:
+ default:
+ ALOGV("SIM Absent or Locked");
+ break;
+
+ case SIM_NOT_READY:
+ // Transition directly to READY. Set default network operator.
+ if (gRadioPowerState == RADIO_STATE_ON) {
+ gSimStatus = SIM_READY;
+ gCurrentNetworkOperator = "310260";
+ }
+
+ gce_ril_env->RequestTimedCallback(pollSIMState, NULL, &TIMEVAL_SIMPOLL);
+ break;
+
+ case SIM_READY:
+ ALOGV("SIM Ready. Notifying network state changed.");
+ break;
+ }
+
+ if (gRadioPowerState != RADIO_STATE_OFF) {
+ gce_ril_env->OnUnsolicitedResponse(RIL_UNSOL_RESPONSE_SIM_STATUS_CHANGED,
+ NULL, 0);
+ gce_ril_env->OnUnsolicitedResponse(
+ RIL_UNSOL_RESPONSE_VOICE_NETWORK_STATE_CHANGED, NULL, 0);
+ }
+}
+
+std::map<SIM_Status, RIL_AppStatus> gRilAppStatus;
+
+static void init_sim_status() {
+ gRilAppStatus[SIM_ABSENT] = (RIL_AppStatus){RIL_APPTYPE_UNKNOWN,
+ RIL_APPSTATE_UNKNOWN,
+ RIL_PERSOSUBSTATE_UNKNOWN,
+ NULL,
+ NULL,
+ 0,
+ RIL_PINSTATE_UNKNOWN,
+ RIL_PINSTATE_UNKNOWN};
+ gRilAppStatus[SIM_NOT_READY] =
+ (RIL_AppStatus){RIL_APPTYPE_SIM,
+ RIL_APPSTATE_DETECTED,
+ RIL_PERSOSUBSTATE_UNKNOWN,
+ NULL,
+ NULL,
+ 0,
+ RIL_PINSTATE_ENABLED_NOT_VERIFIED,
+ RIL_PINSTATE_ENABLED_NOT_VERIFIED};
+ gRilAppStatus[SIM_READY] = (RIL_AppStatus){
+ RIL_APPTYPE_SIM,
+ RIL_APPSTATE_READY,
+ RIL_PERSOSUBSTATE_READY,
+ NULL,
+ NULL,
+ 0,
+ RIL_PINSTATE_ENABLED_VERIFIED,
+ RIL_PINSTATE_ENABLED_VERIFIED,
+ };
+ gRilAppStatus[SIM_PIN] = (RIL_AppStatus){RIL_APPTYPE_SIM,
+ RIL_APPSTATE_PIN,
+ RIL_PERSOSUBSTATE_UNKNOWN,
+ NULL,
+ NULL,
+ 0,
+ RIL_PINSTATE_ENABLED_NOT_VERIFIED,
+ RIL_PINSTATE_UNKNOWN};
+ gRilAppStatus[SIM_PUK] = (RIL_AppStatus){RIL_APPTYPE_SIM,
+ RIL_APPSTATE_PUK,
+ RIL_PERSOSUBSTATE_UNKNOWN,
+ NULL,
+ NULL,
+ 0,
+ RIL_PINSTATE_ENABLED_BLOCKED,
+ RIL_PINSTATE_UNKNOWN};
+ gRilAppStatus[SIM_NETWORK_PERSONALIZATION] =
+ (RIL_AppStatus){RIL_APPTYPE_SIM,
+ RIL_APPSTATE_SUBSCRIPTION_PERSO,
+ RIL_PERSOSUBSTATE_SIM_NETWORK,
+ NULL,
+ NULL,
+ 0,
+ RIL_PINSTATE_ENABLED_NOT_VERIFIED,
+ RIL_PINSTATE_UNKNOWN};
+ gRilAppStatus[RUIM_ABSENT] = (RIL_AppStatus){RIL_APPTYPE_UNKNOWN,
+ RIL_APPSTATE_UNKNOWN,
+ RIL_PERSOSUBSTATE_UNKNOWN,
+ NULL,
+ NULL,
+ 0,
+ RIL_PINSTATE_UNKNOWN,
+ RIL_PINSTATE_UNKNOWN};
+ gRilAppStatus[RUIM_NOT_READY] = (RIL_AppStatus){RIL_APPTYPE_RUIM,
+ RIL_APPSTATE_DETECTED,
+ RIL_PERSOSUBSTATE_UNKNOWN,
+ NULL,
+ NULL,
+ 0,
+ RIL_PINSTATE_UNKNOWN,
+ RIL_PINSTATE_UNKNOWN};
+ gRilAppStatus[RUIM_READY] = (RIL_AppStatus){RIL_APPTYPE_RUIM,
+ RIL_APPSTATE_READY,
+ RIL_PERSOSUBSTATE_READY,
+ NULL,
+ NULL,
+ 0,
+ RIL_PINSTATE_UNKNOWN,
+ RIL_PINSTATE_UNKNOWN};
+ gRilAppStatus[RUIM_PIN] = (RIL_AppStatus){RIL_APPTYPE_RUIM,
+ RIL_APPSTATE_PIN,
+ RIL_PERSOSUBSTATE_UNKNOWN,
+ NULL,
+ NULL,
+ 0,
+ RIL_PINSTATE_ENABLED_NOT_VERIFIED,
+ RIL_PINSTATE_UNKNOWN};
+ gRilAppStatus[RUIM_PUK] = (RIL_AppStatus){RIL_APPTYPE_RUIM,
+ RIL_APPSTATE_PUK,
+ RIL_PERSOSUBSTATE_UNKNOWN,
+ NULL,
+ NULL,
+ 0,
+ RIL_PINSTATE_ENABLED_BLOCKED,
+ RIL_PINSTATE_UNKNOWN};
+ gRilAppStatus[RUIM_NETWORK_PERSONALIZATION] =
+ (RIL_AppStatus){RIL_APPTYPE_RUIM,
+ RIL_APPSTATE_SUBSCRIPTION_PERSO,
+ RIL_PERSOSUBSTATE_SIM_NETWORK,
+ NULL,
+ NULL,
+ 0,
+ RIL_PINSTATE_ENABLED_NOT_VERIFIED,
+ RIL_PINSTATE_UNKNOWN};
+}
+
+/**
+ * Get the current card status.
+ */
+static void getCardStatus(RIL_Token t) {
+ ALOGV("Querying SIM status.");
+ RIL_CardStatus_v6 card_status;
+
+ if (gSimStatus == SIM_ABSENT) {
+ card_status.card_state = RIL_CARDSTATE_ABSENT;
+ card_status.num_applications = 0;
+ } else {
+ card_status.card_state = RIL_CARDSTATE_PRESENT;
+ card_status.num_applications = 1;
+ }
+
+ card_status.universal_pin_state = RIL_PINSTATE_UNKNOWN;
+ card_status.gsm_umts_subscription_app_index = -1;
+ card_status.cdma_subscription_app_index = -1;
+ card_status.ims_subscription_app_index = -1;
+
+ // Initialize application status
+ for (int i = 0; i < RIL_CARD_MAX_APPS; i++) {
+ card_status.applications[i] = gRilAppStatus[SIM_ABSENT];
+ }
+
+ if (card_status.num_applications > 0) {
+ card_status.gsm_umts_subscription_app_index = 0;
+
+ card_status.applications[0] = gRilAppStatus[gSimStatus];
+ card_status.universal_pin_state = card_status.applications[0].pin1;
+ // To enable basic CDMA (currently neither supported nor functional):
+ // card_status.num_applications = 2;
+ // card_status.cdma_subscription_app_index = 1;
+ // card_status.applications[1] =
+ // gRilAppStatus[SIM_Status(gSimStatus + RUIM_ABSENT)];
+ }
+
+ gce_ril_env->OnRequestComplete(t, RIL_E_SUCCESS, &card_status,
+ sizeof(card_status));
+}
+
+struct SimSession {
+ std::string aid;
+};
+
+static int gNextSimSessionId = 1;
+static std::map<int, SimSession> gSimSessions;
+
+static void request_sim_open_channel(void* data, size_t /*datalen*/,
+ RIL_Token t) {
+ char* aid = (char*)data;
+ SimSession session;
+
+ ALOGV("Requesting new SIM session");
+
+ if (aid != NULL) {
+ session.aid = aid;
+ }
+
+ int response = gNextSimSessionId++;
+ gSimSessions[response] = session;
+
+ gce_ril_env->OnRequestComplete(t, RIL_E_SUCCESS, &response, sizeof(response));
+}
+
+static void request_sim_close_channel(void* data, size_t /*datalen*/,
+ RIL_Token t) {
+ int session = *(int*)(data);
+
+ ALOGV("Closing SIM session %d", session);
+
+ if (gSimSessions.erase(session) == 0) {
+ // No such session.
+ gce_ril_env->OnRequestComplete(t, RIL_E_GENERIC_FAILURE, NULL, 0);
+ } else {
+ gce_ril_env->OnRequestComplete(t, RIL_E_SUCCESS, NULL, 0);
+ }
+}
+
+#if VSOC_PLATFORM_SDK_AFTER(K)
+static void request_sim_apdu(void* data, size_t /*datalen*/, RIL_Token t) {
+ RIL_SIM_APDU* apdu = (RIL_SIM_APDU*)data;
+
+ ALOGV("Requesting APDU: Session %d CLA %d INST %d Params: %d %d %d, data %s",
+ apdu->sessionid, apdu->cla, apdu->instruction, apdu->p1, apdu->p2,
+ apdu->p3, apdu->data);
+
+ if (gSimSessions.find(apdu->sessionid) != gSimSessions.end()) {
+ RIL_SIM_IO_Response sr{};
+
+ // Fallback / default behavior.
+ sr.sw1 = 144;
+ sr.sw2 = 0;
+ sr.simResponse = NULL;
+
+ gce_ril_env->OnRequestComplete(t, RIL_E_SUCCESS, &sr, sizeof(sr));
+ } else {
+ gce_ril_env->OnRequestComplete(t, RIL_E_GENERIC_FAILURE, NULL, 0);
+ }
+}
+#endif
+
+// 0 = Lock is available, but disabled.
+// 1 = Lock is available and enabled,
+// 2 = lock is neither available nor enabled
+static const int kFacilityLockAllDisabled = 0;
+
+static void request_facility_lock(void* data, size_t /*datalen*/, RIL_Token t) {
+ char** data_vec = (char**)data;
+
+ int data_vec_len = atoi(data_vec[1]);
+ char* result_vec;
+
+ // TODO(ender): implement this; essentially: AT+CLCK
+ // See http://www.activexperts.com/sms-component/at/commands/?at=%2BCLCK
+ // and
+ // opt/telephony/src/java/com/android/internal/telephony/CommandsInterface.java
+ // opt/telephony/src/java/com/android/internal/telephony/uicc/UiccCardApplication.java
+
+ ALOGV("Query Facility Lock Code: %s PIN2: %s Service(s): %s AID: %s",
+ data_vec[0], data_vec[1], data_vec[2], data_vec[3]);
+
+ // TODO(ender): there should be a bit vector of responses for each of the
+ // services requested.
+ // Depending on lock code, facilities may be unlocked or locked. We report
+ // these are all unlocked, regardless of the query.
+ gce_ril_env->OnRequestComplete(t, RIL_E_SUCCESS,
+ const_cast<int*>(&kFacilityLockAllDisabled),
+ sizeof(kFacilityLockAllDisabled));
+}
+
+static void request_international_subscriber_id_number(RIL_Token t) {
+ // TODO(ender): Reuse MCC and MNC.
+ std::string subscriber_id = gCurrentNetworkOperator.c_str();
+ subscriber_id += "123456789";
+
+ gce_ril_env->OnRequestComplete(t, RIL_E_SUCCESS,
+ strdup(subscriber_id.c_str()), sizeof(char*));
+}
+
+static bool gScreenIsOn = true;
+
+static void request_set_screen_state(void* data, size_t /*datalen*/,
+ RIL_Token t) {
+ gScreenIsOn = *(int*)data ? true : false;
+ ALOGV("Screen is %s", gScreenIsOn ? "on" : "off");
+ gce_ril_env->OnRequestComplete(t, RIL_E_SUCCESS, NULL, 0);
+}
+
+// Unsure which section this belongs in.
+
+static int gModemTtyMode = 1; // 0 = off, 1 = full, 2 = HCO, 3 = VCO.
+static void request_set_tty_mode(void* data, size_t /*datalen*/, RIL_Token t) {
+ int new_tty_mode = *(int*)(data);
+ ALOGV("Switching modem TTY mode %d -> %d", gModemTtyMode, new_tty_mode);
+
+ if (new_tty_mode >= 0 && new_tty_mode <= 3) {
+ gModemTtyMode = new_tty_mode;
+ gce_ril_env->OnRequestComplete(t, RIL_E_SUCCESS, NULL, 0);
+ } else {
+ gce_ril_env->OnRequestComplete(t, RIL_E_GENERIC_FAILURE, NULL, 0);
+ }
+}
+
+static void request_get_tty_mode(RIL_Token t) {
+ ALOGV("Querying TTY mode");
+ gce_ril_env->OnRequestComplete(t, RIL_E_SUCCESS, &gModemTtyMode,
+ sizeof(gModemTtyMode));
+}
+
+static bool gImsRegistered = false;
+static int gImsFormat = RADIO_TECH_3GPP;
+
+static void request_ims_registration_state(RIL_Token t) {
+ ALOGV("Querying IMS mode");
+ int reply[2];
+ reply[0] = gImsRegistered;
+ reply[1] = gImsFormat;
+
+ ALOGV("Requesting IMS Registration state: %d, format=%d ", reply[0],
+ reply[1]);
+
+ gce_ril_env->OnRequestComplete(t, RIL_E_SUCCESS, reply, sizeof(reply));
+}
+
+static void gce_ril_on_request(int request, void* data, size_t datalen,
+ RIL_Token t) {
+ int err;
+
+ // Ignore all requests except RIL_REQUEST_GET_SIM_STATUS
+ // when RADIO_STATE_UNAVAILABLE.
+ if (gRadioPowerState == RADIO_STATE_UNAVAILABLE &&
+ request != RIL_REQUEST_GET_SIM_STATUS) {
+ gce_ril_env->OnRequestComplete(t, RIL_E_RADIO_NOT_AVAILABLE, NULL, 0);
+ return;
+ }
+
+ // Ignore all non-power requests when RADIO_STATE_OFF (except
+ // RIL_REQUEST_GET_SIM_STATUS)
+ if (gRadioPowerState == RADIO_STATE_OFF &&
+ !(request == RIL_REQUEST_RADIO_POWER ||
+ request == RIL_REQUEST_GET_SIM_STATUS)) {
+ gce_ril_env->OnRequestComplete(t, RIL_E_RADIO_NOT_AVAILABLE, NULL, 0);
+ return;
+ }
+
+ ALOGV("Received request %d", request);
+
+ switch (request) {
+ case RIL_REQUEST_QUERY_AVAILABLE_NETWORKS:
+ request_query_available_networks(data, datalen, t);
+ break;
+ case RIL_REQUEST_GET_IMEI:
+ request_get_imei(t);
+ break;
+ case RIL_REQUEST_GET_IMEISV:
+ request_get_imei_sv(t);
+ break;
+ case RIL_REQUEST_DEACTIVATE_DATA_CALL:
+ request_teardown_data_call(data, datalen, t);
+ break;
+ case RIL_REQUEST_SCREEN_STATE:
+ request_set_screen_state(data, datalen, t);
+ break;
+ case RIL_REQUEST_GET_SIM_STATUS:
+ getCardStatus(t);
+ break;
+ case RIL_REQUEST_GET_CURRENT_CALLS:
+ request_get_current_calls(data, datalen, t);
+ break;
+ case RIL_REQUEST_DIAL:
+ request_dial(data, datalen, t);
+ break;
+ case RIL_REQUEST_HANGUP:
+ request_hangup(data, datalen, t);
+ break;
+ case RIL_REQUEST_HANGUP_WAITING_OR_BACKGROUND:
+ request_hangup_waiting(data, datalen, t);
+ break;
+ case RIL_REQUEST_HANGUP_FOREGROUND_RESUME_BACKGROUND:
+ request_hangup_current(t);
+ break;
+ case RIL_REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE:
+ request_switch_current_and_waiting(t);
+ break;
+ case RIL_REQUEST_ANSWER:
+ request_answer_incoming(t);
+ break;
+ case RIL_REQUEST_SET_MUTE:
+ request_set_mute(data, datalen, t);
+ break;
+ case RIL_REQUEST_GET_MUTE:
+ request_get_mute(t);
+ break;
+ case RIL_REQUEST_CONFERENCE:
+ request_combine_multiparty_call(data, datalen, t);
+ break;
+ case RIL_REQUEST_SEPARATE_CONNECTION:
+ request_split_multiparty_call(data, datalen, t);
+ break;
+ case RIL_REQUEST_UDUB:
+ request_udub_on_incoming_calls(t);
+ break;
+ case RIL_REQUEST_SIGNAL_STRENGTH:
+ request_signal_strength(data, datalen, t);
+ break;
+ case RIL_REQUEST_VOICE_REGISTRATION_STATE:
+ case RIL_REQUEST_DATA_REGISTRATION_STATE:
+ request_registration_state(request, data, datalen, t);
+ break;
+ case RIL_REQUEST_OPERATOR:
+ request_operator(data, datalen, t);
+ break;
+ case RIL_REQUEST_RADIO_POWER:
+ request_radio_power(data, datalen, t);
+ break;
+ case RIL_REQUEST_DTMF:
+ case RIL_REQUEST_DTMF_START:
+ request_send_dtmf(data, datalen, t);
+ break;
+ case RIL_REQUEST_DTMF_STOP:
+ request_send_dtmf_stop(t);
+ break;
+ case RIL_REQUEST_SEND_SMS:
+ request_send_SMS(data, t);
+ break;
+ case RIL_REQUEST_CDMA_SEND_SMS:
+ request_cdma_send_SMS(data, t);
+ break;
+ case RIL_REQUEST_SETUP_DATA_CALL:
+ request_setup_data_call(data, datalen, t);
+ break;
+ case RIL_REQUEST_SMS_ACKNOWLEDGE:
+ request_SMS_acknowledge(data, datalen, t);
+ break;
+ case RIL_REQUEST_GET_IMSI:
+ request_international_subscriber_id_number(t);
+ break;
+ case RIL_REQUEST_QUERY_FACILITY_LOCK:
+ request_facility_lock(data, datalen, t);
+ break;
+ case RIL_REQUEST_SIM_IO:
+ request_SIM_IO(data, datalen, t);
+ break;
+ case RIL_REQUEST_SEND_USSD:
+ request_send_ussd(data, datalen, t);
+ break;
+ case RIL_REQUEST_CANCEL_USSD:
+ request_cancel_ussd(t);
+ break;
+ case RIL_REQUEST_SET_NETWORK_SELECTION_AUTOMATIC:
+ request_set_automatic_network_selection(t);
+ break;
+ case RIL_REQUEST_SET_NETWORK_SELECTION_MANUAL:
+ request_set_manual_network_selection(data, datalen, t);
+ break;
+ case RIL_REQUEST_DATA_CALL_LIST:
+ request_data_calllist(data, datalen, t);
+ break;
+ case RIL_REQUEST_LAST_DATA_CALL_FAIL_CAUSE:
+ request_datacall_fail_cause(t);
+ break;
+ case RIL_REQUEST_QUERY_NETWORK_SELECTION_MODE:
+ request_query_network_selection_mode(data, datalen, t);
+ break;
+ case RIL_REQUEST_OEM_HOOK_RAW:
+ case RIL_REQUEST_OEM_HOOK_STRINGS:
+ ALOGV("OEM Hooks not supported!");
+ gce_ril_env->OnRequestComplete(t, RIL_E_REQUEST_NOT_SUPPORTED, NULL, 0);
+ break;
+ case RIL_REQUEST_WRITE_SMS_TO_SIM:
+ request_write_sms_to_sim(data, datalen, t);
+ break;
+ case RIL_REQUEST_DELETE_SMS_ON_SIM:
+ request_delete_sms_on_sim(data, datalen, t);
+ break;
+ case RIL_REQUEST_ENTER_SIM_PIN:
+ case RIL_REQUEST_ENTER_SIM_PUK:
+ case RIL_REQUEST_ENTER_SIM_PIN2:
+ case RIL_REQUEST_ENTER_SIM_PUK2:
+ case RIL_REQUEST_CHANGE_SIM_PIN:
+ case RIL_REQUEST_CHANGE_SIM_PIN2:
+ request_enter_sim_pin(data, datalen, t);
+ break;
+ case RIL_REQUEST_VOICE_RADIO_TECH: {
+ RIL_RadioTechnology tech = getBestVoiceTechnology(gModemCurrentType);
+ gce_ril_env->OnRequestComplete(t, RIL_E_SUCCESS, &tech, sizeof(tech));
+ break;
+ }
+ case RIL_REQUEST_SET_PREFERRED_NETWORK_TYPE:
+ request_set_preferred_network_type(request, data, datalen, t);
+ break;
+ case RIL_REQUEST_GET_PREFERRED_NETWORK_TYPE:
+ request_get_preferred_network_type(request, data, datalen, t);
+ break;
+ case RIL_REQUEST_GET_NEIGHBORING_CELL_IDS:
+ request_get_neighboring_cell_ids(data, datalen, t);
+ break;
+#if VSOC_PLATFORM_SDK_AFTER(J_MR1)
+ case RIL_REQUEST_GET_CELL_INFO_LIST:
+ request_get_cell_info_list(data, datalen, t);
+ break;
+ case RIL_REQUEST_SET_UNSOL_CELL_INFO_LIST_RATE:
+ request_set_cell_info_list_rate(data, datalen, t);
+ break;
+#endif
+ case RIL_REQUEST_BASEBAND_VERSION:
+ request_baseband_version(t);
+ break;
+ case RIL_REQUEST_SET_TTY_MODE:
+ request_set_tty_mode(data, datalen, t);
+ break;
+ case RIL_REQUEST_QUERY_TTY_MODE:
+ request_get_tty_mode(t);
+ break;
+
+#if VSOC_PLATFORM_SDK_AFTER(L)
+ case RIL_REQUEST_GET_RADIO_CAPABILITY:
+ request_get_radio_capability(t);
+ break;
+ case RIL_REQUEST_SET_RADIO_CAPABILITY:
+ request_set_radio_capability(data, datalen, t);
+ break;
+#endif
+#if VSOC_PLATFORM_SDK_AFTER(K)
+ case RIL_REQUEST_GET_HARDWARE_CONFIG:
+ request_hardware_config(t);
+ break;
+ case RIL_REQUEST_IMS_REGISTRATION_STATE:
+ request_ims_registration_state(t);
+ break;
+ case RIL_REQUEST_SIM_TRANSMIT_APDU_CHANNEL:
+ request_sim_apdu(data, datalen, t);
+ break;
+ case RIL_REQUEST_SIM_OPEN_CHANNEL:
+ request_sim_open_channel(data, datalen, t);
+ break;
+ case RIL_REQUEST_SIM_CLOSE_CHANNEL:
+ request_sim_close_channel(data, datalen, t);
+ break;
+#endif
+#if VSOC_PLATFORM_SDK_AFTER(J_MR2)
+ case RIL_REQUEST_IMS_SEND_SMS:
+ request_ims_send_SMS(data, datalen, t);
+ break;
+
+ case RIL_REQUEST_SET_INITIAL_ATTACH_APN:
+ ALOGW("INITIAL ATTACH APN");
+ gce_ril_env->OnRequestComplete(t, RIL_E_SUCCESS, NULL, 0);
+ break;
+
+#endif
+ case RIL_REQUEST_REPORT_STK_SERVICE_IS_RUNNING:
+ gce_ril_env->OnRequestComplete(t, RIL_E_SUCCESS, NULL, 0);
+ break;
+ case RIL_REQUEST_DEVICE_IDENTITY:
+ request_device_identity(request, data, datalen, t);
+ break;
+ case RIL_REQUEST_CDMA_GET_SUBSCRIPTION_SOURCE:
+ request_cdma_get_subscription_source(request, data, datalen, t);
+ break;
+ case RIL_REQUEST_CDMA_SUBSCRIPTION:
+ request_cdma_subscription(request, data, datalen, t);
+ break;
+ case RIL_REQUEST_CDMA_SET_SUBSCRIPTION_SOURCE:
+ request_cdma_set_subscription_source(request, data, datalen, t);
+ break;
+ case RIL_REQUEST_CDMA_QUERY_ROAMING_PREFERENCE:
+ request_cdma_get_roaming_preference(request, data, datalen, t);
+ break;
+ case RIL_REQUEST_CDMA_SET_ROAMING_PREFERENCE:
+ request_cdma_set_roaming_preference(request, data, datalen, t);
+ break;
+ case RIL_REQUEST_EXIT_EMERGENCY_CALLBACK_MODE:
+ request_exit_emergency_mode(data, datalen, t);
+ break;
+ default:
+ ALOGE("Request %d not supported.", request);
+ gce_ril_env->OnRequestComplete(t, RIL_E_REQUEST_NOT_SUPPORTED, NULL, 0);
+ break;
+ }
+}
+
+#define GCE_RIL_VERSION 6
+
+static const RIL_RadioFunctions ril_callbacks = {
+ GCE_RIL_VERSION, gce_ril_on_request, gce_ril_current_state,
+ gce_ril_on_supports, gce_ril_on_cancel, gce_ril_get_version};
+
+extern "C" {
+
+const RIL_RadioFunctions* RIL_Init(const struct RIL_Env* env, int /*argc*/,
+ char** /*argv*/) {
+ time(&gce_ril_start_time);
+ gce_ril_env = env;
+
+ TearDownNetworkInterface();
+
+ init_modem_supported_network_types();
+ init_modem_technologies();
+ init_virtual_network();
+ init_sim_file_system();
+ init_sim_status();
+
+ return &ril_callbacks;
+}
+
+} // extern "C"
diff --git a/guest/hals/ril/vsoc_ril.h b/guest/hals/ril/vsoc_ril.h
new file mode 100644
index 0000000..44000e7
--- /dev/null
+++ b/guest/hals/ril/vsoc_ril.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2017 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 GUEST_RIL_VSOC_RIL_H_
+#define GUEST_RIL_VSOC_RIL_H_
+
+#define RIL_SHLIB
+
+#define LOG_TAG "VSoCRil"
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <cutils/log.h>
+#include <telephony/ril.h>
+#include <telephony/ril_cdma_sms.h>
+#include <sys/time.h>
+
+#endif // GUEST_RIL_VSOC_RIL_H_
diff --git a/guest/hals/sensors/Android.mk b/guest/hals/sensors/Android.mk
new file mode 100644
index 0000000..ff2d4f1
--- /dev/null
+++ b/guest/hals/sensors/Android.mk
@@ -0,0 +1,59 @@
+# Copyright (C) 2017 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.
+
+LOCAL_PATH := $(call my-dir)
+
+# HAL module implemenation stored in
+# hw/<SENSORS_HARDWARE_MODULE_ID>.<ro.hardware>.so
+include $(CLEAR_VARS)
+
+ifeq (0, $(shell test $(PLATFORM_SDK_VERSION) -ge 21; echo $$?))
+LOCAL_MODULE_RELATIVE_PATH := hw
+else
+LOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)/hw
+endif
+LOCAL_MULTILIB := first
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SHARED_LIBRARIES := \
+ $(VSOC_STLPORT_LIBS) \
+ libcuttlefish_fs \
+ cuttlefish_auto_resources \
+ liblog \
+ libcutils
+
+LOCAL_SRC_FILES := \
+ sensors.cpp \
+ sensors_hal.cpp \
+ vsoc_sensors.cpp \
+ vsoc_sensors_message.cpp
+
+LOCAL_CFLAGS := -DLOG_TAG=\"VSoC-Sensors\" \
+ $(VSOC_VERSION_CFLAGS) \
+ -Werror -Wall -Wno-missing-field-initializers -Wno-unused-parameter
+
+LOCAL_C_INCLUDES := \
+ $(VSOC_STLPORT_INCLUDES) \
+ device/google/cuttlefish_common \
+ system/extras
+
+LOCAL_STATIC_LIBRARIES := \
+ libcutils \
+ libcuttlefish_remoter_framework \
+ $(VSOC_STLPORT_STATIC_LIBS)
+
+LOCAL_MODULE := sensors.vsoc
+LOCAL_VENDOR_MODULE := true
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/guest/hals/sensors/sensors.cpp b/guest/hals/sensors/sensors.cpp
new file mode 100644
index 0000000..eb1dda6
--- /dev/null
+++ b/guest/hals/sensors/sensors.cpp
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#include "guest/hals/sensors/vsoc_sensors.h"
+
+#include <limits>
+
+#include "guest/hals/sensors/sensors.h"
+
+namespace cvd {
+namespace {
+const cvd::time::Milliseconds kDefaultSamplingRate(200);
+
+#if !VSOC_SENSORS_DEVICE_API_VERSION_ATLEAST(1_3)
+namespace {
+const int SENSOR_FLAG_WAKE_UP = 0;
+}
+#endif
+
+timespec infinity() {
+ timespec ts;
+ ts.tv_sec = std::numeric_limits<time_t>::max();
+ ts.tv_nsec = 0;
+ return ts;
+}
+} // namespace
+
+const cvd::time::MonotonicTimePoint SensorState::kInfinity =
+ cvd::time::MonotonicTimePoint(infinity());
+
+SensorState::SensorState(SensorInfo info)
+ : enabled_(false),
+ event_(),
+ deadline_(kInfinity),
+ sampling_period_(kDefaultSamplingRate) {
+ event_.sensor = info.handle;
+ event_.type = info.type;
+}
+
+SensorInfo::SensorInfo(const char* name, const char* vendor, int version,
+ int handle, int type, float max_range, float resolution,
+ float power, int32_t min_delay,
+ uint32_t fifo_reserved_event_count,
+ uint32_t fifo_max_event_count, const char* string_type,
+ const char* required_permission, int32_t max_delay,
+ uint32_t reporting_mode) {
+ this->name = name;
+ this->vendor = vendor;
+ this->version = version;
+ this->handle = handle;
+ this->type = type;
+ this->maxRange = max_range;
+ this->resolution = resolution;
+ this->power = power;
+ this->minDelay = min_delay;
+#if VSOC_SENSORS_DEVICE_API_VERSION_ATLEAST(1_1)
+ this->fifoReservedEventCount = fifo_reserved_event_count;
+ this->fifoMaxEventCount = fifo_max_event_count;
+#endif
+#if VSOC_SENSORS_DEVICE_API_VERSION_ATLEAST(1_2)
+ this->stringType = string_type;
+ this->requiredPermission = required_permission;
+#endif
+#if VSOC_SENSORS_DEVICE_API_VERSION_ATLEAST(1_3)
+ this->maxDelay = max_delay;
+ this->flags = reporting_mode;
+#endif
+}
+
+namespace sc = sensors_constants;
+
+SensorInfo AccelerometerSensor() {
+ uint32_t flags = sc::kAccelerometerReportingMode |
+ (sc::kAccelerometerIsWakeup ? SENSOR_FLAG_WAKE_UP : 0);
+
+ return SensorInfo(sc::kAccelerometerName, sc::kVendor, sc::kVersion,
+ sc::kAccelerometerHandle, SENSOR_TYPE_ACCELEROMETER,
+ sc::kAccelerometerMaxRange, sc::kAccelerometerResolution,
+ sc::kAccelerometerPower, sc::kAccelerometerMinDelay,
+ sc::kFifoReservedEventCount, sc::kFifoMaxEventCount,
+ sc::kAccelerometerStringType, sc::kRequiredPermission,
+ sc::kMaxDelay, flags);
+}
+
+SensorInfo GyroscopeSensor() {
+ uint32_t flags = sc::kGyroscopeReportingMode |
+ (sc::kGyroscopeIsWakeup ? SENSOR_FLAG_WAKE_UP : 0);
+
+ return SensorInfo(
+ sc::kGyroscopeName, sc::kVendor, sc::kVersion, sc::kGyroscopeHandle,
+ SENSOR_TYPE_GYROSCOPE, sc::kGyroscopeMaxRange, sc::kGyroscopeResolution,
+ sc::kGyroscopePower, sc::kGyroscopeMinDelay, sc::kFifoReservedEventCount,
+ sc::kFifoMaxEventCount, sc::kGyroscopeStringType, sc::kRequiredPermission,
+ sc::kMaxDelay, flags);
+}
+
+SensorInfo LightSensor() {
+ uint32_t flags = sc::kLightReportingMode |
+ (sc::kLightIsWakeup ? SENSOR_FLAG_WAKE_UP : 0);
+
+ return SensorInfo(sc::kLightName, sc::kVendor, sc::kVersion, sc::kLightHandle,
+ SENSOR_TYPE_LIGHT, sc::kLightMaxRange, sc::kLightResolution,
+ sc::kLightPower, sc::kLightMinDelay,
+ sc::kFifoReservedEventCount, sc::kFifoMaxEventCount,
+ sc::kLightStringType, sc::kRequiredPermission,
+ sc::kMaxDelay, flags);
+}
+
+SensorInfo MagneticFieldSensor() {
+ uint32_t flags = sc::kMagneticFieldReportingMode |
+ (sc::kMagneticFieldIsWakeup ? SENSOR_FLAG_WAKE_UP : 0);
+
+ return SensorInfo(sc::kMagneticFieldName, sc::kVendor, sc::kVersion,
+ sc::kMagneticFieldHandle, SENSOR_TYPE_MAGNETIC_FIELD,
+ sc::kMagneticFieldMaxRange, sc::kMagneticFieldResolution,
+ sc::kMagneticFieldPower, sc::kMagneticFieldMinDelay,
+ sc::kFifoReservedEventCount, sc::kFifoMaxEventCount,
+ sc::kMagneticFieldStringType, sc::kRequiredPermission,
+ sc::kMaxDelay, flags);
+}
+
+SensorInfo PressureSensor() {
+ uint32_t flags = sc::kPressureReportingMode |
+ (sc::kPressureIsWakeup ? SENSOR_FLAG_WAKE_UP : 0);
+
+ return SensorInfo(
+ sc::kPressureName, sc::kVendor, sc::kVersion, sc::kPressureHandle,
+ SENSOR_TYPE_PRESSURE, sc::kPressureMaxRange, sc::kPressureResolution,
+ sc::kPressurePower, sc::kPressureMinDelay, sc::kFifoReservedEventCount,
+ sc::kFifoMaxEventCount, sc::kPressureStringType, sc::kRequiredPermission,
+ sc::kMaxDelay, flags);
+}
+
+SensorInfo ProximitySensor() {
+ uint32_t flags = sc::kProximityReportingMode |
+ (sc::kProximityIsWakeup ? SENSOR_FLAG_WAKE_UP : 0);
+
+ return SensorInfo(
+ sc::kProximityName, sc::kVendor, sc::kVersion, sc::kProximityHandle,
+ SENSOR_TYPE_PROXIMITY, sc::kProximityMaxRange, sc::kProximityResolution,
+ sc::kProximityPower, sc::kProximityMinDelay, sc::kFifoReservedEventCount,
+ sc::kFifoMaxEventCount, sc::kProximityStringType, sc::kRequiredPermission,
+ sc::kMaxDelay, flags);
+}
+
+SensorInfo AmbientTempSensor() {
+ uint32_t flags = sc::kAmbientTempReportingMode |
+ (sc::kAmbientTempIsWakeup ? SENSOR_FLAG_WAKE_UP : 0);
+
+ return SensorInfo(sc::kAmbientTempName, sc::kVendor, sc::kVersion,
+ sc::kAmbientTempHandle, SENSOR_TYPE_AMBIENT_TEMPERATURE,
+ sc::kAmbientTempMaxRange, sc::kAmbientTempResolution,
+ sc::kAmbientTempPower, sc::kAmbientTempMinDelay,
+ sc::kFifoReservedEventCount, sc::kFifoMaxEventCount,
+ sc::kAmbientTempStringType, sc::kRequiredPermission,
+ sc::kMaxDelay, flags);
+}
+
+SensorInfo DeviceTempSensor() {
+ uint32_t flags = sc::kDeviceTempReportingMode |
+ (sc::kDeviceTempIsWakeup ? SENSOR_FLAG_WAKE_UP : 0);
+
+ return SensorInfo(sc::kDeviceTempName, sc::kVendor, sc::kVersion,
+ sc::kDeviceTempHandle, SENSOR_TYPE_TEMPERATURE,
+ sc::kDeviceTempMaxRange, sc::kDeviceTempResolution,
+ sc::kDeviceTempPower, sc::kDeviceTempMinDelay,
+ sc::kFifoReservedEventCount, sc::kFifoMaxEventCount,
+ sc::kDeviceTempStringType, sc::kRequiredPermission,
+ sc::kMaxDelay, flags);
+}
+
+SensorInfo RelativeHumiditySensor() {
+ uint32_t flags = sc::kRelativeHumidityReportingMode |
+ (sc::kRelativeHumidityIsWakeup ? SENSOR_FLAG_WAKE_UP : 0);
+
+ return SensorInfo(sc::kRelativeHumidityName, sc::kVendor, sc::kVersion,
+ sc::kRelativeHumidityHandle, SENSOR_TYPE_RELATIVE_HUMIDITY,
+ sc::kRelativeHumidityMaxRange,
+ sc::kRelativeHumidityResolution, sc::kRelativeHumidityPower,
+ sc::kRelativeHumidityMinDelay, sc::kFifoReservedEventCount,
+ sc::kFifoMaxEventCount, sc::kRelativeHumidityStringType,
+ sc::kRequiredPermission, sc::kMaxDelay,
+ flags);
+}
+
+} // namespace cvd
diff --git a/guest/hals/sensors/sensors.h b/guest/hals/sensors/sensors.h
new file mode 100644
index 0000000..c4305ce
--- /dev/null
+++ b/guest/hals/sensors/sensors.h
@@ -0,0 +1,231 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#pragma once
+
+#include "common/libs/time/monotonic_time.h"
+#include "guest/hals/sensors/sensors_hal.h"
+#include "guest/libs/platform_support/api_level_fixes.h"
+
+namespace cvd {
+
+// Stores static information about a sensor.
+// Must be completely compatible with sensor_t (i.e. no additional
+// information or virtual functions)
+// so we can cast a list of SensorInfo to a list of sensor_t.
+class SensorInfo : public sensor_t {
+ public:
+ // Dummy, empty set of sensor information (value-initialized).
+ SensorInfo() : sensor_t() {}
+
+ private:
+ SensorInfo(const char* name, const char* vendor, int version, int handle,
+ int type, float max_range, float resolution, float power,
+ int32_t min_delay, uint32_t fifo_reserved_event_count,
+ uint32_t fifo_max_event_count, const char* string_type,
+ const char* required_permission, int32_t max_delay,
+ uint32_t reporting_mode);
+
+ friend SensorInfo AccelerometerSensor();
+ friend SensorInfo GyroscopeSensor();
+ friend SensorInfo LightSensor();
+ friend SensorInfo MagneticFieldSensor();
+ friend SensorInfo PressureSensor();
+ friend SensorInfo ProximitySensor();
+ friend SensorInfo AmbientTempSensor();
+ friend SensorInfo DeviceTempSensor();
+ friend SensorInfo RelativeHumiditySensor();
+};
+
+SensorInfo AccelerometerSensor();
+SensorInfo GyroscopeSensor();
+SensorInfo LightSensor();
+SensorInfo MagneticFieldSensor();
+SensorInfo PressureSensor();
+SensorInfo ProximitySensor();
+SensorInfo AmbientTempSensor();
+SensorInfo DeviceTempSensor();
+SensorInfo RelativeHumiditySensor();
+
+// Stores the current state of a sensor.
+class SensorState {
+ public:
+ SensorState(SensorInfo info);
+ virtual ~SensorState() {}
+
+ // What this sensor is activated or not.
+ bool enabled_;
+ // Buffer of incoming events.
+ sensors_event_t event_;
+ // The deadline at which we should report the next sensor event
+ // to the framework in order to meet our frequency constraints.
+ // For disabled sensors, should be 'infinity'.
+ cvd::time::MonotonicTimePoint deadline_;
+ // Delay time between consecutive sensor samples, in ns.
+ cvd::time::Nanoseconds sampling_period_;
+
+ // Time 'infinity'.
+ static const cvd::time::MonotonicTimePoint kInfinity;
+};
+
+namespace sensors_constants {
+// TODO: Verify these numbers.
+// Vendor of the hardware part.
+const char kVendor[] = "Google";
+// Version of the hardware part + driver. The value of this field
+// must increase when the driver is updated in a way that
+// changes the output of the sensor.
+const int kVersion = VSOC_SENSOR_DEVICE_VERSION;
+// Number of events reserved for this sensor in batch mode FIFO.
+// If it has its own FIFO, the size of that FIFO.
+const uint32_t kFifoReservedEventCount = 15;
+// Maximum events that can be batched. In a shared FIFO,
+// the size of that FIFO.
+const uint32_t kFifoMaxEventCount = 15;
+// Permission required to use this sensor, or empty string
+// if none required.
+const char kRequiredPermission[] = "";
+// Defined only for continuous mode and on-change sensors.
+// Delay corresponding with lowest frequency supported.
+const int32_t kMaxDelay = 5000000;
+
+// Name of this sensor. Must be unique.
+const char kAccelerometerName[] = "acceleration";
+const char kGyroscopeName[] = "gyroscope";
+const char kLightName[] = "light";
+const char kMagneticFieldName[] = "magnetic_field";
+const char kPressureName[] = "pressure";
+const char kProximityName[] = "proximity";
+const char kAmbientTempName[] = "ambient_temp";
+const char kDeviceTempName[] = "device_temp";
+const char kRelativeHumidityName[] = "relative_humidity";
+
+// Handle that identifies the sensor. This is used as an array index,
+// so must be unique in the range [0, # sensors)
+
+const int kAccelerometerHandle = 0;
+const int kGyroscopeHandle = 1;
+const int kLightHandle = 2;
+const int kMagneticFieldHandle = 3;
+const int kPressureHandle = 4;
+const int kProximityHandle = 5;
+const int kAmbientTempHandle = 6;
+const int kDeviceTempHandle = 7;
+const int kRelativeHumidityHandle = 8;
+
+// For continuous sensors, minimum sample period (in microseconds).
+// On-Change (0), One-shot (-1), and special (0).
+const int32_t kAccelerometerMinDelay = 4444;
+const int32_t kGyroscopeMinDelay = 4444;
+const int32_t kLightMinDelay = 0;
+const int32_t kMagneticFieldMinDelay = 14285;
+const int32_t kPressureMinDelay = 28571;
+const int32_t kProximityMinDelay = 0;
+const int32_t kAmbientTempMinDelay = 4444;
+const int32_t kDeviceTempMinDelay = 4444;
+const int32_t kRelativeHumidityMinDelay = 4444;
+
+// Maximum range of this sensor's value in SI units.
+const float kAccelerometerMaxRange = 39.226593f;
+const float kGyroscopeMaxRange = 8.726639f;
+const float kLightMaxRange = 10000.0f;
+const float kMagneticFieldMaxRange = 4911.9995f;
+const float kPressureMaxRange = 1100.0f;
+const float kProximityMaxRange = 5.0f;
+const float kAmbientTempMaxRange = 80.0f;
+const float kDeviceTempMaxRange = 80.0f;
+const float kRelativeHumidityMaxRange = 100;
+
+// Smallest difference between two values reported by this sensor.
+const float kAccelerometerResolution = 0.45f;
+const float kGyroscopeResolution = 10.0f;
+const float kLightResolution = 10.0f;
+const float kMagneticFieldResolution = 1.0f;
+const float kPressureResolution = 1.0f;
+const float kProximityResolution = 1.0f;
+const float kAmbientTempResolution = 1.0f;
+const float kDeviceTempResolution = 1.0f;
+const float kRelativeHumidityResolution = 1.0f;
+
+// Rough estimate of this sensor's power consumption in mA.
+const float kAccelerometerPower = 0.45f;
+const float kGyroscopePower = 3.6f;
+const float kLightPower = 0.175f;
+const float kMagneticFieldPower = 5.0f;
+const float kPressurePower = 0.004f;
+const float kProximityPower = 12.675f;
+const float kAmbientTempPower = 1.0f;
+const float kDeviceTempPower = 1.0f;
+const float kRelativeHumidityPower = 1.0f;
+
+// Type of this sensor, represented as a string.
+
+#if VSOC_SENSORS_DEVICE_API_VERSION_ATLEAST(1_2)
+const char kAccelerometerStringType[] = SENSOR_STRING_TYPE_ACCELEROMETER;
+const char kGyroscopeStringType[] = SENSOR_STRING_TYPE_GYROSCOPE;
+const char kLightStringType[] = SENSOR_STRING_TYPE_LIGHT;
+const char kMagneticFieldStringType[] = SENSOR_STRING_TYPE_MAGNETIC_FIELD;
+const char kPressureStringType[] = SENSOR_STRING_TYPE_PRESSURE;
+const char kProximityStringType[] = SENSOR_STRING_TYPE_PROXIMITY;
+const char kAmbientTempStringType[] = SENSOR_STRING_TYPE_AMBIENT_TEMPERATURE;
+const char kDeviceTempStringType[] = SENSOR_STRING_TYPE_TEMPERATURE;
+const char kRelativeHumidityStringType[] = SENSOR_STRING_TYPE_RELATIVE_HUMIDITY;
+#else
+const char kAccelerometerStringType[] = "";
+const char kGyroscopeStringType[] = "";
+const char kLightStringType[] = "";
+const char kMagneticFieldStringType[] = "";
+const char kPressureStringType[] = "";
+const char kProximityStringType[] = "";
+const char kAmbientTempStringType[] = "";
+const char kDeviceTempStringType[] = "";
+const char kRelativeHumidityStringType[] = "";
+#endif
+
+#if VSOC_SENSORS_DEVICE_API_VERSION_ATLEAST(1_3)
+const uint32_t kAccelerometerReportingMode = SENSOR_FLAG_CONTINUOUS_MODE;
+const uint32_t kGyroscopeReportingMode = SENSOR_FLAG_CONTINUOUS_MODE;
+const uint32_t kLightReportingMode = SENSOR_FLAG_ON_CHANGE_MODE;
+const uint32_t kMagneticFieldReportingMode = SENSOR_FLAG_CONTINUOUS_MODE;
+const uint32_t kPressureReportingMode = SENSOR_FLAG_CONTINUOUS_MODE;
+const uint32_t kProximityReportingMode = SENSOR_FLAG_ON_CHANGE_MODE;
+const uint32_t kAmbientTempReportingMode = SENSOR_FLAG_ON_CHANGE_MODE;
+const uint32_t kDeviceTempReportingMode = SENSOR_FLAG_ON_CHANGE_MODE;
+const uint32_t kRelativeHumidityReportingMode = SENSOR_FLAG_ON_CHANGE_MODE;
+#else
+const uint32_t kAccelerometerReportingMode = 0;
+const uint32_t kGyroscopeReportingMode = 0;
+const uint32_t kLightReportingMode = 0;
+const uint32_t kMagneticFieldReportingMode = 0;
+const uint32_t kPressureReportingMode = 0;
+const uint32_t kProximityReportingMode = 0;
+const uint32_t kAmbientTempReportingMode = 0;
+const uint32_t kDeviceTempReportingMode = 0;
+const uint32_t kRelativeHumidityReportingMode = 0;
+#endif
+
+const bool kAccelerometerIsWakeup = false;
+const bool kGyroscopeIsWakeup = false;
+const bool kLightIsWakeup = false;
+const bool kMagneticFieldIsWakeup = false;
+const bool kPressureIsWakeup = false;
+const bool kProximityIsWakeup = true;
+const bool kAmbientTempIsWakeup = false;
+const bool kDeviceTempIsWakeup = false;
+const bool kRelativeHumidityIsWakeup = false;
+
+} // namespace sensors_constants
+} // namespace cvd
+
diff --git a/guest/hals/sensors/sensors_hal.cpp b/guest/hals/sensors/sensors_hal.cpp
new file mode 100644
index 0000000..4761d3d
--- /dev/null
+++ b/guest/hals/sensors/sensors_hal.cpp
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#include "guest/hals/sensors/sensors_hal.h"
+
+#include "guest/hals/sensors/vsoc_sensors.h"
+#include "guest/libs/platform_support/api_level_fixes.h"
+
+static hw_module_methods_t hal_module_methods = {
+ VSOC_STATIC_INITIALIZER(open) cvd::GceSensors::Open,
+};
+
+sensors_module_t HAL_MODULE_INFO_SYM = {
+ VSOC_STATIC_INITIALIZER(common){
+ VSOC_STATIC_INITIALIZER(tag) HARDWARE_MODULE_TAG,
+ VSOC_STATIC_INITIALIZER(module_api_version) 1,
+ VSOC_STATIC_INITIALIZER(hal_api_version) 0,
+ VSOC_STATIC_INITIALIZER(id) SENSORS_HARDWARE_MODULE_ID,
+ VSOC_STATIC_INITIALIZER(name) "Android-GCE SENSORS Module",
+ VSOC_STATIC_INITIALIZER(author) "Google",
+ VSOC_STATIC_INITIALIZER(methods) & hal_module_methods,
+ },
+ VSOC_STATIC_INITIALIZER(get_sensors_list) cvd::GceSensors::GetSensorsList,
+#if VSOC_SENSORS_DEVICE_API_VERSION_ATLEAST(1_4)
+ VSOC_STATIC_INITIALIZER(set_operation_mode) cvd::GceSensors::SetOperationMode,
+#endif
+};
diff --git a/guest/hals/sensors/sensors_hal.h b/guest/hals/sensors/sensors_hal.h
new file mode 100644
index 0000000..4e83790
--- /dev/null
+++ b/guest/hals/sensors/sensors_hal.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#pragma once
+
+#include <cutils/log.h>
+#include <hardware/hardware.h>
+#include <hardware/sensors.h>
+
+#include "guest/libs/platform_support/api_level_fixes.h"
+
+#if VSOC_PLATFORM_SDK_BEFORE(K)
+#define VSOC_SENSOR_DEVICE_VERSION HARDWARE_MAKE_API_VERSION(0, 1)
+#elif VSOC_PLATFORM_SDK_BEFORE(L)
+#define VSOC_SENSOR_DEVICE_VERSION SENSORS_DEVICE_API_VERSION_1_1
+#elif VSOC_PLATFORM_SDK_BEFORE(M)
+#define VSOC_SENSOR_DEVICE_VERSION SENSORS_DEVICE_API_VERSION_1_3
+#else
+#define VSOC_SENSOR_DEVICE_VERSION SENSORS_DEVICE_API_VERSION_1_4
+#endif
+
+#define VSOC_SENSORS_DEVICE_API_VERSION_ATLEAST(X) \
+ (defined (SENSORS_DEVICE_API_VERSION_##X) && \
+ (VSOC_SENSOR_DEVICE_VERSION >= SENSORS_DEVICE_API_VERSION_##X))
+
+#define SENSORS_DEBUG 0
+
+#if SENSORS_DEBUG
+# define D(...) ALOGD(__VA_ARGS__)
+#else
+# define D(...) ((void)0)
+#endif
+
diff --git a/guest/hals/sensors/vsoc_sensors.cpp b/guest/hals/sensors/vsoc_sensors.cpp
new file mode 100644
index 0000000..86809a0
--- /dev/null
+++ b/guest/hals/sensors/vsoc_sensors.cpp
@@ -0,0 +1,527 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#include <cstdint>
+
+#include <cutils/properties.h>
+#include <cutils/sockets.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/system_properties.h>
+#include <unistd.h>
+
+#include <algorithm>
+
+#include "common/libs/fs/shared_select.h"
+#include "common/libs/threads/thunkers.h"
+#include "guest/hals/sensors/sensors_hal.h"
+#include "guest/hals/sensors/vsoc_sensors.h"
+#include "guest/hals/sensors/vsoc_sensors_message.h"
+#include "guest/libs/platform_support/api_level_fixes.h"
+#include "guest/libs/remoter/remoter_framework_pkt.h"
+
+using cvd::LockGuard;
+using cvd::Mutex;
+using cvd::time::Milliseconds;
+using cvd::time::MonotonicTimePoint;
+using cvd::time::Nanoseconds;
+
+namespace cvd {
+
+namespace {
+template <typename F>
+struct HWDeviceThunker : ThunkerBase<hw_device_t, GceSensors, F> {};
+template <typename F>
+struct SensorsThunker : ThunkerBase<sensors_poll_device_t, GceSensors, F> {};
+template <typename F>
+struct SensorsThunker1 : ThunkerBase<sensors_poll_device_1, GceSensors, F> {};
+template <typename F>
+struct SensorsThreadThunker : ThunkerBase<void, GceSensors, F> {};
+}
+
+int GceSensors::total_sensor_count_ = -1;
+SensorInfo* GceSensors::sensor_infos_ = NULL;
+const int GceSensors::kInjectedEventWaitPeriods = 3;
+const Nanoseconds GceSensors::kInjectedEventWaitTime =
+ Nanoseconds(Milliseconds(20));
+
+GceSensors::GceSensors()
+ : sensors_poll_device_1(), deadline_change_(&sensor_state_lock_) {
+ if (total_sensor_count_ == -1) {
+ RegisterSensors();
+ }
+
+ // Create a pair of FDs that would be used to control the
+ // receiver thread.
+ if (control_sender_socket_->IsOpen() || control_receiver_socket_->IsOpen()) {
+ ALOGE("%s: Receiver control FDs are opened", __FUNCTION__);
+ }
+ if (!cvd::SharedFD::Pipe(&control_receiver_socket_,
+ &control_sender_socket_)) {
+ ALOGE("%s: Unable to create thread control FDs: %d -> %s", __FUNCTION__,
+ errno, strerror(errno));
+ }
+
+ // Create the correct number of holding buffers for this client.
+ sensor_states_.resize(total_sensor_count_);
+ int i;
+ for (i = 0; i < total_sensor_count_; i++) {
+ sensor_states_[i] = new SensorState(sensor_infos_[i]);
+ }
+}
+
+GceSensors::~GceSensors() {
+ int i;
+ for (i = 0; i < total_sensor_count_; i++) {
+ delete sensor_states_[i];
+ }
+}
+
+int GceSensors::GetSensorsList(struct sensors_module_t* /*module*/,
+ struct sensor_t const** list) {
+ *list = sensor_infos_;
+ return total_sensor_count_;
+}
+
+int GceSensors::SetOperationMode(unsigned int /* is_loopback_mode */) {
+ return -EINVAL;
+}
+
+int GceSensors::Open(const struct hw_module_t* module, const char* name,
+ struct hw_device_t** device) {
+ int status = -EINVAL;
+
+ if (!strcmp(name, SENSORS_HARDWARE_POLL)) {
+ // Create a new GceSensors object and set all the fields/functions
+ // to their default values.
+ GceSensors* rval = new GceSensors;
+
+ rval->common.tag = HARDWARE_DEVICE_TAG;
+ rval->common.version = VSOC_SENSOR_DEVICE_VERSION;
+ rval->common.module = (struct hw_module_t*)module;
+ rval->common.close = HWDeviceThunker<int()>::call<&GceSensors::Close>;
+ rval->poll =
+ SensorsThunker<int(sensors_event_t*, int)>::call<&GceSensors::Poll>;
+ rval->activate = SensorsThunker<int(int, int)>::call<&GceSensors::Activate>;
+ rval->setDelay =
+ SensorsThunker<int(int, int64_t)>::call<&GceSensors::SetDelay>;
+#if VSOC_SENSORS_DEVICE_API_VERSION_ATLEAST(1_0)
+ rval->batch = SensorsThunker1<int(int, int, int64_t,
+ int64_t)>::call<&GceSensors::Batch>;
+#endif
+#if VSOC_SENSORS_DEVICE_API_VERSION_ATLEAST(1_1)
+ rval->flush = SensorsThunker1<int(int)>::call<&GceSensors::Flush>;
+#endif
+#if VSOC_SENSORS_DEVICE_API_VERSION_ATLEAST(1_4)
+ rval->inject_sensor_data = SensorsThunker1<int(
+ const sensors_event_t*)>::call<&GceSensors::InjectSensorData>;
+#endif
+
+ // Spawn a thread to listen for incoming data from the remoter.
+ int err = pthread_create(
+ &rval->receiver_thread_, NULL,
+ SensorsThreadThunker<void*()>::call<&GceSensors::Receiver>, rval);
+ if (err) {
+ ALOGE("GceSensors::%s: Unable to start receiver thread (%s)",
+ __FUNCTION__, strerror(err));
+ }
+
+ *device = &rval->common;
+ status = 0;
+ }
+ return status;
+}
+
+int GceSensors::Close() {
+ // Make certain the receiver thread wakes up.
+ SensorControlMessage msg;
+ msg.message_type = THREAD_STOP;
+ SendControlMessage(msg);
+ pthread_join(receiver_thread_, NULL);
+ delete this;
+ return 0;
+}
+
+int GceSensors::Activate(int handle, int enabled) {
+ if (handle < 0 || handle >= total_sensor_count_) {
+ ALOGE("GceSensors::%s: Bad handle %d", __FUNCTION__, handle);
+ return -1;
+ }
+
+ {
+ LockGuard<Mutex> guard(sensor_state_lock_);
+ // Update the report deadline, if changed.
+ if (enabled && !sensor_states_[handle]->enabled_) {
+ sensor_states_[handle]->deadline_ =
+ MonotonicTimePoint::Now() + sensor_states_[handle]->sampling_period_;
+ } else if (!enabled && sensor_states_[handle]->enabled_) {
+ sensor_states_[handle]->deadline_ = SensorState::kInfinity;
+ }
+ sensor_states_[handle]->enabled_ = enabled;
+ UpdateDeadline();
+ }
+
+ D("sensor_activate(): handle %d, enabled %d", handle, enabled);
+ if (!UpdateRemoterState(handle)) {
+ ALOGE("Failed to notify remoter about new sensor enable/disable.");
+ }
+ return 0;
+}
+
+int GceSensors::SetDelay(int handle, int64_t sampling_period_ns) {
+ if (handle < 0 || handle >= total_sensor_count_) {
+ ALOGE("GceSensors::%s: Bad handle %d", __FUNCTION__, handle);
+ return -1;
+ }
+ int64_t min_delay_ns = sensor_infos_[handle].minDelay * 1000;
+ if (sampling_period_ns < min_delay_ns) {
+ sampling_period_ns = min_delay_ns;
+ }
+
+ {
+ LockGuard<Mutex> guard(sensor_state_lock_);
+ sensor_states_[handle]->deadline_ -=
+ sensor_states_[handle]->sampling_period_;
+ sensor_states_[handle]->sampling_period_ = Nanoseconds(sampling_period_ns);
+ sensor_states_[handle]->deadline_ +=
+ sensor_states_[handle]->sampling_period_;
+ // If our sampling period has decreased, our deadline
+ // could have already passed. If so, report immediately, but not in the
+ // past.
+ MonotonicTimePoint now = MonotonicTimePoint::Now();
+ if (sensor_states_[handle]->deadline_ < now) {
+ sensor_states_[handle]->deadline_ = now;
+ }
+ UpdateDeadline();
+ }
+
+ D("sensor_set_delay(): handle %d, delay (ms) %" PRId64, handle,
+ Milliseconds(Nanoseconds(sampling_period_ns)).count());
+ if (!UpdateRemoterState(handle)) {
+ ALOGE("Failed to notify remoter about new sensor delay.");
+ }
+ return 0;
+}
+
+int GceSensors::Poll(sensors_event_t* data, int count_unsafe) {
+ if (count_unsafe <= 0) {
+ ALOGE("Framework polled with bad count (%d)", count_unsafe);
+ return -1;
+ }
+ size_t count = size_t(count_unsafe);
+
+ // Poll will block until 1 of 2 things happens:
+ // 1. The next deadline for some active sensor
+ // occurs.
+ // 2. The next deadline changes (either because
+ // a sensor was activated/deactivated or its
+ // delay changed).
+ // In both cases, any sensors whose report deadlines
+ // have passed will report their data (or mock data),
+ // and poll will either return (if at least one deadline
+ // has passed), or repeat by blocking until the next deadline.
+ LockGuard<Mutex> guard(sensor_state_lock_);
+ current_deadline_ = UpdateDeadline();
+ // Sleep until we have something to report
+ while (!fifo_.size()) {
+ deadline_change_.WaitUntil(current_deadline_);
+ current_deadline_ = UpdateDeadline();
+ }
+ // Copy the events from the buffer
+ int num_copied = std::min(fifo_.size(), count);
+ FifoType::iterator first_uncopied = fifo_.begin() + num_copied;
+ std::copy(fifo_.begin(), first_uncopied, data);
+ fifo_.erase(fifo_.begin(), first_uncopied);
+ D("Reported %d sensor events. First: %d %f %f %f", num_copied, data->sensor,
+ data->data[0], data->data[1], data->data[2]);
+ return num_copied;
+}
+
+
+void *GceSensors::Receiver() {
+ // Initialize the server.
+ sensor_listener_socket_ = cvd::SharedFD::SocketSeqPacketServer(
+ gce_sensors_message::kSensorsHALSocketName, 0777);
+ if (!sensor_listener_socket_->IsOpen()) {
+ ALOGE("GceSensors::%s: Could not listen for sensor connections. (%s).",
+ __FUNCTION__, sensor_listener_socket_->StrError());
+ return NULL;
+ }
+ D("GceSensors::%s: Listening for sensor connections at %s", __FUNCTION__,
+ gce_sensors_message::kSensorsHALSocketName);
+ // Announce that we are ready for the remoter to connect.
+ if (!NotifyRemoter()) {
+ ALOGI("Failed to notify remoter that HAL is ready.");
+ } else {
+ ALOGI("Notified remoter that HAL is ready.");
+ }
+
+ typedef std::vector<cvd::SharedFD> FDVec;
+ FDVec connected;
+ // Listen for incoming sensor data and control messages
+ // from the HAL.
+ while (true) {
+ cvd::SharedFDSet fds;
+ for (FDVec::iterator it = connected.begin(); it != connected.end(); ++it) {
+ fds.Set(*it);
+ }
+ fds.Set(control_receiver_socket_);
+ // fds.Set(sensor_listener_socket_);
+ int res = cvd::Select(&fds, NULL, NULL, NULL);
+ if (res == -1) {
+ ALOGE("%s: select returned %d and failed %d -> %s", __FUNCTION__, res,
+ errno, strerror(errno));
+ break;
+ } else if (res == 0) {
+ ALOGE("%s: select timed out", __FUNCTION__);
+ break;
+ } else if (fds.IsSet(sensor_listener_socket_)) {
+ connected.push_back(cvd::SharedFD::Accept(*sensor_listener_socket_));
+ ALOGI("GceSensors::%s: new client connected", __FUNCTION__);
+ } else if (fds.IsSet(control_receiver_socket_)) {
+ // We received a control message.
+ SensorControlMessage msg;
+ int res =
+ control_receiver_socket_->Read(&msg, sizeof(SensorControlMessage));
+ if (res == -1) {
+ ALOGE("GceSensors::%s: Failed to receive control message.",
+ __FUNCTION__);
+ } else if (res == 0) {
+ ALOGE("GceSensors::%s: Control connection closed.", __FUNCTION__);
+ }
+ if (msg.message_type == SENSOR_STATE_UPDATE) {
+ // Forward the update to the remoter.
+ remoter_request_packet packet;
+ remoter_request_packet_init(&packet, kRemoterSensorState, 0);
+ {
+ LockGuard<Mutex> guard(sensor_state_lock_);
+ packet.params.sensor_state_params.type =
+ sensor_infos_[msg.sensor_handle].type;
+ packet.params.sensor_state_params.enabled =
+ sensor_states_[msg.sensor_handle]->enabled_;
+ packet.params.sensor_state_params.delay_ns =
+ sensor_states_[msg.sensor_handle]->sampling_period_.count();
+ packet.params.sensor_state_params.handle = msg.sensor_handle;
+ }
+ struct msghdr msg;
+ iovec msg_iov[1];
+ msg_iov[0].iov_base = &packet;
+ msg_iov[0].iov_len = sizeof(remoter_request_packet);
+ msg.msg_name = NULL;
+ msg.msg_namelen = 0;
+ msg.msg_iov = msg_iov;
+ msg.msg_iovlen = arraysize(msg_iov);
+ msg.msg_control = NULL;
+ msg.msg_controllen = 0;
+ msg.msg_flags = 0;
+
+ for (FDVec::iterator it = connected.begin(); it != connected.end();
+ ++it) {
+ cvd::SharedFD &fd = *it;
+ if (fd->SendMsg(&msg, 0) == -1) {
+ ALOGE("GceSensors::%s. Could not send sensor state (%s).",
+ __FUNCTION__, fd->StrError());
+ }
+ }
+ }
+ if (msg.message_type == THREAD_STOP) {
+ D("Received terminate control message.");
+ return NULL;
+ }
+ } else {
+ for (FDVec::iterator it = connected.begin(); it != connected.end();
+ ++it) {
+ cvd::SharedFD &fd = *it;
+ if (fds.IsSet(fd)) {
+ // We received a sensor update from remoter.
+ sensors_event_t event;
+ struct msghdr msg;
+ iovec msg_iov[1];
+ msg_iov[0].iov_base = &event;
+ msg_iov[0].iov_len = sizeof(event);
+ msg.msg_name = NULL;
+ msg.msg_namelen = 0;
+ msg.msg_iov = msg_iov;
+ msg.msg_iovlen = arraysize(msg_iov);
+ msg.msg_control = NULL;
+ msg.msg_controllen = 0;
+ msg.msg_flags = 0;
+ int res = fd->RecvMsg(&msg, 0);
+ if (res <= 0) {
+ if (res == 0) {
+ ALOGE("GceSensors::%s: Sensors HAL connection closed.",
+ __FUNCTION__);
+ } else {
+ ALOGE("GceSensors::%s: Failed to receive sensor message",
+ __FUNCTION__);
+ }
+ connected.erase(std::find(connected.begin(), connected.end(), fd));
+ break;
+ }
+
+ // We received an event from the remoter.
+ if (event.sensor < 0 || event.sensor >= total_sensor_count_) {
+ ALOGE("Remoter sent us an invalid sensor event! (handle %d)",
+ event.sensor);
+ connected.erase(std::find(connected.begin(), connected.end(), fd));
+ break;
+ }
+
+ D("Received sensor event: %d %f %f %f", event.sensor, event.data[0],
+ event.data[1], event.data[2]);
+
+ {
+ LockGuard<Mutex> guard(sensor_state_lock_);
+ // Increase the delay so that the HAL knows
+ // it shouldn't report on its own for a while.
+ SensorState *holding_buffer = sensor_states_[event.sensor];
+ int wait_periods =
+ std::max(kInjectedEventWaitPeriods,
+ (int)(kInjectedEventWaitTime.count() /
+ holding_buffer->sampling_period_.count()));
+ holding_buffer->deadline_ =
+ MonotonicTimePoint::Now() +
+ holding_buffer->sampling_period_ * wait_periods;
+ holding_buffer->event_.data[0] = event.data[0];
+ holding_buffer->event_.data[1] = event.data[1];
+ holding_buffer->event_.data[2] = event.data[2];
+ // Signal the HAL to report the newly arrived event.
+ fifo_.push_back(event);
+ deadline_change_.NotifyOne();
+ }
+ }
+ }
+ }
+ }
+ return NULL;
+}
+
+bool GceSensors::NotifyRemoter() {
+ remoter_request_packet packet;
+ remoter_request_packet_init(&packet, kRemoterHALReady, 0);
+ packet.send_response = 0;
+ strncpy(packet.params.hal_ready_params.unix_socket,
+ gce_sensors_message::kSensorsHALSocketName,
+ sizeof(packet.params.hal_ready_params.unix_socket));
+ AutoCloseFileDescriptor remoter_socket(remoter_connect());
+ if (remoter_socket.IsError()) {
+ D("GceSensors::%s: Could not connect to remoter to notify ready (%s).",
+ __FUNCTION__, strerror(errno));
+ return false;
+ }
+ int err =
+ remoter_do_single_request_with_socket(remoter_socket, &packet, NULL);
+ if (err == -1) {
+ D("GceSensors::%s: Notify remoter ready: Failed after connect (%s).",
+ __FUNCTION__, strerror(errno));
+ return false;
+ }
+ D("GceSensors::%s: Notify remoter ready Succeeded.", __FUNCTION__);
+ return true;
+}
+
+static bool CompareTimestamps(const sensors_event_t& a,
+ const sensors_event_t& b) {
+ return a.timestamp < b.timestamp;
+}
+
+MonotonicTimePoint GceSensors::UpdateDeadline() {
+ // Get the minimum of all the current deadlines.
+ MonotonicTimePoint now = MonotonicTimePoint::Now();
+ MonotonicTimePoint min = SensorState::kInfinity;
+ int i = 0;
+ bool sort_fifo = false;
+
+ for (i = 0; i < total_sensor_count_; i++) {
+ SensorState* holding_buffer = sensor_states_[i];
+ // Ignore disabled sensors.
+ if (!holding_buffer->enabled_) {
+ continue;
+ }
+ while (holding_buffer->deadline_ < now) {
+ sensors_event_t data = holding_buffer->event_;
+ data.timestamp = holding_buffer->deadline_.SinceEpoch().count();
+ fifo_.push_back(data);
+ holding_buffer->deadline_ += holding_buffer->sampling_period_;
+ sort_fifo = true;
+ }
+ // Now check if we should update the wake time based on the next event
+ // from this sensor.
+ if (sensor_states_[i]->deadline_ < min) {
+ min = sensor_states_[i]->deadline_;
+ }
+ }
+ // We added one or more sensor readings, so do a sort.
+ // This is likely to be cheaper than a traditional priority queue because
+ // a priority queue would try to keep its state correct for each addition.
+ if (sort_fifo) {
+ std::sort(fifo_.begin(), fifo_.end(), CompareTimestamps);
+ }
+ // If we added events or the deadline is lower notify the thread in Poll().
+ // If the deadline went up, don't do anything.
+ if (fifo_.size() || (min < current_deadline_)) {
+ deadline_change_.NotifyOne();
+ }
+ return min;
+}
+
+bool GceSensors::UpdateRemoterState(int handle) {
+ SensorControlMessage msg;
+ msg.message_type = SENSOR_STATE_UPDATE;
+ msg.sensor_handle = handle;
+ return SendControlMessage(msg);
+}
+
+bool GceSensors::SendControlMessage(SensorControlMessage msg) {
+ if (!control_sender_socket_->IsOpen()) {
+ ALOGE("%s: Can't send control message %d, control socket not open.",
+ __FUNCTION__, msg.message_type);
+ return false;
+ }
+ if (control_sender_socket_->Write(&msg, sizeof(SensorControlMessage)) == -1) {
+ ALOGE("GceSensors::%s. Could not send control message %d (%s).",
+ __FUNCTION__, msg.message_type, control_sender_socket_->StrError());
+ return false;
+ }
+ return true;
+}
+
+int GceSensors::RegisterSensors() {
+ if (total_sensor_count_ != -1) {
+ return -1;
+ }
+ total_sensor_count_ = 9;
+ sensor_infos_ = new SensorInfo[total_sensor_count_];
+ sensor_infos_[sensors_constants::kAccelerometerHandle] =
+ AccelerometerSensor();
+ sensor_infos_[sensors_constants::kGyroscopeHandle] = GyroscopeSensor();
+ sensor_infos_[sensors_constants::kLightHandle] = LightSensor();
+ sensor_infos_[sensors_constants::kMagneticFieldHandle] =
+ MagneticFieldSensor();
+ sensor_infos_[sensors_constants::kPressureHandle] = PressureSensor();
+ sensor_infos_[sensors_constants::kProximityHandle] = ProximitySensor();
+ sensor_infos_[sensors_constants::kAmbientTempHandle] = AmbientTempSensor();
+ sensor_infos_[sensors_constants::kDeviceTempHandle] = DeviceTempSensor();
+ sensor_infos_[sensors_constants::kRelativeHumidityHandle] =
+ RelativeHumiditySensor();
+ int i;
+ for (i = 0; i < total_sensor_count_; i++) {
+ D("Found sensor %s with handle %d", sensor_infos_[i].name,
+ sensor_infos_[i].handle);
+ }
+ return total_sensor_count_;
+}
+
+} // namespace cvd
diff --git a/guest/hals/sensors/vsoc_sensors.h b/guest/hals/sensors/vsoc_sensors.h
new file mode 100644
index 0000000..dec7102
--- /dev/null
+++ b/guest/hals/sensors/vsoc_sensors.h
@@ -0,0 +1,241 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#pragma once
+
+#include <vector>
+
+#include "common/libs/threads/cuttlefish_thread.h"
+#include "common/libs/fs/shared_fd.h"
+#include "guest/hals/sensors/sensors.h"
+#include "guest/hals/sensors/sensors_hal.h"
+
+namespace cvd {
+
+// Used for sending control messages to the receiver thread.
+// The sensor_handle field may be left unused if it is not needed.
+enum ControlMessageType {
+ THREAD_STOP,
+ SENSOR_STATE_UPDATE
+};
+typedef struct {
+ ControlMessageType message_type;
+ uint8_t sensor_handle;
+} SensorControlMessage;
+
+#if VSOC_SENSORS_DEVICE_API_VERSION_ATLEAST(1_0)
+// Last updated to HAL 1.4
+// Version history:
+// Before jb, jb-mr1 SENSORS_DEVICE_API_VERSION_0_1 (no version in sensors.h)
+// jb-mr2: SENSORS_DEVICE_API_VERSION_1_0
+// k: SENSORS_DEVICE_API_VERSION_1_1
+// l, l-mr1: SENSORS_DEVICE_API_VERSION_1_3
+// m, n, n-mr1: SENSORS_DEVICE_API_VERSION_1_4
+#else
+// Pre-1.0 sensors do not define the sensors_poll_device_1 type.
+typedef sensors_poll_device_t sensors_poll_device_1;
+#endif
+
+class GceSensors : public sensors_poll_device_1 {
+ public:
+ GceSensors();
+ ~GceSensors();
+
+ /**
+ ** SENSOR HAL API FUNCTIONS FOR MODULE
+ **/
+
+ // Gets a list of all supported sensors and stores in list.
+ // Returns the number of supported sensors.
+ static int GetSensorsList(struct sensors_module_t* module,
+ struct sensor_t const** list);
+
+ // Place the module in a specific mode. The following modes are defined
+ //
+ // 0 - Normal operation. Default state of the module.
+ // 1 - Loopback mode. Data is injected for the supported
+ // sensors by the sensor service in this mode.
+ // @return 0 on success
+ // -EINVAL if requested mode is not supported
+ // -EPERM if operation is not allowed
+ static int SetOperationMode(unsigned int mode);
+
+
+ /**
+ ** SENSOR HAL API FUNCTIONS FOR DEVICE
+ **/
+ // Opens the device.
+ static int Open(const struct hw_module_t* module, const char* name,
+ struct hw_device_t** device);
+
+ // Closes the device, closing all sensors.
+ int Close();
+
+ // Activate (or deactivate) the sensor with the given handle.
+ //
+ // One-shot sensors deactivate themselves automatically upon receiving an
+ // event, and they must still accept to be deactivated through a call to
+ // activate(..., enabled=0).
+ // Non-wake-up sensors never prevent the SoC from going into suspend mode;
+ // that is, the HAL shall not hold a partial wake-lock on behalf of
+ // applications.
+ //
+ // If enabled is 1 and the sensor is already activated, this function is a
+ // no-op and succeeds.
+ //
+ // If enabled is 0 and the sensor is already deactivated, this function is a
+ // no-op and succeeds.
+ //
+ // This function returns 0 on success and a negative error number otherwise.
+ int Activate(int handle, int enabled);
+
+ // Sets the delay (in ns) for the sensor with the given handle.
+ // Deprecated as of HAL 1.1
+ // Called after activate()
+ int SetDelay(int handle, int64_t sampling_period_ns);
+
+ // Returns an array of sensor data by filling the data argument.
+ // This function must block until events are available. It will return
+ // the number of events read on success, or a negative number in case of
+ // an error.
+ int Poll(sensors_event_t* data, int count);
+
+#if VSOC_SENSORS_DEVICE_API_VERSION_ATLEAST(1_0)
+ // Sets a sensor’s parameters, including sampling frequency and maximum
+ // report latency. This function can be called while the sensor is
+ // activated, in which case it must not cause any sensor measurements to
+ // be lost: transitioning from one sampling rate to the other cannot cause
+ // lost events, nor can transitioning from a high maximum report latency to
+ // a low maximum report latency.
+ //
+ // Before SENSORS_DEVICE_API_VERSION_1_3, flags included:
+ // SENSORS_BATCH_DRY_RUN
+ // SENSORS_BATCH_WAKE_UPON_FIFO_FULL
+ //
+ // After SENSORS_DEVICE_API_VERSION_1_3 see WAKE_UPON_FIFO_FULL
+ // in sensor_t.flags
+ int Batch(int sensor_handle, int flags, int64_t sampling_period_ns,
+ int64_t max_report_latency_ns) {
+ // TODO: Add support for maximum report latency with max_report_latency_ns.
+ return SetDelay(sensor_handle, sampling_period_ns);
+ }
+#endif
+
+#if VSOC_SENSORS_DEVICE_API_VERSION_ATLEAST(1_1)
+ // Adds a META_DATA_FLUSH_COMPLETE event (sensors_event_meta_data_t)
+ // to the end of the "batch mode" FIFO for the specified sensor and flushes
+ // the FIFO.
+ //
+ // If the FIFO is empty or if the sensor doesn't support batching (FIFO
+ // size zero), it should return SUCCESS along with a trivial
+ // META_DATA_FLUSH_COMPLETE event added to the event stream. This applies to
+ // all sensors other than one-shot sensors.
+ //
+ // If the sensor is a one-shot sensor, flush must return -EINVAL and not
+ // generate any flush complete metadata.
+ //
+ // If the sensor is not active at the time flush() is called, flush() should
+ // return -EINVAL.
+ int Flush(int sensor_handle) {
+ return -EINVAL;
+ }
+#endif
+
+#if VSOC_SENSORS_DEVICE_API_VERSION_ATLEAST(1_4)
+ // Inject a single sensor sample to be to this device.
+ // data points to the sensor event to be injected
+ // @return 0 on success
+ // -EPERM if operation is not allowed
+ // -EINVAL if sensor event cannot be injected
+ int InjectSensorData(const sensors_event_t *data) {
+ return -EINVAL;
+ }
+#endif
+
+ private:
+ typedef std::vector<SensorState*> SensorStateVector;
+ typedef std::vector<sensors_event_t> FifoType;
+ // Total number of sensors supported by this HAL.
+ static int total_sensor_count_;
+ // Vector of static sensor information for sensors supported by this HAL.
+ // Indexed by the handle. Length must always be equal to total_sensor_count_.
+ static SensorInfo* sensor_infos_;
+ // Vector of sensor state information, indexed by the handle.
+ // Assumption here is that the sensor handles will start at 0 and be
+ // contiguous up to the number of supported sensors.
+ SensorStateVector sensor_states_;
+ // Keep track of the time when the thread in Poll() is scheduled to wake.
+ cvd::time::MonotonicTimePoint current_deadline_;
+
+ // Ordered set of sensor values.
+ // TODO(ghartman): Simulate FIFO overflow.
+ FifoType fifo_;
+ // Thread to handle new connections.
+ pthread_t receiver_thread_;
+ // Socket to receive sensor events on.
+ cvd::SharedFD sensor_listener_socket_;
+ // Socket for listener thread to receive control messages.
+ cvd::SharedFD control_receiver_socket_;
+ // Socket to send control messages to listener thread.
+ cvd::SharedFD control_sender_socket_;
+
+ // Lock to protect shared state, including
+ // sensor_states_ and next_deadline_.
+ // Associated with deadline_change_ condition variable.
+ cvd::Mutex sensor_state_lock_;
+ // Condition variable to signal changes in the deadline.
+ cvd::ConditionVariable deadline_change_;
+
+ // When events are arriving from a client, we report only
+ // when they arrive, rather than at a fixed cycle. After not
+ // receiving a real event for both a given number of periods
+ // and a given time period, we will give up and resume
+ // sending mock events.
+ const static int kInjectedEventWaitPeriods;
+ const static cvd::time::Nanoseconds kInjectedEventWaitTime;
+
+ /**
+ ** UTILITY FUNCTIONS
+ **/
+
+ // Receive data from remoter.
+ void* Receiver();
+
+ // Notifies the remoter that the HAL is awake and ready.
+ inline bool NotifyRemoter();
+
+ // Looks through all active sensor deadlines, and finds the one that
+ // is coming up next. If this is not next_deadline_, then the deadline
+ // has changed. Update it and signal the Poll thread.
+ // This should be called anytime the next deadline may have changed.
+ // Can only be called while holding sensor_state_lock_.
+ // Returns true if the deadline has changed.
+ cvd::time::MonotonicTimePoint UpdateDeadline();
+
+ // Sends an update for the sensor with the given handle to the remoter.
+ // Update will be enqueued for receiver, not send immediately.
+ inline bool UpdateRemoterState(int handle);
+
+ // Sends a control event to the listener.
+ inline bool SendControlMessage(SensorControlMessage msg);
+
+ // Populates the list of static sensor info. Returns the number
+ // of sensors supported. Should only be called once.
+ static inline int RegisterSensors();
+
+};
+
+} //namespace cvd
+
diff --git a/guest/hals/sensors/vsoc_sensors_message.cpp b/guest/hals/sensors/vsoc_sensors_message.cpp
new file mode 100644
index 0000000..1998762
--- /dev/null
+++ b/guest/hals/sensors/vsoc_sensors_message.cpp
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#include <stdlib.h>
+#include "guest/hals/sensors/vsoc_sensors_message.h"
+
+const char* gce_sensors_message::kSensorsHALSocketName =
+ "/var/run/system/sensors_hal_socket";
diff --git a/guest/hals/sensors/vsoc_sensors_message.h b/guest/hals/sensors/vsoc_sensors_message.h
new file mode 100644
index 0000000..3a57700
--- /dev/null
+++ b/guest/hals/sensors/vsoc_sensors_message.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#pragma once
+
+#include <hardware/hardware.h>
+#include <hardware/sensors.h>
+
+struct gce_sensors_message : sensors_event_t {
+ static const char* kSensorsHALSocketName;
+};
+
diff --git a/guest/libs/legacy_framebuffer/Android.mk b/guest/libs/legacy_framebuffer/Android.mk
new file mode 100644
index 0000000..22723de
--- /dev/null
+++ b/guest/libs/legacy_framebuffer/Android.mk
@@ -0,0 +1,59 @@
+# Copyright (C) 2016 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.
+
+# Temporary, this library should go away once the new HALS are in place.
+
+vsocframebuffer_common_src_files := \
+ vsoc_framebuffer.cpp \
+ vsoc_framebuffer_control.cpp \
+ RegionRegistry.cpp
+
+vsocframebuffer_common_c_flags := -Wall -Werror $(VSOC_VERSION_CFLAGS)
+
+vsocframebuffer_common_c_includes := \
+ device/google/cuttlefish_common \
+ device/google/cuttlefish_kernel
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := libvsocframebuffer
+LOCAL_VENDOR_MODULE := true
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := ${vsocframebuffer_common_src_files}
+LOCAL_CFLAGS += ${vsocframebuffer_common_c_flags}
+LOCAL_C_INCLUDES := ${vsocframebuffer_common_c_includes} \
+ $(VSOC_STLPORT_INCLUDES)
+
+LOCAL_STATIC_LIBRARIES := \
+ libjsoncpp
+
+LOCAL_SHARED_LIBRARIES := \
+ libbase \
+ liblog \
+ libutils \
+ libcutils \
+ cuttlefish_auto_resources \
+ libcuttlefish_fs \
+ vsoc_lib \
+ $(VSOC_STLPORT_LIBS)
+
+# See b/67109557
+ifeq (true, $(TARGET_TRANSLATE_2ND_ARCH))
+LOCAL_MULTILIB := first
+endif
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/guest/libs/legacy_framebuffer/RegionRegistry.cpp b/guest/libs/legacy_framebuffer/RegionRegistry.cpp
new file mode 100644
index 0000000..24010b0
--- /dev/null
+++ b/guest/libs/legacy_framebuffer/RegionRegistry.cpp
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+#include <limits.h>
+#include <errno.h>
+#include <pthread.h>
+#include <unistd.h>
+#include <string.h>
+
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <cutils/hashmap.h>
+#define LOG_TAG "VSoCGrallocRegionRegistry"
+#include <cutils/log.h>
+#include <cutils/atomic.h>
+
+#include <linux/ashmem.h>
+
+#include <hardware/hardware.h>
+#include <hardware/gralloc.h>
+#include <system/graphics.h>
+
+#include "guest/hals/gralloc/legacy/gralloc_vsoc_priv.h"
+
+// TODO(ghartman): Make the configurable through a property
+static const bool g_log_refs = false;
+
+struct GrallocRegion {
+ void* base_;
+ int num_references_;
+
+ GrallocRegion() : base_(0), num_references_(0) { }
+ // Copy constructors are ok.
+};
+
+
+static const char* get_buffer_name(
+ const private_handle_t* hnd, char output[ASHMEM_NAME_LEN]) {
+ output[0] = '\0';
+ if (!hnd) {
+ ALOGE("Attempted to log gralloc name hnd=NULL");
+ return output;
+ }
+ if (hnd->fd == -1) {
+ ALOGE("Attempted to log gralloc name hnd=%p with fd == -1", hnd);
+ return output;
+ }
+ int rval = ioctl(hnd->fd, ASHMEM_GET_NAME, output);
+ if (rval == -1) {
+ output[0] = '\0';
+ }
+ return output;
+}
+
+
+static int str_hash(void* str) {
+ return hashmapHash(str, strlen(reinterpret_cast<const char*>(str)));
+}
+
+
+static bool str_equal(void* a, void* b) {
+ return strcmp(
+ reinterpret_cast<const char*>(a),
+ reinterpret_cast<const char*>(b)) == 0;
+}
+
+
+static Hashmap* get_regions() {
+ static Hashmap* regionMap = hashmapCreate(19, str_hash, str_equal);
+ return regionMap;
+}
+
+
+static GrallocRegion* lock_region_for_handle(
+ const private_handle_t* hnd, char region_name[ASHMEM_NAME_LEN]) {
+ region_name[0] = '\0';
+ get_buffer_name(hnd, region_name);
+ Hashmap* hash = get_regions();
+ hashmapLock(hash);
+ GrallocRegion* region = reinterpret_cast<GrallocRegion*>(
+ hashmapGet(hash, region_name));
+ if (!region) {
+ region = new GrallocRegion;
+ hashmapPut(hash, strdup(region_name), region);
+ }
+ return region;
+}
+
+
+/* The current implementation uses only a single lock for all regions.
+ * This method takes a region to simplfy the refactoring if we go to
+ * finer-grained locks.
+ */
+static inline void unlock_region(GrallocRegion* ) {
+ hashmapUnlock(get_regions());
+}
+
+
+void* reference_region(const char* op, const private_handle_t* hnd) {
+ char name_buf[ASHMEM_NAME_LEN];
+ GrallocRegion* region = lock_region_for_handle(hnd, name_buf);
+ if (!region->base_) {
+ void* mappedAddress = mmap(
+ 0, hnd->total_size, PROT_READ|PROT_WRITE, MAP_SHARED, hnd->fd, 0);
+ if (mappedAddress == MAP_FAILED) {
+ ALOGE("Could not mmap %s", strerror(errno));
+ unlock_region(region);
+ return NULL;
+ }
+ if (!(hnd->flags & private_handle_t::PRIV_FLAGS_FRAMEBUFFER)) {
+ // Set up the guard pages. The last page is always a guard
+ uintptr_t base = uintptr_t(mappedAddress);
+ uintptr_t addr = base + hnd->total_size - PAGE_SIZE;
+ if (mprotect((void*)addr, PAGE_SIZE, PROT_NONE) == -1) {
+ ALOGE("mprotect base=%p, pg=%p failed (%s)",
+ (void*)base, (void*)addr, strerror(errno));
+ }
+ }
+ region->base_ = mappedAddress;
+ ALOGI("Mapped %s hnd=%p fd=%d base=%p format=%s(0x%x) width=%d height=%d",
+ name_buf, hnd, hnd->fd, region->base_,
+ pixel_format_to_string(hnd->format), hnd->format,
+ hnd->x_res, hnd->y_res);
+ }
+
+ void* rval = region->base_;
+ ++region->num_references_;
+ ALOGI_IF(g_log_refs, "Referencing name=%s op=%s addr=%p new numRefs=%d",
+ name_buf, op, region->base_, region->num_references_);
+ unlock_region(region);
+ return rval;
+}
+
+
+int unreference_region(const char* op, const private_handle_t* hnd) {
+ char name_buf[ASHMEM_NAME_LEN];
+
+ GrallocRegion* region = lock_region_for_handle(hnd, name_buf);
+ if (!region->base_) {
+ ALOGE("Unmapping region with no map hnd=%p", hnd);
+ unlock_region(region);
+ return -1;
+ }
+ if (region->num_references_ < 1) {
+ ALOGE(
+ "unmap with hnd=%p, numReferences=%d", hnd, region->num_references_);
+ unlock_region(region);
+ return -1;
+ }
+ --region->num_references_;
+ if (!region->num_references_) {
+ ALOGI("Unmapped %s hnd=%p fd=%d base=%p", name_buf, hnd,
+ hnd->fd, region->base_);
+ if (munmap(region->base_, hnd->total_size) < 0) {
+ ALOGE("Could not unmap %s", strerror(errno));
+ }
+ region->base_ = 0;
+ }
+ ALOGI_IF(g_log_refs, "Unreferencing name=%s op=%s addr=%p new numRefs=%d",
+ name_buf, op, region->base_, region->num_references_);
+ unlock_region(region);
+ return 0;
+}
diff --git a/guest/libs/legacy_framebuffer/RegionRegistry.h b/guest/libs/legacy_framebuffer/RegionRegistry.h
new file mode 100644
index 0000000..210ba9a
--- /dev/null
+++ b/guest/libs/legacy_framebuffer/RegionRegistry.h
@@ -0,0 +1,30 @@
+#pragma once
+/*
+ * Copyright (C) 2016 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.
+ */
+struct private_handle_t;
+
+/**
+ * Map the memory associated with hnd->fd or, if already mapped, increment
+ * its reference count.
+ */
+void* reference_region(
+ const char* op, const private_handle_t* hnd);
+
+/**
+ * Decrement the reference count associated with hnd->fd, unmapping its
+ * memory iff the reference count reaches 0.
+ */
+int unreference_region(const char* op, const private_handle_t* hnd);
diff --git a/guest/libs/legacy_framebuffer/vsoc_framebuffer.cpp b/guest/libs/legacy_framebuffer/vsoc_framebuffer.cpp
new file mode 100644
index 0000000..32071a0
--- /dev/null
+++ b/guest/libs/legacy_framebuffer/vsoc_framebuffer.cpp
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+#include <guest/libs/platform_support/api_level_fixes.h>
+
+#define LOG_TAG "VSoCFrameBuffer"
+
+#include "vsoc_framebuffer.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <utils/String8.h>
+#include <cutils/log.h>
+#include <system/graphics.h>
+
+#include "common/vsoc/lib/fb_bcast_region_view.h"
+
+const char* const VSoCFrameBuffer::kFrameBufferPath =
+ "/dev/userspace_framebuffer";
+
+
+static vsoc::framebuffer::FBBroadcastRegionView* GetFBBroadcastRegionView() {
+ static vsoc::framebuffer::FBBroadcastRegionView instance;
+ return &instance;
+}
+
+const VSoCFrameBuffer & VSoCFrameBuffer::getInstance() {
+ static VSoCFrameBuffer instance;
+ instance.Configure();
+ return instance;
+}
+
+
+VSoCFrameBuffer::VSoCFrameBuffer()
+ : fb_region_view_(GetFBBroadcastRegionView()), line_length_(-1) { }
+
+
+void VSoCFrameBuffer::Configure() {
+ if (!fb_region_view_->Open()) {
+ SLOGE("Failed to open broadcaster region");
+ }
+ line_length_ = align(x_res() * sizeof(Pixel));
+}
+
+
+int VSoCFrameBuffer::dpi() const {
+ return fb_region_view_->dpi();
+}
+
+
+int VSoCFrameBuffer::x_res() const {
+ return fb_region_view_->x_res();
+}
+
+
+int VSoCFrameBuffer::y_res() const {
+ return fb_region_view_->y_res();
+}
+
+
+bool VSoCFrameBuffer::OpenFrameBuffer(int* frame_buffer_fd) {
+ int fb_fd;
+ if ((fb_fd = open(VSoCFrameBuffer::kFrameBufferPath, O_RDWR)) < 0) {
+ SLOGE("Failed to open '%s' (%s)",
+ VSoCFrameBuffer::kFrameBufferPath, strerror(errno));
+ return false;
+ }
+
+ const VSoCFrameBuffer& config = VSoCFrameBuffer::getInstance();
+
+ if (ftruncate(fb_fd, config.total_buffer_size()) < 0) {
+ SLOGE("Failed to truncate framebuffer (%s)", strerror(errno));
+ return false;
+ }
+
+ *frame_buffer_fd = fb_fd;
+ return true;
+}
+
+
+bool VSoCFrameBuffer::OpenAndMapFrameBuffer(void** fb_memory,
+ int* frame_buffer_fd) {
+ int fb_fd;
+ if (!VSoCFrameBuffer::OpenFrameBuffer(&fb_fd)) { return false; }
+
+ size_t fb_size = VSoCFrameBuffer::getInstance().total_buffer_size();
+
+ void* mmap_res = mmap(0, fb_size, PROT_READ, MAP_SHARED, fb_fd, 0);
+ if (mmap_res == MAP_FAILED) {
+ SLOGE("Failed to mmap framebuffer (%s)", strerror(errno));
+ close(fb_fd);
+ return false;
+ }
+
+ // Modify the pointers only after mmap succeeds.
+ *fb_memory = mmap_res;
+ *frame_buffer_fd = fb_fd;
+
+ return true;
+}
+
+bool VSoCFrameBuffer::UnmapAndCloseFrameBuffer(void* fb_memory,
+ int frame_buffer_fd) {
+ size_t fb_size = VSoCFrameBuffer::getInstance().total_buffer_size();
+ return munmap(fb_memory, fb_size) == 0 && close(frame_buffer_fd) == 0;
+}
+
+
+int VSoCFrameBuffer::hal_format() const {
+ switch(bits_per_pixel()) {
+ case 32:
+ if (kRedShift) {
+ return HAL_PIXEL_FORMAT_BGRA_8888;
+ } else {
+ return HAL_PIXEL_FORMAT_RGBX_8888;
+ }
+ default:
+ return HAL_PIXEL_FORMAT_RGB_565;
+ }
+}
+
+const char* pixel_format_to_string(int format) {
+ switch (format) {
+ // Formats that are universal across versions
+ case HAL_PIXEL_FORMAT_RGBA_8888:
+ return "RGBA_8888";
+ case HAL_PIXEL_FORMAT_RGBX_8888:
+ return "RGBX_8888";
+ case HAL_PIXEL_FORMAT_BGRA_8888:
+ return "BGRA_8888";
+ case HAL_PIXEL_FORMAT_RGB_888:
+ return "RGB_888";
+ case HAL_PIXEL_FORMAT_RGB_565:
+ return "RGB_565";
+ case HAL_PIXEL_FORMAT_YV12:
+ return "YV12";
+ case HAL_PIXEL_FORMAT_YCrCb_420_SP:
+ return "YCrCb_420_SP";
+ case HAL_PIXEL_FORMAT_YCbCr_422_SP:
+ return "YCbCr_422_SP";
+ case HAL_PIXEL_FORMAT_YCbCr_422_I:
+ return "YCbCr_422_I";
+
+#if VSOC_PLATFORM_SDK_AFTER(J)
+ // First supported on JBMR1 (API 17)
+ case HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED:
+ return "IMPLEMENTATION_DEFINED";
+ case HAL_PIXEL_FORMAT_BLOB:
+ return "BLOB";
+#endif
+#if VSOC_PLATFORM_SDK_AFTER(J_MR1)
+ // First supported on JBMR2 (API 18)
+ case HAL_PIXEL_FORMAT_YCbCr_420_888:
+ return "YCbCr_420_888";
+ case HAL_PIXEL_FORMAT_Y8:
+ return "Y8";
+ case HAL_PIXEL_FORMAT_Y16:
+ return "Y16";
+#endif
+#if VSOC_PLATFORM_SDK_AFTER(K)
+ // Support was added in L (API 21)
+ case HAL_PIXEL_FORMAT_RAW_OPAQUE:
+ return "RAW_OPAQUE";
+ // This is an alias for RAW_SENSOR in L and replaces it in M.
+ case HAL_PIXEL_FORMAT_RAW16:
+ return "RAW16";
+ case HAL_PIXEL_FORMAT_RAW10:
+ return "RAW10";
+#endif
+#if VSOC_PLATFORM_SDK_AFTER(L_MR1)
+ case HAL_PIXEL_FORMAT_YCbCr_444_888:
+ return "YCbCr_444_888";
+ case HAL_PIXEL_FORMAT_YCbCr_422_888:
+ return "YCbCr_422_888";
+ case HAL_PIXEL_FORMAT_RAW12:
+ return "RAW12";
+ case HAL_PIXEL_FORMAT_FLEX_RGBA_8888:
+ return "FLEX_RGBA_8888";
+ case HAL_PIXEL_FORMAT_FLEX_RGB_888:
+ return "FLEX_RGB_888";
+#endif
+
+ // Formats that have been removed
+#if VSOC_PLATFORM_SDK_BEFORE(K)
+ // Support was dropped on K (API 19)
+ case HAL_PIXEL_FORMAT_RGBA_5551:
+ return "RGBA_5551";
+ case HAL_PIXEL_FORMAT_RGBA_4444:
+ return "RGBA_4444";
+#endif
+#if VSOC_PLATFORM_SDK_BEFORE(L)
+ // Renamed to RAW_16 in L. Both were present for L, but it was completely
+ // removed in M.
+ case HAL_PIXEL_FORMAT_RAW_SENSOR:
+ return "RAW_SENSOR";
+#endif
+#if VSOC_PLATFORM_SDK_AFTER(J_MR2) && VSOC_PLATFORM_SDK_BEFORE(M)
+ // Supported K, L, and LMR1. Not supported on JBMR0, JBMR1, JBMR2, and M
+ case HAL_PIXEL_FORMAT_sRGB_X_8888:
+ return "sRGB_X_8888";
+ case HAL_PIXEL_FORMAT_sRGB_A_8888:
+ return "sRGB_A_8888";
+#endif
+ }
+ return "UNKNOWN";
+}
diff --git a/guest/libs/legacy_framebuffer/vsoc_framebuffer.h b/guest/libs/legacy_framebuffer/vsoc_framebuffer.h
new file mode 100644
index 0000000..9f1d405
--- /dev/null
+++ b/guest/libs/legacy_framebuffer/vsoc_framebuffer.h
@@ -0,0 +1,110 @@
+#pragma once
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#include <UniquePtr.h>
+#include <pthread.h>
+#include <sys/mman.h>
+#include <climits>
+
+struct private_handle_t;
+struct remoter_request_packet;
+
+namespace vsoc {
+namespace framebuffer {
+class FBBroadcastRegionView;
+}
+}
+
+inline size_t roundUpToPageSize(size_t x) {
+ return (x + (PAGE_SIZE-1)) & ~(PAGE_SIZE-1);
+}
+
+class VSoCFrameBuffer {
+public:
+ static const VSoCFrameBuffer& getInstance();
+
+ static int align(int input, int alignment = kAlignment) {
+ return (input + alignment - 1) & -alignment;
+ }
+
+ int bits_per_pixel() const {
+ return kBitsPerPixel;
+ }
+
+ size_t bufferSize() const {
+ return line_length_ * y_res();
+ }
+
+ int dpi() const;
+
+ int hal_format() const;
+
+ int line_length() const { return line_length_; }
+
+ int total_buffer_size() const {
+ return roundUpToPageSize(line_length_ * y_res_virtual() +
+ VSoCFrameBuffer::kSwiftShaderPadding);
+ }
+
+ int x_res() const;
+
+ int y_res() const;
+
+ int y_res_virtual() const {
+ return y_res() * kNumBuffers;
+ }
+
+ static const int kAlignment = 8;
+ static const int kNumHwcBuffers = 3;
+ // Without sync fences enabled surfaceflinger uses only 2 framebuffers,
+ // regardless of how many are available
+ static const int kNumSfBuffers = 3;
+ static const int kNumBuffers = kNumHwcBuffers + kNumSfBuffers;
+ static const char* const kFrameBufferPath;
+
+ static const int kRedShift = 0;
+ static const int kRedBits = 8;
+ static const int kGreenShift = 8;
+ static const int kGreenBits = 8;
+ static const int kBlueShift = 16;
+ static const int kBlueBits = 8;
+ static const int kAlphaShift = 24;
+ static const int kAlphaBits = 8;
+ typedef uint32_t Pixel;
+ static const int kSwiftShaderPadding = 4;
+
+ // Opens the framebuffer file. Ensures the file has the appropriate size by
+ // calling ftruncate.
+ static bool OpenFrameBuffer(int* frame_buffer_fd);
+
+ // Maps the framebuffer into memory. It's the caller's responsibility to
+ // unmap the memory and close the file when done.
+ static bool OpenAndMapFrameBuffer(void** fb_memory, int* frame_buffer_fd);
+ static bool UnmapAndCloseFrameBuffer(void* fb_memory, int frame_buffer_fd);
+
+private:
+ VSoCFrameBuffer();
+ void Configure();
+
+ vsoc::framebuffer::FBBroadcastRegionView* fb_region_view_;
+ static const int kBitsPerPixel = sizeof(Pixel) * CHAR_BIT;
+ // Length of a scan-line in bytes.
+ int line_length_;
+ DISALLOW_COPY_AND_ASSIGN(VSoCFrameBuffer);
+};
+
+const char* pixel_format_to_string(int format);
diff --git a/guest/libs/legacy_framebuffer/vsoc_framebuffer_control.cpp b/guest/libs/legacy_framebuffer/vsoc_framebuffer_control.cpp
new file mode 100644
index 0000000..83c9b00
--- /dev/null
+++ b/guest/libs/legacy_framebuffer/vsoc_framebuffer_control.cpp
@@ -0,0 +1,294 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+#include <errno.h>
+#include <fcntl.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/time.h>
+#include <unistd.h>
+
+#include <utils/String8.h>
+
+#define LOG_TAG "VSoCFrameBufferControl"
+#include <cutils/log.h>
+#include <system/graphics.h>
+
+#include "guest/libs/legacy_framebuffer/vsoc_framebuffer_control.h"
+#include "guest/hals/gralloc/legacy/gralloc_vsoc_priv.h"
+
+enum { NOT_YET = 0, IN_PROGRESS, DONE };
+
+struct FrameBufferControl {
+ pthread_mutex_t mutex;
+ pthread_cond_t cond_var;
+ uint32_t seq_num;
+ volatile int yoffset;
+ volatile int initialized;
+ volatile uint32_t buffer_bits;
+ CompositionStats stats;
+};
+
+// __sync_lock_test_and_set is described to work on intel, but not on many other
+// targets.
+#define ATOMICALLY_SET(x, val) __sync_lock_test_and_set(&(x), (val))
+#define ATOMICALLY_COMPARE_AND_SWAP(x, old, val) \
+ __sync_val_compare_and_swap(&(x), (old), (val))
+// fetch the value, don't modify it (or with 0)
+#define ATOMICALLY_GET(x) __sync_or_and_fetch(&(x), 0)
+
+const char* const VSoCFrameBufferControl::kFrameBufferControlPath =
+ "/dev/framebuffer_control";
+
+VSoCFrameBufferControl& VSoCFrameBufferControl::getInstance() {
+ static VSoCFrameBufferControl instance;
+ // If not initialized before and fails to initialize now
+ if (!instance.Initialize()) {
+ LOG_ALWAYS_FATAL(
+ "Unable to initialize the framebuffer control structure (%s)... "
+ "aborting!",
+ strerror(errno));
+ }
+ return instance;
+}
+
+uint32_t VSoCFrameBufferControl::GetAndSetNextAvailableBufferBit(uint32_t filter) {
+ if (pthread_mutex_lock(&control_memory_->mutex)) {
+ ALOGE("Failed to acquire lock on framebuffer control mutex (%s) - %s",
+ strerror(errno), __FUNCTION__);
+ return 0;
+ }
+ uint32_t bit = control_memory_->buffer_bits;
+ bit &= filter;
+ if (bit == filter) {
+ // All bits in the filter are already set in the set
+ bit = 0LU;
+ } else {
+ // Set available bits to 1
+ bit = (bit^filter);
+ // isolate first available bit
+ bit &= ~bit + 1LU;
+ // set it on bit set on shared memory
+ control_memory_->buffer_bits |= bit;
+ }
+
+ pthread_mutex_unlock(&control_memory_->mutex);
+ return bit;
+}
+
+int VSoCFrameBufferControl::UnsetBufferBits(uint32_t bits) {
+ if (pthread_mutex_lock(&control_memory_->mutex)) {
+ ALOGE("Failed to acquire lock on framebuffer control mutex (%s) - %s",
+ strerror(errno), __FUNCTION__);
+ return -1;
+ }
+
+ control_memory_->buffer_bits &= ~bits;
+
+ pthread_mutex_unlock(&control_memory_->mutex);
+ return 0;
+}
+
+VSoCFrameBufferControl::VSoCFrameBufferControl()
+ : control_fd_(-1), control_memory_(NULL) {}
+
+namespace {
+
+bool MapFrameBufferControl(FrameBufferControl** control_memory_ptr,
+ int* fbc_fd) {
+ size_t control_size = sizeof(FrameBufferControl);
+ mode_t fb_mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH;
+ int control_fd;
+ if ((control_fd = open(VSoCFrameBufferControl::kFrameBufferControlPath, O_RDWR,
+ fb_mode)) < 0) {
+ ALOGE("Failed to open framebuffer control at %s (%s)",
+ VSoCFrameBufferControl::kFrameBufferControlPath, strerror(errno));
+ return false;
+ }
+
+ if (ftruncate(control_fd, sizeof(FrameBufferControl)) < 0) {
+ ALOGE("Failed to truncate framebuffer control at %s (%s)",
+ VSoCFrameBufferControl::kFrameBufferControlPath, strerror(errno));
+ return false;
+ }
+
+ void* control_memory;
+ control_memory =
+ mmap(0, control_size, PROT_READ | PROT_WRITE, MAP_SHARED, control_fd, 0);
+ if (control_memory == MAP_FAILED) {
+ ALOGE("Failed to mmap framebuffer control (%s)", strerror(errno));
+ close(control_fd);
+ return false;
+ }
+
+ *control_memory_ptr = reinterpret_cast<FrameBufferControl*>(control_memory);
+ *fbc_fd = control_fd;
+
+ return true;
+}
+
+void UnmapFrameBufferControl(FrameBufferControl** control_memory_ptr,
+ int* fbc_fd) {
+ munmap(*control_memory_ptr, sizeof(FrameBufferControl));
+ *control_memory_ptr = NULL;
+ close(*fbc_fd);
+ *fbc_fd = -1;
+}
+}
+
+bool VSoCFrameBufferControl::Initialize() {
+ if (control_fd_ >= 0) {
+ return true;
+ }
+
+ if (!MapFrameBufferControl(&control_memory_, &control_fd_)) {
+ return false;
+ }
+
+ int initializing_state = ATOMICALLY_COMPARE_AND_SWAP(
+ control_memory_->initialized, NOT_YET, IN_PROGRESS);
+ switch (initializing_state) {
+ case DONE:
+ return true;
+
+ case IN_PROGRESS: { // wait 1 sec and try again
+ do {
+ sleep(1);
+ initializing_state = ATOMICALLY_GET(control_memory_->initialized);
+ if (initializing_state != DONE) {
+ ALOGW(
+ "Framebuffer control structure has not yet been initialized "
+ "after one second. Value of initialized flag: %d",
+ initializing_state);
+ }
+ } while (initializing_state != DONE);
+ return true;
+ }
+
+ case NOT_YET: { // flag set to IN_PROGRESS, proceed to initialize
+ pthread_mutexattr_t mutex_attr;
+ pthread_mutexattr_init(&mutex_attr);
+ pthread_mutexattr_setpshared(&mutex_attr, PTHREAD_PROCESS_SHARED);
+ int retval = pthread_mutex_init(&(control_memory_->mutex), &mutex_attr);
+ if (retval) {
+ ALOGE("Failed to acquire lock on framebuffer control mutex (%s) - %s",
+ strerror(errno), __FUNCTION__);
+ UnmapFrameBufferControl(&control_memory_, &control_fd_);
+ return false;
+ }
+ pthread_mutexattr_destroy(&mutex_attr);
+
+ pthread_condattr_t cond_attr;
+ pthread_condattr_init(&cond_attr);
+ pthread_condattr_setpshared(&cond_attr, PTHREAD_PROCESS_SHARED);
+ retval = pthread_cond_init(&(control_memory_->cond_var), &cond_attr);
+ if (retval) {
+ ALOGE("Failed to initialize cond var for framebuffer control (%s)",
+ strerror(errno));
+ pthread_mutex_destroy(&(control_memory_->mutex));
+ UnmapFrameBufferControl(&control_memory_, &control_fd_);
+ return false;
+ }
+ pthread_condattr_destroy(&cond_attr);
+
+ ATOMICALLY_SET(control_memory_->buffer_bits, 0LU);
+ ATOMICALLY_SET(control_memory_->seq_num, 0);
+ ATOMICALLY_SET(control_memory_->initialized, DONE);
+
+ return true;
+ }
+
+ default: { // unrecognized value
+ ALOGE("Framebuffer control memory is corrupt, initialized = %d",
+ initializing_state);
+ UnmapFrameBufferControl(&control_memory_, &control_fd_);
+ return false;
+ }
+ }
+
+ return true;
+}
+
+int VSoCFrameBufferControl::GetCurrentYOffset() const {
+ if (!control_memory_) return -1;
+ return control_memory_->yoffset;
+}
+
+int VSoCFrameBufferControl::WaitForFrameBufferChangeSince(
+ uint32_t previous_fb_seq,
+ int* yoffset_p,
+ uint32_t* fb_seq_p,
+ CompositionStats* stats_p) {
+ if (pthread_mutex_lock(&control_memory_->mutex)) {
+ ALOGE("Failed to acquire lock on framebuffer control mutex (%s) - %s",
+ strerror(errno), __FUNCTION__);
+ return -1;
+ }
+ int retval = 0;
+
+ while (control_memory_->seq_num == previous_fb_seq) {
+ retval =
+ pthread_cond_wait(&control_memory_->cond_var, &control_memory_->mutex);
+ }
+
+ if (fb_seq_p) {
+ *fb_seq_p = control_memory_->seq_num;
+ }
+ if (yoffset_p) {
+ *yoffset_p = control_memory_->yoffset;
+ }
+ if (stats_p) {
+ *stats_p = control_memory_->stats;
+ }
+
+ pthread_mutex_unlock(&control_memory_->mutex);
+ return retval;
+}
+
+int VSoCFrameBufferControl::WaitForFrameBufferChange(int* yoffset_p) {
+ return WaitForFrameBufferChangeSince(
+ control_memory_->seq_num, yoffset_p, NULL, NULL);
+}
+
+int VSoCFrameBufferControl::BroadcastFrameBufferChanged(int yoffset) {
+ return BroadcastFrameBufferChanged(yoffset, NULL);
+}
+
+// increments the framebuffer sequential number, ensuring it's never zero
+static inline uint32_t seq_inc(uint32_t num) {
+ ++num;
+ return num? num: 1;
+}
+
+int VSoCFrameBufferControl::BroadcastFrameBufferChanged(
+ int yoffset, const CompositionStats* stats) {
+ if (pthread_mutex_lock(&control_memory_->mutex)) {
+ ALOGE("Failed to acquire lock on framebuffer control mutex (%s)",
+ strerror(errno));
+ return -1;
+ }
+ control_memory_->yoffset = yoffset;
+ control_memory_->seq_num = seq_inc(control_memory_->seq_num);
+ if (stats) { control_memory_->stats = *stats; }
+ pthread_cond_broadcast(&control_memory_->cond_var);
+ pthread_mutex_unlock(&control_memory_->mutex);
+
+ return 0;
+}
diff --git a/guest/libs/legacy_framebuffer/vsoc_framebuffer_control.h b/guest/libs/legacy_framebuffer/vsoc_framebuffer_control.h
new file mode 100644
index 0000000..5e1ec7e
--- /dev/null
+++ b/guest/libs/legacy_framebuffer/vsoc_framebuffer_control.h
@@ -0,0 +1,88 @@
+#pragma once
+/*
+ * Copyright (C) 2016 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.
+ */
+#include <common/libs/time/monotonic_time.h>
+
+struct FrameBufferControl;
+
+struct CompositionStats {
+ cvd::time::MonotonicTimePoint prepare_start;
+ cvd::time::MonotonicTimePoint prepare_end;
+ cvd::time::MonotonicTimePoint set_start;
+ cvd::time::MonotonicTimePoint set_end;
+ cvd::time::MonotonicTimePoint last_vsync;
+ // There may be more than one call to prepare, the timestamps are with regards to the last one (the one that precedes the set call)
+ int num_prepare_calls;
+ int num_layers;
+ // The number of layers composed by the hwcomposer
+ int num_hwc_layers;
+};
+
+class VSoCFrameBufferControl {
+ public:
+ static VSoCFrameBufferControl& getInstance();
+
+ static const char* const kFrameBufferControlPath;
+
+ // The framebuffer control structure mantains a bit set to keep track of the
+ // buffers that have been allocated already. This function atomically finds an
+ // unset (0) bit in the set, sets it to 1 and returns it. It will only
+ // consider bits already set in the filter parameter.
+ uint32_t GetAndSetNextAvailableBufferBit(uint32_t filter);
+ // Returns 0 on success
+ int UnsetBufferBits(uint32_t bits);
+
+ // Returns the yoffset of the last framebuffer update or a negative number on
+ // error.
+ int GetCurrentYOffset() const;
+ // Returns the value returned by the pthread_cond_wait, or -1 if the control
+ // structure has not been initialized by the hwcomposer yet.
+ int WaitForFrameBufferChange(int* yoffset_p);
+ // Uses a sequential number to determine whether the client was notified of
+ // the last framebuffer change and therefore needs to wait for a new one or if
+ // it can just return with the last one. It also provides the timings of the
+ // composition. Any NULL input parameters will be ignored. The sequential
+ // numbers are guaranteed to never be zero, so a value of zero can be used to
+ // get the last frame without waiting (useful when we want to get a frame for
+ // the first time).
+ int WaitForFrameBufferChangeSince(uint32_t previous_fb_seq,
+ int* yoffset_p,
+ uint32_t* fb_seq_p,
+ CompositionStats* stats_p);
+
+ // Returns 0 on success, a negative number on error.
+ int BroadcastFrameBufferChanged(int yoffset);
+
+ // Returns 0 on success, a negative number on error.
+ int BroadcastFrameBufferChanged(int yoffset, const CompositionStats* stats);
+
+ private:
+ VSoCFrameBufferControl();
+
+ // Map the control structure to memory and initialize its contents.
+ bool Initialize();
+
+ // FD for the frame buffer control.
+ int control_fd_;
+ // Pointer to the mapped frame buffer control.
+ FrameBufferControl* control_memory_;
+
+ // Disallow copy and assign
+ VSoCFrameBufferControl(const VSoCFrameBufferControl&) {}
+ VSoCFrameBufferControl& operator=(const VSoCFrameBufferControl&) {
+ return *this;
+ }
+};
diff --git a/guest/libs/platform_support/api_level_fixes.h b/guest/libs/platform_support/api_level_fixes.h
new file mode 100644
index 0000000..d0da546
--- /dev/null
+++ b/guest/libs/platform_support/api_level_fixes.h
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#pragma once
+
+// Fixes for various things that go wrong between Android versions.
+// By convention this should be the very first include: it tweaks some
+// flags that affect the processing of the system headers.
+//
+// Code that needs to cope with platform changes should use the
+// VSOC_PLATFORM_SDK_BEFORE and VSOC_PLATFORM_SDK_AFTER macros below.
+// It's fine to provide declarations for broadly used things in this file
+// if that's easier.
+//
+// To use this header add $(VSOC_VERSION_CFLAGS) to the LOCAL_CFLAGS
+// in the corresponding Android.mk. There is an error check to catch
+// cases where this wasn't done.
+//
+// Code should not examine the SDK_PLATFORM_VERSION, and generally shouldn't
+// look at the VSOC_PLATFORM_SDK_* values. While these currently track
+// PLATFORM_SDK_VERSION, that's an implementation detail that will probably
+// change: Android will eventually break things without bumping
+// PLATFORM_SDK_VERSION.
+//
+// This is also why there is no SDK_PLATFORM_VERSION_IS(). Convert these
+// statements into BEFORE and/or AFTER.
+//
+// To check for master/AOSP use VSOC_PLATFORM_VERSION_AFTER(LAST_SHIPPED)
+#include <time.h>
+
+#ifndef VSOC_PLATFORM_SDK_VERSION
+#error VSOC_PLATFORM_SDK_VERSION is not set. Check your Android.mk
+#endif
+
+// Hide some C++ annotations that we'd like to use but need to avoid on older
+// compilers.
+#if __cplusplus <= 199711L
+#define override
+#endif
+
+#define VSOC_PLATFORM_SDK_J 16
+#define VSOC_PLATFORM_SDK_J_MR1 17
+#define VSOC_PLATFORM_SDK_J_MR2 18
+#define VSOC_PLATFORM_SDK_K 19
+// Version 20 reserved for KitKat wearables only. See
+// http://developer.android.com/guide/topics/manifest/uses-sdk-element.html
+#define VSOC_PLATFORM_SDK_L 21
+#define VSOC_PLATFORM_SDK_L_MR1 22
+#define VSOC_PLATFORM_SDK_M 23
+#define VSOC_PLATFORM_SDK_N 24
+#define VSOC_PLATFORM_SDK_N_MR1 25
+#define VSOC_PLATFORM_SDK_O 26
+#define VSOC_PLATFORM_SDK_O_MR1 27
+#define VSOC_PLATFORM_SDK_LAST_SHIPPED 27
+
+#define VSOC_PLATFORM_SDK_BEFORE(X) (VSOC_PLATFORM_SDK_VERSION < VSOC_PLATFORM_SDK_##X)
+#define VSOC_PLATFORM_SDK_AFTER(X) (VSOC_PLATFORM_SDK_VERSION > VSOC_PLATFORM_SDK_##X)
+
+#if VSOC_PLATFORM_SDK_BEFORE(J_MR2)
+#define VSOC_STATIC_INITIALIZER(X) X:
+#else
+#define VSOC_STATIC_INITIALIZER(X) .X =
+#endif
+
+#if VSOC_PLATFORM_SDK_BEFORE(K)
+// audio_input_flags_t was first defind in K.
+// JBMR2 and K use the same audio HAL version, so define a work-around here.
+typedef enum {
+ AUDIO_INPUT_FLAG_NONE = 0x0, // no attributes
+} audio_input_flags_t;
+#endif
+
+// cstdint doesn't provide PRI... before C++11. On some branches (K) inttypes.h
+// also doesn't behave as if it's C++. Just define the PRI... types here
+// the kludgy way so our source is clean from a C++11 point-of-view.
+#ifndef __STDC_FORMAT_MACROS
+#define __STDC_FORMAT_MACROS 1
+#endif
+#include <inttypes.h>
+
+#if VSOC_PLATFORM_SDK_BEFORE(L)
+#define HAL_PIXEL_FORMAT_RAW16 HAL_PIXEL_FORMAT_RAW_SENSOR
+#define VSOC_FDPRINTF fdprintf
+
+#define KLOG_ERROR_LEVEL 3
+#define KLOG_WARNING_LEVEL 4
+#define KLOG_NOTICE_LEVEL 5
+#define KLOG_INFO_LEVEL 6
+#define KLOG_DEBUG_LEVEL 7
+
+#else
+#define VSOC_FDPRINTF dprintf
+#endif
+
+#if VSOC_PLATFORM_SDK_BEFORE(M)
+__BEGIN_DECLS
+extern int clock_nanosleep(clockid_t, int, const struct timespec*,
+ struct timespec*);
+__END_DECLS
+#endif
diff --git a/guest/libs/remoter/Android.mk b/guest/libs/remoter/Android.mk
new file mode 100644
index 0000000..bb0dba7
--- /dev/null
+++ b/guest/libs/remoter/Android.mk
@@ -0,0 +1,32 @@
+# Copyright (C) 2017 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.
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+ remoter_framework_pkt.cpp
+LOCAL_MODULE := libcuttlefish_remoter_framework
+LOCAL_MODULE_TAGS := optional
+LOCAL_SHARED_LIBRARIES := \
+ libcutils \
+ liblog \
+ libcuttlefish_fs
+LOCAL_C_INCLUDES := \
+ device/google/cuttlefish_common
+LOCAL_CFLAGS := $(VSOC_VERSION_CFLAGS)
+LOCAL_VENDOR_MODULE := true
+include $(BUILD_STATIC_LIBRARY)
+
+
diff --git a/guest/libs/remoter/remoter_framework_pkt.cpp b/guest/libs/remoter/remoter_framework_pkt.cpp
new file mode 100644
index 0000000..68564f1
--- /dev/null
+++ b/guest/libs/remoter/remoter_framework_pkt.cpp
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#include "guest/libs/remoter/remoter_framework_pkt.h"
+
+void remoter_connect(cvd::SharedFD* dest) {
+ *dest = cvd::SharedFD::SocketLocalClient(
+ "remoter", ANDROID_SOCKET_NAMESPACE_RESERVED, SOCK_STREAM);
+ if ((*dest)->IsOpen()) {
+ ALOGE("Failed to connect to remoter (%s)", (*dest)->StrError());
+ } else {
+#ifdef DEBUG_CONNECTIONS
+ ALOGI("Connected to remoter (socket %d)", socket);
+#endif
+ }
+}
+
+int remoter_connect() {
+ int socket = socket_local_client(
+ "remoter", ANDROID_SOCKET_NAMESPACE_RESERVED, SOCK_STREAM);
+ if (socket < 0) {
+ ALOGE("Failed to connect to remoter (%s)", strerror(errno));
+ return -1;
+ } else {
+#ifdef DEBUG_CONNECTIONS
+ ALOGI("Connected to remoter (socket %d)", socket);
+#endif
+ }
+ return socket;
+}
+
diff --git a/guest/libs/remoter/remoter_framework_pkt.h b/guest/libs/remoter/remoter_framework_pkt.h
new file mode 100644
index 0000000..9fc6173
--- /dev/null
+++ b/guest/libs/remoter/remoter_framework_pkt.h
@@ -0,0 +1,284 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#pragma once
+
+#include <stdlib.h>
+
+#include <cutils/sockets.h>
+#include <cutils/log.h>
+
+#include "common/libs/fs/shared_fd.h"
+
+// #define DEBUG_CONNECTIONS
+
+/*
+ * Packet structures for commands sent to the remoter from the GCE HAL.
+ * This is a private protocol between the HAL and the remoter.
+ */
+
+static const size_t kSensorNameMaxLen = 64;
+// Don't use PATH_MAX here because it would increate the size of every
+// packet that we send to the remoter.
+static const size_t kScreenRecordFilePathMaxLen = 128;
+static const size_t kUnixSocketPathMaxLen = 128;
+
+struct remoter_request_packet {
+ /* Length of the packet in bytes. */
+ uint32_t length;
+
+ /* Operation to perform. */
+ uint8_t operation;
+
+ /* Set to '1' if a response packet is desired */
+ uint8_t send_response;
+
+ /* Operation arguments. */
+ union {
+ /* Arguments for the frame buffer 'post' operation. */
+ struct {
+ /* Y offset in the double-buffer where this frame starts. */
+ uint32_t y_offset;
+ } fb_post_params;
+ /* Arguments for the frame buffer 'update rect' operation. */
+ struct {
+ uint32_t left;
+ uint32_t top;
+ uint32_t width;
+ uint32_t height;
+ } fb_update_rect_params;
+ struct {
+ uint32_t type;
+ bool enabled;
+ int64_t delay_ns;
+ int handle;
+ } sensor_state_params;
+ struct {
+ char filepath[kScreenRecordFilePathMaxLen];
+ } screenrecord_params;
+ struct {
+ char unix_socket[kUnixSocketPathMaxLen];
+ } hal_ready_params;
+ } params;
+} __attribute__((packed));
+
+enum {
+ kRemoterHALReady = 1,
+ kRemoterSensorState
+};
+
+/*
+ * If 'send_response' is set in a request then the remoter will respond
+ * with the following structure.
+ */
+struct remoter_response_packet {
+ uint32_t length;
+ uint8_t status;
+ union {
+ struct {
+ /* Number of 'struct sensor_list_element_packet's to follow */
+ uint8_t num_sensors;
+ } sensor_list_data;
+ } data;
+} __attribute__((packed));
+
+struct sensor_list_element_packet {
+ int handle;
+ int type;
+ char name[kSensorNameMaxLen];
+ char vendor[kSensorNameMaxLen];
+ int version;
+ float max_range;
+ float resolution;
+ float power;
+} __attribute__((packed));
+
+enum {
+ kResponseStatusOk = 1,
+ kResponseStatusFailed
+};
+
+static inline void remoter_request_packet_init(
+ struct remoter_request_packet* pkt, uint8_t operation,
+ uint8_t send_response) {
+ memset(pkt, 0, sizeof(*pkt));
+ pkt->length = sizeof(*pkt);
+ pkt->operation = operation;
+ pkt->send_response = send_response;
+}
+
+static inline void remoter_response_packet_init(
+ struct remoter_response_packet* pkt, uint8_t status) {
+ memset(pkt, 0, sizeof(*pkt));
+ pkt->length = sizeof(*pkt);
+ pkt->status = status;
+}
+
+void remoter_connect(cvd::SharedFD* dest);
+int remoter_connect();
+
+static inline int remoter_read_request(
+ const cvd::SharedFD& socket,
+ struct remoter_request_packet* request) {
+ int len;
+ int remaining_data;
+ /* Packets start with a 4 byte length (which includes the length). */
+
+ if ((len = socket->Read(request, sizeof(request->length))) < 0) {
+ ALOGE("%s: Failed to read remoter request (%s)",
+ __FUNCTION__, socket->StrError());
+ return -1;
+ } else if (len == 0) {
+ return 0;
+ } else if (len != sizeof(request->length)) {
+ ALOGE("%s: Failed to read remoter request (Short read)", __FUNCTION__);
+ return -1;
+ }
+
+ /* Extra paranoia. */
+ if (request->length != sizeof(*request)) {
+ ALOGE("%s: Malformed remoter request", __FUNCTION__);
+ return -1;
+ }
+ remaining_data = request->length - sizeof(request->length);
+ uint8_t* cursor = ((uint8_t*)request) + sizeof(request->length);
+ if ((len = socket->Read(cursor, remaining_data)) < 0) {
+ ALOGE("%s: Failed to read remoter request (%s)",
+ __FUNCTION__, socket->StrError());
+ return -1;
+ } else if (len == 0) {
+ return 0;
+ } else if (len != (int) remaining_data) {
+ ALOGE("%s: Failed to read remoter request (Short read)", __FUNCTION__);
+ return -1;
+ }
+ return 1;
+}
+
+static inline int remoter_read_response(
+ int socket, struct remoter_response_packet* response) {
+ int len;
+ int remaining_data;
+ /* Packets start with a 4 byte length (which includes the length). */
+
+#ifdef DEBUG_CONNECTIONS
+ ALOGI("remoter_read_response(): socket %d, length length = %d", socket,
+ sizeof(response->length));
+#endif
+ if ((len = TEMP_FAILURE_RETRY(
+ read(socket, response, sizeof(response->length)))) < 0) {
+ ALOGE("%s: Failed to read remoter response (%s)",
+ __FUNCTION__, strerror(errno));
+ return -1;
+ } else if (len == 0) {
+ return 0;
+ } else if (len != sizeof(response->length)) {
+ ALOGE("%s: Failed to read remoter response (Short read)", __FUNCTION__);
+ return -1;
+ }
+
+ /* Extra paranoia. */
+ if (response->length != sizeof(*response)) {
+ ALOGE("%s: Malformed remoter response", __FUNCTION__);
+ return -1;
+ }
+ remaining_data = response->length - sizeof(response->length);
+ uint8_t* cursor = ((uint8_t*)response) + sizeof(response->length);
+#ifdef DEBUG_CONNECTIONS
+ ALOGI("remoter_read_request(): socket %d, data length = %d",
+ socket, remaining_data);
+#endif
+ if ((len = TEMP_FAILURE_RETRY(read(socket, cursor, remaining_data))) < 0) {
+ ALOGE("%s: Failed to read remoter response (%s)",
+ __FUNCTION__, strerror(errno));
+ return -1;
+ } else if (len == 0) {
+ return 0;
+ } else if (len != (int) remaining_data) {
+ ALOGE("%s: Failed to read remoter response (Short read)", __FUNCTION__);
+ return -1;
+ }
+ return 1;
+}
+
+static inline int remoter_send_request(
+ int socket, struct remoter_request_packet* request) {
+#ifdef DEBUG_CONNECTIONS
+ ALOGI(
+ "remoter_send_request(): socket %d, length %u", socket, sizeof(*request));
+#endif
+ int len = TEMP_FAILURE_RETRY(write(socket, request, sizeof(*request)));
+ if (len <= 0) {
+ ALOGE("Failed to write request to remoter (%s)", strerror(errno));
+ return -1;
+ } else if (len != sizeof(*request)) {
+ ALOGE("Failed to write request to remoter (short write)");
+ return -1;
+ }
+ return 0;
+}
+
+static inline int remoter_send_response(
+ const cvd::SharedFD& socket,
+ struct remoter_response_packet* response) {
+ int len = socket->Write(response, sizeof(*response));
+ if (len <=0) {
+ ALOGE("%s: Failed to send response to remoter (%s)",
+ __FUNCTION__, strerror(errno));
+ return -1;
+ }
+ return 0;
+}
+
+static inline int remoter_do_single_request_with_socket(
+ int socket, struct remoter_request_packet* request,
+ struct remoter_response_packet* response) {
+
+ if (request->send_response && !response) {
+ ALOGE("%s: Request specifies a response but no response ptr set",
+ __FUNCTION__);
+ return -1;
+ } else if (!request->send_response && response) {
+ ALOGE("%s: Request specifies no response but has response ptr set",
+ __FUNCTION__);
+ return -1;
+ }
+
+ if (remoter_send_request(socket, request) < 0) {
+ return -1;
+ }
+
+ if (response && (remoter_read_response(socket, response) <= 0)) {
+ return -1;
+ }
+ return 0;
+}
+
+static inline int remoter_do_single_request(
+ struct remoter_request_packet* request,
+ struct remoter_response_packet* response) {
+ int socket;
+ if ((socket = remoter_connect()) < 0) {
+ return -1;
+ }
+
+ if (remoter_do_single_request_with_socket(socket, request, response) < 0) {
+ close(socket);
+ return -1;
+ }
+ close(socket);
+ return 0;
+}
+
diff --git a/guest/libs/wpa_supplicant_8_lib/Android.mk b/guest/libs/wpa_supplicant_8_lib/Android.mk
new file mode 100644
index 0000000..eba81f4
--- /dev/null
+++ b/guest/libs/wpa_supplicant_8_lib/Android.mk
@@ -0,0 +1,79 @@
+# Copyright (C) 2016 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.
+
+LOCAL_PATH := $(call my-dir)
+
+ifeq ($(WPA_SUPPLICANT_VERSION),VER_0_8_X)
+
+ifneq ($(BOARD_WPA_SUPPLICANT_DRIVER),)
+ CONFIG_DRIVER_$(BOARD_WPA_SUPPLICANT_DRIVER) := y
+endif
+
+# Use a custom libnl on releases before N
+ifeq (0, $(shell test $(PLATFORM_SDK_VERSION) -lt 24; echo $$?))
+EXTERNAL_VSOC_LIBNL_INCLUDE := external/gce/libnl/include
+else
+EXTERNAL_VSOC_LIBNL_INCLUDE :=
+endif
+
+
+WPA_SUPPL_DIR = external/wpa_supplicant_8
+WPA_SRC_FILE :=
+
+include $(WPA_SUPPL_DIR)/wpa_supplicant/android.config
+
+WPA_SUPPL_DIR_INCLUDE = $(WPA_SUPPL_DIR)/src \
+ $(WPA_SUPPL_DIR)/src/common \
+ $(WPA_SUPPL_DIR)/src/drivers \
+ $(WPA_SUPPL_DIR)/src/l2_packet \
+ $(WPA_SUPPL_DIR)/src/utils \
+ $(WPA_SUPPL_DIR)/src/wps \
+ $(WPA_SUPPL_DIR)/wpa_supplicant \
+ $(EXTERNAL_VSOC_LIBNL_INCLUDE)
+
+WPA_SUPPL_DIR_INCLUDE += external/libnl/include
+
+ifdef CONFIG_DRIVER_NL80211
+WPA_SRC_FILE += driver_cmd_nl80211.c
+endif
+
+ifeq ($(TARGET_ARCH),arm)
+# To force sizeof(enum) = 4
+L_CFLAGS += -mabi=aapcs-linux
+endif
+
+ifdef CONFIG_ANDROID_LOG
+L_CFLAGS += -DCONFIG_ANDROID_LOG
+endif
+
+########################
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := lib_driver_cmd_simulated
+LOCAL_SHARED_LIBRARIES := libc libcutils
+
+LOCAL_CFLAGS := $(L_CFLAGS) \
+ $(VSOC_VERSION_CFLAGS)
+
+LOCAL_SRC_FILES := $(WPA_SRC_FILE)
+
+LOCAL_C_INCLUDES := \
+ device/google/cuttlefish_common \
+ $(WPA_SUPPL_DIR_INCLUDE)\
+
+include $(BUILD_STATIC_LIBRARY)
+
+########################
+
+endif
diff --git a/guest/libs/wpa_supplicant_8_lib/driver_cmd_nl80211.c b/guest/libs/wpa_supplicant_8_lib/driver_cmd_nl80211.c
new file mode 100644
index 0000000..ff7ba23
--- /dev/null
+++ b/guest/libs/wpa_supplicant_8_lib/driver_cmd_nl80211.c
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+/*
+ * Driver interaction with extended Linux CFG8021
+ */
+
+#include "driver_cmd_nl80211.h"
+
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#if VSOC_PLATFORM_SDK_AFTER(L_MR1)
+// Android M exposes headers more directly.
+#include <netinet/in.h>
+#include <linux/if.h>
+#include "driver_nl80211.h"
+#elif VSOC_PLATFORM_SDK_AFTER(J_MR2)
+// Android versions K and L put structures in hardware_legacy
+#include "hardware_legacy/driver_nl80211.h"
+#else
+// Android version J does not expose structures directly. These structures are
+// manually defined later.
+#include <netinet/in.h>
+#include <linux/if.h>
+#endif
+
+#include "common.h"
+#include "wpa_supplicant_i.h"
+#include "config.h"
+#include "android_drv.h"
+
+
+int wpa_driver_nl80211_driver_cmd(
+ void* priv, char* cmd, char* buf, size_t buf_len) {
+ struct i802_bss* bss = priv;
+ struct wpa_driver_nl80211_data* drv = bss->drv;
+ struct ifreq ifr;
+ android_wifi_priv_cmd priv_cmd;
+ int ret = 0;
+
+ D("%s: called", __FUNCTION__);
+ if (os_strcasecmp(cmd, "STOP") == 0) {
+ linux_set_iface_flags(drv->global->ioctl_sock, bss->ifname, 0);
+ wpa_msg(drv->ctx, MSG_INFO, WPA_EVENT_DRIVER_STATE "STOPPED");
+ } else if (os_strcasecmp(cmd, "START") == 0) {
+ linux_set_iface_flags(drv->global->ioctl_sock, bss->ifname, 1);
+ wpa_msg(drv->ctx, MSG_INFO, WPA_EVENT_DRIVER_STATE "STARTED");
+ } else if (os_strcasecmp(cmd, "MACADDR") == 0) {
+ u8 macaddr[ETH_ALEN] = {};
+
+ ret = linux_get_ifhwaddr(drv->global->ioctl_sock, bss->ifname, macaddr);
+ if (!ret)
+ ret = os_snprintf(
+ buf, buf_len, "Macaddr = " MACSTR "\n", MAC2STR(macaddr));
+ } else if (os_strcasecmp(cmd, "RELOAD") == 0) {
+ wpa_msg(drv->ctx, MSG_INFO, WPA_EVENT_DRIVER_STATE "HANGED");
+ } else { // Use private command
+ return 0;
+ }
+ return ret;
+}
+
+
+int wpa_driver_set_p2p_noa(void* priv, u8 count, int start, int duration) {
+ D("%s: called", __FUNCTION__);
+ return 0;
+}
+
+
+int wpa_driver_get_p2p_noa(void* priv, u8* buf, size_t len) {
+ D("%s: called", __FUNCTION__);
+ return 0;
+}
+
+
+int wpa_driver_set_p2p_ps(void* priv, int legacy_ps, int opp_ps, int ctwindow) {
+ D("%s: called", __FUNCTION__);
+ return -1;
+}
+
+
+int wpa_driver_set_ap_wps_p2p_ie(
+ void* priv, const struct wpabuf* beacon,
+ const struct wpabuf* proberesp, const struct wpabuf* assocresp) {
+ D("%s: called", __FUNCTION__);
+ return 0;
+}
diff --git a/guest/libs/wpa_supplicant_8_lib/driver_cmd_nl80211.h b/guest/libs/wpa_supplicant_8_lib/driver_cmd_nl80211.h
new file mode 100644
index 0000000..5d2046e
--- /dev/null
+++ b/guest/libs/wpa_supplicant_8_lib/driver_cmd_nl80211.h
@@ -0,0 +1,107 @@
+#pragma once
+/*
+ * Copyright (C) 2016 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.
+ */
+#include <guest/libs/platform_support/api_level_fixes.h>
+
+#include <memory.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "common.h"
+#include "linux_ioctl.h"
+#include "wpa_supplicant_i.h"
+
+#define LOG_TAG "VSoCWpaSupplicant8Driver"
+
+#include "cutils/log.h"
+
+#define VSOC_WPA_SUPPLICANT_DEBUG 0
+
+#if VSOC_WPA_SUPPLICANT_DEBUG
+# define D(...) ALOGD(__VA_ARGS__)
+#else
+# define D(...) ((void)0)
+#endif
+
+
+typedef struct android_wifi_priv_cmd {
+ char* buf;
+ int used_len;
+ int total_len;
+} android_wifi_priv_cmd;
+
+#if VSOC_PLATFORM_SDK_BEFORE(K)
+
+#include "driver.h"
+
+struct i802_bss {
+ struct wpa_driver_nl80211_data* drv;
+ struct i802_bss* next;
+ int ifindex;
+ char ifname[IFNAMSIZ + 1];
+ char brname[IFNAMSIZ];
+
+ unsigned int beacon_set:1;
+ unsigned int added_if_into_bridge:1;
+ unsigned int added_bridge:1;
+ unsigned int in_deinit:1;
+
+ u8 addr[ETH_ALEN];
+
+ int freq;
+
+ void* ctx;
+ struct nl_handle* nl_preq;
+ struct nl_handle* nl_mgmt;
+ struct nl_cb* nl_cb;
+
+ struct nl80211_wiphy_data *wiphy_data;
+ struct dl_list wiphy_list;
+};
+
+struct nl80211_global {
+ struct dl_list interfaces;
+ int if_add_ifindex;
+ struct netlink_data *netlink;
+ struct nl_cb* nl_cb;
+ struct nl_handle* nl;
+ int nl80211_id;
+ int ioctl_sock; // socket for ioctl() use
+
+ struct nl_handle* nl_event;
+};
+
+struct wpa_driver_nl80211_data {
+ struct nl80211_global* global;
+ struct dl_list list;
+ struct dl_list wiphy_list;
+ char phyname[32];
+ void* ctx;
+ int ifindex;
+ int if_removed;
+ int if_disabled;
+ int ignore_if_down_event;
+ struct rfkill_data* rfkill;
+ struct wpa_driver_capa capa;
+ u8* extended_capa;
+ u8* extended_capa_mask;
+ unsigned int extended_capa_len;
+ int has_capability;
+ // More internal data follows.
+};
+
+#endif // VSOC_PLATFORM_SDK_AFTER(J)
diff --git a/guest/monitoring/dumpstate_ext/Android.mk b/guest/monitoring/dumpstate_ext/Android.mk
new file mode 100644
index 0000000..3fcd22f
--- /dev/null
+++ b/guest/monitoring/dumpstate_ext/Android.mk
@@ -0,0 +1,58 @@
+# Copyright (C) 2017 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_C_INCLUDES := \
+ device/google/cuttlefish_common
+LOCAL_CFLAGS := $(VSOC_VERSION_CFLAGS) -DLOG_TAG=\"VSoC-dumpstate\"
+LOCAL_SRC_FILES := dumpstate.cpp
+LOCAL_MODULE := libdumpstate.vsoc
+LOCAL_MODULE_TAGS := optional
+LOCAL_SHARED_LIBRARIES := \
+ libbase \
+ libdumpstateaidl \
+ libdumpstateutil \
+ libz
+include $(BUILD_STATIC_LIBRARY)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := android.hardware.dumpstate@1.0-service.cuttlefish
+LOCAL_INIT_RC := android.hardware.dumpstate@1.0-service.cuttlefish.rc
+LOCAL_MODULE_RELATIVE_PATH := hw
+LOCAL_SRC_FILES := \
+ dumpstate_device.cpp \
+ service.cpp
+LOCAL_CFLAGS := $(VSOC_VERSION_CFLAGS) -DLOG_TAG=\"VSoC-dumpstate\"
+LOCAL_SHARED_LIBRARIES := \
+ android.hardware.dumpstate@1.0 \
+ libbase \
+ libcutils \
+ libdumpstateutil \
+ libhidlbase \
+ libhidltransport \
+ libhwbinder \
+ liblog \
+ libutils
+LOCAL_C_INCLUDES := \
+ device/google/cuttlefish_common \
+ frameworks/native/cmds/dumpstate
+
+LOCAL_MODULE_TAGS := optional
+LOCAL_PROPRIETARY_MODULE := true
+LOCAL_VENDOR_MODULE := true
+include $(BUILD_EXECUTABLE)
diff --git a/guest/monitoring/dumpstate_ext/android.hardware.dumpstate@1.0-service.cuttlefish.rc b/guest/monitoring/dumpstate_ext/android.hardware.dumpstate@1.0-service.cuttlefish.rc
new file mode 100644
index 0000000..ad68f35
--- /dev/null
+++ b/guest/monitoring/dumpstate_ext/android.hardware.dumpstate@1.0-service.cuttlefish.rc
@@ -0,0 +1,4 @@
+service dumpstate-1-0 /vendor/bin/hw/android.hardware.dumpstate@1.0-service.cuttlefish
+ class hal
+ user system
+ group system
diff --git a/guest/monitoring/dumpstate_ext/dumpstate.cpp b/guest/monitoring/dumpstate_ext/dumpstate.cpp
new file mode 100644
index 0000000..89e820b
--- /dev/null
+++ b/guest/monitoring/dumpstate_ext/dumpstate.cpp
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#include "guest/libs/platform_support/api_level_fixes.h"
+
+#if VSOC_PLATFORM_SDK_BEFORE(N)
+extern "C" {
+#endif
+#include <dumpstate.h>
+#if VSOC_PLATFORM_SDK_BEFORE(N)
+}
+#endif
+
+void dumpstate_board() { Dumpstate& ds = Dumpstate::GetInstance(); };
diff --git a/guest/monitoring/dumpstate_ext/dumpstate_device.cpp b/guest/monitoring/dumpstate_ext/dumpstate_device.cpp
new file mode 100644
index 0000000..d8481c7
--- /dev/null
+++ b/guest/monitoring/dumpstate_ext/dumpstate_device.cpp
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#include "guest/monitoring/dumpstate_ext/dumpstate_device.h"
+
+#include <log/log.h>
+#include <DumpstateUtil.h>
+
+using android::os::dumpstate::DumpFileToFd;
+
+namespace android {
+namespace hardware {
+namespace dumpstate {
+namespace V1_0 {
+namespace implementation {
+
+// Methods from ::android::hardware::dumpstate::V1_0::IDumpstateDevice follow.
+Return<void> DumpstateDevice::dumpstateBoard(const hidl_handle& handle) {
+ if (handle == nullptr || handle->numFds < 1) {
+ ALOGE("no FDs\n");
+ return Void();
+ }
+
+ int fd = handle->data[0];
+ if (fd < 0) {
+ ALOGE("invalid FD: %d\n", handle->data[0]);
+ return Void();
+ }
+
+ DumpFileToFd(fd, "GCE INITIAL METADATA", "/initial.metadata");
+
+ return Void();
+}
+
+} // namespace implementation
+} // namespace V1_0
+} // namespace dumpstate
+} // namespace hardware
+} // namespace android
diff --git a/guest/monitoring/dumpstate_ext/dumpstate_device.h b/guest/monitoring/dumpstate_ext/dumpstate_device.h
new file mode 100644
index 0000000..581a96b
--- /dev/null
+++ b/guest/monitoring/dumpstate_ext/dumpstate_device.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#pragma once
+
+#include <android/hardware/dumpstate/1.0/IDumpstateDevice.h>
+#include <hidl/MQDescriptor.h>
+#include <hidl/Status.h>
+
+namespace android {
+namespace hardware {
+namespace dumpstate {
+namespace V1_0 {
+namespace implementation {
+
+using ::android::hardware::Return;
+using ::android::hardware::Void;
+using ::android::hardware::dumpstate::V1_0::IDumpstateDevice;
+using ::android::hardware::hidl_array;
+using ::android::hardware::hidl_handle;
+using ::android::hardware::hidl_string;
+using ::android::hardware::hidl_vec;
+using ::android::sp;
+
+struct DumpstateDevice : public IDumpstateDevice {
+ // Methods from ::android::hardware::dumpstate::V1_0::IDumpstateDevice follow.
+ Return<void> dumpstateBoard(const hidl_handle& h) override;
+};
+
+} // namespace implementation
+} // namespace V1_0
+} // namespace dumpstate
+} // namespace hardware
+} // namespace android
diff --git a/guest/monitoring/dumpstate_ext/service.cpp b/guest/monitoring/dumpstate_ext/service.cpp
new file mode 100644
index 0000000..67eebe5
--- /dev/null
+++ b/guest/monitoring/dumpstate_ext/service.cpp
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#include <hidl/HidlSupport.h>
+#include <hidl/HidlTransportSupport.h>
+
+#include "guest/monitoring/dumpstate_ext/dumpstate_device.h"
+
+using ::android::OK;
+using ::android::hardware::configureRpcThreadpool;
+using ::android::hardware::dumpstate::V1_0::IDumpstateDevice;
+using ::android::hardware::dumpstate::V1_0::implementation::DumpstateDevice;
+using ::android::hardware::joinRpcThreadpool;
+using ::android::sp;
+
+int main() {
+ sp<IDumpstateDevice> dumpstate = new DumpstateDevice;
+ // This method MUST be called before interacting with any HIDL interfaces.
+ configureRpcThreadpool(1, true);
+ if (dumpstate->registerAsService() != OK) {
+ ALOGE("Could not register service.");
+ return 1;
+ }
+ joinRpcThreadpool();
+}
diff --git a/guest/monitoring/vsoc_service/Android.mk b/guest/monitoring/vsoc_service/Android.mk
new file mode 100644
index 0000000..fb9f6cb
--- /dev/null
+++ b/guest/monitoring/vsoc_service/Android.mk
@@ -0,0 +1,26 @@
+# Copyright (C) 2016 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+LOCAL_CERTIFICATE := platform
+LOCAL_MODULE_TAGS := optional
+LOCAL_SRC_FILES := $(call all-java-files-under, java)
+LOCAL_PACKAGE_NAME := VSoCService
+LOCAL_PROGUARD_FLAGS := -include build/core/proguard.flags
+LOCAL_PROGUARD_FLAG_FILES := proguard.flags
+LOCAL_VENDOR_MODULE := true
+
+include $(BUILD_PACKAGE)
diff --git a/guest/monitoring/vsoc_service/AndroidManifest.xml b/guest/monitoring/vsoc_service/AndroidManifest.xml
new file mode 100644
index 0000000..85abd09
--- /dev/null
+++ b/guest/monitoring/vsoc_service/AndroidManifest.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.google.gce.gceservice"
+ android:sharedUserId="android.uid.system">
+
+ <uses-sdk android:minSdkVersion="5" />
+
+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+ <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
+ <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.WRITE_SETTINGS" />
+ <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+ <uses-permission android:name="android.permission.BLUETOOTH" />
+ <uses-permission android:name="android.permission.OVERRIDE_WIFI_CONFIG" />
+ <uses-permission android:name="android.permission.DUMP" />
+
+ <application
+ android:label="GceService"
+ android:allowBackup="false">
+
+ <receiver android:name=".GceBroadcastReceiver">
+ <intent-filter android:priority="1000">
+ <!--
+ Do not register for other events here.
+ Use GceService.registerBroadcastReceivers() instead.
+ -->
+ <action android:name="android.intent.action.BOOT_COMPLETED" />
+ </intent-filter>
+ </receiver>
+
+ <service android:name=".GceService">
+ <intent-filter>
+ <action android:name="com.android.google.gce.gceservice.CONFIGURE" />
+ <action android:name="com.android.google.gce.gceservice.CONNECTIVITY_CHANGE" />
+ <action android:name="com.android.google.gce.gceservice.BLUETOOTH_CHANGED" />
+ </intent-filter>
+ </service>
+ </application>
+</manifest>
diff --git a/guest/monitoring/vsoc_service/java/com/android/google/gce/gceservice/BluetoothChecker.java b/guest/monitoring/vsoc_service/java/com/android/google/gce/gceservice/BluetoothChecker.java
new file mode 100644
index 0000000..23d4fec
--- /dev/null
+++ b/guest/monitoring/vsoc_service/java/com/android/google/gce/gceservice/BluetoothChecker.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+package com.android.google.gce.gceservice;
+
+import android.bluetooth.BluetoothAdapter;
+import android.util.Log;
+
+/*
+ * A job that checks for Bluetooth being enabled before reporting VIRTUAL_DEVICE_BOOT_COMPLETED. Our
+ * devices should always boot with bt enabled, it can be configured in
+ * gce_x86/overlay_<device>/frameworks/base/packages/SettingsProvider/res/values/defaults.xml
+ */
+public class BluetoothChecker extends JobBase {
+ private static final String LOG_TAG = "GceBluetoothChecker";
+ private final GceFuture<Boolean> mEnabled = new GceFuture<Boolean>("Bluetooth");
+
+
+ public BluetoothChecker() {
+ super(LOG_TAG);
+ }
+
+
+ @Override
+ public int execute() {
+ if (mEnabled.isDone()) {
+ return 0;
+ }
+ BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
+ if (bluetoothAdapter == null) {
+ Log.e(LOG_TAG, "No bluetooth adapter found");
+ mEnabled.set(new Exception("No bluetooth adapter found"));
+ } else {
+ if (bluetoothAdapter.isEnabled()) {
+ Log.i(LOG_TAG, "Bluetooth enabled with name: " + bluetoothAdapter.getName());
+ mEnabled.set(true);
+ } else {
+ Log.i(LOG_TAG, "Bluetooth disabled with name: " + bluetoothAdapter.getName());
+ }
+ }
+ return 0;
+ }
+
+
+ @Override
+ public void onDependencyFailed(Exception e) {
+ mEnabled.set(e);
+ }
+
+
+ public GceFuture<Boolean> getEnabled() {
+ return mEnabled;
+ }
+}
diff --git a/guest/monitoring/vsoc_service/java/com/android/google/gce/gceservice/BootReporter.java b/guest/monitoring/vsoc_service/java/com/android/google/gce/gceservice/BootReporter.java
new file mode 100644
index 0000000..0a1eecc
--- /dev/null
+++ b/guest/monitoring/vsoc_service/java/com/android/google/gce/gceservice/BootReporter.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+package com.android.google.gce.gceservice;
+
+import android.os.Handler;
+import android.util.Log;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.List;
+
+/**
+ * Report boot status to console.
+ *
+ * This class sends messages to kernel log (and serial console) directly by
+ * writing to /dev/kmsg.
+ */
+public class BootReporter extends JobBase {
+ private static final String LOG_TAG = "GceBootReporter";
+ private static final int KLOG_NOTICE = 5;
+ private static final String KLOG_OUTPUT = "/dev/kmsg";
+ private static final String KLOG_FORMAT = "<%d>%s: %s\n";
+ private static final String VIRTUAL_DEVICE_BOOT_STARTED = "VIRTUAL_DEVICE_BOOT_STARTED";
+ private static final String VIRTUAL_DEVICE_BOOT_PENDING = "VIRTUAL_DEVICE_BOOT_PENDING";
+ private static final String VIRTUAL_DEVICE_BOOT_COMPLETED = "VIRTUAL_DEVICE_BOOT_COMPLETED";
+ private static final String VIRTUAL_DEVICE_BOOT_FAILED = "VIRTUAL_DEVICE_BOOT_FAILED";
+ private FileOutputStream mKmsgStream = null;
+ private PrintWriter mKmsgWriter = null;
+ private List<String> mMessageList = new ArrayList<String>();
+
+
+ /** Constructor. */
+ public BootReporter() {
+ super(LOG_TAG);
+
+ try {
+ mKmsgStream = new FileOutputStream(KLOG_OUTPUT);
+ mKmsgWriter = new PrintWriter(mKmsgStream);
+ } catch (IOException e) {
+ Log.e(LOG_TAG, "Could not open output stream.", e);
+ }
+ }
+
+
+ /** Report boot failure.
+ *
+ * Send message to kernel log and serial console explaining boot failure.
+ */
+ @Override
+ public void onDependencyFailed(Exception e) {
+ reportMessage(String.format("%s: %s", VIRTUAL_DEVICE_BOOT_FAILED, e.getMessage()));
+ }
+
+
+ /** Report straggling jobs.
+ *
+ * Reports boot pending, if any of the parent jobs is still awaiting completion
+ * and reschedules itself for re-execution.
+ *
+ * If all jobs have completed, reports boot completed and stops.
+ */
+ @Override
+ public void onDependencyStraggling(ArrayList<GceFuture<?>> deps) {
+ reportMessage(String.format("%s: %s", VIRTUAL_DEVICE_BOOT_PENDING,
+ GceFuture.toString(deps)));
+ }
+
+
+ /** Report successful boot completion.
+ *
+ * Issue message to serial port confirming successful boot completion and
+ * custom boot completion message, if specified by the user prior to reboot.
+ */
+ @Override
+ public int execute() {
+ // We suspect that something is throttling our messages and preventing
+ // the following message from being logged to bugreport.
+ // The log is present in logcat log (that we collect independently), yet
+ // occasionally most of the GCEService logs never make it to show up on
+ // bug report.
+ // This may or may not prove to be effective. We need to monitor bugreports
+ // for VIRTUAL_DEVICE_BOOT_COMPLETED messages are being dropped.
+ //
+ // Number chosen at random - yet guaranteed to be prime.
+ try {
+ Thread.sleep(937);
+ } catch (InterruptedException e) {}
+
+ reportMessage(VIRTUAL_DEVICE_BOOT_COMPLETED);
+ return 0;
+ }
+
+
+ private void reportMessage(String message) {
+ Log.i(LOG_TAG, message);
+ mKmsgWriter.printf(KLOG_FORMAT, KLOG_NOTICE, LOG_TAG, message);
+ mKmsgWriter.flush();
+ DateFormat df = new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
+ String date = df.format(Calendar.getInstance().getTime());
+ mMessageList.add("[" + date + "] " + message);
+ }
+
+
+ public void reportBootStarted() {
+ reportMessage(VIRTUAL_DEVICE_BOOT_STARTED);
+ }
+
+ /** Get the list of reported messages so far.
+ */
+ public List<String> getMessageList() {
+ return mMessageList;
+ }
+}
diff --git a/guest/monitoring/vsoc_service/java/com/android/google/gce/gceservice/ConnectivityChecker.java b/guest/monitoring/vsoc_service/java/com/android/google/gce/gceservice/ConnectivityChecker.java
new file mode 100644
index 0000000..dfa23d6
--- /dev/null
+++ b/guest/monitoring/vsoc_service/java/com/android/google/gce/gceservice/ConnectivityChecker.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+package com.android.google.gce.gceservice;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.util.Log;
+
+import java.util.ArrayList;
+
+public class ConnectivityChecker extends JobBase {
+ private static final String LOG_TAG = "GceConnChecker";
+
+ private final Context mContext;
+ private final GceFuture<Boolean> mConnected = new GceFuture<Boolean>("Connectivity");
+
+
+ public ConnectivityChecker(Context context) {
+ super(LOG_TAG);
+ mContext = context;
+ }
+
+
+ @Override
+ public int execute() {
+ ConnectivityManager connManager = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
+ if (mConnected.isDone()) return 0;
+
+ NetworkInfo[] networks = connManager.getAllNetworkInfo();
+ ArrayList<String> connected = new ArrayList<String>();
+ ArrayList<String> disconnected = new ArrayList<String>();
+
+ for (NetworkInfo network : networks) {
+ if (network.isConnected()) {
+ connected.add(network.getTypeName());
+ mConnected.set(true);
+ break;
+ } else {
+ disconnected.add(network.getTypeName());
+ }
+ }
+
+ Log.i(LOG_TAG, "Connectivity status: connected:" + connected + ", disconnected:" + disconnected);
+
+ return 0;
+ }
+
+
+ @Override
+ public void onDependencyFailed(Exception e) {
+ mConnected.set(e);
+ }
+
+
+ public GceFuture<Boolean> getConnected() {
+ return mConnected;
+ }
+}
diff --git a/guest/monitoring/vsoc_service/java/com/android/google/gce/gceservice/GceBroadcastReceiver.java b/guest/monitoring/vsoc_service/java/com/android/google/gce/gceservice/GceBroadcastReceiver.java
new file mode 100644
index 0000000..754eb8f
--- /dev/null
+++ b/guest/monitoring/vsoc_service/java/com/android/google/gce/gceservice/GceBroadcastReceiver.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+package com.android.google.gce.gceservice;
+
+import android.bluetooth.BluetoothAdapter;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.util.Log;
+
+import com.android.google.gce.gceservice.GceService;
+
+public class GceBroadcastReceiver extends BroadcastReceiver {
+ private static final String LOG_TAG = "GceBroadcastReceiver";
+
+
+ private void reportIntent(Context context, String intentType) {
+ Intent intent = new Intent(context, GceService.class);
+ intent.setAction(intentType);
+ context.startService(intent);
+ }
+
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent != null) {
+ final String action = intent.getAction();
+ Log.i(LOG_TAG, "Received broadcast: " + action);
+
+ if (action.equals(Intent.ACTION_BOOT_COMPLETED)) {
+ reportIntent(context, GceService.INTENT_ACTION_CONFIGURE);
+ } else if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
+ reportIntent(context, GceService.INTENT_ACTION_NETWORK_CHANGED);
+ } else if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
+ reportIntent(context, GceService.INTENT_ACTION_BLUETOOTH_CHANGED);
+ }
+ }
+ }
+}
diff --git a/guest/monitoring/vsoc_service/java/com/android/google/gce/gceservice/GceFuture.java b/guest/monitoring/vsoc_service/java/com/android/google/gce/gceservice/GceFuture.java
new file mode 100644
index 0000000..ea41409
--- /dev/null
+++ b/guest/monitoring/vsoc_service/java/com/android/google/gce/gceservice/GceFuture.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+package com.android.google.gce.gceservice;
+
+import android.util.Log;
+import java.util.ArrayList;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.TimeUnit;
+
+public class GceFuture<T> implements Future<T> {
+ private static final String LOG_TAG = "GceFuture";
+ private boolean mDone = false;
+ private Exception mException = null;
+ private T mResult = null;
+ private final String mName;
+
+
+ public GceFuture(String name) {
+ mName = name;
+ }
+
+
+ public String getName() {
+ return mName;
+ }
+
+
+ public void set(T value) {
+ synchronized(this) {
+ if (mDone) {
+ Exception e = new Exception();
+ Log.e(LOG_TAG, mName + ": Multiple return values from a future object.", e);
+ return;
+ }
+
+ mResult = value;
+ mDone = true;
+ notifyAll();
+ }
+ }
+
+
+ public void set(Exception e) {
+ synchronized(this) {
+ if (mDone) {
+ Log.w(LOG_TAG, mName + ": Discarding execution exception -- job done.", e);
+ return;
+ }
+
+ Log.w(LOG_TAG, mName + ": Could not complete job: " + e.getMessage(), e);
+ mException = e;
+ mDone = true;
+ notifyAll();
+ }
+ }
+
+
+ @Override
+ public boolean cancel(boolean canInterrupt) {
+ // We do not support interrupting jobs on purpose:
+ // this offers us little benefit (stripping maybe a second or two), at the expense
+ // of killing something that may cascade, like BroadcastReceiver.
+ synchronized(this) {
+ if (mDone) return false;
+ set(new CancellationException("cancelled"));
+ }
+
+ return true;
+ }
+
+
+ @Override
+ public boolean isCancelled() {
+ synchronized(this) {
+ return (mException != null) && (mException instanceof CancellationException);
+ }
+ }
+
+
+ @Override
+ public boolean isDone() {
+ synchronized(this) {
+ return mDone;
+ }
+ }
+
+
+ @Override
+ public T get() throws CancellationException, ExecutionException, InterruptedException {
+ try {
+ return get(-1, TimeUnit.SECONDS);
+ } catch (TimeoutException e) {
+ // This is a really interesting case to consider.
+ // Fatal error to add to the drama.
+ Log.wtf(LOG_TAG, mName + ": Unexpected condition: Infinite wait timed out.");
+ return null;
+ }
+ }
+
+
+ @Override
+ public T get(long timeout, TimeUnit units)
+ throws CancellationException, ExecutionException, InterruptedException, TimeoutException {
+ waitDone(timeout, units);
+
+ if (mException != null) {
+ if (mException instanceof CancellationException)
+ throw (CancellationException)mException;
+ throw new ExecutionException(mException);
+ }
+
+ return mResult;
+ }
+
+
+ /** Wait for final result.
+ *
+ * Result is considered available, when:
+ * - provider returned value,
+ * - this object was cancelled,
+ * - provider threw an exception.
+ */
+ private void waitDone(long timeout, TimeUnit units)
+ throws InterruptedException, TimeoutException {
+ while (!mDone) {
+ synchronized(this) {
+ if (timeout >= 0) {
+ this.wait(units.toMillis(timeout));
+ if (!mDone) throw new InterruptedException();
+ } else {
+ this.wait();
+ }
+ }
+ }
+ }
+
+
+ /** Convert list of GceFuture objects to string representation.
+ */
+ public static String toString(ArrayList<GceFuture<?>> futures) {
+ StringBuilder b = new StringBuilder();
+ for (GceFuture<?> dep : futures) {
+ if (b.length() > 0) b.append(", ");
+ b.append(dep.getName());
+ }
+
+ return b.toString();
+ }
+}
diff --git a/guest/monitoring/vsoc_service/java/com/android/google/gce/gceservice/GceService.java b/guest/monitoring/vsoc_service/java/com/android/google/gce/gceservice/GceService.java
new file mode 100644
index 0000000..c6c2457
--- /dev/null
+++ b/guest/monitoring/vsoc_service/java/com/android/google/gce/gceservice/GceService.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+package com.android.google.gce.gceservice;
+
+import android.app.Service;
+import android.bluetooth.BluetoothAdapter;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.ConnectivityManager;
+import android.util.Log;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.ServiceManager;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.List;
+
+/**
+ * Service is started by the BootCompletedReceiver at the end of Android Boot process.
+ * Responsible for final configuration changes and emitting final BOOT_COMPLETED message.
+ */
+public class GceService extends Service {
+ private static final String LOG_TAG = "GceService";
+ /* Intent sent by the BootCompletedReceiver upon receiving ACTION_BOOT_COMPLETED broadcast. */
+ public static final String INTENT_ACTION_CONFIGURE = "com.android.google.gce.gceservice.CONFIGURE";
+ public static final String INTENT_ACTION_NETWORK_CHANGED = "com.android.google.gce.gceservice.NETWORK_CHANGED";
+ public static final String INTENT_ACTION_BLUETOOTH_CHANGED = "com.android.google.gce.gceservice.BLUETOOTH_CHANGED";
+ private static final int NETWORK_OR_BOOT_TIMEOUT = 30;
+
+ private final JobExecutor mExecutor = new JobExecutor();
+ private final ConnectivityChecker mConnChecker = new ConnectivityChecker(this);
+ private final LocationServicesManager mLocationServices = new LocationServicesManager(this);
+ private final PackageVerifierManager mPackageVerifier = new PackageVerifierManager(this);
+ private final PackageVerificationConsentEnforcer mConsentEnforcer = new PackageVerificationConsentEnforcer(this);
+ private final BootReporter mBootReporter = new BootReporter();
+ private final GceBroadcastReceiver mBroadcastReceiver = new GceBroadcastReceiver();
+ private final BluetoothChecker mBluetoothChecker = new BluetoothChecker();
+ private final TombstoneChecker mTombstoneChecker = new TombstoneChecker();
+
+ private GceWifiManager mWifiManager = null;
+ private String mMostRecentAction = null;
+ private BinderService mBinderService;
+
+
+ public GceService() {}
+
+
+ @Override
+ public void onCreate() {
+ try {
+ super.onCreate();
+ mBootReporter.reportBootStarted();
+ registerBroadcastReceivers();
+
+ mWifiManager = new GceWifiManager(this, mExecutor);
+
+ mExecutor.schedule(mLocationServices);
+ mExecutor.schedule(mPackageVerifier);
+ mExecutor.schedule(mConsentEnforcer);
+ mExecutor.schedule(mWifiManager);
+ mExecutor.schedule(mBluetoothChecker);
+ // TODO(ender): TombstoneChecker is disabled, because we no longer have the code that
+ // produces /ts_snap.txt file. We need to rethink how TombstoneChecker should work.
+ // mExecutor.schedule(mTombstoneChecker);
+
+ mExecutor.schedule(mBootReporter,
+ mLocationServices.getLocationServicesReady(),
+ mPackageVerifier.getPackageVerifierReady(),
+ mConnChecker.getConnected(),
+ mWifiManager.getInitialWifiStateChangeReady(),
+ mBluetoothChecker.getEnabled()
+ // mTombstoneChecker.getTombstoneResult()
+ );
+ } catch (Exception e) {
+ Log.e(LOG_TAG, "Exception caught", e);
+ }
+ }
+
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+
+
+ /** Register broadcast listeners.
+ *
+ * Certain intents can no longer be used to start a service or activity, but
+ * can still be registered for, such as CONNECTIVITY_ACTION (Android N).
+ */
+ private void registerBroadcastReceivers() {
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
+ filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
+ this.registerReceiver(mBroadcastReceiver, filter);
+ }
+
+
+ /** StartService entry point.
+ */
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ if (intent != null) {
+ mMostRecentAction = intent.getAction();
+ } else {
+ Log.w(LOG_TAG, "Previous execution failed. Retrying.");
+ }
+
+ if (mMostRecentAction == null) {
+ Log.e(LOG_TAG, "Missing intent action.");
+ }
+
+ if (INTENT_ACTION_CONFIGURE.equals(mMostRecentAction)) {
+ mExecutor.schedule(mConnChecker);
+ } else if (INTENT_ACTION_NETWORK_CHANGED.equals(mMostRecentAction)) {
+ mExecutor.schedule(mConnChecker);
+ } else if (INTENT_ACTION_BLUETOOTH_CHANGED.equals(mMostRecentAction)) {
+ mExecutor.schedule(mBluetoothChecker);
+ }
+
+ mBinderService = new BinderService();
+ ServiceManager.addService("gce", mBinderService, false);
+
+ /* If anything goes wrong, make sure we receive intent again. */
+ return Service.START_STICKY;
+ }
+
+ /** Dump the virtual device state
+ */
+ private void dumpInternal(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("Boot reporter:");
+ List<String> messageList = mBootReporter.getMessageList();
+ for (int i = 0; i < messageList.size(); i++) {
+ pw.println(" " + messageList.get(i));
+ }
+ pw.println("");
+ pw.println("Current system service state:");
+ pw.println(" Location service ready: "
+ + mLocationServices.getLocationServicesReady().isDone());
+ pw.println(" Package verifier ready: "
+ + mPackageVerifier.getPackageVerifierReady().isDone());
+ pw.println(" Network connected: " + mConnChecker.getConnected().isDone());
+ pw.println(" WiFi configured: " + mWifiManager.getInitialWifiStateChangeReady().isDone());
+ pw.println(" Bluetooth enabled: " + mBluetoothChecker.getEnabled().isDone());
+ pw.println(" Tombstone dropped (on boot): "
+ + !mTombstoneChecker.getTombstoneResult().isDone());
+ pw.println("");
+ }
+
+ private final class BinderService extends Binder {
+ @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ dumpInternal(fd, pw, args);
+ }
+ }
+}
diff --git a/guest/monitoring/vsoc_service/java/com/android/google/gce/gceservice/GceWifiManager.java b/guest/monitoring/vsoc_service/java/com/android/google/gce/gceservice/GceWifiManager.java
new file mode 100644
index 0000000..da1b3cf
--- /dev/null
+++ b/guest/monitoring/vsoc_service/java/com/android/google/gce/gceservice/GceWifiManager.java
@@ -0,0 +1,283 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+package com.android.google.gce.gceservice;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.net.wifi.SupplicantState;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.os.Handler;
+import android.util.Log;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Manage WIFI state.
+ */
+public class GceWifiManager extends JobBase {
+ private static final String LOG_TAG = "GceWifiManager";
+ /* Timeout after which another attempt to re-connect wifi will be made. */
+ private static final int WIFI_RECONNECTION_TIMEOUT_S = 3;
+ /* Maximum number of retries before giving up and marking WIFI as inoperable. */
+ private static final int WIFI_RECONNECTION_MAX_ATTEMPTS = 10;
+
+ /** Describes possible WIFI states.
+ * WifiState is:
+ * - UNKNOWN only at the initialization time, replaced with state from
+ * from Android's WifiManager.
+ * - ENABLED when WIFI is connected and operational,
+ * - DISABLED when WIFI is turned off,
+ * - FAILED if GceWifiManager was unable to configure WIFI.
+ */
+ public enum WifiState {
+ DISABLED,
+ ENABLED;
+ };
+
+ private final JobExecutor mJobExecutor;
+ private final Context mContext;
+ private final WifiManager mWifiManager;
+ private final ConnectivityManager mConnManager;
+
+ private ConfigureWifi mConfigureWifiJob = new ConfigureWifi();
+ private SetWifiState mSetInitialWifiStateJob = new SetWifiState();
+
+
+ /** Constructor.
+ */
+ public GceWifiManager(Context context, JobExecutor executor) {
+ super(LOG_TAG);
+
+ mContext = context;
+ mWifiManager = (WifiManager)context.getSystemService(Context.WIFI_SERVICE);
+ mConnManager = (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);
+ mJobExecutor = executor;
+ }
+
+
+ private boolean isMobileNetworkAvailable() {
+ for (NetworkInfo network : mConnManager.getAllNetworkInfo()) {
+ if (network.getType() == ConnectivityManager.TYPE_MOBILE) return true;
+ }
+ return false;
+ }
+
+
+ private WifiState getExpectedWifiState() {
+ // TODO(ender): we will probably want to define this differently once virtual WIFI is
+ // available again.
+ return WifiState.DISABLED;
+ }
+
+
+ /** Executed during initial configuration.
+ */
+ @Override
+ public synchronized int execute() {
+ WifiState initialState = getExpectedWifiState();
+ mSetInitialWifiStateJob.setState(initialState);
+
+ // Only configure wifi if expected state is ENABLED.
+ // Configuring wifi *requires* wpa_supplicant to be up.
+ // This means that in order to configure wifi, we have to enable it first.
+ if (initialState == WifiState.ENABLED) {
+ mJobExecutor.schedule(mConfigureWifiJob);
+ mJobExecutor.schedule(mSetInitialWifiStateJob, mConfigureWifiJob.getWifiConfigured());
+ } else {
+ // If initial state is DISABLED, there's no need to wait for Wifi configuration to
+ // complete. Just shut it off.
+ mJobExecutor.schedule(mSetInitialWifiStateJob);
+ }
+ return 0;
+ }
+
+
+ @Override
+ public void onDependencyFailed(Exception e) {
+ Log.e(LOG_TAG, "Initial WIFI configuration failed due to dependency.", e);
+ getInitialWifiStateChangeReady().set(e);
+ }
+
+
+ public GceFuture<Boolean> getInitialWifiStateChangeReady() {
+ return mSetInitialWifiStateJob.getWifiReady();
+ }
+
+
+ /* Configure WIFI network stack.
+ *
+ * Adds network configuration that covers AndroidWifi virtual hotspot.
+ */
+ private class ConfigureWifi extends JobBase {
+ private final GceFuture<Boolean> mWifiConfigured =
+ new GceFuture<Boolean>("WIFI Configured");
+ private boolean mReportedWaitingForSupplicant = false;
+
+
+ public ConfigureWifi() {
+ super(LOG_TAG);
+ }
+
+
+ @Override
+ public int execute() {
+ if (mWifiConfigured.isDone()) return 0;
+
+ if (!mWifiManager.pingSupplicant()) {
+ if (!mWifiManager.isWifiEnabled()) {
+ mWifiManager.setWifiEnabled(true);
+ }
+ if (!mReportedWaitingForSupplicant) {
+ Log.i(LOG_TAG, "Supplicant not ready.");
+ mReportedWaitingForSupplicant = true;
+ }
+ return WIFI_RECONNECTION_TIMEOUT_S;
+ }
+
+ WifiConfiguration conf = new WifiConfiguration();
+ conf.SSID = "\"AndroidWifi\"";
+ conf.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
+ int network_id = mWifiManager.addNetwork(conf);
+ if (network_id < 0) {
+ Log.e(LOG_TAG, "Could not update wifi network.");
+ mWifiConfigured.set(new Exception("Could not add WIFI network"));
+ } else {
+ mWifiManager.enableNetwork(network_id, false);
+ mWifiConfigured.set(true);
+ }
+ return 0;
+ }
+
+
+ @Override
+ public void onDependencyFailed(Exception e) {
+ Log.e(LOG_TAG, "Could not configure WIFI.", e);
+ mWifiConfigured.set(e);
+ }
+
+
+ public GceFuture<Boolean> getWifiConfigured() {
+ return mWifiConfigured;
+ }
+ }
+
+
+ /* Modifies Wifi state:
+ * - if wifi disable requested (state == false), simply turns off wifi.
+ * - if wifi enable requested (state == true), turns on wifi and arms the
+ * connection timeout (see startWifiReconnectionTimeout).
+ */
+ private class SetWifiState extends JobBase {
+ private final GceFuture<Boolean> mWifiReady =
+ new GceFuture<Boolean>("WIFI Ready");
+ private WifiState mDesiredState = WifiState.DISABLED;
+ private int mWifiStateChangeAttempt = 0;
+ private boolean mReportedWifiNotConnected = false;
+
+
+ public SetWifiState() {
+ super(LOG_TAG);
+ }
+
+
+ public void setState(WifiState state) {
+ mDesiredState = state;
+ }
+
+
+ public synchronized void cancel() {
+ if (!mWifiReady.isDone()) {
+ mWifiReady.cancel(false);
+ }
+ }
+
+
+ @Override
+ public synchronized int execute() {
+ WifiState currentState = mWifiManager.isWifiEnabled() ?
+ WifiState.ENABLED : WifiState.DISABLED;
+
+ // Could be cancelled or exception.
+ if (mWifiReady.isDone()) return 0;
+
+ if (mWifiStateChangeAttempt >= WIFI_RECONNECTION_MAX_ATTEMPTS) {
+ mWifiReady.set(new Exception(
+ String.format("Unable to change wifi state after %d attempts.",
+ WIFI_RECONNECTION_MAX_ATTEMPTS)));
+ return 0;
+ }
+
+ if (currentState == mDesiredState) {
+ switch (currentState) {
+ case ENABLED:
+ // Wifi is enabled, but probably not yet connected. Check.
+ WifiInfo info = mWifiManager.getConnectionInfo();
+ if (info.getSupplicantState() != SupplicantState.COMPLETED) {
+ if (!mReportedWifiNotConnected) {
+ Log.w(LOG_TAG, "Wifi not yet connected.");
+ mReportedWifiNotConnected = true;
+ }
+ } else {
+ Log.i(LOG_TAG, "Wifi connected.");
+ mWifiReady.set(true);
+ }
+ break;
+
+ case DISABLED:
+ // There's nothing extra to check for disable wifi.
+ mWifiReady.set(true);
+ break;
+ }
+
+ if (mWifiReady.isDone()) {
+ return 0;
+ }
+ }
+
+ // At this point we know that:
+ // - current state is different that desired state, or
+ // - current state is enabled, but wifi is not yet connected.
+ ++mWifiStateChangeAttempt;
+
+ switch (mDesiredState) {
+ case DISABLED:
+ mWifiManager.setWifiEnabled(false);
+ break;
+
+ case ENABLED:
+ mWifiManager.setWifiEnabled(true);
+ mWifiManager.reconnect();
+ break;
+ }
+ return WIFI_RECONNECTION_TIMEOUT_S;
+ }
+
+
+ @Override
+ public void onDependencyFailed(Exception e) {
+ Log.e(LOG_TAG, "Wifi state change failed due to failing dependency.", e);
+ mWifiReady.set(e);
+ }
+
+
+ public GceFuture<Boolean> getWifiReady() {
+ return mWifiReady;
+ }
+ }
+}
diff --git a/guest/monitoring/vsoc_service/java/com/android/google/gce/gceservice/JobBase.java b/guest/monitoring/vsoc_service/java/com/android/google/gce/gceservice/JobBase.java
new file mode 100644
index 0000000..aceb70f
--- /dev/null
+++ b/guest/monitoring/vsoc_service/java/com/android/google/gce/gceservice/JobBase.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+package com.android.google.gce.gceservice;
+
+import android.util.Log;
+import java.util.ArrayList;
+
+public abstract class JobBase {
+ private final String mTag;
+
+ JobBase(String tag) {
+ mTag = tag;
+ }
+
+
+ /** Invoked, when job could not be started due to dependency failure.
+ *
+ * Supplied exception describes reason of failure.
+ */
+ public abstract void onDependencyFailed(Exception exception);
+
+
+ /** Invoked, when one or more tasks have taken substantial time to complete.
+ *
+ * This is not a direct indication of an error, but merely an information
+ * about what stops current job from being executed.
+ *
+ * @param stragglingDependencies list of dependencies that have not completed
+ * in last 10 seconds.
+ */
+ public void onDependencyStraggling(ArrayList<GceFuture<?>> stragglingDependencies) {
+ Log.i(mTag, "Waiting for: " + GceFuture.toString(stragglingDependencies));
+ }
+
+
+ /** Invoked, when all dependencies are ready and job is clear to commence.
+ *
+ * Returns number of second before re-execution, or 0, if job is done.
+ */
+ public abstract int execute();
+}
diff --git a/guest/monitoring/vsoc_service/java/com/android/google/gce/gceservice/JobExecutor.java b/guest/monitoring/vsoc_service/java/com/android/google/gce/gceservice/JobExecutor.java
new file mode 100644
index 0000000..9cb3225
--- /dev/null
+++ b/guest/monitoring/vsoc_service/java/com/android/google/gce/gceservice/JobExecutor.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+package com.android.google.gce.gceservice;
+
+import android.util.Log;
+import com.android.google.gce.gceservice.JobBase;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.TimeUnit;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Set;
+import java.util.HashSet;
+
+public class JobExecutor {
+ private static final int THREAD_POOL_SIZE = 8;
+ private static final String LOG_TAG = "GceExecutor";
+ private static final int ITERATION_PERIOD_MS = 10 * 1000;
+ private final ScheduledExecutorService mExecutor;
+ private final Set<String> mStartedJobs;
+
+ public JobExecutor() {
+ mExecutor = Executors.newScheduledThreadPool(THREAD_POOL_SIZE);
+ mStartedJobs = new HashSet<String>();
+ }
+
+ /** Schedule job for (periodic) execution.
+ *
+ * First execution is performed at the earliest possibility.
+ * Execution stops when a call to execute() returns a nonpositive number.
+ *
+ * Note: iteration time is |delaySeconds| + total time needed to execute job.
+ */
+ public void schedule(final JobBase job, final GceFuture<?>... futures) {
+ mExecutor.schedule(new Runnable() {
+ private boolean mDependenciesReady = false;
+ private ArrayList<GceFuture<?>> mFutures =
+ new ArrayList<GceFuture<?>>(Arrays.asList(futures));
+
+ public void run() {
+ if (!mDependenciesReady) {
+ while (!mFutures.isEmpty()) {
+ ArrayList<GceFuture<?>> stragglers = new ArrayList<GceFuture<?>>();
+ long endTime = System.currentTimeMillis() + ITERATION_PERIOD_MS;
+ for (GceFuture<?> future : mFutures) {
+ try {
+ // Wait for at most ITERATION_PERIOD_MS. Check all futures,
+ // collect only those that still failed to complete.
+ future.get(
+ Math.max(0, endTime - System.currentTimeMillis()),
+ TimeUnit.MILLISECONDS);
+ } catch (TimeoutException e) {
+ stragglers.add(future);
+ } catch (InterruptedException e) {
+ // In theory we should re-try this one.
+ // In practice this hardly ever happens, so let's just
+ // give it another go in the second loop.
+ stragglers.add(future);
+ } catch (Exception e) {
+ Log.e(LOG_TAG, String.format(
+ "Could not start job %s.", job.getClass().getName()),
+ e);
+ job.onDependencyFailed(e);
+ return;
+ }
+ }
+
+ mFutures = stragglers;
+ if (!mFutures.isEmpty()) {
+ job.onDependencyStraggling(mFutures);
+ }
+ }
+ mDependenciesReady = true;
+ }
+
+ try {
+ String jobName = job.getClass().getName();
+ if (!mStartedJobs.contains(jobName)) {
+ mStartedJobs.add(jobName);
+ }
+ int delaySeconds = job.execute();
+ if (delaySeconds > 0) {
+ mExecutor.schedule(this, delaySeconds, TimeUnit.SECONDS);
+ } else {
+ mStartedJobs.remove(jobName);
+ }
+ } catch (Exception e) {
+ Log.e(LOG_TAG, String.format("Job %s threw an exception and will not be re-scheduled.",
+ job.getClass().getName()), e);
+ }
+ }
+ }, 0, TimeUnit.SECONDS);
+ }
+}
diff --git a/guest/monitoring/vsoc_service/java/com/android/google/gce/gceservice/LocationServicesManager.java b/guest/monitoring/vsoc_service/java/com/android/google/gce/gceservice/LocationServicesManager.java
new file mode 100644
index 0000000..f9ad388
--- /dev/null
+++ b/guest/monitoring/vsoc_service/java/com/android/google/gce/gceservice/LocationServicesManager.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+package com.android.google.gce.gceservice;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Build;
+import android.util.Log;
+import com.android.google.gce.gceservice.GceFuture;
+import com.android.google.gce.gceservice.JobBase;
+
+/**
+ * Configure Location Services on Android Jellybean.
+ * No action on more recent versions of Android.
+ */
+class LocationServicesManager extends JobBase {
+ private static final String LOG_TAG = "GceLocationServicesManager";
+ private static final String ACTION_LOCATION_SERVICES_CONSENT_INTENT =
+ "com.google.android.gsf.action.SET_USE_LOCATION_FOR_SERVICES";
+ private static final String EXTRA_LOCATION_SERVICES_CONSENT_DISABLE =
+ "disable";
+ private final Context mContext;
+ private final GceFuture<Boolean> mResult = new GceFuture<Boolean>("Location Services");
+
+
+ LocationServicesManager(Context context) {
+ super(LOG_TAG);
+ mContext = context;
+ }
+
+
+ public int execute() {
+ /* Check if we're running Jellybean.
+ * Sadly, we can't use version name Build.VERSION_CODES.JELLY_BEAN_MR2
+ * because MR1 and MR0 don't know this number.
+ */
+ if (Build.VERSION.SDK_INT <= 18) {
+ Intent intent = new Intent();
+ intent.setAction(ACTION_LOCATION_SERVICES_CONSENT_INTENT);
+ intent.setFlags(intent.getFlags() |
+ Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ intent.putExtra(EXTRA_LOCATION_SERVICES_CONSENT_DISABLE, false);
+ mContext.startActivity(intent);
+ }
+
+ mResult.set(true);
+ return 0;
+ }
+
+
+ public void onDependencyFailed(Exception e) {
+ Log.e(LOG_TAG, "Could not configure LocationServices.", e);
+ mResult.set(e);
+ }
+
+
+ public GceFuture<Boolean> getLocationServicesReady() {
+ return mResult;
+ }
+}
diff --git a/guest/monitoring/vsoc_service/java/com/android/google/gce/gceservice/PackageVerificationConsentEnforcer.java b/guest/monitoring/vsoc_service/java/com/android/google/gce/gceservice/PackageVerificationConsentEnforcer.java
new file mode 100644
index 0000000..e45d656
--- /dev/null
+++ b/guest/monitoring/vsoc_service/java/com/android/google/gce/gceservice/PackageVerificationConsentEnforcer.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+package com.android.google.gce.gceservice;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.provider.Settings;
+import android.provider.Settings.SettingNotFoundException;
+import android.util.Log;
+
+/**
+ * Forces pacakge verification to be off on N and N-MR1 by adjusting package_verifier_user_consent.
+ *
+ * This is needed because CVDs don't have a touch screen, and the consent
+ * dialog will block apk installs.
+ *
+ * Possible values for consent seem to be:
+ * -1 The user refused
+ * 0 Ask the user
+ * 1 The user accepted
+ *
+ * This code polls because Android may overwrite a non-zero value with a 0
+ * at some point after boot completes. However, this happens only on some
+ * boots, so it can't be a blocker for boot complete.
+ */
+class PackageVerificationConsentEnforcer extends JobBase {
+ private static final String LOG_TAG = "GcePVCR";
+ private static final String PACKAGE_VERIFIER_USER_CONSENT = "package_verifier_user_consent";
+ private final Context mContext;
+
+ // Chosen to avoid the possible values (see top comment).
+ private int mLastObservedValue = -2;
+
+
+ public PackageVerificationConsentEnforcer(Context context) {
+ super(LOG_TAG);
+ mContext = context;
+ }
+
+
+ public int execute() {
+ if (android.os.Build.VERSION.SDK_INT < 24) {
+ // Skip older android versions.
+ return 0;
+ }
+
+ try {
+ ContentResolver contentResolver = mContext.getContentResolver();
+ int value = Settings.Secure.getInt(contentResolver, PACKAGE_VERIFIER_USER_CONSENT);
+ if (value != mLastObservedValue) {
+ mLastObservedValue = value;
+ }
+
+ if (value == 0) {
+ Settings.Secure.putInt(mContext.getContentResolver(), PACKAGE_VERIFIER_USER_CONSENT, -1);
+ }
+ } catch (SettingNotFoundException e) {
+ }
+
+ return 1;
+ }
+
+
+ public void onDependencyFailed(Exception e) {
+ Log.e(LOG_TAG, "Could not start Consent Enforcer.", e);
+ }
+}
+
+
diff --git a/guest/monitoring/vsoc_service/java/com/android/google/gce/gceservice/PackageVerifierManager.java b/guest/monitoring/vsoc_service/java/com/android/google/gce/gceservice/PackageVerifierManager.java
new file mode 100644
index 0000000..e10845a
--- /dev/null
+++ b/guest/monitoring/vsoc_service/java/com/android/google/gce/gceservice/PackageVerifierManager.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+package com.android.google.gce.gceservice;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.provider.Settings;
+import android.provider.Settings.SettingNotFoundException;
+import android.util.Log;
+
+/**
+ * Disable package verifier.
+ */
+public class PackageVerifierManager extends JobBase {
+ private static final String LOG_TAG = "GcePackageVerifierManager";
+ private static final String SETTING_PACKAGE_VERIFIER_ENABLE = "verifier_verify_adb_installs";
+ private final Context mContext;
+ private final GceFuture<Boolean> mResult =
+ new GceFuture<Boolean>("Package Verifier");
+
+
+ PackageVerifierManager(Context context) {
+ super(LOG_TAG);
+ mContext = context;
+ }
+
+
+ private boolean getAndLogPackageVerifierState() {
+ int package_verifier_state = 1;
+ try {
+ ContentResolver contentResolver = mContext.getContentResolver();
+ package_verifier_state = Settings.Secure.getInt(contentResolver, SETTING_PACKAGE_VERIFIER_ENABLE);
+ } catch (SettingNotFoundException e) {
+ Log.w(LOG_TAG, "Could not read package verifier state. Assuming it's enabled.");
+ }
+
+ return package_verifier_state != 0;
+ }
+
+
+ public int execute() {
+ if (getAndLogPackageVerifierState()) {
+ Settings.Secure.putInt(mContext.getContentResolver(), SETTING_PACKAGE_VERIFIER_ENABLE, 0);
+ // One more call, just to log the state.
+ getAndLogPackageVerifierState();
+ }
+
+ mResult.set(true);
+ return 0;
+ }
+
+
+ public void onDependencyFailed(Exception e) {
+ Log.e(LOG_TAG, "Could not disable Package Verifier.", e);
+ mResult.set(e);
+ }
+
+
+ public GceFuture<Boolean> getPackageVerifierReady() {
+ return mResult;
+ }
+}
diff --git a/guest/monitoring/vsoc_service/java/com/android/google/gce/gceservice/TombstoneChecker.java b/guest/monitoring/vsoc_service/java/com/android/google/gce/gceservice/TombstoneChecker.java
new file mode 100644
index 0000000..dec0837
--- /dev/null
+++ b/guest/monitoring/vsoc_service/java/com/android/google/gce/gceservice/TombstoneChecker.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+package com.android.google.gce.gceservice;
+
+import android.util.Log;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Scanner;
+
+/** A job that checks for any new tombstones before reporting VIRTUAL_DEVICE_BOOT_COMPLETED.
+ *
+ */
+public class TombstoneChecker extends JobBase {
+ private static final String LOG_TAG = "GceTombstoneChecker";
+ private static final String sSnapshotDir = "/data/tombstones";
+ private static final String sSnapshotFile = "/ts_snap.txt";
+ private static final String sTsExceptionMessage = "GceTombstoneChecker internal error. ";
+ private static final String sTsFilePrefix = "tombstone";
+ private final GceFuture<Boolean> mPassed = new GceFuture<Boolean>("GceTombstoneChecker");
+ private ArrayList<Record> mPreBootRecords = new ArrayList<Record>();
+ private ArrayList<Record> mPostBootRecords = new ArrayList<Record>();
+
+ public TombstoneChecker() {
+ super(LOG_TAG);
+ }
+
+
+ @Override
+ public int execute() {
+ if (mPassed.isDone()) {
+ return 0;
+ }
+
+ try {
+ readPreBootSnapshot();
+ capturePostBootSnapshot();
+ if (seenNewTombstones()) {
+ Log.e(LOG_TAG, "Tombstones created during boot. ");
+ for (int i = 0; i < mPostBootRecords.size(); i++) {
+ Log.i(LOG_TAG, mPostBootRecords.get(i).getFileName());
+ }
+ mPassed.set(new Exception("Tombstones created. "));
+ } else {
+ mPassed.set(true);
+ }
+ } catch(Exception e) {
+ Log.e(LOG_TAG, sTsExceptionMessage + e);
+ mPassed.set(new Exception(sTsExceptionMessage, e));
+ }
+
+ return 0;
+ }
+
+ @Override
+ public void onDependencyFailed(Exception e) {
+ mPassed.set(e);
+ }
+
+ public GceFuture<Boolean> getTombstoneResult() {
+ return mPassed;
+ }
+
+ private void capturePostBootSnapshot() throws Exception {
+ File dir = new File(sSnapshotDir);
+ File[] files = dir.listFiles();
+
+ // In K & L, /data/tombstones directory is not created during boot. So
+ // dir.listFiles() can return null.
+ if (files == null) {
+ return;
+ }
+
+ for (int i = 0; i < files.length; i++) {
+ if (files[i].isFile() && files[i].getName().startsWith(sTsFilePrefix)) {
+ long ctime = files[i].lastModified() / 1000;
+ mPostBootRecords.add(new Record(files[i].getName(), ctime));
+ }
+ }
+ Collections.sort(mPostBootRecords);
+
+ return;
+ }
+
+ private void readPreBootSnapshot() throws Exception {
+ File file = new File(sSnapshotFile);
+ if (!file.isFile()) {
+ throw new FileNotFoundException(sSnapshotFile);
+ }
+
+ Scanner scanner = new Scanner(file);
+ while (scanner.hasNext()) {
+ String[] fields = scanner.nextLine().split(" ");
+ mPreBootRecords.add(new Record(fields[0], Long.parseLong(fields[1])));
+ }
+ Collections.sort(mPreBootRecords);
+
+ return;
+ }
+
+ private boolean seenNewTombstones() {
+ return !isEqual(mPreBootRecords, mPostBootRecords);
+ }
+
+ private boolean isEqual(ArrayList<Record> preBoot, ArrayList<Record> postBoot) {
+ postBoot.removeAll(preBoot);
+ if (postBoot.size() != 0) {
+ return false;
+ }
+
+ return true;
+ }
+
+ private class Record implements Comparable<Record> {
+ private String mFilename;
+ private long mCtime;
+
+ public Record(String filename, long ctime) {
+ this.mFilename = filename;
+ this.mCtime = ctime;
+ }
+
+ public String getFileName() {
+ return mFilename;
+ }
+
+ public int compareTo(Record r) {
+ if (this == r) {
+ return 0;
+ }
+
+ return (mFilename.compareTo(r.mFilename));
+ }
+
+ public boolean equals(Object o) {
+ if (o == null) {
+ return false;
+ }
+
+ if (this == o) {
+ return true;
+ }
+
+ Record r = (Record) o;
+ return (mFilename.equals(r.mFilename) && (mCtime == r.mCtime));
+ }
+
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(mFilename).append(" ").append(String.valueOf(mCtime));
+ return sb.toString();
+ }
+ }
+}
diff --git a/guest/monitoring/vsoc_service/proguard.flags b/guest/monitoring/vsoc_service/proguard.flags
new file mode 100644
index 0000000..c09a728
--- /dev/null
+++ b/guest/monitoring/vsoc_service/proguard.flags
@@ -0,0 +1,26 @@
+# Copyright (C) 2016 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.
+
+# Keep enough data for stack traces
+-renamesourcefileattribute SourceFile
+-keepattributes SourceFile,LineNumberTable,*Annotation*
+
+# Keep classes and methods that have the guava @VisibleForTesting annotation
+-keep @com.google.common.annotations.VisibleForTesting class *
+-keepclassmembers class * {
+ @com.google.common.annotations.VisibleForTesting *;
+}
+
+-keep public class * extends android.app.Service
+-keep public class * extends android.content.BroadcastReceiver
diff --git a/guest/vsoc/lib/Android.mk b/guest/vsoc/lib/Android.mk
new file mode 100644
index 0000000..5ea78d7
--- /dev/null
+++ b/guest/vsoc/lib/Android.mk
@@ -0,0 +1,39 @@
+# Copyright (C) 2017 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.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := vsoc_guest_region_e2e_test
+LOCAL_MODULE_TAGS := optional
+LOCAL_SRC_FILES := guest_region_e2e_test.cpp
+
+LOCAL_C_INCLUDES := \
+ device/google/cuttlefish_common \
+ device/google/cuttlefish_kernel
+
+LOCAL_CFLAGS += -DGTEST_OS_LINUX_ANDROID -DGTEST_HAS_STD_STRING
+
+LOCAL_STATIC_LIBRARIES := \
+ libgtest
+
+LOCAL_SHARED_LIBRARIES := \
+ vsoc_lib \
+ cuttlefish_auto_resources \
+ libcuttlefish_fs \
+ libbase
+
+LOCAL_MULTILIB := first
+LOCAL_VENDOR_MODULE := true
+include $(BUILD_EXECUTABLE)
diff --git a/guest/vsoc/lib/gralloc_region_view.cpp b/guest/vsoc/lib/gralloc_region_view.cpp
new file mode 100644
index 0000000..2ec1aba
--- /dev/null
+++ b/guest/vsoc/lib/gralloc_region_view.cpp
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include "guest/vsoc/lib/gralloc_region_view.h"
+
+#include <atomic>
+#include <common/vsoc/lib/lock_guard.h>
+#include <log/log.h>
+#include <sys/types.h>
+#include <uapi/vsoc_shm.h>
+
+using vsoc::gralloc::GrallocRegionView;
+using vsoc::layout::gralloc::BufferEntry;
+using vsoc::layout::gralloc::GrallocBufferLayout;
+using vsoc::layout::gralloc::GrallocManagerLayout;
+
+namespace {
+template <typename T>
+inline T gralloc_align(T val) {
+ return (val + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1);
+}
+
+// Use the thread id to identify the original creator of a buffer.
+inline uint32_t gralloc_owned_value() {
+ return gettid();
+}
+
+} // namespace
+
+GrallocRegionView::GrallocRegionView() {
+ // The construction in the singleton is thread safe, so we call Open here to
+ // make sure it opens thread safe too. The singleton will return null if the
+ // region failed to open.
+ Open();
+}
+
+bool GrallocRegionView::Open() {
+ if (is_open_) {
+ return true;
+ }
+ if (!vsoc::ManagerRegionView<GrallocManagerLayout>::Open()) {
+ return false;
+ }
+ std::shared_ptr<vsoc::RegionControl> managed_region =
+ vsoc::RegionControl::Open(
+ GrallocManagerLayout::ManagedRegion::region_name);
+ if (!managed_region) {
+ LOG_FATAL("Unable to open managed region");
+ return false;
+ }
+ offset_of_buffer_memory_ = gralloc_align<vsoc_reg_off_t>(
+ managed_region->region_desc().offset_of_region_data);
+ total_buffer_memory_ =
+ managed_region->region_size() - offset_of_buffer_memory_;
+
+ // TODO(jemoreira): Handle the case of unexpected values in the region.
+ is_open_ = true;
+ return true;
+}
+
+int GrallocRegionView::AllocateBuffer(size_t size, uint32_t* begin_offset) {
+ size = gralloc_align<size_t>(size);
+ // Cache the value of buffer_count in shared memory.
+ uint32_t buffer_count_local = 0;
+ {
+ vsoc::LockGuard<vsoc::layout::GuestLock>(&data()->new_buffer_lock);
+ buffer_count_local = data()->buffer_count;
+ }
+ // Find a free buffer entry of the appropriate size.
+ for (uint32_t idx = 0; idx < buffer_count_local; ++idx) {
+ BufferEntry& entry = data()->buffers_table[idx];
+ if (entry.owned_by == VSOC_REGION_FREE && entry.buffer_size() == size) {
+ int fd = control_->CreateFdScopedPermission(
+ GrallocManagerLayout::ManagedRegion::region_name,
+ pointer_to_region_offset(&entry.owned_by),
+ gralloc_owned_value(),
+ entry.buffer_begin,
+ entry.buffer_end);
+ if (fd >= 0) {
+ if (begin_offset) {
+ *begin_offset = entry.buffer_begin;
+ }
+ return fd;
+ }
+ }
+ }
+
+ // We couldn't find any suitable buffer, create one
+ {
+ vsoc::LockGuard<vsoc::layout::GuestLock>(&data()->new_buffer_lock);
+ // don't use the cached value here!!!
+ uint32_t idx = data()->buffer_count;
+ if (pointer_to_region_offset(&data()->buffers_table[idx + 1]) >
+ control_->region_size()) {
+ ALOGE(
+ "Out of memory in gralloc_manager (total: %d, used: %d, "
+ "requested: %d)",
+ static_cast<int>(control_->region_size()),
+ static_cast<int>(
+ pointer_to_region_offset(&data()->buffers_table[idx])),
+ static_cast<int>(sizeof(data()->buffers_table[idx])));
+ return -ENOMEM;
+ }
+ if (total_buffer_memory_ - data()->allocated_buffer_memory < size) {
+ ALOGE(
+ "Out of memory in gralloc_memory (total: %d, used: %d, requested: %d)",
+ static_cast<int>(total_buffer_memory_),
+ static_cast<int>(data()->allocated_buffer_memory),
+ static_cast<int>(size));
+ return -ENOMEM;
+ }
+ // Initialize the buffer entry and acquire ownership
+ // Do it before increasing buffer_count so that another thread looking for
+ // free entries doesn't find this one
+ BufferEntry& new_entry = data()->buffers_table[idx];
+ new_entry.buffer_begin =
+ offset_of_buffer_memory_ + data()->allocated_buffer_memory;
+ data()->allocated_buffer_memory += size;
+ new_entry.buffer_end = new_entry.buffer_begin + size;
+ int fd = control_->CreateFdScopedPermission(
+ GrallocManagerLayout::ManagedRegion::region_name,
+ pointer_to_region_offset(&new_entry.owned_by),
+ gralloc_owned_value(),
+ new_entry.buffer_begin,
+ new_entry.buffer_end);
+ if (fd < 0) {
+ LOG_FATAL(
+ "Unexpected error while creating fd scoped permission over "
+ "uncontested memory: %s",
+ strerror(-fd));
+ return fd;
+ }
+ // Increment buffer_count now that the entry can't be taken from us
+ data()->buffer_count++;
+ if (begin_offset) {
+ *begin_offset = new_entry.buffer_begin;
+ }
+ return fd;
+ }
+}
+
+/* static */
+// The C++03 standard does not guarantee this singleton implemention to be
+// thread safe, however magic statics are part of the gcc compiler since
+// version 4.3.
+GrallocRegionView* GrallocRegionView::GetInstance() {
+ static GrallocRegionView singleton;
+ if (!singleton.is_open_) {
+ return NULL;
+ }
+ return &singleton;
+}
diff --git a/guest/vsoc/lib/gralloc_region_view.h b/guest/vsoc/lib/gralloc_region_view.h
new file mode 100644
index 0000000..2432e8c
--- /dev/null
+++ b/guest/vsoc/lib/gralloc_region_view.h
@@ -0,0 +1,50 @@
+#pragma once
+
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include <common/vsoc/shm/gralloc_layout.h>
+#include <guest/vsoc/lib/manager_region_view.h>
+#include <stdlib.h>
+
+namespace vsoc {
+namespace gralloc {
+
+class GrallocRegionView : public vsoc::ManagerRegionView<
+ vsoc::layout::gralloc::GrallocManagerLayout> {
+ public:
+ // Allocates a gralloc buffer of (at least) the specified size. Returns a file
+ // descriptor that exposes the buffer when mmapped from 0 to (the page
+ // aligned) size (and fails to mmap anything outside of that range) or a
+ // negative number in case of error (e.g not enough free memory left).
+ // TODO(jemoreira): Include debug info like stride, width, height, etc
+ int AllocateBuffer(size_t size, uint32_t* begin_offset = nullptr);
+
+ static GrallocRegionView* GetInstance();
+ protected:
+ GrallocRegionView();
+ GrallocRegionView(const GrallocRegionView&) = delete;
+ GrallocRegionView & operator=(const GrallocRegionView&) = delete;
+
+ bool Open();
+
+ vsoc_reg_off_t offset_of_buffer_memory_{};
+ uint32_t total_buffer_memory_{};
+ bool is_open_{false};
+};
+
+} // namespace gralloc
+} // namespace vsoc
diff --git a/guest/vsoc/lib/guest_lock.cpp b/guest/vsoc/lib/guest_lock.cpp
new file mode 100644
index 0000000..67ce665
--- /dev/null
+++ b/guest/vsoc/lib/guest_lock.cpp
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#include "common/vsoc/shm/lock.h"
+
+#include "common/vsoc/lib/compat.h"
+#include "common/vsoc/lib/single_sided_signal.h"
+
+namespace vsoc {
+namespace layout {
+
+void GuestLock::Lock() {
+ uint32_t tid = gettid();
+ uint32_t expected_value;
+ uint32_t* uaddr = reinterpret_cast<uint32_t*>(&lock_uint32_);
+
+ while (1) {
+ if (TryLock(tid, &expected_value)) {
+ return;
+ }
+ SingleSidedSignal::AwaitSignal(expected_value, uaddr);
+ }
+}
+
+void GuestLock::Unlock() {
+ Sides sides_to_signal = UnlockCommon(gettid());
+ if (sides_to_signal.value_ != Sides::NoSides) {
+ SingleSidedSignal::Signal(&lock_uint32_);
+ }
+}
+
+} // namespace layout
+} // namespace vsoc
diff --git a/guest/vsoc/lib/guest_region_e2e_test.cpp b/guest/vsoc/lib/guest_region_e2e_test.cpp
new file mode 100644
index 0000000..4b71249
--- /dev/null
+++ b/guest/vsoc/lib/guest_region_e2e_test.cpp
@@ -0,0 +1,231 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+/*
+ * End-to-end test to ensure that mapping of vsoc regions works on the guest.
+ */
+
+#include "common/vsoc/lib/e2e_test_region_view.h"
+// TODO(b/64462568) Move the manager tests to a separate target
+#include "guest/vsoc/lib/manager_region_view.h"
+
+#include <android-base/logging.h>
+#include <gtest/gtest.h>
+
+#define DEATH_TEST_MESSAGE "abort converted to exit of 2 during death test"
+
+using vsoc::layout::e2e_test::E2EManagedTestRegionLayout;
+using vsoc::layout::e2e_test::E2EManagerTestRegionLayout;
+
+static inline void disable_tombstones() {
+ // We don't want a tombstone, and we're already in the child, so we modify the
+ // behavior of LOG(ABORT) to print the well known message and do an
+ // error-based exit.
+ android::base::SetAborter([](const char*) {
+ fputs(DEATH_TEST_MESSAGE, stderr);
+ fflush(stderr);
+ exit(2);
+ });
+}
+
+template <typename View>
+void DeathTestView(View *r) {
+ disable_tombstones();
+ // region.Open should never return.
+ EXPECT_FALSE(r->Open());
+}
+
+// Here is a summary of the two regions interrupt and write test:
+// 1. Write our strings to the first region
+// 2. Ensure that our peer hasn't signalled the second region. That would
+// indicate that it didn't wait for our interrupt.
+// 3. Send the interrupt on the first region
+// 4. Wait for our peer's interrupt on the first region
+// 5. Confirm that we can see our peer's writes in the first region
+// 6. Initialize our strings in the second region
+// 7. Send an interrupt on the second region to our peer
+// 8. Wait for our peer's interrupt on the second region
+// 9. Confirm that we can see our peer's writes in the second region
+// 10. Repeat the process for signaling.
+// 11. Confirm that no interrupt is pending in the first region
+// 12. Confirm that no interrupt is pending in the second region
+
+template <typename View>
+void SetGuestStrings(View* in) {
+ size_t num_data = in->string_size();
+ EXPECT_LE(2U, num_data);
+ for (size_t i = 0; i < num_data; ++i) {
+ EXPECT_TRUE(!in->guest_string(i)[0] ||
+ !strcmp(in->guest_string(i), View::Layout::guest_pattern));
+ in->set_guest_string(i, View::Layout::guest_pattern);
+ EXPECT_STREQ(in->guest_string(i), View::Layout::guest_pattern);
+ }
+}
+
+template <typename View>
+void CheckPeerStrings(View* in) {
+ size_t num_data = in->string_size();
+ EXPECT_LE(2, num_data);
+ for (size_t i = 0; i < num_data; ++i) {
+ EXPECT_STREQ(View::Layout::host_pattern, in->host_string(i));
+ }
+}
+
+TEST(RegionTest, BasicPeerTests) {
+ vsoc::E2EPrimaryRegionView primary;
+ vsoc::E2ESecondaryRegionView secondary;
+ ASSERT_TRUE(primary.Open());
+ ASSERT_TRUE(secondary.Open());
+ LOG(INFO) << "Regions are open";
+ SetGuestStrings(&primary);
+ LOG(INFO) << "Primary guest strings are set";
+ EXPECT_FALSE(secondary.HasIncomingInterrupt());
+ LOG(INFO) << "Verified no early second interrupt";
+ EXPECT_TRUE(primary.MaybeInterruptPeer());
+ LOG(INFO) << "Interrupt sent. Waiting for first interrupt from peer";
+ primary.WaitForInterrupt();
+ LOG(INFO) << "First interrupt received";
+ CheckPeerStrings(&primary);
+ LOG(INFO) << "Verified peer's primary strings";
+ SetGuestStrings(&secondary);
+ LOG(INFO) << "Secondary guest strings are set";
+ EXPECT_TRUE(secondary.MaybeInterruptPeer());
+ LOG(INFO) << "Second interrupt sent";
+ secondary.WaitForInterrupt();
+ LOG(INFO) << "Second interrupt received";
+ CheckPeerStrings(&secondary);
+ LOG(INFO) << "Verified peer's secondary strings";
+
+ // Test signals
+ EXPECT_FALSE(secondary.HasIncomingInterrupt());
+ LOG(INFO) << "Verified no early second signal";
+ vsoc::layout::Sides side;
+ side.value_ = vsoc::layout::Sides::Peer;
+ primary.SendSignal(side, &primary.data()->guest_to_host_signal);
+ LOG(INFO) << "Signal sent. Waiting for first signal from peer";
+ primary.WaitForInterrupt();
+ int count = 0; // counts the number of signals received.
+ primary.ProcessSignalsFromPeer([&primary, &count](uint32_t* uaddr){
+ ++count;
+ EXPECT_TRUE(uaddr == &primary.data()->host_to_guest_signal);
+ });
+ EXPECT_TRUE(count == 1);
+ LOG(INFO) << "Signal received on primary region";
+ secondary.SendSignal(side, &secondary.data()->guest_to_host_signal);
+ LOG(INFO) << "Signal sent. Waiting for second signal from peer";
+ secondary.WaitForInterrupt();
+ count = 0;
+ secondary.ProcessSignalsFromPeer([&secondary, &count](uint32_t* uaddr){
+ ++count;
+ EXPECT_TRUE(uaddr == &secondary.data()->host_to_guest_signal);
+ });
+ EXPECT_TRUE(count == 1);
+ LOG(INFO) << "Signal received on secondary region";
+
+ EXPECT_FALSE(primary.HasIncomingInterrupt());
+ EXPECT_FALSE(secondary.HasIncomingInterrupt());
+ LOG(INFO) << "PASS: BasicPeerTests";
+}
+
+TEST(RegionTest, MissingRegionDeathTest) {
+ vsoc::E2EUnfindableRegionView test;
+ // EXPECT_DEATH creates a child for the test, so we do it out here.
+ // DeathTestGuestRegion will actually do the deadly call after ensuring
+ // that we don't create an unwanted tombstone.
+ EXPECT_EXIT(DeathTestView(&test), testing::ExitedWithCode(2),
+ ".*" DEATH_TEST_MESSAGE ".*");
+}
+
+class ManagedRegionTest {
+ public:
+ void testManagedRegionFailMap() {
+ vsoc::TypedRegionView<E2EManagedTestRegionLayout> managed_region;
+ disable_tombstones();
+ // managed_region.Open should never return.
+ EXPECT_FALSE(managed_region.Open());
+ }
+
+ void testManagedRegionMap() {
+ EXPECT_TRUE(manager_region_.Open());
+
+ // Maps correctly with permission
+ const uint32_t owned_value = 65, begin_offset = 4096, end_offset = 8192;
+ int perm_fd = manager_region_.CreateFdScopedPermission(
+ &manager_region_.data()->data[0], owned_value, begin_offset,
+ end_offset);
+ EXPECT_TRUE(perm_fd >= 0);
+ fd_scoped_permission perm;
+ ASSERT_TRUE(ioctl(perm_fd, VSOC_GET_FD_SCOPED_PERMISSION, &perm) == 0);
+ void* mapped_ptr = mmap(NULL, perm.end_offset - perm.begin_offset,
+ PROT_WRITE | PROT_READ, MAP_SHARED, perm_fd, 0);
+ EXPECT_FALSE(mapped_ptr == MAP_FAILED);
+
+ // Owned value gets written
+ EXPECT_TRUE(manager_region_.data()->data[0] == owned_value);
+
+ // Data written to the mapped memory stays there after unmap
+ std::string str = "managed by e2e_manager";
+ strcpy(reinterpret_cast<char*>(mapped_ptr), str.c_str());
+ EXPECT_TRUE(munmap(mapped_ptr, end_offset - begin_offset) == 0);
+ mapped_ptr = mmap(NULL, end_offset - begin_offset, PROT_WRITE | PROT_READ,
+ MAP_SHARED, perm_fd, 0);
+ EXPECT_FALSE(mapped_ptr == MAP_FAILED);
+ EXPECT_TRUE(strcmp(reinterpret_cast<char*>(mapped_ptr), str.c_str()) == 0);
+
+ // Create permission elsewhere in the region, map same offset and length,
+ // ensure data isn't there
+ EXPECT_TRUE(munmap(mapped_ptr, end_offset - begin_offset) == 0);
+ close(perm_fd);
+ EXPECT_TRUE(manager_region_.data()->data[0] == 0);
+ perm_fd = manager_region_.CreateFdScopedPermission(
+ &manager_region_.data()->data[1], owned_value, begin_offset + 4096,
+ end_offset + 4096);
+ EXPECT_TRUE(perm_fd >= 0);
+ mapped_ptr = mmap(NULL, end_offset - begin_offset, PROT_WRITE | PROT_READ,
+ MAP_SHARED, perm_fd, 0);
+ EXPECT_FALSE(mapped_ptr == MAP_FAILED);
+ EXPECT_FALSE(strcmp(reinterpret_cast<char*>(mapped_ptr), str.c_str()) == 0);
+ }
+ ManagedRegionTest() {}
+
+ private:
+ vsoc::ManagerRegionView<E2EManagerTestRegionLayout> manager_region_;
+};
+
+TEST(ManagedRegionTest, ManagedRegionFailMap) {
+ ManagedRegionTest test;
+ EXPECT_EXIT(test.testManagedRegionFailMap(),
+ testing::ExitedWithCode(2),
+ ".*" DEATH_TEST_MESSAGE ".*");
+}
+
+TEST(ManagedRegionTest, ManagedRegionMap) {
+ ManagedRegionTest test;
+ test.testManagedRegionMap();
+}
+
+int main(int argc, char** argv) {
+ android::base::InitLogging(argv);
+ testing::InitGoogleTest(&argc, argv);
+ int rval = RUN_ALL_TESTS();
+ if (!rval) {
+ vsoc::E2EPrimaryRegionView region;
+ region.Open();
+ region.guest_status(vsoc::layout::e2e_test::E2E_MEMORY_FILLED);
+ LOG(INFO) << "stage_1_guest_region_e2e_tests PASSED";
+ }
+ return rval;
+}
diff --git a/guest/vsoc/lib/manager_region_view.h b/guest/vsoc/lib/manager_region_view.h
new file mode 100644
index 0000000..6c65dec
--- /dev/null
+++ b/guest/vsoc/lib/manager_region_view.h
@@ -0,0 +1,58 @@
+#pragma once
+
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include "common/vsoc/lib/typed_region_view.h"
+
+namespace vsoc {
+
+/**
+ * Adds methods to create file descriptor scoped permissions. Just like
+ * TypedRegionView it can be directly constructed or subclassed.
+ *
+ * The Layout type must (in addition to requirements for TypedRegionView) also
+ * provide a nested type for the layout of the managed region.
+ */
+template <typename Layout>
+class ManagerRegionView : public TypedRegionView<Layout> {
+ public:
+ ManagerRegionView() = default;
+ /**
+ * Creates a fd scoped permission on the managed region.
+ *
+ * The managed_region_fd is in/out parameter that can be a not yet open file
+ * descriptor. If the fd is not open yet it will open the managed region
+ * device and then create the permission. If the function returns EBUSY
+ * (meaning that we lost the race to acquire the memory) the same fd can (and
+ * is expected to) be used in a subsequent call to create a permission on
+ * another memory location.
+ *
+ * On success returns an open fd with the requested permission asociated to
+ * it. If another thread/process acquired ownership of *owner_ptr before this
+ * one returns -EBUSY, returns a different negative number otherwise.
+ */
+ int CreateFdScopedPermission(uint32_t* owner_ptr, uint32_t owned_val,
+ vsoc_reg_off_t begin_offset,
+ vsoc_reg_off_t end_offset) {
+ return this->control_->CreateFdScopedPermission(
+ Layout::ManagedRegion::region_name,
+ this->pointer_to_region_offset(owner_ptr), owned_val, begin_offset,
+ end_offset);
+ }
+};
+
+} // namespace vsoc
diff --git a/guest/vsoc/lib/region_control.cpp b/guest/vsoc/lib/region_control.cpp
new file mode 100644
index 0000000..8c6864c
--- /dev/null
+++ b/guest/vsoc/lib/region_control.cpp
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#include "common/vsoc/lib/region_view.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <string>
+#include <thread>
+
+#include <android-base/logging.h>
+#include <uapi/vsoc_shm.h>
+
+using cvd::SharedFD;
+
+namespace {
+class GuestRegionControl : public vsoc::RegionControl {
+ public:
+ explicit GuestRegionControl(const SharedFD& region_fd,
+ const vsoc_device_region& desc)
+ : region_fd_{region_fd} {
+ region_desc_ = desc;
+ }
+ virtual bool InterruptPeer() override;
+ virtual void InterruptSelf() override;
+ virtual void WaitForInterrupt() override;
+ virtual void* Map() override;
+
+ protected:
+ int CreateFdScopedPermission(const char* managed_region_name,
+ vsoc_reg_off_t owner_offset, uint32_t owned_val,
+ vsoc_reg_off_t begin_offset,
+ vsoc_reg_off_t end_offset) override;
+ cvd::SharedFD region_fd_;
+};
+
+std::string device_path_from_name(const char* region_name) {
+ return std::string("/dev/") + region_name;
+}
+
+bool GuestRegionControl::InterruptPeer() {
+ int rval = region_fd_->Ioctl(VSOC_SEND_INTERRUPT_TO_HOST, 0);
+ if ((rval == -1) && (errno != EBUSY)) {
+ LOG(INFO) << __FUNCTION__ << ": ioctl failed (" << strerror(errno) << ")";
+ }
+ return !rval;
+}
+
+void GuestRegionControl::InterruptSelf() {
+ region_fd_->Ioctl(VSOC_SELF_INTERRUPT, 0);
+}
+
+void GuestRegionControl::WaitForInterrupt() {
+ region_fd_->Ioctl(VSOC_WAIT_FOR_INCOMING_INTERRUPT, 0);
+}
+
+int GuestRegionControl::CreateFdScopedPermission(
+ const char* managed_region_name, vsoc_reg_off_t owner_offset,
+ uint32_t owned_value, vsoc_reg_off_t begin_offset,
+ vsoc_reg_off_t end_offset) {
+ if (!region_fd_->IsOpen()) {
+ LOG(FATAL) << "Can't create permission before opening controller region";
+ return -EINVAL;
+ }
+ int managed_region_fd =
+ open(device_path_from_name(managed_region_name).c_str(), O_RDWR);
+ if (managed_region_fd < 0) {
+ int errno_ = errno;
+ LOG(FATAL) << "Can't open managed region: " << managed_region_name;
+ return -errno_;
+ }
+
+ fd_scoped_permission_arg perm;
+ perm.perm.begin_offset = begin_offset;
+ perm.perm.end_offset = end_offset;
+ perm.perm.owned_value = owned_value;
+ perm.perm.owner_offset = owner_offset;
+ perm.managed_region_fd = managed_region_fd;
+ LOG(INFO) << "owner offset: " << perm.perm.owner_offset;
+ int retval = region_fd_->Ioctl(VSOC_CREATE_FD_SCOPED_PERMISSION, &perm);
+ if (retval) {
+ int errno_ = errno;
+ close(managed_region_fd);
+ if (errno_ != EBUSY) {
+ LOG(FATAL) << "Unable to create fd scoped permission ("
+ << strerror(errno_) << ")";
+ }
+ return -errno_;
+ }
+ return managed_region_fd;
+}
+
+void* GuestRegionControl::Map() {
+ region_base_ =
+ region_fd_->Mmap(0, region_size(), PROT_READ | PROT_WRITE, MAP_SHARED, 0);
+ if (region_base_ == MAP_FAILED) {
+ LOG(FATAL) << "mmap failed (" << region_fd_->StrError() << ")";
+ region_base_ = nullptr;
+ }
+ return region_base_;
+}
+} // namespace
+
+// domain is here to ensure that this method has the same signature as the
+// method on host regions.
+std::shared_ptr<vsoc::RegionControl> vsoc::RegionControl::Open(
+ const char* region_name) {
+ std::string path = device_path_from_name(region_name);
+ SharedFD fd = SharedFD::Open(path.c_str(), O_RDWR);
+ if (!fd->IsOpen()) {
+ LOG(FATAL) << "Unable to open region " << region_name << " ("
+ << fd->StrError() << ")";
+ return nullptr;
+ }
+ vsoc_device_region desc;
+ if (fd->Ioctl(VSOC_DESCRIBE_REGION, &desc)) {
+ LOG(FATAL) << "Unable to obtain region descriptor (" << fd->StrError()
+ << ")";
+ return nullptr;
+ }
+ return std::shared_ptr<vsoc::RegionControl>(new GuestRegionControl(fd, desc));
+}
diff --git a/guest/vsoc/lib/region_view.cpp b/guest/vsoc/lib/region_view.cpp
new file mode 100644
index 0000000..db1bec0
--- /dev/null
+++ b/guest/vsoc/lib/region_view.cpp
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#include "common/vsoc/lib/region_view.h"
+#include "common/vsoc/lib/region_control.h"
+
+const vsoc_signal_table_layout& vsoc::RegionView::incoming_signal_table() {
+ return control_->region_desc().host_to_guest_signal_table;
+}
+
+// Returns a pointer to the table that will be used to post signals
+const vsoc_signal_table_layout& vsoc::RegionView::outgoing_signal_table() {
+ return control_->region_desc().guest_to_host_signal_table;
+}
diff --git a/host/Android.bp b/host/Android.bp
new file mode 100644
index 0000000..2280c23
--- /dev/null
+++ b/host/Android.bp
@@ -0,0 +1,20 @@
+//
+// Copyright (C) 2017 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.
+
+subdirs = [
+ "commands",
+ "frontend",
+ "libs",
+]
diff --git a/host/commands/Android.bp b/host/commands/Android.bp
new file mode 100644
index 0000000..618dd00
--- /dev/null
+++ b/host/commands/Android.bp
@@ -0,0 +1,18 @@
+//
+// Copyright (C) 2017 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.
+
+subdirs = [
+ "launch",
+]
diff --git a/host/commands/launch/Android.bp b/host/commands/launch/Android.bp
new file mode 100644
index 0000000..2a3eb0d
--- /dev/null
+++ b/host/commands/launch/Android.bp
@@ -0,0 +1,28 @@
+cc_binary_host {
+ name: "launch_cvd",
+ srcs: [
+ "main.cc",
+ "fb_bcast_region_handler.cc",
+ ],
+ header_libs: [
+ "cuttlefish_glog",
+ ],
+ shared_libs: [
+ "vsoc_lib",
+ "libcuttlefish_fs",
+ "cuttlefish_auto_resources",
+ "libicuuc",
+ "libbase",
+ ],
+ static_libs: [
+ "libcuttlefish_host_config",
+ "libivserver",
+ "libvadb",
+ "libusbip",
+ "libgflags",
+ "libxml2",
+ "libjsoncpp",
+ "libmonitor",
+ ],
+ defaults: ["cuttlefish_host_only"],
+}
diff --git a/host/commands/launch/README.md b/host/commands/launch/README.md
new file mode 100644
index 0000000..05bb716
--- /dev/null
+++ b/host/commands/launch/README.md
@@ -0,0 +1,123 @@
+# Host-side binary for Android Virtual Device
+
+## Launcher package
+
+This is the cuttlefish launcher implementation, that integrates following
+features:
+
+* `libvirt` domain configuration,
+* `ivshmem` server,
+* USB forwarding.
+
+## Overview
+
+### ivshmem
+
+We are breaking from the general philosophy of ivshmem-server inter-vm
+communication. In this prototype there is no concept of inter-vm communication;
+guests can only talk to daemons running on host.
+
+### Requirements
+
+Cuttlefish requires the following packages to be installed on your system:
+
+Compiling:
+
+* `libjsoncpp-dev`
+* `libudev-dev`,
+* `libvirt-dev`,
+* `libxml2-dev`
+
+Running:
+
+* `linux-image-extra-virtual` to supply `vhci-hcd` module (module must be
+ loaded manually)
+* `libvirt-bin`
+* `libxml2`
+* `qemu-2.8` (or newer)
+
+### Building and Installing debian package
+
+To build debian package:
+
+```sh
+host$ cd dist
+host$ debuild --no-tgz-check -us -uc
+host$ cd ..
+host$ scp cuttlefish-common*.deb ${USER}@123.45.67.89:
+```
+
+This will create file named `cuttlefish-common_0.1-1_amd64.deb` in the root
+folder of your workspace. You will have to manually upload this file to
+your remote instance and install it as:
+
+```sh
+host$ ssh 123.45.67.89
+gce$ sudo apt install -f --reinstall ./cuttlefish-common*.deb
+```
+
+`apt` will pull in all necessary dependencies. After it's done, support files
+will be ready to use.
+
+### Host Configuration
+
+All configuration and basic deployment is covered by scripts in the
+`host/deploy` folder. To configure remote instance for cuttlefish, execute:
+
+```sh
+host$ cd host/deploy
+host$ python main.py config -i 123.45.67.89
+```
+
+The script will automatically update libvirt configuration files and user group
+membership, as well as create necessary folder for Cuttlefish images.
+
+### Uploading images
+
+To deploy cuttlefish images from build server, execute:
+
+```sh
+host$ cd host/deploy
+host$ python main.py deploy -i 123.45.67.89
+```
+
+By default, the script will pull the latest build of `cf_x86_phone-userdebug`
+target from `oc-gce-dev` branch, and latest kernel from cuttlefish kernel
+target and branch. Both system and kernel locations can be tuned by supplying
+relevant arguments via command line.
+
+Optionally, files can be populated and uploaded manually. Please ensure that
+at all times user `libvirt-qemu` can access each of these files by specifying
+correct ACL permissions using `setfacl` command, eg:
+
+```sh
+gce$ setfacl -m u:libvirt-qemu:rw /path/to/system.img
+```
+
+### Starting Cuttlefish
+
+To start cuttlefish, assuming you executed all the above:
+
+```sh
+gce$ sudo modprobe vhci-hcd
+gce$ sudo cf -system_image_dir /srv/cf/latest/ \
+ -kernel /srv/cf/latest/kernel \
+ -data_image /srv/cf/latest/data.img \
+ -cache_image /srv/cf/latest/cache.img \
+ --logtostderr
+```
+
+Shortly after, you should be able to execute `adb devices` and see your device
+listed. If device is reported as `????????`, or as:
+
+```log
+CUTTLEFISHCVD01 no permissions (verify udev rules); see [http://developer.android.com/tools/device.html]
+```
+
+you may have to re-start adb as root (we don't have udev rules updating virtual
+usb permissions yet):
+
+```sh
+gce$ adb kill-server
+gce$ sudo adb devices
+```
diff --git a/host/commands/launch/fb_bcast_region_handler.cc b/host/commands/launch/fb_bcast_region_handler.cc
new file mode 100644
index 0000000..4e89b79
--- /dev/null
+++ b/host/commands/launch/fb_bcast_region_handler.cc
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include <glog/logging.h>
+#include <gflags/gflags.h>
+
+#include "common/vsoc/lib/fb_bcast_region_view.h"
+#include "host/commands/launch/pre_launch_initializers.h"
+#include "host/libs/config/host_config.h"
+
+DEFINE_int32(x_res, 720, "Width of the screen in pixels");
+DEFINE_int32(y_res, 1280, "Height of the screen in pixels");
+DEFINE_int32(dpi, 160, "Pixels per inch for the screen");
+
+void InitializeFBBroadcastRegion() {
+ vsoc::framebuffer::FBBroadcastRegionView region;
+ if (!region.Open(vsoc::GetDomain().c_str())) {
+ LOG(INFO) << "Framebuffer region was not found";
+ return;
+ }
+ auto dest = region.data();
+ dest->x_res = FLAGS_x_res;
+ dest->y_res = FLAGS_y_res;
+ dest->dpi = FLAGS_dpi;
+}
diff --git a/host/commands/launch/main.cc b/host/commands/launch/main.cc
new file mode 100644
index 0000000..435d2b2
--- /dev/null
+++ b/host/commands/launch/main.cc
@@ -0,0 +1,377 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <fstream>
+#include <iomanip>
+#include <memory>
+#include <sstream>
+
+#include <gflags/gflags.h>
+#include <glog/logging.h>
+
+#include "common/libs/fs/shared_select.h"
+#include "host/commands/launch/pre_launch_initializers.h"
+#include "host/libs/config/file_partition.h"
+#include "host/libs/config/guest_config.h"
+#include "host/libs/config/host_config.h"
+#include "host/libs/ivserver/ivserver.h"
+#include "host/libs/ivserver/options.h"
+#include "host/libs/monitor/kernel_log_server.h"
+#include "host/libs/usbip/server.h"
+#include "host/libs/vadb/virtual_adb_server.h"
+
+namespace {
+std::string StringFromEnv(const char* varname, std::string defval) {
+ const char* const valstr = getenv(varname);
+ if (!valstr) {
+ return defval;
+ }
+ return valstr;
+}
+} // namespace
+
+using vsoc::GetPerInstanceDefault;
+using vsoc::GetDefaultPerInstancePath;
+
+DEFINE_string(cache_image, "", "Location of the cache partition image.");
+DEFINE_int32(cpus, 2, "Virtual CPU count.");
+DEFINE_string(data_image, "", "Location of the data partition image.");
+DEFINE_bool(disable_app_armor_security, false,
+ "Disable AppArmor security in libvirt. For debug only.");
+DEFINE_bool(disable_dac_security, false,
+ "Disable DAC security in libvirt. For debug only.");
+DEFINE_string(extra_kernel_command_line, "",
+ "Additional flags to put on the kernel command line");
+DECLARE_int32(instance);
+DEFINE_string(initrd, "", "Location of cuttlefish initrd file.");
+DEFINE_string(kernel, "", "Location of cuttlefish kernel file.");
+DEFINE_string(kernel_command_line, "",
+ "Location of a text file with the kernel command line.");
+DEFINE_string(launch_command, "virsh create /dev/fd/0",
+ "Command to start an instance");
+DEFINE_string(layout,
+ StringFromEnv("ANDROID_HOST_OUT", StringFromEnv("HOME", ".")) +
+ "/config/vsoc_mem.json",
+ "Location of the vsoc_mem.json file.");
+DEFINE_bool(log_xml, false, "Log the XML machine configuration");
+DEFINE_int32(memory_mb, 2048,
+ "Total amount of memory available for guest, MB.");
+std::string g_default_mempath{GetPerInstanceDefault("/var/run/shm/cvd-")};
+DEFINE_string(mempath, g_default_mempath.c_str(),
+ "Target location for the shmem file.");
+std::string g_default_mobile_interface{GetPerInstanceDefault("cvd-mobile-")};
+DEFINE_string(mobile_interface, g_default_mobile_interface.c_str(),
+ "Network interface to use for mobile networking");
+std::string g_default_qemusocket = GetDefaultPerInstancePath("ivshmem_socket_qemu");
+DEFINE_string(qemusocket, g_default_qemusocket.c_str(), "QEmu socket path");
+std::string g_default_serial_number{GetPerInstanceDefault("CUTTLEFISHCVD")};
+DEFINE_string(serial_number, g_default_serial_number.c_str(),
+ "Serial number to use for the device");
+DEFINE_string(system_image_dir,
+ StringFromEnv("ANDROID_PRODUCT_OUT", StringFromEnv("HOME", ".")),
+ "Location of the system partition images.");
+DEFINE_string(vendor_image, "", "Location of the vendor partition image.");
+
+std::string g_default_uuid{
+ GetPerInstanceDefault("699acfc4-c8c4-11e7-882b-5065f31dc1")};
+DEFINE_string(uuid, g_default_uuid.c_str(),
+ "UUID to use for the device. Random if not specified");
+DEFINE_bool(deprecated_boot_completed, false, "Log boot completed message to"
+ "host kernel. This is only used during transition of our clients."
+ "Will be deprecated soon.");
+
+namespace {
+Json::Value LoadLayoutFile(const std::string& file) {
+ char real_file_path[PATH_MAX];
+ if (realpath(file.c_str(), real_file_path) == nullptr) {
+ LOG(FATAL) << "Could not get real path for file " << file << ": "
+ << strerror(errno);
+ }
+
+ Json::Value result;
+ Json::Reader reader;
+ std::ifstream ifs(real_file_path);
+ if (!reader.parse(ifs, result)) {
+ LOG(FATAL) << "Could not read layout file " << file << ": "
+ << reader.getFormattedErrorMessages();
+ }
+ return result;
+}
+
+// VirtualUSBManager manages virtual USB device presence for Cuttlefish.
+class VirtualUSBManager {
+ public:
+ VirtualUSBManager(const std::string& usbsocket,
+ const std::string& android_usbipsocket)
+ : adb_{usbsocket, android_usbipsocket},
+ usbip_{android_usbipsocket, adb_.Pool()} {}
+
+ ~VirtualUSBManager() = default;
+
+ // Initialize Virtual USB and start USB management thread.
+ void Start() {
+ CHECK(adb_.Init()) << "Could not initialize Virtual ADB server";
+ CHECK(usbip_.Init()) << "Could not start USB/IP server";
+ thread_.reset(new std::thread([this]() { Thread(); }));
+ }
+
+ private:
+ void Thread() {
+ for (;;) {
+ cvd::SharedFDSet fd_read;
+ fd_read.Zero();
+
+ adb_.BeforeSelect(&fd_read);
+ usbip_.BeforeSelect(&fd_read);
+
+ int ret = cvd::Select(&fd_read, nullptr, nullptr, nullptr);
+ if (ret <= 0) continue;
+
+ adb_.AfterSelect(fd_read);
+ usbip_.AfterSelect(fd_read);
+ }
+ }
+
+ vadb::VirtualADBServer adb_;
+ vadb::usbip::Server usbip_;
+ std::unique_ptr<std::thread> thread_;
+
+ VirtualUSBManager(const VirtualUSBManager&) = delete;
+ VirtualUSBManager& operator=(const VirtualUSBManager&) = delete;
+};
+
+// IVServerManager takes care of serving shared memory segments between
+// Cuttlefish and host-side daemons.
+class IVServerManager {
+ public:
+ IVServerManager(const Json::Value& json_root)
+ : server_(ivserver::IVServerOptions(FLAGS_layout, FLAGS_mempath,
+ FLAGS_qemusocket,
+ vsoc::GetDomain()),
+ json_root) {}
+
+ ~IVServerManager() = default;
+
+ // Start IVServer thread.
+ void Start() {
+ thread_.reset(new std::thread([this]() { server_.Serve(); }));
+ }
+
+ private:
+ ivserver::IVServer server_;
+ std::unique_ptr<std::thread> thread_;
+
+ IVServerManager(const IVServerManager&) = delete;
+ IVServerManager& operator=(const IVServerManager&) = delete;
+};
+
+// KernelLogMonitor receives and monitors kernel log for Cuttlefish.
+class KernelLogMonitor {
+ public:
+ KernelLogMonitor(const std::string& socket_name,
+ const std::string& log_name,
+ bool deprecated_boot_completed)
+ : klog_{socket_name, log_name, deprecated_boot_completed} {}
+
+ ~KernelLogMonitor() = default;
+
+ void Start() {
+ CHECK(klog_.Init()) << "Could not initialize kernel log server";
+ thread_.reset(new std::thread([this]() { Thread(); }));
+ }
+
+ private:
+ void Thread() {
+ for (;;) {
+ cvd::SharedFDSet fd_read;
+ fd_read.Zero();
+
+ klog_.BeforeSelect(&fd_read);
+
+ int ret = cvd::Select(&fd_read, nullptr, nullptr, nullptr);
+ if (ret <= 0) continue;
+
+ klog_.AfterSelect(fd_read);
+ }
+ }
+
+ monitor::KernelLogServer klog_;
+ std::unique_ptr<std::thread> thread_;
+
+ KernelLogMonitor(const KernelLogMonitor&) = delete;
+ KernelLogMonitor& operator=(const KernelLogMonitor&) = delete;
+};
+
+} // anonymous namespace
+
+void subprocess(const char* const* command) {
+ pid_t pid = fork();
+ if (!pid) {
+ int rval = execv(command[0], const_cast<char* const*>(command));
+ // No need for an if: if exec worked it wouldn't have returned
+ LOG(ERROR) << "exec of " << command[0] << " failed (" << strerror(errno)
+ << ")";
+ exit(rval);
+ }
+ if (pid == -1) {
+ LOG(ERROR) << "fork of " << command[0] << " failed (" << strerror(errno)
+ << ")";
+ } else {
+ waitpid(pid, 0, 0);
+ }
+}
+
+int main(int argc, char** argv) {
+ ::android::base::InitLogging(argv, android::base::StderrLogger);
+ google::ParseCommandLineFlags(&argc, &argv, true);
+
+ LOG_IF(FATAL, FLAGS_system_image_dir.empty())
+ << "--system_image_dir must be specified.";
+
+ std::string per_instance_dir = vsoc::GetDefaultPerInstanceDir();
+ struct stat unused;
+ if ((stat(per_instance_dir.c_str(), &unused) == -1) && (errno == ENOENT)) {
+ LOG(INFO) << "Setting up " << per_instance_dir;
+ const char* mkdir_command[]{
+ "/usr/bin/sudo", "/bin/mkdir", "-m", "0775",
+ per_instance_dir.c_str(), NULL};
+ subprocess(mkdir_command);
+ std::string owner_group{getenv("USER")};
+ owner_group += ":libvirt-qemu";
+ const char* chown_command[]{"/usr/bin/sudo", "/bin/chown",
+ owner_group.c_str(), per_instance_dir.c_str(),
+ NULL};
+ subprocess(chown_command);
+ }
+
+ // If user did not specify location of either of these files, expect them to
+ // be placed in --system_image_dir location.
+ if (FLAGS_kernel.empty()) {
+ FLAGS_kernel = FLAGS_system_image_dir + "/kernel";
+ }
+
+ if (FLAGS_kernel_command_line.empty()) {
+ FLAGS_kernel_command_line = FLAGS_system_image_dir + "/cmdline";
+ }
+
+ if (FLAGS_initrd.empty()) {
+ FLAGS_initrd = FLAGS_system_image_dir + "/ramdisk.img";
+ }
+
+ if (FLAGS_cache_image.empty()) {
+ FLAGS_cache_image = FLAGS_system_image_dir + "/cache.img";
+ }
+
+ if (FLAGS_data_image.empty()) {
+ FLAGS_data_image = FLAGS_system_image_dir + "/userdata.img";
+ }
+
+ if (FLAGS_vendor_image.empty()) {
+ FLAGS_vendor_image = FLAGS_system_image_dir + "/vendor.img";
+ }
+
+ Json::Value json_root = LoadLayoutFile(FLAGS_layout);
+
+ // Each of these calls is free to fail and terminate launch if file does not
+ // exist or could not be created.
+ auto system_partition = config::FilePartition::ReuseExistingFile(
+ FLAGS_system_image_dir + "/system.img");
+ auto data_partition =
+ config::FilePartition::ReuseExistingFile(FLAGS_data_image);
+ auto cache_partition =
+ config::FilePartition::ReuseExistingFile(FLAGS_cache_image);
+ auto vendor_partition =
+ config::FilePartition::ReuseExistingFile(FLAGS_vendor_image);
+
+ std::ostringstream cmdline;
+ std::ifstream t(FLAGS_kernel_command_line);
+ if (!t) {
+ LOG(FATAL) << "Unable to open " << FLAGS_kernel_command_line;
+ }
+ cmdline << t.rdbuf();
+ t.close();
+ cmdline << " androidboot.serialno=" << FLAGS_serial_number;
+ if (FLAGS_extra_kernel_command_line.size()) {
+ cmdline << " " << FLAGS_extra_kernel_command_line;
+ }
+
+ std::string entropy_source = "/dev/urandom";
+
+ config::GuestConfig cfg;
+ cfg.SetID(FLAGS_instance)
+ .SetVCPUs(FLAGS_cpus)
+ .SetMemoryMB(FLAGS_memory_mb)
+ .SetKernelName(FLAGS_kernel)
+ .SetInitRDName(FLAGS_initrd)
+ .SetKernelArgs(cmdline.str())
+ .SetIVShMemSocketPath(FLAGS_qemusocket)
+ .SetIVShMemVectorCount(json_root["vsoc_device_regions"].size())
+ .SetSystemPartitionPath(system_partition->GetName())
+ .SetCachePartitionPath(cache_partition->GetName())
+ .SetDataPartitionPath(data_partition->GetName())
+ .SetVendorPartitionPath(vendor_partition->GetName())
+ .SetMobileBridgeName(FLAGS_mobile_interface)
+ .SetEntropySource(entropy_source)
+ .SetDisableDACSecurity(FLAGS_disable_dac_security)
+ .SetDisableAppArmorSecurity(FLAGS_disable_app_armor_security)
+ .SetUUID(FLAGS_uuid);
+ cfg.SetUSBV1SocketName(
+ GetDefaultPerInstancePath(cfg.GetInstanceName() + "-usb"));
+ cfg.SetKernelLogSocketName(
+ GetDefaultPerInstancePath(cfg.GetInstanceName() + "-kernel-log"));
+
+ std::string xml = cfg.Build();
+ if (FLAGS_log_xml) {
+ LOG(INFO) << "Using XML:\n" << xml;
+ }
+
+ VirtualUSBManager vadb(cfg.GetUSBV1SocketName(),
+ GetPerInstanceDefault("android_usbip"));
+ vadb.Start();
+ IVServerManager ivshmem(json_root);
+ ivshmem.Start();
+ KernelLogMonitor kmon(cfg.GetKernelLogSocketName(),
+ GetDefaultPerInstancePath("kernel.log"),
+ FLAGS_deprecated_boot_completed);
+ kmon.Start();
+
+ sleep(1);
+
+ // Initialize the regions that require it before the VM starts.
+ PreLaunchInitializers::Initialize();
+
+ FILE* launch = popen(FLAGS_launch_command.c_str(), "w");
+ if (!launch) {
+ LOG(FATAL) << "Unable to execute " << FLAGS_launch_command;
+ }
+ int rval = fputs(xml.c_str(), launch);
+ if (rval == EOF) {
+ LOG(FATAL) << "Launch command exited while accepting XML";
+ }
+ int exit_code = pclose(launch);
+ if (exit_code) {
+ LOG(FATAL) << "Launch command exited with status " << exit_code;
+ }
+ pause();
+}
diff --git a/host/commands/launch/pre_launch_initializers.h b/host/commands/launch/pre_launch_initializers.h
new file mode 100644
index 0000000..5d43bce
--- /dev/null
+++ b/host/commands/launch/pre_launch_initializers.h
@@ -0,0 +1,30 @@
+#pragma once
+
+/*
+ * Copyright (C) 2017 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.
+ */
+
+// Handles initialization of regions that require it strictly before the virtual
+// machine is started.
+// To add initializers for more regions declare here, implement in its own
+// source file and call from PreLaunchInitializers::Initialize().
+void InitializeFBBroadcastRegion();
+
+class PreLaunchInitializers {
+ public:
+ static void Initialize() {
+ InitializeFBBroadcastRegion();
+ }
+};
diff --git a/host/config/Android.mk b/host/config/Android.mk
new file mode 100644
index 0000000..6c176fe
--- /dev/null
+++ b/host/config/Android.mk
@@ -0,0 +1,27 @@
+# Copyright (C) 2017 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.
+
+#
+# Common definitions for all variants.
+#
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := vsoc_mem_json
+LOCAL_MODULE_CLASS := ETC
+LOCAL_MODULE_PATH := $(HOST_OUT)/config
+LOCAL_MODULE_STEM := vsoc_mem.json
+LOCAL_SRC_FILES := vsoc_mem.json
+include $(BUILD_PREBUILT)
diff --git a/host/config/vsoc_mem.json b/host/config/vsoc_mem.json
new file mode 100644
index 0000000..411b3c5
--- /dev/null
+++ b/host/config/vsoc_mem.json
@@ -0,0 +1,175 @@
+{
+ "vsoc_device_regions" : [
+ {
+ "device_name" : "vsoc_input",
+ "__comment" : "Send input events to the guest",
+ "current_version" : 1,
+ "min_compatible_version" : 1,
+ "region_size" : 4096,
+
+ "guest_to_host_signal_table" : {
+ "__comment" : "sizeof each node is based on common/libs/shm_compatible/lock.h",
+ "num_nodes_lg2" : 0
+ },
+
+ "host_to_guest_signal_table" : {
+ "num_nodes_lg2" : 0
+ }
+ },
+
+ {
+ "device_name" : "hwcomposer",
+ "__comment" : "HWComposer related",
+ "current_version" : 1,
+ "min_compatible_version" : 1,
+ "region_size" : 16384,
+
+ "guest_to_host_signal_table" : {
+ "__comment" : "sizeof each node is based on common/libs/shm_compatible/lock.h",
+ "num_nodes_lg2" : 2
+ },
+
+ "host_to_guest_signal_table" : {
+ "num_nodes_lg2" : 2
+ }
+ },
+
+ {
+ "device_name" : "fb_broadcast",
+ "__comment" : "Broadcasts new frames",
+ "current_version" : 0,
+ "min_compatible_version" : 0,
+ "region_size": 4096,
+
+ "guest_to_host_signal_table" : {
+ "__comment" : "sizeof each node is based on common/libs/shm_compatible/lock.h",
+ "num_nodes_lg2" : 2
+ },
+
+ "host_to_guest_signal_table" : {
+ "num_nodes_lg2" : 2
+ }
+ },
+
+ {
+ "device_name" : "gralloc_manager",
+ "__comment" : "Manages allocation of gralloc buffers",
+ "current_version" : 0,
+ "min_compatible_version" : 0,
+ "region_size" : 40960,
+
+ "guest_to_host_signal_table" : {
+ "__comment" : "sizeof each node is based on common/libs/shm/lock.h",
+ "num_nodes_lg2" : 2
+ },
+
+ "host_to_guest_signal_table" : {
+ "num_nodes_lg2" : 2
+ }
+ },
+
+ {
+ "device_name" : "gralloc_memory",
+ "__comment" : "Holds the gralloc buffers",
+ "managed_by" : "gralloc_manager",
+ "current_version" : 0,
+ "min_compatible_version" : 0,
+ "region_size" : 419430400,
+
+ "guest_to_host_signal_table" : {
+ "__comment" : "sizeof each node is based on common/libs/shm/lock.h",
+ "num_nodes_lg2" : 0
+ },
+
+ "host_to_guest_signal_table" : {
+ "num_nodes_lg2" : 0
+ }
+ },
+
+ {
+ "device_name" : "wifi_exchange",
+ "__comment" : [
+ "WIFI exchange buffer for primary wireless network interface.",
+ "Must be able to accomodate at least one pagesize long packet each direction",
+ "Region size = 2 * CircularPacketQueue64k_size + 1024 bytes for control structures."
+ ],
+ "current_version" : 0,
+ "min_compatible_version" : 0,
+ "region_size" : 139264,
+
+ "guest_to_host_signal_table" : {
+ "num_nodes_lg2" : 2
+ },
+
+ "host_to_guest_signal_table" : {
+ "num_nodes_lg2" : 2
+ }
+ },
+
+
+ {
+ "device_name" : "e2e_primary",
+ "__comment" : "Sanity testing",
+ "current_version" : 1,
+ "min_compatible_version" : 1,
+ "region_size" : 16384,
+
+ "guest_to_host_signal_table" : {
+ "num_nodes_lg2" : 1
+ },
+
+ "host_to_guest_signal_table" : {
+ "num_nodes_lg2" : 1
+ }
+ },
+
+ {
+ "device_name" : "e2e_secondary",
+ "__comment" : "Sanity testing",
+ "current_version" : 1,
+ "min_compatible_version" : 1,
+ "region_size" : 16384,
+
+ "guest_to_host_signal_table" : {
+ "num_nodes_lg2" : 1
+ },
+
+ "host_to_guest_signal_table" : {
+ "num_nodes_lg2" : 1
+ }
+ },
+
+ {
+ "device_name" : "e2e_manager",
+ "__comment" : "Sanity testing of managed regions",
+ "current_version" : 1,
+ "min_compatible_version" : 1,
+ "region_size" : 4096,
+
+ "guest_to_host_signal_table" : {
+ "num_nodes_lg2" : 1
+ },
+
+ "host_to_guest_signal_table" : {
+ "num_nodes_lg2" : 1
+ }
+ },
+
+ {
+ "device_name" : "e2e_managed",
+ "__comment" : "Sanity testing of managed regions",
+ "current_version" : 1,
+ "min_compatible_version" : 1,
+ "region_size" : 16384,
+ "managed_by" : "e2e_manager",
+
+ "guest_to_host_signal_table" : {
+ "num_nodes_lg2" : 1
+ },
+
+ "host_to_guest_signal_table" : {
+ "num_nodes_lg2" : 1
+ }
+ }
+ ]
+}
diff --git a/host/frontend/Android.bp b/host/frontend/Android.bp
new file mode 100644
index 0000000..bf82b3c
--- /dev/null
+++ b/host/frontend/Android.bp
@@ -0,0 +1,18 @@
+//
+// Copyright (C) 2017 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.
+
+subdirs = [
+ "vnc_server",
+]
diff --git a/host/frontend/vnc_server/Android.bp b/host/frontend/vnc_server/Android.bp
new file mode 100644
index 0000000..b5ec78b
--- /dev/null
+++ b/host/frontend/vnc_server/Android.bp
@@ -0,0 +1,46 @@
+//
+// Copyright (C) 2017 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.
+
+cc_binary_host {
+ name: "vnc_server",
+ srcs: [
+ "blackboard.cpp",
+ "frame_buffer_watcher.cpp",
+ "jpeg_compressor.cpp",
+ "main.cpp",
+ "simulated_hw_composer.cpp",
+ "tcp_socket.cpp",
+ "virtual_inputs.cpp",
+ "virtual_input_device.cpp",
+ "vnc_client_connection.cpp",
+ "vnc_server.cpp",
+ "vnc_utils.cpp",
+ ],
+ header_libs: [
+ "cuttlefish_glog",
+ ],
+ shared_libs: [
+ "vsoc_lib",
+ "libcuttlefish_fs",
+ "cuttlefish_auto_resources",
+ "libbase",
+ ],
+ static_libs: [
+ "libcuttlefish_host_config",
+ "libjpeg",
+ "libgflags",
+ ],
+ defaults: ["cuttlefish_host_only"],
+}
diff --git a/host/frontend/vnc_server/README.md b/host/frontend/vnc_server/README.md
new file mode 100644
index 0000000..a702303
--- /dev/null
+++ b/host/frontend/vnc_server/README.md
@@ -0,0 +1,40 @@
+# Host side VNC server
+
+## Build instructions
+
+```shell
+bazel build host/frontend/vnc_server/:vnc_server
+```
+
+### Requirements
+
+* libjpeg-turbo8-dev
+
+## Run Instructions
+
+The vnc server receives frame updates from the hwcomposer through the shared
+memory, for which it needs to connect to the ivserver. To send input to the
+instance it temporarily connects to monkey, which is set up to run on the
+instance as a service and listens on port 6445, so you need to forward that port
+to a local socket.
+
+* Make sure to wait for monkey to start (about 16s), it's one of the last
+services to start since it's started by the zygote:
+
+```shell
+adb shell ps -e | grep monkey
+```
+
+* Forward the port to a local socket:
+
+```shell
+adb forward localfilesystem:/tmp/android-cuttlefish-1-input tcp:6445
+```
+
+* Run the server:
+
+```shell
+bazel-bin/host/vnc_server/vnc_server --port=6444 --input_socket=/tmp/android-cuttlefish-1-input
+```
+
+The VNC server will then be listening for connections on port 6444 on the host.
diff --git a/host/frontend/vnc_server/blackboard.cpp b/host/frontend/vnc_server/blackboard.cpp
new file mode 100644
index 0000000..b842648
--- /dev/null
+++ b/host/frontend/vnc_server/blackboard.cpp
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include "host/frontend/vnc_server/blackboard.h"
+
+#include <algorithm>
+#include <utility>
+
+#include <gflags/gflags.h>
+#include <glog/logging.h>
+#include "host/frontend/vnc_server/frame_buffer_watcher.h"
+
+DEFINE_bool(debug_blackboard, false,
+ "Turn on detailed logging for the blackboard");
+
+#define DLOG(LEVEL) \
+ if (FLAGS_debug_blackboard) LOG(LEVEL)
+
+using cvd::vnc::BlackBoard;
+using cvd::vnc::Stripe;
+
+cvd::vnc::SeqNumberVec cvd::vnc::MakeSeqNumberVec() {
+ return SeqNumberVec(FrameBufferWatcher::StripesPerFrame());
+}
+
+void BlackBoard::NewStripeReady(int index, StripeSeqNumber seq_num) {
+ std::lock_guard<std::mutex> guard(m_);
+ DLOG(INFO) << "new stripe arrived from frame watcher";
+ auto& current_seq_num = most_recent_stripe_seq_nums_[index];
+ current_seq_num = std::max(current_seq_num, seq_num);
+ for (auto& client : clients_) {
+ if (client.second.ready_to_receive) {
+ client.second.new_frame_cv.notify_one();
+ }
+ }
+}
+
+void BlackBoard::Register(const VncClientConnection* conn) {
+ {
+ std::lock_guard<std::mutex> guard(m_);
+ CHECK(!clients_.count(conn));
+ clients_[conn]; // constructs new state in place
+ }
+ new_client_cv_.notify_one();
+}
+
+void BlackBoard::Unregister(const VncClientConnection* conn) {
+ std::lock_guard<std::mutex> guard(m_);
+ CHECK(clients_.count(conn));
+ clients_.erase(clients_.find(conn));
+}
+
+bool BlackBoard::NoNewStripesFor(const SeqNumberVec& seq_nums) const {
+ CHECK(seq_nums.size() == most_recent_stripe_seq_nums_.size());
+ for (auto state_seq_num = seq_nums.begin(),
+ held_seq_num = most_recent_stripe_seq_nums_.begin();
+ state_seq_num != seq_nums.end(); ++state_seq_num, ++held_seq_num) {
+ if (*state_seq_num < *held_seq_num) {
+ return false;
+ }
+ }
+ return true;
+}
+
+cvd::vnc::StripePtrVec BlackBoard::WaitForSenderWork(
+ const VncClientConnection* conn) {
+ std::unique_lock<std::mutex> guard(m_);
+ auto& state = GetStateForClient(conn);
+ DLOG(INFO) << "Waiting for stripe...";
+ while (!state.closed &&
+ (!state.ready_to_receive || NoNewStripesFor(state.stripe_seq_nums))) {
+ state.new_frame_cv.wait(guard);
+ }
+ DLOG(INFO) << "At least one new stripe is available, should unblock " << conn;
+ state.ready_to_receive = false;
+ auto new_stripes = frame_buffer_watcher_->StripesNewerThan(
+ state.orientation, state.stripe_seq_nums);
+ for (auto& s : new_stripes) {
+ state.stripe_seq_nums[s->index] = s->seq_number;
+ }
+ return new_stripes;
+}
+
+void BlackBoard::WaitForAtLeastOneClientConnection() {
+ std::unique_lock<std::mutex> guard(m_);
+ while (clients_.empty()) {
+ new_client_cv_.wait(guard);
+ }
+}
+
+void BlackBoard::SetOrientation(const VncClientConnection* conn,
+ ScreenOrientation orientation) {
+ std::lock_guard<std::mutex> guard(m_);
+ auto& state = GetStateForClient(conn);
+ state.orientation = orientation;
+ // After an orientation change the vnc client will need all stripes from
+ // the new orientation, regardless of age.
+ ResetToZero(&state.stripe_seq_nums);
+}
+
+void BlackBoard::SignalClientNeedsEntireScreen(
+ const VncClientConnection* conn) {
+ std::lock_guard<std::mutex> guard(m_);
+ ResetToZero(&GetStateForClient(conn).stripe_seq_nums);
+}
+
+void BlackBoard::ResetToZero(SeqNumberVec* seq_nums) {
+ seq_nums->assign(FrameBufferWatcher::StripesPerFrame(), StripeSeqNumber{});
+}
+
+void BlackBoard::FrameBufferUpdateRequestReceived(
+ const VncClientConnection* conn) {
+ std::lock_guard<std::mutex> guard(m_);
+ DLOG(INFO) << "Received frame buffer update request";
+ auto& state = GetStateForClient(conn);
+ state.ready_to_receive = true;
+ state.new_frame_cv.notify_one();
+}
+
+void BlackBoard::StopWaiting(const VncClientConnection* conn) {
+ std::lock_guard<std::mutex> guard(m_);
+ auto& state = GetStateForClient(conn);
+ state.closed = true;
+ // Wake up the thread that might be in WaitForSenderWork()
+ state.new_frame_cv.notify_one();
+}
+
+void BlackBoard::set_frame_buffer_watcher(
+ cvd::vnc::FrameBufferWatcher* frame_buffer_watcher) {
+ std::lock_guard<std::mutex> guard(m_);
+ frame_buffer_watcher_ = frame_buffer_watcher;
+}
+
+void BlackBoard::set_jpeg_quality_level(int quality_level) {
+ // NOTE all vnc clients share a common jpeg quality level because the
+ // server doesn't compress per-client. The quality level for all clients
+ // will be whatever the most recent set was by any client.
+ std::lock_guard<std::mutex> guard(m_);
+ if (quality_level < kJpegMinQualityEncoding ||
+ quality_level > kJpegMaxQualityEncoding) {
+ LOG(WARNING) << "Bogus jpeg quality level: " << quality_level
+ << ". Quality must be in range [" << kJpegMinQualityEncoding
+ << ", " << kJpegMaxQualityEncoding << "]";
+ return;
+ }
+ jpeg_quality_level_ = 55 + (5 * (quality_level + 32));
+ DLOG(INFO) << "jpeg quality level set to " << jpeg_quality_level_ << "%";
+}
+
+BlackBoard::ClientFBUState& BlackBoard::GetStateForClient(
+ const VncClientConnection* conn) {
+ CHECK(clients_.count(conn));
+ return clients_[conn];
+}
diff --git a/host/frontend/vnc_server/blackboard.h b/host/frontend/vnc_server/blackboard.h
new file mode 100644
index 0000000..1119dd3
--- /dev/null
+++ b/host/frontend/vnc_server/blackboard.h
@@ -0,0 +1,113 @@
+#pragma once
+
+/*
+ * Copyright (C) 2017 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.
+ */
+
+
+#include <condition_variable>
+#include <memory>
+#include <mutex>
+#include <unordered_map>
+
+#include "common/libs/threads/thread_annotations.h"
+#include "host/frontend/vnc_server/vnc_utils.h"
+
+namespace cvd {
+namespace vnc {
+
+class VncClientConnection;
+class FrameBufferWatcher;
+using StripePtrVec = std::vector<std::shared_ptr<const Stripe>>;
+using SeqNumberVec = std::vector<StripeSeqNumber>;
+
+SeqNumberVec MakeSeqNumberVec();
+
+class BlackBoard {
+ private:
+ struct ClientFBUState {
+ bool ready_to_receive{};
+ ScreenOrientation orientation{};
+ std::condition_variable new_frame_cv;
+ SeqNumberVec stripe_seq_nums = MakeSeqNumberVec();
+ bool closed{};
+ };
+
+ public:
+ class Registerer {
+ public:
+ Registerer(BlackBoard* bb, const VncClientConnection* conn)
+ : bb_{bb}, conn_{conn} {
+ bb->Register(conn);
+ }
+ ~Registerer() { bb_->Unregister(conn_); }
+ Registerer(const Registerer&) = delete;
+ Registerer& operator=(const Registerer&) = delete;
+
+ private:
+ BlackBoard* bb_{};
+ const VncClientConnection* conn_{};
+ };
+
+ BlackBoard() = default;
+ BlackBoard(const BlackBoard&) = delete;
+ BlackBoard& operator=(const BlackBoard&) = delete;
+
+ bool NoNewStripesFor(const SeqNumberVec& seq_nums) const REQUIRES(m_);
+ void NewStripeReady(int index, StripeSeqNumber seq_num);
+ void Register(const VncClientConnection* conn);
+ void Unregister(const VncClientConnection* conn);
+
+ StripePtrVec WaitForSenderWork(const VncClientConnection* conn);
+
+ void WaitForAtLeastOneClientConnection();
+
+ void FrameBufferUpdateRequestReceived(const VncClientConnection* conn);
+ // Setting orientation implies needing the entire screen
+ void SetOrientation(const VncClientConnection* conn,
+ ScreenOrientation orientation);
+ void SignalClientNeedsEntireScreen(const VncClientConnection* conn);
+
+ void StopWaiting(const VncClientConnection* conn);
+
+ void set_frame_buffer_watcher(FrameBufferWatcher* frame_buffer_watcher);
+
+ // quality_level must be the value received from the client, in the range
+ // [kJpegMinQualityEncoding, kJpegMaxQualityEncoding], else it is ignored.
+ void set_jpeg_quality_level(int quality_level);
+
+ int jpeg_quality_level() const {
+ std::lock_guard<std::mutex> guard(m_);
+ return jpeg_quality_level_;
+ }
+
+ private:
+ ClientFBUState& GetStateForClient(const VncClientConnection* conn)
+ REQUIRES(m_);
+ static void ResetToZero(SeqNumberVec* seq_nums);
+
+ mutable std::mutex m_;
+ SeqNumberVec most_recent_stripe_seq_nums_ GUARDED_BY(m_) = MakeSeqNumberVec();
+ std::unordered_map<const VncClientConnection*, ClientFBUState> clients_
+ GUARDED_BY(m_);
+ int jpeg_quality_level_ GUARDED_BY(m_) = 100;
+ std::condition_variable new_client_cv_;
+ // NOTE the FrameBufferWatcher pointer itself should be
+ // guarded, but not the pointee.
+ FrameBufferWatcher* frame_buffer_watcher_ GUARDED_BY(m_){};
+};
+
+} // namespace vnc
+} // namespace cvd
diff --git a/host/frontend/vnc_server/frame_buffer_watcher.cpp b/host/frontend/vnc_server/frame_buffer_watcher.cpp
new file mode 100644
index 0000000..a09269a
--- /dev/null
+++ b/host/frontend/vnc_server/frame_buffer_watcher.cpp
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include "host/frontend/vnc_server/frame_buffer_watcher.h"
+
+#include <algorithm>
+#include <cstdint>
+#include <cstring>
+#include <iterator>
+#include <memory>
+#include <mutex>
+#include <thread>
+#include <utility>
+
+#include <glog/logging.h>
+#include "host/frontend/vnc_server/vnc_utils.h"
+
+using cvd::vnc::FrameBufferWatcher;
+
+FrameBufferWatcher::FrameBufferWatcher(BlackBoard* bb)
+ : bb_{bb}, hwcomposer{bb_} {
+ for (auto& stripes_vec : stripes_) {
+ std::generate_n(std::back_inserter(stripes_vec),
+ SimulatedHWComposer::NumberOfStripes(),
+ std::make_shared<Stripe>);
+ }
+ bb_->set_frame_buffer_watcher(this);
+ auto num_workers = std::max(std::thread::hardware_concurrency(), 1u);
+ std::generate_n(std::back_inserter(workers_), num_workers, [this] {
+ return std::thread{&FrameBufferWatcher::Worker, this};
+ });
+}
+
+FrameBufferWatcher::~FrameBufferWatcher() {
+ {
+ std::lock_guard<std::mutex> guard(m_);
+ closed_ = true;
+ }
+ for (auto& tid : workers_) {
+ tid.join();
+ }
+}
+
+bool FrameBufferWatcher::closed() const {
+ std::lock_guard<std::mutex> guard(m_);
+ return closed_;
+}
+
+cvd::vnc::Stripe FrameBufferWatcher::Rotated(Stripe stripe) {
+ if (stripe.orientation == ScreenOrientation::Landscape) {
+ LOG(FATAL) << "Rotating a landscape stripe, this is a mistake";
+ }
+ auto w = stripe.width;
+ auto h = stripe.height;
+ const auto& raw = stripe.raw_data;
+ Message rotated(raw.size(), 0xAA);
+ for (std::uint16_t i = 0; i < w; ++i) {
+ for (std::uint16_t j = 0; j < h; ++j) {
+ auto to = (i * h + j) * BytesPerPixel();
+ auto from = (w - (i + 1) + w * j) * BytesPerPixel();
+ CHECK(from < raw.size());
+ CHECK(to < rotated.size());
+ std::memcpy(&rotated[to], &raw[from], BytesPerPixel());
+ }
+ }
+ std::swap(stripe.x, stripe.y);
+ std::swap(stripe.width, stripe.height);
+ stripe.raw_data = std::move(rotated);
+ stripe.orientation = ScreenOrientation::Landscape;
+ return stripe;
+}
+
+bool FrameBufferWatcher::StripeIsDifferentFromPrevious(
+ const Stripe& stripe) const {
+ return Stripes(stripe.orientation)[stripe.index]->raw_data != stripe.raw_data;
+}
+
+cvd::vnc::StripePtrVec FrameBufferWatcher::StripesNewerThan(
+ ScreenOrientation orientation, const SeqNumberVec& seq_numbers) const {
+ std::lock_guard<std::mutex> guard(stripes_lock_);
+ const auto& stripes = Stripes(orientation);
+ CHECK(seq_numbers.size() == stripes.size());
+ StripePtrVec new_stripes;
+ auto seq_number_it = seq_numbers.begin();
+ std::copy_if(stripes.begin(), stripes.end(), std::back_inserter(new_stripes),
+ [seq_number_it](const StripePtrVec::value_type& s) mutable {
+ return *(seq_number_it++) < s->seq_number;
+ });
+ return new_stripes;
+}
+
+cvd::vnc::StripePtrVec& FrameBufferWatcher::Stripes(
+ ScreenOrientation orientation) {
+ return stripes_[static_cast<int>(orientation)];
+}
+
+const cvd::vnc::StripePtrVec& FrameBufferWatcher::Stripes(
+ ScreenOrientation orientation) const {
+ return stripes_[static_cast<int>(orientation)];
+}
+
+bool FrameBufferWatcher::UpdateMostRecentSeqNumIfStripeIsNew(
+ const Stripe& stripe) {
+ if (most_recent_identical_stripe_seq_nums_[stripe.index] <=
+ stripe.seq_number) {
+ most_recent_identical_stripe_seq_nums_[stripe.index] = stripe.seq_number;
+ return true;
+ }
+ return false;
+}
+
+bool FrameBufferWatcher::UpdateStripeIfStripeIsNew(
+ const std::shared_ptr<const Stripe>& stripe) {
+ std::lock_guard<std::mutex> guard(stripes_lock_);
+ if (UpdateMostRecentSeqNumIfStripeIsNew(*stripe)) {
+ Stripes(stripe->orientation)[stripe->index] = stripe;
+ return true;
+ }
+ return false;
+}
+
+void FrameBufferWatcher::CompressStripe(JpegCompressor* jpeg_compressor,
+ Stripe* stripe) {
+ stripe->jpeg_data = jpeg_compressor->Compress(
+ stripe->raw_data, bb_->jpeg_quality_level(), 0, 0, stripe->width,
+ stripe->height, stripe->width);
+}
+
+void FrameBufferWatcher::Worker() {
+ JpegCompressor jpeg_compressor;
+#ifdef FUZZ_TEST_VNC
+ std::default_random_engine e{std::random_device{}()};
+ std::uniform_int_distribution<int> random{0, 2};
+#endif
+ while (!closed()) {
+ auto portrait_stripe = hwcomposer.GetNewStripe();
+ if (closed()) {
+ break;
+ }
+ {
+ // TODO(haining) use if (with init) and else for c++17 instead of extra
+ // scope and continue
+ // if (std::lock_guard guard(stripes_lock_); /*condition*/) { }
+ std::lock_guard<std::mutex> guard(stripes_lock_);
+ if (!StripeIsDifferentFromPrevious(portrait_stripe)) {
+ UpdateMostRecentSeqNumIfStripeIsNew(portrait_stripe);
+ continue;
+ }
+ }
+ auto seq_num = portrait_stripe.seq_number;
+ auto index = portrait_stripe.index;
+ auto landscape_stripe = Rotated(portrait_stripe);
+ auto stripes = {std::make_shared<Stripe>(std::move(portrait_stripe)),
+ std::make_shared<Stripe>(std::move(landscape_stripe))};
+ for (auto& stripe : stripes) {
+#ifdef FUZZ_TEST_VNC
+ if (random(e)) {
+ usleep(10000);
+ }
+#endif
+ CompressStripe(&jpeg_compressor, stripe.get());
+ }
+ bool any_new_stripes = false;
+ for (auto& stripe : stripes) {
+ any_new_stripes = UpdateStripeIfStripeIsNew(stripe) || any_new_stripes;
+ }
+ if (any_new_stripes) {
+ bb_->NewStripeReady(index, seq_num);
+ }
+ }
+}
+
+int FrameBufferWatcher::StripesPerFrame() {
+ return SimulatedHWComposer::NumberOfStripes();
+}
diff --git a/host/frontend/vnc_server/frame_buffer_watcher.h b/host/frontend/vnc_server/frame_buffer_watcher.h
new file mode 100644
index 0000000..40ab270
--- /dev/null
+++ b/host/frontend/vnc_server/frame_buffer_watcher.h
@@ -0,0 +1,76 @@
+#pragma once
+
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include <memory>
+#include <mutex>
+#include <thread>
+#include <utility>
+#include <vector>
+
+#include "common/libs/threads/thread_annotations.h"
+#include "host/frontend/vnc_server/blackboard.h"
+#include "host/frontend/vnc_server/jpeg_compressor.h"
+#include "host/frontend/vnc_server/simulated_hw_composer.h"
+
+namespace cvd {
+namespace vnc {
+class FrameBufferWatcher {
+ public:
+ explicit FrameBufferWatcher(BlackBoard* bb);
+ FrameBufferWatcher(const FrameBufferWatcher&) = delete;
+ FrameBufferWatcher& operator=(const FrameBufferWatcher&) = delete;
+ ~FrameBufferWatcher();
+
+ StripePtrVec StripesNewerThan(ScreenOrientation orientation,
+ const SeqNumberVec& seq_num) const;
+ static int StripesPerFrame();
+
+ private:
+ static Stripe Rotated(Stripe stripe);
+
+ bool closed() const;
+ bool StripeIsDifferentFromPrevious(const Stripe& stripe) const
+ REQUIRES(stripes_lock_);
+ // returns true if stripe is still considered new and seq number was updated
+ bool UpdateMostRecentSeqNumIfStripeIsNew(const Stripe& stripe)
+ REQUIRES(stripes_lock_);
+ // returns true if stripe is still considered new and was updated
+ bool UpdateStripeIfStripeIsNew(const std::shared_ptr<const Stripe>& stripe)
+ EXCLUDES(stripes_lock_);
+ // Compresses stripe->raw_data to stripe->jpeg_data
+ void CompressStripe(JpegCompressor* jpeg_compressor, Stripe* stripe);
+ void Worker();
+ void Updater();
+
+ StripePtrVec& Stripes(ScreenOrientation orientation) REQUIRES(stripes_lock_);
+ const StripePtrVec& Stripes(ScreenOrientation orientation) const
+ REQUIRES(stripes_lock_);
+
+ std::vector<std::thread> workers_;
+ mutable std::mutex stripes_lock_;
+ std::array<StripePtrVec, kNumOrientations> stripes_ GUARDED_BY(stripes_lock_);
+ SeqNumberVec most_recent_identical_stripe_seq_nums_
+ GUARDED_BY(stripes_lock_) = MakeSeqNumberVec();
+ mutable std::mutex m_;
+ bool closed_ GUARDED_BY(m_){};
+ BlackBoard* bb_{};
+ SimulatedHWComposer hwcomposer{bb_};
+};
+
+} // namespace vnc
+} // namespace cvd
diff --git a/host/frontend/vnc_server/jpeg_compressor.cpp b/host/frontend/vnc_server/jpeg_compressor.cpp
new file mode 100644
index 0000000..711a762
--- /dev/null
+++ b/host/frontend/vnc_server/jpeg_compressor.cpp
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include <stdio.h> // stdio.h must appear before jpeglib.h
+#include <jpeglib.h>
+
+#include <glog/logging.h>
+#include "host/frontend/vnc_server/jpeg_compressor.h"
+#include "host/frontend/vnc_server/vnc_utils.h"
+
+using cvd::vnc::JpegCompressor;
+
+namespace {
+void InitCinfo(jpeg_compress_struct* cinfo, jpeg_error_mgr* err,
+ std::uint16_t width, std::uint16_t height, int jpeg_quality) {
+ cinfo->err = jpeg_std_error(err);
+ jpeg_create_compress(cinfo);
+
+ cinfo->image_width = width;
+ cinfo->image_height = height;
+ cinfo->input_components = cvd::vnc::BytesPerPixel();
+ cinfo->in_color_space = JCS_EXT_RGBX;
+
+ jpeg_set_defaults(cinfo);
+ jpeg_set_quality(cinfo, jpeg_quality, true);
+}
+} // namespace
+
+cvd::vnc::Message JpegCompressor::Compress(const Message& frame,
+ int jpeg_quality, std::uint16_t x,
+ std::uint16_t y, std::uint16_t width,
+ std::uint16_t height,
+ int screen_width) {
+ jpeg_compress_struct cinfo{};
+ jpeg_error_mgr err{};
+ InitCinfo(&cinfo, &err, width, height, jpeg_quality);
+
+ auto* compression_buffer = buffer_.get();
+ auto compression_buffer_size = buffer_capacity_;
+ jpeg_mem_dest(&cinfo, &compression_buffer, &compression_buffer_size);
+ jpeg_start_compress(&cinfo, true);
+
+ while (cinfo.next_scanline < cinfo.image_height) {
+ auto row = static_cast<JSAMPROW>(const_cast<std::uint8_t*>(
+ &frame[(y * screen_width * BytesPerPixel()) +
+ (cinfo.next_scanline * BytesPerPixel() * screen_width) +
+ (x * BytesPerPixel())]));
+ jpeg_write_scanlines(&cinfo, &row, 1);
+ }
+ jpeg_finish_compress(&cinfo);
+ jpeg_destroy_compress(&cinfo);
+
+ UpdateBuffer(compression_buffer, compression_buffer_size);
+ return {compression_buffer, compression_buffer + compression_buffer_size};
+}
+
+void JpegCompressor::UpdateBuffer(std::uint8_t* compression_buffer,
+ unsigned long compression_buffer_size) {
+ if (buffer_.get() != compression_buffer) {
+ buffer_capacity_ = compression_buffer_size;
+ buffer_.reset(compression_buffer);
+ }
+}
diff --git a/host/frontend/vnc_server/jpeg_compressor.h b/host/frontend/vnc_server/jpeg_compressor.h
new file mode 100644
index 0000000..ae3af18
--- /dev/null
+++ b/host/frontend/vnc_server/jpeg_compressor.h
@@ -0,0 +1,52 @@
+#pragma once
+
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include <cstdint>
+#include <cstdlib>
+#include <memory>
+
+#include "host/frontend/vnc_server/vnc_utils.h"
+
+namespace cvd {
+namespace vnc {
+
+// libjpeg-turbo with jpeg_mem_dest (using memory as a destination) is funky.
+// If you give it a buffer that is big enough it will use it.
+// If you give it a buffer that is too small, it will allocate a new buffer
+// but will NOT free the buffer you gave it.
+// This class keeps track of the capacity of the working buffer, and frees the
+// old buffer if libjpeg-turbo silently discards it.
+class JpegCompressor {
+ public:
+ Message Compress(const Message& frame, int jpeg_quality, std::uint16_t x,
+ std::uint16_t y, std::uint16_t width, std::uint16_t height,
+ int screen_width);
+
+ private:
+ void UpdateBuffer(std::uint8_t* compression_buffer,
+ unsigned long compression_buffer_size);
+ struct Freer {
+ void operator()(void* p) const { std::free(p); }
+ };
+
+ std::unique_ptr<std::uint8_t, Freer> buffer_;
+ unsigned long buffer_capacity_{};
+};
+
+} // namespace vnc
+} // namespace cvd
diff --git a/host/frontend/vnc_server/keysyms.h b/host/frontend/vnc_server/keysyms.h
new file mode 100644
index 0000000..41ff7c7
--- /dev/null
+++ b/host/frontend/vnc_server/keysyms.h
@@ -0,0 +1,54 @@
+#pragma once
+
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include <cstdint>
+
+namespace cvd {
+namespace xk {
+
+constexpr uint32_t BackSpace = 0xff08, Tab = 0xff09, Return = 0xff0d,
+ Enter = Return, Escape = 0xff1b, MultiKey = 0xff20,
+ Insert = 0xff63, Delete = 0xffff, Pause = 0xff13,
+ Home = 0xff50, End = 0xff57, PageUp = 0xff55,
+ PageDown = 0xff56, Left = 0xff51, Up = 0xff52,
+ Right = 0xff53, Down = 0xff54, F1 = 0xffbe, F2 = 0xffbf,
+ F3 = 0xffc0, F4 = 0xffc1, F5 = 0xffc2, F6 = 0xffc3,
+ F7 = 0xffc4, F8 = 0xffc5, F9 = 0xffc6, F10 = 0xffc7,
+ F11 = 0xffc8, F12 = 0xffc9, F13 = 0xffca, F14 = 0xffcb,
+ F15 = 0xffcc, F16 = 0xffcd, F17 = 0xffce, F18 = 0xffcf,
+ F19 = 0xffd0, F20 = 0xffd1, F21 = 0xffd2, F22 = 0xffd3,
+ F23 = 0xffd4, F24 = 0xffd5, ShiftLeft = 0xffe1,
+ ShiftRight = 0xffe2, ControlLeft = 0xffe3,
+ ControlRight = 0xffe4, MetaLeft = 0xffe7, MetaRight = 0xffe8,
+ AltLeft = 0xffe9, AltRight = 0xffea, CapsLock = 0xffe5,
+ NumLock = 0xff7f, ScrollLock = 0xff14, Keypad0 = 0xffb0,
+ Keypad1 = 0xffb1, Keypad2 = 0xffb2, Keypad3 = 0xffb3,
+ Keypad4 = 0xffb4, Keypad5 = 0xffb5, Keypad6 = 0xffb6,
+ Keypad7 = 0xffb7, Keypad8 = 0xffb8, Keypad9 = 0xffb9,
+ KeypadMultiply = 0xffaa, KeypadSubtract = 0xffad,
+ KeypadAdd = 0xffab, KeypadDecimal = 0xffae,
+ KeypadEnter = 0xff8d, KeypadDivide = 0xffaf,
+ KeypadEqual = 0xffbd, PlusMinus = 0xb1, SysReq = 0xff15,
+ LineFeed = 0xff0a, KeypadSeparator = 0xffac, Yen = 0xa5,
+ Cancel = 0xff69, Undo = 0xff65, Redo = 0xff66, Find = 0xff68,
+ Print = 0xff61, VolumeDown = 0x1008ff11, Mute = 0x1008ff12,
+ VolumeUp = 0x1008ff13, Menu = 0xff67,
+ VNCMenu = 0xffed; // VNC seems to translate MENU to this
+
+} // namespace xk
+} // namespace cvd
diff --git a/host/frontend/vnc_server/main.cpp b/host/frontend/vnc_server/main.cpp
new file mode 100644
index 0000000..65971d4
--- /dev/null
+++ b/host/frontend/vnc_server/main.cpp
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include <algorithm>
+#include <string>
+
+#include <gflags/gflags.h>
+
+#include "common/libs/glog/logging.h"
+#include "host/frontend/vnc_server/vnc_server.h"
+#include "host/frontend/vnc_server/vnc_utils.h"
+#include "host/libs/config/host_config.h"
+
+DEFINE_bool(agressive, false, "Whether to use agressive server");
+DEFINE_int32(port, 6444, "Port where to listen for connections");
+
+int main(int argc, char* argv[]) {
+ using ::android::base::ERROR;
+ ::android::base::InitLogging(argv, android::base::StderrLogger);
+ ::gflags::ParseCommandLineFlags(&argc, &argv, true);
+ if (!cvd::vnc::GetFBBroadcastRegionView()->Open(vsoc::GetDomain().c_str())) {
+ LOG(FATAL) << "Unable to open FBBroadcastRegion";
+ }
+ cvd::vnc::VncServer vnc_server(FLAGS_port, FLAGS_agressive);
+ vnc_server.MainLoop();
+}
diff --git a/host/frontend/vnc_server/mocks.h b/host/frontend/vnc_server/mocks.h
new file mode 100644
index 0000000..e69eb43
--- /dev/null
+++ b/host/frontend/vnc_server/mocks.h
@@ -0,0 +1,35 @@
+#pragma once
+
+/*
+ * Copyright (C) 2017 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.
+ */
+
+struct GceFrameBuffer {
+ typedef uint32_t Pixel;
+
+ static const int kRedShift = 0;
+ static const int kRedBits = 8;
+ static const int kGreenShift = 8;
+ static const int kGreenBits = 8;
+ static const int kBlueShift = 16;
+ static const int kBlueBits = 8;
+ static const int kAlphaShift = 24;
+ static const int kAlphaBits = 8;
+};
+
+// Sensors
+struct gce_sensors_message {
+ static constexpr const char* const kSensorsHALSocketName = "";
+};
diff --git a/host/frontend/vnc_server/simulated_hw_composer.cpp b/host/frontend/vnc_server/simulated_hw_composer.cpp
new file mode 100644
index 0000000..001006b
--- /dev/null
+++ b/host/frontend/vnc_server/simulated_hw_composer.cpp
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include "host/frontend/vnc_server/simulated_hw_composer.h"
+
+#include "common/vsoc/lib/typed_region_view.h"
+#include "host/frontend/vnc_server/vnc_utils.h"
+#include "host/libs/config/host_config.h"
+#include "host/vsoc/lib/gralloc_buffer_region_view.h"
+
+using cvd::vnc::SimulatedHWComposer;
+using vsoc::gralloc::GrallocBufferRegionView;
+
+SimulatedHWComposer::SimulatedHWComposer(BlackBoard* bb)
+ :
+#ifdef FUZZ_TEST_VNC
+ engine_{std::random_device{}()},
+#endif
+ bb_{bb},
+ stripes_(kMaxQueueElements, &SimulatedHWComposer::EraseHalfOfElements) {
+ stripe_maker_ = std::thread(&SimulatedHWComposer::MakeStripes, this);
+}
+
+SimulatedHWComposer::~SimulatedHWComposer() {
+ close();
+ stripe_maker_.join();
+}
+
+cvd::vnc::Stripe SimulatedHWComposer::GetNewStripe() {
+ auto s = stripes_.Pop();
+#ifdef FUZZ_TEST_VNC
+ if (random_(engine_)) {
+ usleep(7000);
+ stripes_.Push(std::move(s));
+ s = stripes_.Pop();
+ }
+#endif
+ return s;
+}
+
+bool SimulatedHWComposer::closed() {
+ std::lock_guard<std::mutex> guard(m_);
+ return closed_;
+}
+
+void SimulatedHWComposer::close() {
+ std::lock_guard<std::mutex> guard(m_);
+ closed_ = true;
+}
+
+// Assuming the number of stripes is less than half the size of the queue
+// this will be safe as the newest stripes won't be lost. In the real
+// hwcomposer, where stripes are coming in a different order, the full
+// queue case would probably need a different approach to be safe.
+void SimulatedHWComposer::EraseHalfOfElements(
+ ThreadSafeQueue<Stripe>::QueueImpl* q) {
+ q->erase(q->begin(), std::next(q->begin(), kMaxQueueElements / 2));
+}
+
+void SimulatedHWComposer::MakeStripes() {
+ std::uint32_t previous_seq_num{};
+ auto screen_height = ActualScreenHeight();
+ Message raw_screen;
+ std::uint64_t stripe_seq_num = 1;
+ while (!closed()) {
+ bb_->WaitForAtLeastOneClientConnection();
+ vsoc_reg_off_t buffer_offset =
+ GetFBBroadcastRegionView()->WaitForNewFrameSince(&previous_seq_num);
+
+ const auto* frame_start =
+ GrallocBufferRegionView::GetInstance(vsoc::GetDomain().c_str())
+ ->OffsetToBufferPtr(buffer_offset);
+ raw_screen.assign(frame_start, frame_start + ScreenSizeInBytes());
+
+ for (int i = 0; i < kNumStripes; ++i) {
+ ++stripe_seq_num;
+ std::uint16_t y = (screen_height / kNumStripes) * i;
+
+ // Last frames on the right and/or bottom handle extra pixels
+ // when a screen dimension is not evenly divisible by Frame::kNumSlots.
+ std::uint16_t height =
+ screen_height / kNumStripes +
+ (i + 1 == kNumStripes ? screen_height % kNumStripes : 0);
+ const auto* raw_start =
+ &raw_screen[y * ActualScreenWidth() * BytesPerPixel()];
+ const auto* raw_end =
+ raw_start + (height * ActualScreenWidth() * BytesPerPixel());
+ // creating a named object and setting individual data members in order
+ // to make klp happy
+ // TODO (haining) construct this inside the call when not compiling
+ // on klp
+ Stripe s{};
+ s.index = i;
+ s.frame_id = previous_seq_num;
+ s.x = 0;
+ s.y = y;
+ s.width = ActualScreenWidth();
+ s.height = height;
+ s.raw_data.assign(raw_start, raw_end);
+ s.seq_number = StripeSeqNumber{stripe_seq_num};
+ s.orientation = ScreenOrientation::Portrait;
+ stripes_.Push(std::move(s));
+ }
+ }
+}
+
+int SimulatedHWComposer::NumberOfStripes() { return kNumStripes; }
diff --git a/host/frontend/vnc_server/simulated_hw_composer.h b/host/frontend/vnc_server/simulated_hw_composer.h
new file mode 100644
index 0000000..6078584
--- /dev/null
+++ b/host/frontend/vnc_server/simulated_hw_composer.h
@@ -0,0 +1,65 @@
+#pragma once
+
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include <condition_variable>
+#include <mutex>
+#ifdef FUZZ_TEST_VNC
+#include <random>
+#endif
+#include <thread>
+
+#include "common/libs/thread_safe_queue/thread_safe_queue.h"
+#include "common/libs/threads/thread_annotations.h"
+#include "common/vsoc/lib/fb_bcast_region_view.h"
+#include "host/frontend/vnc_server/blackboard.h"
+
+namespace cvd {
+namespace vnc {
+class SimulatedHWComposer {
+ public:
+ SimulatedHWComposer(BlackBoard* bb);
+ SimulatedHWComposer(const SimulatedHWComposer&) = delete;
+ SimulatedHWComposer& operator=(const SimulatedHWComposer&) = delete;
+ ~SimulatedHWComposer();
+
+ Stripe GetNewStripe();
+
+ // NOTE not constexpr on purpose
+ static int NumberOfStripes();
+
+ private:
+ bool closed();
+ void close();
+ static void EraseHalfOfElements(ThreadSafeQueue<Stripe>::QueueImpl* q);
+ void MakeStripes();
+
+#ifdef FUZZ_TEST_VNC
+ std::default_random_engine engine_;
+ std::uniform_int_distribution<int> random_ =
+ std::uniform_int_distribution<int>{0, 2};
+#endif
+ static constexpr int kNumStripes = 8;
+ constexpr static std::size_t kMaxQueueElements = 64;
+ bool closed_ GUARDED_BY(m_){};
+ std::mutex m_;
+ BlackBoard* bb_{};
+ ThreadSafeQueue<Stripe> stripes_;
+ std::thread stripe_maker_;
+};
+} // namespace vnc
+} // namespace cvd
diff --git a/host/frontend/vnc_server/tcp_socket.cpp b/host/frontend/vnc_server/tcp_socket.cpp
new file mode 100644
index 0000000..92aed93
--- /dev/null
+++ b/host/frontend/vnc_server/tcp_socket.cpp
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include "host/frontend/vnc_server/tcp_socket.h"
+
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+
+#include <cerrno>
+
+#include <glog/logging.h>
+
+using cvd::SharedFD;
+using cvd::vnc::ClientSocket;
+using cvd::vnc::Message;
+using cvd::vnc::ServerSocket;
+
+Message ClientSocket::Recv(size_t length) {
+ Message buf(length);
+ ssize_t total_read = 0;
+ while (total_read < static_cast<ssize_t>(length)) {
+ auto just_read = fd_->Read(&buf[total_read], buf.size() - total_read);
+ if (just_read <= 0) {
+ if (just_read < 0) {
+ LOG(ERROR) << "read() error: " << strerror(errno);
+ }
+ other_side_closed_ = true;
+ return Message{};
+ }
+ total_read += just_read;
+ }
+ CHECK(total_read == static_cast<ssize_t>(length));
+ return buf;
+}
+
+ssize_t ClientSocket::Send(const uint8_t* data, std::size_t size) {
+ std::lock_guard<std::mutex> lock(send_lock_);
+ ssize_t written{};
+ while (written < static_cast<ssize_t>(size)) {
+ auto just_written = fd_->Write(data + written, size - written);
+ if (just_written <= 0) {
+ LOG(INFO) << "Couldn't write to vnc client: " << strerror(errno);
+ return just_written;
+ }
+ written += just_written;
+ }
+ return written;
+}
+
+ssize_t ClientSocket::Send(const Message& message) {
+ return Send(&message[0], message.size());
+}
+
+ServerSocket::ServerSocket(int port)
+ : fd_{SharedFD::SocketLocalServer(port, SOCK_STREAM)} {
+ if (!fd_->IsOpen()) {
+ LOG(FATAL) << "Couldn't open streaming server on port " << port;
+ }
+}
+
+ClientSocket ServerSocket::Accept() {
+ SharedFD client = SharedFD::Accept(*fd_);
+ if (!client->IsOpen()) {
+ LOG(FATAL) << "Error attemping to accept: " << strerror(errno);
+ }
+ return ClientSocket{client};
+}
diff --git a/host/frontend/vnc_server/tcp_socket.h b/host/frontend/vnc_server/tcp_socket.h
new file mode 100644
index 0000000..f926642
--- /dev/null
+++ b/host/frontend/vnc_server/tcp_socket.h
@@ -0,0 +1,80 @@
+#pragma once
+
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include "host/frontend/vnc_server/vnc_utils.h"
+
+#include <unistd.h>
+
+#include <cstddef>
+#include <cstdint>
+#include <mutex>
+
+namespace cvd {
+namespace vnc {
+
+class ServerSocket;
+
+// Recv and Send wait until all data has been received or sent.
+// Send is thread safe in this regard, Recv is not.
+class ClientSocket {
+ public:
+ ClientSocket(ClientSocket&& other) : fd_{other.fd_} {}
+
+ ClientSocket& operator=(ClientSocket&& other) {
+ fd_ = other.fd_;
+ return *this;
+ }
+
+ ClientSocket(const ClientSocket&) = delete;
+ ClientSocket& operator=(const ClientSocket&) = delete;
+
+ Message Recv(std::size_t length);
+ ssize_t Send(const std::uint8_t* data, std::size_t size);
+ ssize_t Send(const Message& message);
+
+ template <std::size_t N>
+ ssize_t Send(const std::uint8_t (&data)[N]) {
+ return Send(data, N);
+ }
+
+ bool closed() const { return other_side_closed_; }
+
+ private:
+ friend ServerSocket;
+ explicit ClientSocket(cvd::SharedFD fd) : fd_(fd) {}
+
+ cvd::SharedFD fd_;
+ bool other_side_closed_{};
+ std::mutex send_lock_;
+};
+
+class ServerSocket {
+ public:
+ explicit ServerSocket(int port);
+
+ ServerSocket(const ServerSocket&) = delete;
+ ServerSocket& operator=(const ServerSocket&) = delete;
+
+ ClientSocket Accept();
+
+ private:
+ cvd::SharedFD fd_;
+};
+
+} // namespace vnc
+} // namespace cvd
diff --git a/host/frontend/vnc_server/virtual_input_device.cpp b/host/frontend/vnc_server/virtual_input_device.cpp
new file mode 100644
index 0000000..66cedc2
--- /dev/null
+++ b/host/frontend/vnc_server/virtual_input_device.cpp
@@ -0,0 +1,284 @@
+/*
+ * Copyright (C) 2014 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.
+ */
+
+#include "host/frontend/vnc_server/virtual_input_device.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/input.h>
+#include <linux/uinput.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <vector>
+
+#include <glog/logging.h>
+#include "host/frontend/vnc_server/keysyms.h"
+
+namespace cvd {
+
+//////////////////////////
+// VirtualButton Support
+//////////////////////////
+
+void VirtualButton::HandleButtonPressEvent(bool button_down) {
+ SendCommand(std::string("key ") + "down " + input_keycode_ + "\n");
+}
+
+//////////////////////////
+// VirtualKeyboard Support
+//////////////////////////
+
+struct KeyEventToInput {
+ uint32_t xk;
+ std::string input_code;
+};
+
+static const KeyEventToInput key_table[] = {
+ {xk::AltLeft, "KEYCODE_ALT_LEFT"},
+ {xk::ControlLeft, "KEYCODE_CTRL_LEFT"},
+ {xk::ShiftLeft, "KEYCODE_SHIFT_LEFT"},
+ {xk::AltRight, "KEYCODE_ALT_RIGHT"},
+ {xk::ControlRight, "KEYCODE_CTRL_RIGHT"},
+ {xk::ShiftRight, "KEYCODE_SHIFT_RIGHT"},
+ {xk::MetaLeft, "KEYCODE_META_LEFT"},
+ {xk::MetaRight, "KEYCODE_META_RIGHT"},
+ // {xk::MultiKey, "KEYCODE_COMPOSE"},
+
+ {xk::CapsLock, "KEYCODE_CAPS_LOCK"},
+ {xk::NumLock, "KEYCODE_NUM_LOCK"},
+ {xk::ScrollLock, "KEYCODE_SCROLL_LOCK"},
+
+ {xk::BackSpace, "KEYCODE_DEL"},
+ {xk::Tab, "KEYCODE_TAB"},
+ {xk::Return, "KEYCODE_ENTER"},
+ {xk::Escape, "KEYCODE_ESCAPE"},
+
+ {' ', "KEYCODE_SPACE"},
+ {'!', "KEYCODE_1"},
+ {'"', "KEYCODE_APOSTROPHE"},
+ {'#', "KEYCODE_POUND"},
+ {'$', "KEYCODE_4"},
+ {'%', "KEYCODE_5"},
+ {'^', "KEYCODE_6"},
+ {'&', "KEYCODE_7"},
+ {'\'', "KEYCODE_APOSTROPHE"},
+ {'(', "KEYCODE_NUMPAD_LEFT_PAREN"},
+ {')', "KEYCODE_NUMPAD_RIGHT_PAREN"},
+ {'*', "KEYCODE_STAR"},
+ {'+', "KEYCODE_EQUALS"},
+ {',', "KEYCODE_COMMA"},
+ {'-', "KEYCODE_MINUS"},
+ {'.', "KEYCODE_PERIOD"},
+ {'/', "KEYCODE_SLASH"},
+ {'0', "KEYCODE_0"},
+ {'1', "KEYCODE_1"},
+ {'2', "KEYCODE_2"},
+ {'3', "KEYCODE_3"},
+ {'4', "KEYCODE_4"},
+ {'5', "KEYCODE_5"},
+ {'6', "KEYCODE_6"},
+ {'7', "KEYCODE_7"},
+ {'8', "KEYCODE_8"},
+ {'9', "KEYCODE_9"},
+ {':', "KEYCODE_SEMICOLON"},
+ {';', "KEYCODE_SEMICOLON"},
+ {'<', "KEYCODE_COMMA"},
+ {'=', "KEYCODE_EQUALS"},
+ {'>', "KEYCODE_PERIOD"},
+ {'?', "KEYCODE_SLASH"},
+ {'@', "KEYCODE_2"},
+ {'A', "KEYCODE_A"},
+ {'B', "KEYCODE_B"},
+ {'C', "KEYCODE_C"},
+ {'D', "KEYCODE_D"},
+ {'E', "KEYCODE_E"},
+ {'F', "KEYCODE_F"},
+ {'G', "KEYCODE_G"},
+ {'H', "KEYCODE_H"},
+ {'I', "KEYCODE_I"},
+ {'J', "KEYCODE_J"},
+ {'K', "KEYCODE_K"},
+ {'L', "KEYCODE_L"},
+ {'M', "KEYCODE_M"},
+ {'N', "KEYCODE_N"},
+ {'O', "KEYCODE_O"},
+ {'P', "KEYCODE_P"},
+ {'Q', "KEYCODE_Q"},
+ {'R', "KEYCODE_R"},
+ {'S', "KEYCODE_S"},
+ {'T', "KEYCODE_T"},
+ {'U', "KEYCODE_U"},
+ {'V', "KEYCODE_V"},
+ {'W', "KEYCODE_W"},
+ {'X', "KEYCODE_X"},
+ {'Y', "KEYCODE_Y"},
+ {'Z', "KEYCODE_Z"},
+ {'[', "KEYCODE_LEFT_BRACKET"},
+ {'\\', "KEYCODE_BACKSLASH"},
+ {']', "KEYCODE_RIGHT_BRACKET"},
+ {'-', "KEYCODE_MINUS"},
+ {'_', "KEYCODE_MINUS"},
+ {'`', "KEYCODE_GRAVE"},
+ {'a', "KEYCODE_A"},
+ {'b', "KEYCODE_B"},
+ {'c', "KEYCODE_C"},
+ {'d', "KEYCODE_D"},
+ {'e', "KEYCODE_E"},
+ {'f', "KEYCODE_F"},
+ {'g', "KEYCODE_G"},
+ {'h', "KEYCODE_H"},
+ {'i', "KEYCODE_I"},
+ {'j', "KEYCODE_J"},
+ {'k', "KEYCODE_K"},
+ {'l', "KEYCODE_L"},
+ {'m', "KEYCODE_M"},
+ {'n', "KEYCODE_N"},
+ {'o', "KEYCODE_O"},
+ {'p', "KEYCODE_P"},
+ {'q', "KEYCODE_Q"},
+ {'r', "KEYCODE_R"},
+ {'s', "KEYCODE_S"},
+ {'t', "KEYCODE_T"},
+ {'u', "KEYCODE_U"},
+ {'v', "KEYCODE_V"},
+ {'w', "KEYCODE_W"},
+ {'x', "KEYCODE_X"},
+ {'y', "KEYCODE_Y"},
+ {'z', "KEYCODE_Z"},
+ {'{', "KEYCODE_LEFT_BRACKET"},
+ {'\\', "KEYCODE_BACKSLASH"},
+ // {'|', "|"},
+ {'}', "KEYCODE_RIGHT_BRACKET"},
+ {'~', "KEYCODE_GRAVE"},
+
+ {xk::F1, "KEYCODE_F1"},
+ {xk::F2, "KEYCODE_F2"},
+ {xk::F3, "KEYCODE_F3"},
+ {xk::F4, "KEYCODE_F4"},
+ {xk::F5, "KEYCODE_F5"},
+ {xk::F6, "KEYCODE_F6"},
+ {xk::F7, "KEYCODE_F7"},
+ {xk::F8, "KEYCODE_F8"},
+ {xk::F9, "KEYCODE_F9"},
+ {xk::F10, "KEYCODE_F10"},
+ {xk::F11, "KEYCODE_F11"},
+ {xk::F12, "KEYCODE_F12"},
+ // {xk::F13, "KEYCODE_F13"},
+ // {xk::F14, "KEYCODE_F14"},
+ // {xk::F15, "KEYCODE_F15"},
+ // {xk::F16, "KEYCODE_F16"},
+ // {xk::F17, "KEYCODE_F17"},
+ // {xk::F18, "KEYCODE_F18"},
+ // {xk::F19, "KEYCODE_F19"},
+ // {xk::F20, "KEYCODE_F20"},
+ // {xk::F21, "KEYCODE_F21"},
+ // {xk::F22, "KEYCODE_F22"},
+ // {xk::F23, "KEYCODE_F23"},
+ // {xk::F24, "KEYCODE_F24"},
+
+ {xk::Keypad0, "KEYCODE_NUMPAD_0"},
+ {xk::Keypad1, "KEYCODE_NUMPAD_1"},
+ {xk::Keypad2, "KEYCODE_NUMPAD_2"},
+ {xk::Keypad3, "KEYCODE_NUMPAD_3"},
+ {xk::Keypad4, "KEYCODE_NUMPAD_4"},
+ {xk::Keypad5, "KEYCODE_NUMPAD_5"},
+ {xk::Keypad6, "KEYCODE_NUMPAD_6"},
+ {xk::Keypad7, "KEYCODE_NUMPAD_7"},
+ {xk::Keypad8, "KEYCODE_NUMPAD_8"},
+ {xk::Keypad9, "KEYCODE_NUMPAD_9"},
+ {xk::KeypadMultiply, "KEYCODE_NUMPAD_MULTIPLY"},
+ {xk::KeypadSubtract, "KEYCODE_NUMPAD_SUBTRACT"},
+ {xk::KeypadAdd, "KEYCODE_NUMPAD_ADD"},
+ {xk::KeypadDecimal, "KEYCODE_NUMPAD_DOT"},
+ {xk::KeypadEnter, "KEYCODE_NUMPAD_ENTER"},
+ {xk::KeypadDivide, "KEYCODE_NUMPAD_DIVIDE"},
+ {xk::KeypadEqual, "KEYCODE_NUMPAD_EQUALS"},
+ // {xk::PlusMinus, "KEYCODE_NUMPAD_PLUSMINUS"},
+
+ {xk::SysReq, "KEYCODE_SYSRQ"},
+ // {xk::LineFeed, "KEYCODE_LINEFEED"},
+ {xk::Home, "KEYCODE_HOME"},
+ {xk::Up, "KEYCODE_DPAD_UP"},
+ {xk::PageUp, "KEYCODE_PAGE_UP"},
+ {xk::Left, "KEYCODE_DPAD_LEFT"},
+ {xk::Right, "KEYCODE_DPAD_RIGHT"},
+ {xk::End, "KEYCODE_MOVE_END"},
+ {xk::Down, "KEYCODE_DPAD_DOWN"},
+ {xk::PageDown, "KEYCODE_PAGE_DOWN"},
+ {xk::Insert, "KEYCODE_INSERT"},
+ {xk::Delete, "KEYCODE_FORWARD_DEL"},
+ {xk::Pause, "KEYCODE_BREAK"},
+ {xk::KeypadSeparator, "KEYCODE_NUMPAD_COMMA"},
+ {xk::Yen, "KEYCODE_YEN"},
+ // {xk::Cancel, "KEYCODE_STOP"},
+ // {xk::Redo, "KEYCODE_AGAIN"},
+ // {xk::Undo, "KEYCODE_UNDO"},
+ // {xk::Find, "KEYCODE_FIND"},
+ // {xk::Print, "KEYCODE_PRINT"},
+ {xk::VolumeDown, "KEYCODE_VOLUME_DOWN"},
+ {xk::Mute, "KEYCODE_MUTE"},
+ {xk::VolumeUp, "KEYCODE_VOLUME_UP"},
+ {xk::Menu, "KEYCODE_MENU"},
+ {xk::VNCMenu, "KEYCODE_MENU"},
+};
+
+VirtualKeyboard::VirtualKeyboard(std::function<bool(std::string)> cmd_sender)
+ : VirtualInputDevice(cmd_sender) {
+ for (const auto& key : key_table) {
+ keymapping_[key.xk] = key.input_code;
+ }
+}
+
+void VirtualKeyboard::GenerateKeyPressEvent(int keycode, bool button_down) {
+ if (keymapping_.count(keycode)) {
+ SendCommand(std::string("key ") + (button_down ? "down " : "up ") +
+ keymapping_[keycode] + "\n");
+ } else {
+ LOG(INFO) << "Unknown keycode " << keycode;
+ }
+}
+
+//////////////////////////
+// VirtualTouchPad Support
+//////////////////////////
+
+std::string VirtualTouchPad::GetCommand(bool touch_down, int x, int y) {
+ std::string cmd;
+ if (touch_down == prev_touch_) {
+ if (touch_down && (x != prev_x_ || y != prev_y_)) {
+ cmd = "touch move ";
+ } // else don't repeat last event or send non touch mouse movements
+ } else if (touch_down) {
+ cmd = "touch down ";
+ } else {
+ cmd = "touch up ";
+ }
+ prev_touch_ = touch_down;
+ prev_x_ = x;
+ prev_y_ = y;
+ return cmd;
+}
+
+void VirtualTouchPad::HandlePointerEvent(bool touch_down, int x, int y) {
+ SendCommand(GetCommand(touch_down, x, y) + std::to_string(x) + " " +
+ std::to_string(y) + "\n");
+}
+
+} // namespace cvd
diff --git a/host/frontend/vnc_server/virtual_input_device.h b/host/frontend/vnc_server/virtual_input_device.h
new file mode 100644
index 0000000..5fb0be5
--- /dev/null
+++ b/host/frontend/vnc_server/virtual_input_device.h
@@ -0,0 +1,78 @@
+#pragma once
+
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include <functional>
+#include <map>
+#include <string>
+
+namespace cvd {
+
+// Base virtual input device class which contains a bunch of boiler-plate code.
+class VirtualInputDevice {
+ public:
+ VirtualInputDevice(std::function<bool(std::string)> cmd_sender)
+ : send_command_(cmd_sender) {}
+
+ protected:
+ bool SendCommand(std::string cmd) { return send_command_(cmd); }
+
+ private:
+ std::function<bool(std::string)> send_command_;
+};
+
+// Virtual touch-pad.
+class VirtualTouchPad : public VirtualInputDevice {
+ public:
+ VirtualTouchPad(std::function<bool(std::string)> cmd_sender)
+ : VirtualInputDevice(cmd_sender) {}
+
+ void HandlePointerEvent(bool touch_down, int x, int y);
+
+ private:
+ std::string GetCommand(bool touch_down, int x, int y);
+ bool prev_touch_ = false;
+ int prev_x_ = -1;
+ int prev_y_ = -1;
+};
+
+// Virtual button.
+class VirtualButton : public VirtualInputDevice {
+ public:
+ VirtualButton(std::string input_keycode,
+ std::function<bool(std::string)> cmd_sender)
+ : VirtualInputDevice(cmd_sender), input_keycode_(input_keycode) {}
+
+ void HandleButtonPressEvent(bool button_down);
+
+ private:
+ std::string input_keycode_;
+};
+
+// Virtual keyboard.
+class VirtualKeyboard : public VirtualInputDevice {
+ public:
+ VirtualKeyboard(std::function<bool(std::string)> cmd_sender);
+ virtual ~VirtualKeyboard() {}
+
+ void GenerateKeyPressEvent(int code, bool down);
+
+ private:
+ std::map<uint32_t, std::string> keymapping_;
+};
+
+} // namespace cvd
diff --git a/host/frontend/vnc_server/virtual_inputs.cpp b/host/frontend/vnc_server/virtual_inputs.cpp
new file mode 100644
index 0000000..369fa96
--- /dev/null
+++ b/host/frontend/vnc_server/virtual_inputs.cpp
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include "host/frontend/vnc_server/virtual_inputs.h"
+#include <gflags/gflags.h>
+
+#include <mutex>
+#include <thread>
+
+DEFINE_string(input_socket, "/tmp/android-cuttlefish-1-input",
+ "The name of unix socket where the monkey server is listening "
+ "for input commands");
+using cvd::vnc::VirtualInputs;
+
+VirtualInputs::VirtualInputs()
+ : virtual_keyboard_(
+ [this](std::string cmd) { return SendMonkeyComand(cmd); }),
+ virtual_touch_pad_(
+ [this](std::string cmd) { return SendMonkeyComand(cmd); }),
+ virtual_power_button_("KEYCODE_POWER", [this](std::string cmd) {
+ return SendMonkeyComand(cmd);
+ }) {
+ monkey_socket_ = cvd::SharedFD::SocketLocalClient(FLAGS_input_socket.c_str(),
+ false, SOCK_STREAM);
+ if (!monkey_socket_->IsOpen()) {
+ // Monkey is known to die on the second conection, so let's wait a litttle
+ // bit and try again.
+ std::this_thread::sleep_for(std::chrono::milliseconds(500));
+ monkey_socket_ = cvd::SharedFD::SocketLocalClient(
+ FLAGS_input_socket.c_str(), false, SOCK_STREAM);
+ if (!monkey_socket_->IsOpen()) {
+ LOG(FATAL) << "Unable to connect to the mokey server";
+ }
+ }
+}
+
+namespace {
+constexpr char kCmdDone[] = "done\n";
+} // anonymous namespace
+
+VirtualInputs::~VirtualInputs() {
+ if (monkey_socket_->IsOpen()) {
+ monkey_socket_->Send(kCmdDone, sizeof(kCmdDone) - 1, 0);
+ }
+}
+
+bool VirtualInputs::SendMonkeyComand(std::string cmd) {
+ return monkey_socket_->Send(cmd.c_str(), cmd.size(), 0) == cmd.size();
+ // TODO(jemoreira): If monkey is going to be used for a long time it may be
+ // useful to check the response to this commands.
+}
+
+void VirtualInputs::GenerateKeyPressEvent(int code, bool down) {
+ std::lock_guard<std::mutex> guard(m_);
+ virtual_keyboard_.GenerateKeyPressEvent(code, down);
+}
+
+void VirtualInputs::PressPowerButton(bool down) {
+ std::lock_guard<std::mutex> guard(m_);
+ virtual_power_button_.HandleButtonPressEvent(down);
+}
+
+void VirtualInputs::HandlePointerEvent(bool touch_down, int x, int y) {
+ std::lock_guard<std::mutex> guard(m_);
+ virtual_touch_pad_.HandlePointerEvent(touch_down, x, y);
+}
diff --git a/host/frontend/vnc_server/virtual_inputs.h b/host/frontend/vnc_server/virtual_inputs.h
new file mode 100644
index 0000000..d5d00b9
--- /dev/null
+++ b/host/frontend/vnc_server/virtual_inputs.h
@@ -0,0 +1,50 @@
+#pragma once
+
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include <linux/input.h>
+
+#include <mutex>
+
+#include "common/libs/fs/shared_fd.h"
+#include "common/libs/threads/thread_annotations.h"
+#include "host/frontend/vnc_server/virtual_input_device.h"
+#include "host/frontend/vnc_server/vnc_utils.h"
+
+namespace cvd {
+namespace vnc {
+
+class VirtualInputs {
+ public:
+ VirtualInputs();
+ ~VirtualInputs();
+
+ void GenerateKeyPressEvent(int code, bool down);
+ void PressPowerButton(bool down);
+ void HandlePointerEvent(bool touch_down, int x, int y);
+
+ private:
+ cvd::SharedFD monkey_socket_;
+ bool SendMonkeyComand(std::string cmd);
+ std::mutex m_;
+ VirtualKeyboard virtual_keyboard_ GUARDED_BY(m_);
+ VirtualTouchPad virtual_touch_pad_ GUARDED_BY(m_);
+ VirtualButton virtual_power_button_ GUARDED_BY(m_);
+};
+
+} // namespace vnc
+} // namespace cvd
diff --git a/host/frontend/vnc_server/vnc_client_connection.cpp b/host/frontend/vnc_server/vnc_client_connection.cpp
new file mode 100644
index 0000000..c9a87c8
--- /dev/null
+++ b/host/frontend/vnc_server/vnc_client_connection.cpp
@@ -0,0 +1,723 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include "host/frontend/vnc_server/vnc_client_connection.h"
+
+#include <netinet/in.h>
+#include <sys/time.h>
+
+#include <algorithm>
+#include <cmath>
+#include <cstdint>
+#include <cstring>
+#include <memory>
+#include <mutex>
+#include <string>
+#include <thread>
+#include <utility>
+#include <vector>
+
+#include <gflags/gflags.h>
+#include <glog/logging.h>
+#include "host/frontend/vnc_server/keysyms.h"
+#include "host/frontend/vnc_server/mocks.h"
+#include "host/frontend/vnc_server/tcp_socket.h"
+#include "host/frontend/vnc_server/vnc_utils.h"
+
+using cvd::vnc::Message;
+using cvd::vnc::Stripe;
+using cvd::vnc::StripePtrVec;
+using cvd::vnc::VncClientConnection;
+
+DEFINE_bool(debug_client, false, "Turn on detailed logging for the client");
+
+#define DLOG(LEVEL) \
+ if (FLAGS_debug_client) LOG(LEVEL)
+
+
+namespace {
+class BigEndianChecker {
+ public:
+ BigEndianChecker() {
+ uint32_t u = 1;
+ is_big_endian_ = *reinterpret_cast<const char*>(&u) == 0;
+ }
+ bool operator()() const { return is_big_endian_; }
+
+ private:
+ bool is_big_endian_{};
+};
+
+const BigEndianChecker ImBigEndian;
+
+constexpr int32_t kDesktopSizeEncoding = -223;
+constexpr int32_t kTightEncoding = 7;
+
+// These are the lengths not counting the first byte. The first byte
+// indicates the message type.
+constexpr size_t kSetPixelFormatLength = 19;
+constexpr size_t kFramebufferUpdateRequestLength = 9;
+constexpr size_t kSetEncodingsLength = 3; // more bytes follow
+constexpr size_t kKeyEventLength = 7;
+constexpr size_t kPointerEventLength = 5;
+constexpr size_t kClientCutTextLength = 7; // more bytes follow
+
+void AppendInNetworkByteOrder(Message* msg, const std::uint8_t b) {
+ msg->push_back(b);
+}
+
+void AppendInNetworkByteOrder(Message* msg, const std::uint16_t s) {
+ const std::uint16_t n = htons(s);
+ auto p = reinterpret_cast<const std::uint8_t*>(&n);
+ msg->insert(msg->end(), p, p + sizeof n);
+}
+
+void AppendInNetworkByteOrder(Message* msg, const std::uint32_t w) {
+ const std::uint32_t n = htonl(w);
+ auto p = reinterpret_cast<const std::uint8_t*>(&n);
+ msg->insert(msg->end(), p, p + sizeof n);
+}
+
+void AppendInNetworkByteOrder(Message* msg, const int32_t w) {
+ std::uint32_t u{};
+ std::memcpy(&u, &w, sizeof u);
+ AppendInNetworkByteOrder(msg, u);
+}
+
+void AppendInNetworkByteOrder(Message* msg, const std::string& str) {
+ msg->insert(msg->end(), str.begin(), str.end());
+}
+
+void AppendToMessage(Message*) {}
+
+template <typename T, typename... Ts>
+void AppendToMessage(Message* msg, T v, Ts... vals) {
+ AppendInNetworkByteOrder(msg, v);
+ AppendToMessage(msg, vals...);
+}
+
+template <typename... Ts>
+Message CreateMessage(Ts... vals) {
+ Message m;
+ AppendToMessage(&m, vals...);
+ return m;
+}
+
+std::string HostName() {
+ // Localhost is good enough for local development and to connect through ssh
+ // tunneling, for something else this probably needs to change.
+ return "localhost";
+}
+
+std::uint16_t uint16_tAt(const void* p) {
+ std::uint16_t u{};
+ std::memcpy(&u, p, sizeof u);
+ return ntohs(u);
+}
+
+std::uint32_t uint32_tAt(const void* p) {
+ std::uint32_t u{};
+ std::memcpy(&u, p, sizeof u);
+ return ntohl(u);
+}
+
+std::int32_t int32_tAt(const void* p) {
+ std::uint32_t u{};
+ std::memcpy(&u, p, sizeof u);
+ u = ntohl(u);
+ std::int32_t s{};
+ std::memcpy(&s, &u, sizeof s);
+ return s;
+}
+
+std::uint32_t RedVal(std::uint32_t pixel) {
+ return (pixel >> GceFrameBuffer::kRedShift) &
+ ((0x1 << GceFrameBuffer::kRedBits) - 1);
+}
+
+std::uint32_t BlueVal(std::uint32_t pixel) {
+ return (pixel >> GceFrameBuffer::kBlueShift) &
+ ((0x1 << GceFrameBuffer::kBlueBits) - 1);
+}
+
+std::uint32_t GreenVal(std::uint32_t pixel) {
+ return (pixel >> GceFrameBuffer::kGreenShift) &
+ ((0x1 << GceFrameBuffer::kGreenBits) - 1);
+}
+} // namespace
+namespace cvd {
+namespace vnc {
+bool operator==(const VncClientConnection::FrameBufferUpdateRequest& lhs,
+ const VncClientConnection::FrameBufferUpdateRequest& rhs) {
+ return lhs.x_pos == rhs.x_pos && lhs.y_pos == rhs.y_pos &&
+ lhs.width == rhs.width && lhs.height == rhs.height;
+}
+
+bool operator!=(const VncClientConnection::FrameBufferUpdateRequest& lhs,
+ const VncClientConnection::FrameBufferUpdateRequest& rhs) {
+ return !(lhs == rhs);
+}
+} // namespace vnc
+} // namespace cvd
+
+VncClientConnection::VncClientConnection(ClientSocket client,
+ VirtualInputs* virtual_inputs,
+ BlackBoard* bb, bool aggressive)
+ : client_{std::move(client)},
+ sensor_event_hal_{cvd::SharedFD::SocketSeqPacketClient(
+ gce_sensors_message::kSensorsHALSocketName)},
+ virtual_inputs_{virtual_inputs},
+ bb_{bb} {
+ frame_buffer_request_handler_tid_ = std::thread(
+ &VncClientConnection::FrameBufferUpdateRequestHandler, this, aggressive);
+}
+
+VncClientConnection::~VncClientConnection() {
+ {
+ std::lock_guard<std::mutex> guard(m_);
+ closed_ = true;
+ }
+ bb_->StopWaiting(this);
+ frame_buffer_request_handler_tid_.join();
+}
+
+void VncClientConnection::StartSession() {
+ LOG(INFO) << "Starting session";
+ SetupProtocol();
+ LOG(INFO) << "Protocol set up";
+ if (client_.closed()) {
+ return;
+ }
+ SetupSecurityType();
+ LOG(INFO) << "Security type set";
+ if (client_.closed()) {
+ return;
+ }
+ GetClientInit();
+ LOG(INFO) << "Gotten client init";
+ if (client_.closed()) {
+ return;
+ }
+ SendServerInit();
+ LOG(INFO) << "Sent server init";
+ if (client_.closed()) {
+ return;
+ }
+ NormalSession();
+ LOG(INFO) << "vnc session terminated";
+}
+
+bool VncClientConnection::closed() {
+ std::lock_guard<std::mutex> guard(m_);
+ return closed_;
+}
+
+void VncClientConnection::SetupProtocol() {
+ static constexpr char kRFBVersion[] = "RFB 003.008\n";
+ static constexpr auto kVersionLen = (sizeof kRFBVersion) - 1;
+ client_.Send(reinterpret_cast<const std::uint8_t*>(kRFBVersion), kVersionLen);
+ auto client_protocol = client_.Recv(kVersionLen);
+ if (std::memcmp(&client_protocol[0], kRFBVersion,
+ std::min(kVersionLen, client_protocol.size())) != 0) {
+ client_protocol.push_back('\0');
+ LOG(ERROR) << "vnc client wants a different protocol: "
+ << reinterpret_cast<const char*>(&client_protocol[0]);
+ }
+}
+
+void VncClientConnection::SetupSecurityType() {
+ static constexpr std::uint8_t kNoneSecurity = 0x1;
+ // The first '0x1' indicates the number of items that follow
+ static constexpr std::uint8_t kOnlyNoneSecurity[] = {0x01, kNoneSecurity};
+ client_.Send(kOnlyNoneSecurity);
+ auto client_security = client_.Recv(1);
+ if (client_.closed()) {
+ return;
+ }
+ if (client_security.front() != kNoneSecurity) {
+ LOG(ERROR) << "vnc client is asking for security type "
+ << static_cast<int>(client_security.front());
+ }
+ static constexpr std::uint8_t kZero[4] = {};
+ client_.Send(kZero);
+}
+
+void VncClientConnection::GetClientInit() {
+ auto client_shared = client_.Recv(1);
+}
+
+void VncClientConnection::SendServerInit() {
+ const std::string server_name = HostName();
+ std::lock_guard<std::mutex> guard(m_);
+ auto server_init = CreateMessage(
+ static_cast<std::uint16_t>(ScreenWidth()),
+ static_cast<std::uint16_t>(ScreenHeight()), pixel_format_.bits_per_pixel,
+ pixel_format_.depth, pixel_format_.big_endian, pixel_format_.true_color,
+ pixel_format_.red_max, pixel_format_.green_max, pixel_format_.blue_max,
+ pixel_format_.red_shift, pixel_format_.green_shift,
+ pixel_format_.blue_shift, std::uint16_t{}, // padding
+ std::uint8_t{}, // padding
+ static_cast<std::uint32_t>(server_name.size()), server_name);
+ client_.Send(server_init);
+}
+
+Message VncClientConnection::MakeFrameBufferUpdateHeader(
+ std::uint16_t num_stripes) {
+ return CreateMessage(std::uint8_t{0}, // message-type
+ std::uint8_t{}, // padding
+ std::uint16_t{num_stripes});
+}
+
+void VncClientConnection::AppendRawStripeHeader(Message* frame_buffer_update,
+ const Stripe& stripe) {
+ static constexpr int32_t kRawEncoding = 0;
+ AppendToMessage(frame_buffer_update, std::uint16_t{stripe.x},
+ std::uint16_t{stripe.y}, std::uint16_t{stripe.width},
+ std::uint16_t{stripe.height}, kRawEncoding);
+}
+
+void VncClientConnection::AppendJpegSize(Message* frame_buffer_update,
+ size_t jpeg_size) {
+ constexpr size_t kJpegSizeOneByteMax = 127;
+ constexpr size_t kJpegSizeTwoByteMax = 16383;
+ constexpr size_t kJpegSizeThreeByteMax = 4194303;
+
+ if (jpeg_size <= kJpegSizeOneByteMax) {
+ AppendToMessage(frame_buffer_update, static_cast<std::uint8_t>(jpeg_size));
+ } else if (jpeg_size <= kJpegSizeTwoByteMax) {
+ auto sz = static_cast<std::uint32_t>(jpeg_size);
+ AppendToMessage(frame_buffer_update,
+ static_cast<std::uint8_t>((sz & 0x7F) | 0x80),
+ static_cast<std::uint8_t>((sz >> 7) & 0xFF));
+ } else {
+ if (jpeg_size > kJpegSizeThreeByteMax) {
+ LOG(FATAL) << "jpeg size is too big: " << jpeg_size << " must be under "
+ << kJpegSizeThreeByteMax;
+ }
+ const auto sz = static_cast<std::uint32_t>(jpeg_size);
+ AppendToMessage(frame_buffer_update,
+ static_cast<std::uint8_t>((sz & 0x7F) | 0x80),
+ static_cast<std::uint8_t>(((sz >> 7) & 0x7F) | 0x80),
+ static_cast<std::uint8_t>((sz >> 14) & 0xFF));
+ }
+}
+
+void VncClientConnection::AppendRawStripe(Message* frame_buffer_update,
+ const Stripe& stripe) const {
+ using Pixel = GceFrameBuffer::Pixel;
+ auto& fbu = *frame_buffer_update;
+ AppendRawStripeHeader(&fbu, stripe);
+ auto init_size = fbu.size();
+ fbu.insert(fbu.end(), stripe.raw_data.begin(), stripe.raw_data.end());
+ for (size_t i = init_size; i < fbu.size(); i += sizeof(Pixel)) {
+ CHECK((i + sizeof(Pixel)) < fbu.size());
+ Pixel raw_pixel{};
+ std::memcpy(&raw_pixel, &fbu[i], sizeof raw_pixel);
+ auto red = RedVal(raw_pixel);
+ auto green = GreenVal(raw_pixel);
+ auto blue = BlueVal(raw_pixel);
+ Pixel pixel = Pixel{red} << pixel_format_.red_shift |
+ Pixel{blue} << pixel_format_.blue_shift |
+ Pixel{green} << pixel_format_.green_shift;
+
+ if (bool(pixel_format_.big_endian) != ImBigEndian()) {
+ // flip them bits (refactor into function)
+ auto p = reinterpret_cast<char*>(&pixel);
+ std::swap(p[0], p[3]);
+ std::swap(p[1], p[2]);
+ }
+ CHECK(i + sizeof pixel <= fbu.size());
+ std::memcpy(&fbu[i], &pixel, sizeof pixel);
+ }
+}
+
+Message VncClientConnection::MakeRawFrameBufferUpdate(
+ const StripePtrVec& stripes) const {
+ auto fbu =
+ MakeFrameBufferUpdateHeader(static_cast<std::uint16_t>(stripes.size()));
+ for (auto& stripe : stripes) {
+ AppendRawStripe(&fbu, *stripe);
+ }
+ return fbu;
+}
+
+void VncClientConnection::AppendJpegStripeHeader(Message* frame_buffer_update,
+ const Stripe& stripe) {
+ static constexpr std::uint8_t kJpegEncoding = 0x90;
+ AppendToMessage(frame_buffer_update, stripe.x, stripe.y, stripe.width,
+ stripe.height, kTightEncoding, kJpegEncoding);
+ AppendJpegSize(frame_buffer_update, stripe.jpeg_data.size());
+}
+
+void VncClientConnection::AppendJpegStripe(Message* frame_buffer_update,
+ const Stripe& stripe) {
+ AppendJpegStripeHeader(frame_buffer_update, stripe);
+ frame_buffer_update->insert(frame_buffer_update->end(),
+ stripe.jpeg_data.begin(), stripe.jpeg_data.end());
+}
+
+Message VncClientConnection::MakeJpegFrameBufferUpdate(
+ const StripePtrVec& stripes) {
+ auto fbu =
+ MakeFrameBufferUpdateHeader(static_cast<std::uint16_t>(stripes.size()));
+ for (auto& stripe : stripes) {
+ AppendJpegStripe(&fbu, *stripe);
+ }
+ return fbu;
+}
+
+Message VncClientConnection::MakeFrameBufferUpdate(
+ const StripePtrVec& stripes) {
+ return use_jpeg_compression_ ? MakeJpegFrameBufferUpdate(stripes)
+ : MakeRawFrameBufferUpdate(stripes);
+}
+
+void VncClientConnection::FrameBufferUpdateRequestHandler(bool aggressive) {
+ BlackBoard::Registerer reg(bb_, this);
+ const StripeSeqNumber kBeginningOfTime{};
+
+ while (!closed()) {
+ auto stripes = bb_->WaitForSenderWork(this);
+ if (closed()) {
+ break;
+ }
+ if (stripes.empty()) {
+ LOG(FATAL) << "Got 0 stripes";
+ }
+ {
+ // lock here so a portrait frame can't be sent after a landscape
+ // DesktopSize update, or vice versa.
+ std::lock_guard<std::mutex> guard(m_);
+ DLOG(INFO) << "Sending update in "
+ << (current_orientation_ == ScreenOrientation::Portrait
+ ? "portrait"
+ : "landscape")
+ << " mode";
+ client_.Send(MakeFrameBufferUpdate(stripes));
+ }
+ if (aggressive) {
+ bb_->FrameBufferUpdateRequestReceived(this);
+ }
+ }
+}
+
+void VncClientConnection::SendDesktopSizeUpdate() {
+ static constexpr int32_t kDesktopSizeEncoding = -223;
+ client_.Send(CreateMessage(std::uint8_t{0}, // message-type,
+ std::uint8_t{}, // padding
+ std::uint16_t{1}, // one pseudo rectangle
+ std::uint16_t{0}, std::uint16_t{0},
+ static_cast<std::uint16_t>(ScreenWidth()),
+ static_cast<std::uint16_t>(ScreenHeight()),
+ kDesktopSizeEncoding));
+}
+
+bool VncClientConnection::IsUrgent(
+ const FrameBufferUpdateRequest& update_request) const {
+ return !update_request.incremental ||
+ update_request != previous_update_request_;
+}
+
+void VncClientConnection::HandleFramebufferUpdateRequest() {
+ auto msg = client_.Recv(kFramebufferUpdateRequestLength);
+ if (msg.size() != kFramebufferUpdateRequestLength) {
+ return;
+ }
+ FrameBufferUpdateRequest fbur{msg[1] == 0, uint16_tAt(&msg[1]),
+ uint16_tAt(&msg[3]), uint16_tAt(&msg[5]),
+ uint16_tAt(&msg[7])};
+ if (IsUrgent(fbur)) {
+ bb_->SignalClientNeedsEntireScreen(this);
+ }
+ bb_->FrameBufferUpdateRequestReceived(this);
+ previous_update_request_ = fbur;
+}
+
+void VncClientConnection::HandleSetEncodings() {
+ auto msg = client_.Recv(kSetEncodingsLength);
+ if (msg.size() != kSetEncodingsLength) {
+ return;
+ }
+ auto count = uint16_tAt(&msg[1]);
+ auto encodings = client_.Recv(count * sizeof(int32_t));
+ if (encodings.size() % sizeof(int32_t) != 0) {
+ return;
+ }
+ {
+ std::lock_guard<std::mutex> guard(m_);
+ use_jpeg_compression_ = false;
+ }
+ for (size_t i = 0; i < encodings.size(); i += sizeof(int32_t)) {
+ auto enc = int32_tAt(&encodings[i]);
+ DLOG(INFO) << "client requesting encoding: " << enc;
+ if (enc == kTightEncoding) {
+ // This is a deviation from the spec which says that if a jpeg quality
+ // level is not specified, tight encoding won't use jpeg.
+ std::lock_guard<std::mutex> guard(m_);
+ use_jpeg_compression_ = true;
+ }
+ if (kJpegMinQualityEncoding <= enc && enc <= kJpegMaxQualityEncoding) {
+ DLOG(INFO) << "jpeg compression level: " << enc;
+ bb_->set_jpeg_quality_level(enc);
+ }
+ if (enc == kDesktopSizeEncoding) {
+ supports_desktop_size_encoding_ = true;
+ }
+ }
+}
+
+void VncClientConnection::HandleSetPixelFormat() {
+ std::lock_guard<std::mutex> guard(m_);
+ auto msg = client_.Recv(kSetPixelFormatLength);
+ if (msg.size() != kSetPixelFormatLength) {
+ return;
+ }
+ pixel_format_.bits_per_pixel = msg[3];
+ pixel_format_.depth = msg[4];
+ pixel_format_.big_endian = msg[5];
+ pixel_format_.true_color = msg[7];
+ pixel_format_.red_max = uint16_tAt(&msg[8]);
+ pixel_format_.green_max = uint16_tAt(&msg[10]);
+ pixel_format_.blue_max = uint16_tAt(&msg[12]);
+ pixel_format_.red_shift = msg[13];
+ pixel_format_.green_shift = msg[14];
+ pixel_format_.blue_shift = msg[15];
+}
+
+void VncClientConnection::HandlePointerEvent() {
+ auto msg = client_.Recv(kPointerEventLength);
+ if (msg.size() != kPointerEventLength) {
+ return;
+ }
+ std::uint8_t button_mask = msg[0];
+ auto x_pos = uint16_tAt(&msg[1]);
+ auto y_pos = uint16_tAt(&msg[3]);
+ {
+ std::lock_guard<std::mutex> guard(m_);
+ if (current_orientation_ == ScreenOrientation::Landscape) {
+ std::tie(x_pos, y_pos) =
+ std::make_pair(ActualScreenWidth() - y_pos, x_pos);
+ }
+ }
+ virtual_inputs_->HandlePointerEvent(button_mask, x_pos, y_pos);
+}
+
+void VncClientConnection::UpdateAccelerometer(float x, float y, float z) {
+ // // Discard the event if we don't have a connection to the HAL.
+ // if (!sensor_event_hal_->IsOpen()) {
+ // LOG(ERROR) << "sensor event client not open";
+ // return;
+ // }
+ // timespec current_time{};
+ // clock_gettime(CLOCK_MONOTONIC, ¤t_time);
+ // // Construct the sensor message.
+ // gce_sensors_message message{};
+ // message.version = sizeof message;
+ // message.sensor = cvd::sensors_constants::kAccelerometerHandle;
+ // message.type = SENSOR_TYPE_ACCELEROMETER;
+ // message.timestamp = current_time.tv_sec * static_cast<int64_t>(1000000000)
+ // +
+ // current_time.tv_nsec;
+ // message.data[0] = x;
+ // message.data[1] = y;
+ // message.data[2] = z;
+
+ // std::array<iovec, 1> msg_iov{};
+ // msg_iov[0].iov_base = &message;
+ // msg_iov[0].iov_len = sizeof(sensors_event_t);
+
+ // msghdr msg;
+ // msg.msg_name = nullptr;
+ // msg.msg_namelen = 0;
+ // msg.msg_iov = msg_iov.data();
+ // msg.msg_iovlen = msg_iov.size();
+ // msg.msg_control = nullptr;
+ // msg.msg_controllen = 0;
+ // msg.msg_flags = 0;
+ // if (sensor_event_hal_->SendMsg(&msg, 0) == -1) {
+ // LOG(ERROR) << __FUNCTION__ << ": Could not send sensor data. (%s)." <<
+ // sensor_event_hal_->StrError();
+ // }
+}
+
+VncClientConnection::Coordinates VncClientConnection::CoordinatesForOrientation(
+ ScreenOrientation orientation) const {
+ // Compute the acceleration vector that we need to send to mimic
+ // this change.
+ constexpr float g = 9.81;
+ constexpr float angle = 20.0;
+ const float cos_angle = std::cos(angle / M_PI);
+ const float sin_angle = std::sin(angle / M_PI);
+ const float z = g * sin_angle;
+ switch (orientation) {
+ case ScreenOrientation::Portrait:
+ return {0, g * cos_angle, z};
+ case ScreenOrientation::Landscape:
+ return {g * cos_angle, 0, z};
+ }
+}
+
+int VncClientConnection::ScreenWidth() const {
+ return current_orientation_ == ScreenOrientation::Portrait
+ ? ActualScreenWidth()
+ : ActualScreenHeight();
+}
+
+int VncClientConnection::ScreenHeight() const {
+ return current_orientation_ == ScreenOrientation::Portrait
+ ? ActualScreenHeight()
+ : ActualScreenWidth();
+}
+
+void VncClientConnection::SetScreenOrientation(ScreenOrientation orientation) {
+ std::lock_guard<std::mutex> guard(m_);
+ auto coords = CoordinatesForOrientation(orientation);
+ UpdateAccelerometer(coords.x, coords.y, coords.z);
+ if (supports_desktop_size_encoding_) {
+ auto previous_orientation = current_orientation_;
+ current_orientation_ = orientation;
+ if (current_orientation_ != previous_orientation &&
+ supports_desktop_size_encoding_) {
+ SendDesktopSizeUpdate();
+ bb_->SetOrientation(this, current_orientation_);
+ // TODO not sure if I should be sending a frame update along with this,
+ // or just letting the next FBUR handle it. This seems to me like it's
+ // sending one more frame buffer update than was requested, which is
+ // maybe a violation of the spec?
+ }
+ }
+}
+
+bool VncClientConnection::RotateIfIsRotationCommand(std::uint32_t key) {
+ // Due to different configurations on different platforms we're supporting
+ // a set of options for rotating the screen. These are similar to what
+ // the emulator supports and has supported.
+ // ctrl+left and ctrl+right work on windows and linux
+ // command+left and command+right work on Mac
+ // ctrl+fn+F11 and ctrl+fn+F12 work when chromoting to ubuntu from a Mac
+ if (!control_key_down_ && !meta_key_down_) {
+ return false;
+ }
+ switch (key) {
+ case cvd::xk::Right:
+ case cvd::xk::F12:
+ DLOG(INFO) << "switching to portrait";
+ SetScreenOrientation(ScreenOrientation::Portrait);
+ break;
+ case cvd::xk::Left:
+ case cvd::xk::F11:
+ DLOG(INFO) << "switching to landscape";
+ SetScreenOrientation(ScreenOrientation::Landscape);
+ break;
+ default:
+ return false;
+ }
+ return true;
+}
+
+void VncClientConnection::HandleKeyEvent() {
+ auto msg = client_.Recv(kKeyEventLength);
+ if (msg.size() != kKeyEventLength) {
+ return;
+ }
+
+ auto key = uint32_tAt(&msg[3]);
+ bool key_down = msg[0];
+ switch (key) {
+ case cvd::xk::ControlLeft:
+ case cvd::xk::ControlRight:
+ control_key_down_ = key_down;
+ break;
+ case cvd::xk::MetaLeft:
+ case cvd::xk::MetaRight:
+ meta_key_down_ = key_down;
+ break;
+ case cvd::xk::F5:
+ key = cvd::xk::Menu;
+ break;
+ case cvd::xk::F7:
+ virtual_inputs_->PressPowerButton(key_down);
+ return;
+ default:
+ break;
+ }
+
+ if (RotateIfIsRotationCommand(key)) {
+ return;
+ }
+
+ virtual_inputs_->GenerateKeyPressEvent(key, key_down);
+}
+
+void VncClientConnection::HandleClientCutText() {
+ auto msg = client_.Recv(kClientCutTextLength);
+ if (msg.size() != kClientCutTextLength) {
+ return;
+ }
+ auto len = uint32_tAt(&msg[3]);
+ client_.Recv(len);
+}
+
+void VncClientConnection::NormalSession() {
+ static constexpr std::uint8_t kSetPixelFormatMessage{0};
+ static constexpr std::uint8_t kSetEncodingsMessage{2};
+ static constexpr std::uint8_t kFramebufferUpdateRequestMessage{3};
+ static constexpr std::uint8_t kKeyEventMessage{4};
+ static constexpr std::uint8_t kPointerEventMessage{5};
+ static constexpr std::uint8_t kClientCutTextMessage{6};
+ while (true) {
+ if (client_.closed()) {
+ return;
+ }
+ auto msg = client_.Recv(1);
+ if (client_.closed()) {
+ return;
+ }
+ auto msg_type = msg.front();
+ DLOG(INFO) << "Received message type " << msg_type;
+
+ switch (msg_type) {
+ case kSetPixelFormatMessage:
+ HandleSetPixelFormat();
+ break;
+
+ case kSetEncodingsMessage:
+ HandleSetEncodings();
+ break;
+
+ case kFramebufferUpdateRequestMessage:
+ HandleFramebufferUpdateRequest();
+ break;
+
+ case kKeyEventMessage:
+ HandleKeyEvent();
+ break;
+
+ case kPointerEventMessage:
+ HandlePointerEvent();
+ break;
+
+ case kClientCutTextMessage:
+ HandleClientCutText();
+ break;
+
+ default:
+ LOG(WARNING) << "message type not handled: "
+ << static_cast<int>(msg_type);
+ break;
+ }
+ }
+}
diff --git a/host/frontend/vnc_server/vnc_client_connection.h b/host/frontend/vnc_server/vnc_client_connection.h
new file mode 100644
index 0000000..9b403bb
--- /dev/null
+++ b/host/frontend/vnc_server/vnc_client_connection.h
@@ -0,0 +1,171 @@
+#pragma once
+
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include "common/libs/fs/shared_fd.h"
+#include "common/libs/threads/thread_annotations.h"
+
+#include <cstdint>
+#include <memory>
+#include <mutex>
+#include <thread>
+#include <vector>
+
+#include "host/frontend/vnc_server/blackboard.h"
+#include "host/frontend/vnc_server/tcp_socket.h"
+#include "host/frontend/vnc_server/virtual_inputs.h"
+#include "host/frontend/vnc_server/vnc_utils.h"
+
+namespace cvd {
+namespace vnc {
+
+class VncClientConnection {
+ public:
+ VncClientConnection(ClientSocket client, VirtualInputs* virtual_inputs,
+ BlackBoard* bb, bool aggressive);
+ VncClientConnection(const VncClientConnection&) = delete;
+ VncClientConnection& operator=(const VncClientConnection&) = delete;
+ ~VncClientConnection();
+
+ void StartSession();
+
+ private:
+ struct PixelFormat {
+ std::uint8_t bits_per_pixel;
+ std::uint8_t depth;
+ std::uint8_t big_endian;
+ std::uint8_t true_color;
+ std::uint16_t red_max;
+ std::uint16_t green_max;
+ std::uint16_t blue_max;
+ std::uint8_t red_shift;
+ std::uint8_t green_shift;
+ std::uint8_t blue_shift;
+ };
+
+ struct FrameBufferUpdateRequest {
+ bool incremental;
+ std::uint16_t x_pos;
+ std::uint16_t y_pos;
+ std::uint16_t width;
+ std::uint16_t height;
+ };
+
+ friend bool operator==(const FrameBufferUpdateRequest&,
+ const FrameBufferUpdateRequest&);
+ friend bool operator!=(const FrameBufferUpdateRequest&,
+ const FrameBufferUpdateRequest&);
+
+ bool closed();
+ void SetupProtocol();
+ void SetupSecurityType();
+
+ void GetClientInit();
+
+ void SendServerInit() EXCLUDES(m_);
+ static Message MakeFrameBufferUpdateHeader(std::uint16_t num_stripes);
+
+ static void AppendRawStripeHeader(Message* frame_buffer_update,
+ const Stripe& stripe);
+ void AppendRawStripe(Message* frame_buffer_update, const Stripe& stripe) const
+ REQUIRES(m_);
+ Message MakeRawFrameBufferUpdate(const StripePtrVec& stripes) const
+ REQUIRES(m_);
+
+ static void AppendJpegSize(Message* frame_buffer_update, size_t jpeg_size);
+ static void AppendJpegStripeHeader(Message* frame_buffer_update,
+ const Stripe& stripe);
+ static void AppendJpegStripe(Message* frame_buffer_update,
+ const Stripe& stripe);
+ static Message MakeJpegFrameBufferUpdate(const StripePtrVec& stripes);
+
+ Message MakeFrameBufferUpdate(const StripePtrVec& frame) REQUIRES(m_);
+
+ void FrameBufferUpdateRequestHandler(bool aggressive) EXCLUDES(m_);
+
+ void SendDesktopSizeUpdate() REQUIRES(m_);
+
+ bool IsUrgent(const FrameBufferUpdateRequest& update_request) const;
+ static StripeSeqNumber MostRecentStripeSeqNumber(const StripePtrVec& stripes);
+
+ void HandleFramebufferUpdateRequest() EXCLUDES(m_);
+
+ void HandleSetEncodings();
+
+ void HandleSetPixelFormat();
+
+ void HandlePointerEvent() EXCLUDES(m_);
+
+ void UpdateAccelerometer(float x, float y, float z);
+
+ struct Coordinates {
+ float x;
+ float y;
+ float z;
+ };
+
+ Coordinates CoordinatesForOrientation(ScreenOrientation orientation) const;
+
+ int ScreenWidth() const REQUIRES(m_);
+
+ int ScreenHeight() const REQUIRES(m_);
+
+ void SetScreenOrientation(ScreenOrientation orientation) EXCLUDES(m_);
+
+ // Returns true if key is special and the screen was rotated.
+ bool RotateIfIsRotationCommand(std::uint32_t key);
+
+ void HandleKeyEvent();
+
+ void HandleClientCutText();
+
+ void NormalSession();
+
+ mutable std::mutex m_;
+ ClientSocket client_;
+ cvd::SharedFD sensor_event_hal_;
+ bool control_key_down_ = false;
+ bool meta_key_down_ = false;
+ VirtualInputs* virtual_inputs_{};
+
+ FrameBufferUpdateRequest previous_update_request_{};
+ BlackBoard* bb_;
+ bool use_jpeg_compression_ GUARDED_BY(m_) = false;
+
+ std::thread frame_buffer_request_handler_tid_;
+ bool closed_ GUARDED_BY(m_){};
+
+ PixelFormat pixel_format_ GUARDED_BY(m_) = {
+ std::uint8_t{32}, // bits per pixel
+ std::uint8_t{8}, // depth
+ std::uint8_t{}, // big_endian
+ std::uint8_t{}, // true_color
+ std::uint16_t{}, // red_max, (maxes not used when true color flag is 0)
+ std::uint16_t{}, // green_max
+ std::uint16_t{}, // blue_max
+ std::uint8_t{}, // red_shift (shifts not used when true color flag is 0)
+ std::uint8_t{}, // green_shift
+ std::uint8_t{}, // blue_shift
+ };
+
+ bool supports_desktop_size_encoding_ = false;
+ ScreenOrientation current_orientation_ GUARDED_BY(m_) =
+ ScreenOrientation::Portrait;
+};
+
+} // namespace vnc
+} // namespace cvd
diff --git a/host/frontend/vnc_server/vnc_server.cpp b/host/frontend/vnc_server/vnc_server.cpp
new file mode 100644
index 0000000..2c013af
--- /dev/null
+++ b/host/frontend/vnc_server/vnc_server.cpp
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include "host/frontend/vnc_server/vnc_server.h"
+
+#include <glog/logging.h>
+#include "host/frontend/vnc_server/blackboard.h"
+#include "host/frontend/vnc_server/frame_buffer_watcher.h"
+#include "host/frontend/vnc_server/jpeg_compressor.h"
+#include "host/frontend/vnc_server/tcp_socket.h"
+#include "host/frontend/vnc_server/virtual_inputs.h"
+#include "host/frontend/vnc_server/vnc_client_connection.h"
+#include "host/frontend/vnc_server/vnc_utils.h"
+
+using cvd::vnc::VncServer;
+
+VncServer::VncServer(int port, bool aggressive)
+ : server_(port), frame_buffer_watcher_{&bb_}, aggressive_{aggressive} {}
+
+void VncServer::MainLoop() {
+ while (true) {
+ LOG(INFO) << "Awaiting connections";
+ auto connection = server_.Accept();
+ LOG(INFO) << "Accepted a client connection";
+ StartClient(std::move(connection));
+ }
+}
+
+void VncServer::StartClient(ClientSocket sock) {
+ std::thread t(&VncServer::StartClientThread, this, std::move(sock));
+ t.detach();
+}
+
+void VncServer::StartClientThread(ClientSocket sock) {
+ // NOTE if VncServer is expected to be destroyed, we have a problem here.
+ // All of the client threads will be pointing to the VncServer's
+ // data members. In the current setup, if the VncServer is destroyed with
+ // clients still running, the clients will all be left with dangling
+ // pointers.
+ VncClientConnection client(std::move(sock), &virtual_inputs_, &bb_,
+ aggressive_);
+ client.StartSession();
+}
diff --git a/host/frontend/vnc_server/vnc_server.h b/host/frontend/vnc_server/vnc_server.h
new file mode 100644
index 0000000..7b50367
--- /dev/null
+++ b/host/frontend/vnc_server/vnc_server.h
@@ -0,0 +1,56 @@
+#pragma once
+
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include <string>
+#include <thread>
+#include <utility>
+
+#include "host/frontend/vnc_server/blackboard.h"
+#include "host/frontend/vnc_server/frame_buffer_watcher.h"
+#include "host/frontend/vnc_server/jpeg_compressor.h"
+#include "host/frontend/vnc_server/tcp_socket.h"
+#include "host/frontend/vnc_server/virtual_inputs.h"
+#include "host/frontend/vnc_server/vnc_client_connection.h"
+#include "host/frontend/vnc_server/vnc_utils.h"
+
+namespace cvd {
+namespace vnc {
+
+class VncServer {
+ public:
+ explicit VncServer(int port, bool aggressive);
+
+ VncServer(const VncServer&) = delete;
+ VncServer& operator=(const VncServer&) = delete;
+
+ [[noreturn]] void MainLoop();
+
+ private:
+ void StartClient(ClientSocket sock);
+
+ void StartClientThread(ClientSocket sock);
+
+ ServerSocket server_;
+ VirtualInputs virtual_inputs_;
+ BlackBoard bb_;
+ FrameBufferWatcher frame_buffer_watcher_;
+ bool aggressive_{};
+};
+
+} // namespace vnc
+} // namespace cvd
diff --git a/host/frontend/vnc_server/vnc_utils.cpp b/host/frontend/vnc_server/vnc_utils.cpp
new file mode 100644
index 0000000..883a9d1
--- /dev/null
+++ b/host/frontend/vnc_server/vnc_utils.cpp
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include "common/vsoc/lib/fb_bcast_region_view.h"
+
+using vsoc::framebuffer::FBBroadcastRegionView;
+
+namespace cvd {
+namespace vnc {
+
+FBBroadcastRegionView* GetFBBroadcastRegionView() {
+ static FBBroadcastRegionView instance;
+ return &instance;
+}
+
+} // namespace vnc
+} // namespace cvd
diff --git a/host/frontend/vnc_server/vnc_utils.h b/host/frontend/vnc_server/vnc_utils.h
new file mode 100644
index 0000000..84a22dc
--- /dev/null
+++ b/host/frontend/vnc_server/vnc_utils.h
@@ -0,0 +1,86 @@
+#pragma once
+
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include <array>
+#include <cstdint>
+#include <utility>
+#include <vector>
+
+#include "common/vsoc/lib/fb_bcast_region_view.h"
+
+namespace cvd {
+namespace vnc {
+
+// TODO(haining) when the hwcomposer gives a sequence number type, use that
+// instead. It might just work to replace this class with a type alias
+// using StripeSeqNumber = whatever_the_hwcomposer_uses;
+class StripeSeqNumber {
+ public:
+ StripeSeqNumber() = default;
+ explicit StripeSeqNumber(std::uint64_t t) : t_{t} {}
+ bool operator<(const StripeSeqNumber& other) const { return t_ < other.t_; }
+
+ bool operator<=(const StripeSeqNumber& other) const { return t_ <= other.t_; }
+
+ private:
+ std::uint64_t t_{};
+};
+
+using Message = std::vector<std::uint8_t>;
+
+constexpr int32_t kJpegMaxQualityEncoding = -23;
+constexpr int32_t kJpegMinQualityEncoding = -32;
+
+enum class ScreenOrientation { Portrait, Landscape };
+constexpr int kNumOrientations = 2;
+
+struct Stripe {
+ int index = -1;
+ std::uint64_t frame_id{};
+ std::uint16_t x{};
+ std::uint16_t y{};
+ std::uint16_t width{};
+ std::uint16_t height{};
+ Message raw_data{};
+ Message jpeg_data{};
+ StripeSeqNumber seq_number{};
+ ScreenOrientation orientation{};
+};
+
+vsoc::framebuffer::FBBroadcastRegionView* GetFBBroadcastRegionView();
+
+inline int BytesPerPixel() {
+ return GetFBBroadcastRegionView()->bytes_per_pixel();
+}
+
+// The width of the screen regardless of orientation. Does not change.
+inline int ActualScreenWidth() {
+ return GetFBBroadcastRegionView()->x_res();
+}
+
+// The height of the screen regardless of orientation. Does not change.
+inline int ActualScreenHeight() {
+ return GetFBBroadcastRegionView()->y_res();
+}
+
+inline int ScreenSizeInBytes() {
+ return ActualScreenWidth() * ActualScreenHeight() * BytesPerPixel();
+}
+
+} // namespace vnc
+} // namespace cvd
diff --git a/host/libs/Android.bp b/host/libs/Android.bp
new file mode 100644
index 0000000..c7aa2ec
--- /dev/null
+++ b/host/libs/Android.bp
@@ -0,0 +1,22 @@
+//
+// Copyright (C) 2017 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.
+
+subdirs = [
+ "config",
+ "monitor",
+ "ivserver",
+ "vadb",
+ "usbip",
+]
diff --git a/host/libs/config/Android.bp b/host/libs/config/Android.bp
new file mode 100644
index 0000000..4a02ec1
--- /dev/null
+++ b/host/libs/config/Android.bp
@@ -0,0 +1,21 @@
+cc_library_host_static {
+ name: "libcuttlefish_host_config",
+ srcs: [
+ "file_partition.cpp",
+ "guest_config.cpp",
+ "host_config.cpp",
+ ],
+ header_libs: [
+ "cuttlefish_glog",
+ ],
+ shared_libs: [
+ "libcuttlefish_fs",
+ "cuttlefish_auto_resources",
+ "libbase",
+ ],
+ static_libs: [
+ "libxml2",
+ "libgflags",
+ ],
+ defaults: ["cuttlefish_host_only"],
+}
diff --git a/host/libs/config/README.md b/host/libs/config/README.md
new file mode 100644
index 0000000..f2a8138
--- /dev/null
+++ b/host/libs/config/README.md
@@ -0,0 +1,18 @@
+# VM Configuration libraries
+
+This package supplies library that supports VM configuration by:
+
+* creating, initializing and verifying images used to create a VM,
+* creating configuration file that can be used with `libvirt` directly.
+
+## Details
+
+### `FilePartition`
+
+`FilePartition` class offers means to create (both persistent and ephemeral),
+initialize and access image files.
+
+### `GuestConfig`
+
+`GuestConfig` class builds XML configuration string based on supplied details
+that is used to initialize `libvirt` domain.
diff --git a/host/libs/config/file_partition.cpp b/host/libs/config/file_partition.cpp
new file mode 100644
index 0000000..ede4870
--- /dev/null
+++ b/host/libs/config/file_partition.cpp
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#include <errno.h>
+#include <limits.h>
+
+#include <sstream>
+
+#include <glog/logging.h>
+
+#include "common/libs/fs/shared_fd.h"
+#include "host/libs/config/file_partition.h"
+
+namespace config {
+namespace {
+constexpr char kTempFileSuffix[] = ".img";
+
+void Initialize(const std::string& path) {
+ std::string command = "/sbin/mkfs.ext4 -F '" + path + "' &>/dev/null";
+ CHECK(system(command.c_str()) == 0)
+ << "Could not initialize filesystem on partition image " << path << ": "
+ << strerror(errno);
+}
+} // namespace
+
+FilePartition::~FilePartition() {
+ if (should_delete_) {
+ LOG(INFO) << "Deleting partition image file " << name_;
+ errno = 0;
+ unlink(name_.c_str());
+ if (errno != 0) {
+ LOG(WARNING) << "Could not delete partition image file: "
+ << strerror(errno);
+ }
+ }
+}
+
+std::unique_ptr<FilePartition> FilePartition::ReuseExistingFile(
+ const std::string& path) {
+ return std::unique_ptr<FilePartition>(new FilePartition(path, false));
+}
+
+std::unique_ptr<FilePartition> FilePartition::CreateNewFile(
+ const std::string& path, int size_mb) {
+ {
+ cvd::SharedFD fd(cvd::SharedFD::Open(path.c_str(), O_CREAT | O_RDWR, 0600));
+ CHECK(fd->IsOpen()) << "Could not open file: " << path << ": "
+ << fd->StrError();
+ CHECK(fd->Truncate(size_mb << 20) == 0)
+ << "Could not truncate file " << path << ": " << fd->StrError();
+ }
+
+ Initialize(path);
+ return std::unique_ptr<FilePartition>(new FilePartition(path, false));
+}
+
+// Create temporary FilePartition object using supplied prefix.
+// Newly created file will be deleted after this instance is destroyed.
+std::unique_ptr<FilePartition> FilePartition::CreateTemporaryFile(
+ const std::string& prefix, int size_mb) {
+ std::stringstream ss;
+ ss << prefix << "-XXXXXX" << kTempFileSuffix;
+ char path[PATH_MAX];
+ strncpy(&path[0], ss.str().c_str(), sizeof(path));
+
+ {
+ int raw_fd = mkostemps(&path[0], strlen(kTempFileSuffix), O_RDWR | O_CREAT);
+ CHECK(raw_fd > 0) << "Could not create temporary file: " << strerror(errno);
+ CHECK(ftruncate(raw_fd, size_mb << 20) == 0)
+ << "Could not truncate file " << path << ": " << strerror(errno);
+ close(raw_fd);
+ }
+
+ Initialize(path);
+ return std::unique_ptr<FilePartition>(new FilePartition(path, true));
+}
+
+} // namespace config
diff --git a/host/libs/config/file_partition.h b/host/libs/config/file_partition.h
new file mode 100644
index 0000000..3c8e55b
--- /dev/null
+++ b/host/libs/config/file_partition.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#pragma once
+
+#include <memory>
+#include <string>
+
+namespace config {
+// FilePartition class manages partition image files.
+// Partition image files can be reused or created on demand. Temporary images
+// are deleted when corresponding instances of FilePartition object are
+// destroyed.
+class FilePartition {
+ public:
+ ~FilePartition();
+
+ // Create FilePartition object from existing file.
+ // Specified file will not be disposed of after this instance is destroyed.
+ static std::unique_ptr<FilePartition> ReuseExistingFile(
+ const std::string& path);
+
+ // Create FilePartition object at specified location and initialize content.
+ // Specified file will not be disposed of after this instance is destroyed.
+ static std::unique_ptr<FilePartition> CreateNewFile(const std::string& path,
+ int size_mb);
+
+ // Create temporary FilePartition object using supplied prefix.
+ // Newly created file will be deleted after this instance is destroyed.
+ static std::unique_ptr<FilePartition> CreateTemporaryFile(
+ const std::string& prefix, int size_mb);
+
+ const std::string& GetName() const { return name_; }
+
+ private:
+ std::string name_;
+ bool should_delete_ = false;
+
+ FilePartition(const std::string& name, bool should_delete)
+ : name_(name), should_delete_(should_delete) {}
+
+ FilePartition(const FilePartition&) = delete;
+ FilePartition& operator=(const FilePartition&) = delete;
+};
+
+} // namespace config
diff --git a/host/libs/config/guest_config.cpp b/host/libs/config/guest_config.cpp
new file mode 100644
index 0000000..359cb24
--- /dev/null
+++ b/host/libs/config/guest_config.cpp
@@ -0,0 +1,312 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include "host/libs/config/guest_config.h"
+#include <iomanip>
+#include <sstream>
+
+#include <glog/logging.h>
+#include <gflags/gflags.h>
+#include "host/libs/config/host_config.h"
+
+std::string g_default_libvirt_domain{vsoc::GetPerInstanceDefault("cvd-")};
+DEFINE_string(libvirt_domain, g_default_libvirt_domain.c_str(),
+ "Domain name to use with libvirt");
+
+// This class represents libvirt guest configuration.
+// A lot of useful information about the document created here can be found on
+// these websites:
+// - https://libvirt.org/formatdomain.html
+// - https://wiki.libvirt.org/page/Virtio
+namespace config {
+namespace {
+// This trivial no-op helper function serves purpose of making libxml2 happy.
+// Apparently, *most* (not all!) string literals in libxml2 have to be of
+// unsigned char* (aka xmlChar*) type.
+inline const xmlChar* xc(const char* str) {
+ return reinterpret_cast<const xmlChar*>(str);
+}
+
+// Helper functions that allow us to combine any set of arguments to a single
+// string.
+// Example:
+// concat("Answer", ' ', "is: ", 42);
+// will produce string "Answer is: 42"
+template <typename Arg>
+inline std::ostream& concat_helper(std::ostream& o, Arg a) {
+ o << a;
+ return o;
+}
+
+template <typename Arg, typename... Args>
+inline std::ostream& concat_helper(std::ostream& o, Arg a, Args... args) {
+ o << a;
+ concat_helper(o, args...);
+ return o;
+}
+
+template <typename... Args>
+inline std::string concat(Args... args) {
+ std::ostringstream str;
+ concat_helper(str, args...);
+ return str.str();
+}
+
+enum class DeviceSourceType {
+ kFile,
+ kUnixSocketClient,
+ kUnixSocketServer,
+};
+
+// Basic VM configuration.
+// This section configures name, basic resource allocation and response to
+// events.
+void ConfigureVM(xmlNode* root, const std::string& instance_name, int cpus,
+ int mem_mb, const std::string& uuid) {
+ xmlNewChild(root, nullptr, xc("name"), xc(instance_name.c_str()));
+
+ // TODO(ender): should this all be 'restart'?
+ xmlNewChild(root, nullptr, xc("on_poweroff"), xc("destroy"));
+ xmlNewChild(root, nullptr, xc("on_reboot"), xc("restart"));
+ xmlNewChild(root, nullptr, xc("on_crash"), xc("restart"));
+ xmlNewChild(root, nullptr, xc("vcpu"), xc(concat(cpus).c_str()));
+ xmlNewChild(root, nullptr, xc("memory"), xc(concat(mem_mb << 10).c_str()));
+ if (uuid.size()) {
+ xmlNewChild(root, nullptr, xc("uuid"), xc(uuid.c_str()));
+ }
+}
+
+// Configure VM features.
+// This section takes care of the <features> section of the target XML file.
+void ConfigureVMFeatures(xmlNode* root,
+ const std::initializer_list<std::string>& features) {
+ auto ch = xmlNewChild(root, nullptr, xc("features"), nullptr);
+ for (const auto& str : features) {
+ xmlNewChild(ch, nullptr, xc(str.c_str()), nullptr);
+ }
+}
+
+// Configure VM OS.
+// This section configures target os (<os>).
+void ConfigureOperatingSystem(xmlNode* root, const std::string& kernel,
+ const std::string& initrd,
+ const std::string& args) {
+ auto os = xmlNewChild(root, nullptr, xc("os"), nullptr);
+
+ auto type = xmlNewChild(os, nullptr, xc("type"), xc("hvm"));
+ xmlNewProp(type, xc("arch"), xc("x86_64"));
+ xmlNewProp(type, xc("machine"), xc("pc"));
+
+ xmlNewChild(os, nullptr, xc("kernel"), xc(kernel.c_str()));
+ xmlNewChild(os, nullptr, xc("initrd"), xc(initrd.c_str()));
+ xmlNewChild(os, nullptr, xc("cmdline"), xc(args.c_str()));
+}
+
+// Configure QEmu specific arguments.
+// This section adds the <qemu:commandline> node.
+void ConfigureQEmuSpecificOptions(
+ xmlNode* root, std::initializer_list<std::string> qemu_args) {
+ xmlNs* qemu_ns{xmlNewNs(
+ root, xc("http://libvirt.org/schemas/domain/qemu/1.0"), xc("qemu"))};
+
+ auto cmd = xmlNewChild(root, qemu_ns, xc("commandline"), nullptr);
+ for (const auto& str : qemu_args) {
+ auto arg = xmlNewChild(cmd, qemu_ns, xc("arg"), nullptr);
+ xmlNewProp(arg, xc("value"), xc(str.c_str()));
+ }
+}
+
+void ConfigureDeviceSource(xmlNode* device, DeviceSourceType type,
+ const std::string& path) {
+ auto source = xmlNewChild(device, nullptr, xc("source"), nullptr);
+ xmlNewProp(source, xc("path"), xc(path.c_str()));
+
+ switch (type) {
+ case DeviceSourceType::kFile:
+ xmlNewProp(device, xc("type"), xc("file"));
+ break;
+
+ case DeviceSourceType::kUnixSocketClient:
+ xmlNewProp(device, xc("type"), xc("unix"));
+ xmlNewProp(source, xc("mode"), xc("connect"));
+ break;
+
+ case DeviceSourceType::kUnixSocketServer:
+ xmlNewProp(device, xc("type"), xc("unix"));
+ xmlNewProp(source, xc("mode"), xc("bind"));
+ break;
+ }
+}
+
+// Configure serial port.
+// This section adds <serial> elements to <device> node.
+void ConfigureSerialPort(xmlNode* devices, int port, DeviceSourceType type,
+ const std::string& path) {
+ auto tty = xmlNewChild(devices, nullptr, xc("serial"), nullptr);
+ ConfigureDeviceSource(tty, type, path);
+
+ if (type == DeviceSourceType::kFile) {
+ LOG(INFO) << "Non-interactive serial port will send output to " << path;
+ } else {
+ LOG(INFO) << "Interactive serial port set up. To access the console run:";
+ LOG(INFO) << "$ sudo socat file:$(tty),raw,echo=0 " << path;
+ }
+ auto tgt = xmlNewChild(tty, nullptr, xc("target"), nullptr);
+ xmlNewProp(tgt, xc("port"), xc(concat(port).c_str()));
+}
+
+// Configure disk partition.
+// This section adds <disk> elements to <devices> node.
+void ConfigureDisk(xmlNode* devices, const std::string& name,
+ const std::string& path) {
+ auto ch = xmlNewChild(devices, nullptr, xc("disk"), nullptr);
+ xmlNewProp(ch, xc("type"), xc("file"));
+
+ auto dr = xmlNewChild(ch, nullptr, xc("driver"), nullptr);
+ xmlNewProp(dr, xc("name"), xc("qemu"));
+ xmlNewProp(dr, xc("type"), xc("raw"));
+ xmlNewProp(dr, xc("io"), xc("threads"));
+
+ auto tg = xmlNewChild(ch, nullptr, xc("target"), nullptr);
+ xmlNewProp(tg, xc("dev"), xc(name.c_str()));
+ xmlNewProp(tg, xc("bus"), xc("virtio"));
+
+ auto sr = xmlNewChild(ch, nullptr, xc("source"), nullptr);
+ xmlNewProp(sr, xc("file"), xc(path.c_str()));
+}
+
+// Configure virtio channel.
+// This section adds <channel> elements to <devices> node.
+void ConfigureVirtioChannel(xmlNode* devices, int port, const std::string& name,
+ DeviceSourceType type, const std::string& path) {
+ auto vch = xmlNewChild(devices, nullptr, xc("channel"), nullptr);
+ ConfigureDeviceSource(vch, type, path);
+
+ auto tgt = xmlNewChild(vch, nullptr, xc("target"), nullptr);
+ xmlNewProp(tgt, xc("type"), xc("virtio"));
+ xmlNewProp(tgt, xc("name"), xc(name.c_str()));
+
+ auto adr = xmlNewChild(vch, nullptr, xc("address"), nullptr);
+ xmlNewProp(adr, xc("type"), xc("virtio-serial"));
+ xmlNewProp(adr, xc("controller"), xc("0"));
+ xmlNewProp(adr, xc("bus"), xc("0"));
+ xmlNewProp(adr, xc("port"), xc(concat(port).c_str()));
+}
+
+// Configure network interface.
+// This section adds <interface> elements to <devices> node.
+void ConfigureNIC(xmlNode* devices, const std::string& name,
+ const std::string& bridge, int guest_id, int nic_id) {
+ auto nic = xmlNewChild(devices, nullptr, xc("interface"), nullptr);
+ xmlNewProp(nic, xc("type"), xc("bridge"));
+
+ auto brg = xmlNewChild(nic, nullptr, xc("source"), nullptr);
+ xmlNewProp(brg, xc("bridge"), xc(bridge.c_str()));
+
+ auto mac = xmlNewChild(nic, nullptr, xc("mac"), nullptr);
+ xmlNewProp(mac, xc("address"),
+ xc(concat("00:43:56:44:", std::setfill('0'), std::hex,
+ std::setw(2), guest_id, ':', std::setw(2), nic_id)
+ .c_str()));
+
+ auto mdl = xmlNewChild(nic, nullptr, xc("model"), nullptr);
+ xmlNewProp(mdl, xc("type"), xc("virtio"));
+
+ auto tgt = xmlNewChild(nic, nullptr, xc("target"), nullptr);
+ xmlNewProp(tgt, xc("dev"), xc(name.c_str()));
+}
+
+// Configure Harwdare Random Number Generator.
+// This section adds <rng> element to <devices> node.
+void ConfigureHWRNG(xmlNode* devices, const std::string& entsrc) {
+ auto rng = xmlNewChild(devices, nullptr, xc("rng"), nullptr);
+ xmlNewProp(rng, xc("model"), xc("virtio"));
+
+ auto rate = xmlNewChild(rng, nullptr, xc("rate"), nullptr);
+ xmlNewProp(rate, xc("period"), xc("2000"));
+ xmlNewProp(rate, xc("bytes"), xc("1024"));
+
+ auto bend = xmlNewChild(rng, nullptr, xc("backend"), xc(entsrc.c_str()));
+ xmlNewProp(bend, xc("model"), xc("random"));
+}
+
+} // namespace
+
+std::string GuestConfig::GetInstanceName() const {
+ return FLAGS_libvirt_domain;
+}
+
+std::string GuestConfig::Build() const {
+ std::string instance_name = GetInstanceName();
+
+ std::unique_ptr<xmlDoc, void (*)(xmlDocPtr)> xml{xmlNewDoc(xc("1.0")),
+ xmlFreeDoc};
+ auto root{xmlNewNode(nullptr, xc("domain"))};
+ xmlDocSetRootElement(xml.get(), root);
+ xmlNewProp(root, xc("type"), xc("kvm"));
+
+ ConfigureVM(root, instance_name, vcpus_, memory_mb_, uuid_);
+ ConfigureVMFeatures(root, {"acpi", "apic", "hap"});
+ ConfigureOperatingSystem(root, kernel_name_, initrd_name_, kernel_args_);
+ ConfigureQEmuSpecificOptions(
+ root,
+ {"-chardev", concat("socket,path=", ivshmem_socket_path_, ",id=ivsocket"),
+ "-device",
+ concat("ivshmem-doorbell,chardev=ivsocket,vectors=",
+ ivshmem_vector_count_),
+ "-cpu", "host"});
+
+ if (disable_app_armor_security_) {
+ auto seclabel = xmlNewChild(root, nullptr, xc("seclabel"), nullptr);
+ xmlNewProp(seclabel, xc("type"), xc("none"));
+ xmlNewProp(seclabel, xc("model"), xc("apparmor"));
+ }
+ if (disable_dac_security_) {
+ auto seclabel = xmlNewChild(root, nullptr, xc("seclabel"), nullptr);
+ xmlNewProp(seclabel, xc("type"), xc("none"));
+ xmlNewProp(seclabel, xc("model"), xc("dac"));
+ }
+
+ auto devices = xmlNewChild(root, nullptr, xc("devices"), nullptr);
+
+ ConfigureSerialPort(devices, 0, DeviceSourceType::kUnixSocketClient,
+ GetKernelLogSocketName());
+ ConfigureSerialPort(devices, 1, DeviceSourceType::kUnixSocketServer,
+ vsoc::GetDefaultPerInstancePath("console"));
+ ConfigureVirtioChannel(devices, 1, "cf-logcat", DeviceSourceType::kFile,
+ vsoc::GetDefaultPerInstancePath("logcat"));
+ ConfigureVirtioChannel(devices, 2, "cf-gadget-usb-v1",
+ DeviceSourceType::kUnixSocketClient,
+ GetUSBV1SocketName());
+
+ ConfigureDisk(devices, "vda", system_partition_path_);
+ ConfigureDisk(devices, "vdb", data_partition_path_);
+ ConfigureDisk(devices, "vdc", cache_partition_path_);
+ ConfigureDisk(devices, "vdd", vendor_partition_path_);
+
+ ConfigureNIC(devices, concat("amobile", id_), mobile_bridge_name_, id_, 1);
+ ConfigureHWRNG(devices, entropy_source_);
+
+ xmlChar* tgt;
+ int tgt_len;
+
+ xmlDocDumpFormatMemoryEnc(xml.get(), &tgt, &tgt_len, "utf-8", true);
+ std::string out((const char*)(tgt), tgt_len);
+ xmlFree(tgt);
+ return out;
+}
+
+} // namespace config
diff --git a/host/libs/config/guest_config.h b/host/libs/config/guest_config.h
new file mode 100644
index 0000000..5d4c566
--- /dev/null
+++ b/host/libs/config/guest_config.h
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#pragma once
+
+#include <libxml/tree.h>
+#include <memory>
+
+namespace config {
+// GuestConfig builds XML document describing target VM.
+// Documents built by GuestConfig can be directly used by libvirt to instantiate
+// new virtual machine.
+class GuestConfig {
+ public:
+ GuestConfig() = default;
+ ~GuestConfig() = default;
+
+ // Set instance ID.
+ GuestConfig& SetID(int id) {
+ id_ = id;
+ return *this;
+ }
+
+ // Set number of virtual CPUs.
+ GuestConfig& SetVCPUs(int vcpus) {
+ vcpus_ = vcpus;
+ return *this;
+ }
+
+ // Set total memory amount it MB.
+ GuestConfig& SetMemoryMB(int mem_mb) {
+ memory_mb_ = mem_mb;
+ return *this;
+ }
+
+ // Set kernel path.
+ GuestConfig& SetKernelName(const std::string& kernel) {
+ kernel_name_ = kernel;
+ return *this;
+ }
+
+ // Set kernel cmdline arguments.
+ GuestConfig& SetKernelArgs(const std::string& args) {
+ kernel_args_ = args;
+ return *this;
+ }
+
+ // Set initrd path.
+ GuestConfig& SetInitRDName(const std::string& initrd) {
+ initrd_name_ = initrd;
+ return *this;
+ }
+
+ // Set Android system partition image path.
+ GuestConfig& SetSystemPartitionPath(const std::string& path) {
+ system_partition_path_ = path;
+ return *this;
+ }
+
+ // Set Android data partition image path.
+ GuestConfig& SetCachePartitionPath(const std::string& path) {
+ cache_partition_path_ = path;
+ return *this;
+ }
+
+ // Set Android data partition image path.
+ GuestConfig& SetDataPartitionPath(const std::string& path) {
+ data_partition_path_ = path;
+ return *this;
+ }
+
+ // Set Android vendor partition image path.
+ GuestConfig& SetVendorPartitionPath(const std::string& path) {
+ vendor_partition_path_ = path;
+ return *this;
+ }
+
+ // Set ivshmem server socket path.
+ GuestConfig& SetIVShMemSocketPath(const std::string& path) {
+ ivshmem_socket_path_ = path;
+ return *this;
+ }
+
+ // Set number of vectors supplied by ivserver.
+ GuestConfig& SetIVShMemVectorCount(int count) {
+ ivshmem_vector_count_ = count;
+ return *this;
+ }
+
+ // Set name of the mobile bridge, eg. br0
+ GuestConfig& SetMobileBridgeName(const std::string& name) {
+ mobile_bridge_name_ = name;
+ return *this;
+ }
+
+ // Set source of entropy, eg. /dev/urandom.
+ GuestConfig& SetEntropySource(const std::string& source) {
+ entropy_source_ = source;
+ return *this;
+ }
+
+ // Flags to disable the AppArmor security features of libvirt
+ GuestConfig& SetDisableAppArmorSecurity(bool value) {
+ disable_app_armor_security_ = value;
+ return *this;
+ }
+
+ // Flags to disable the DAC security features of libvirt
+ GuestConfig& SetDisableDACSecurity(bool value) {
+ disable_dac_security_ = value;
+ return *this;
+ }
+
+ // The UUID that libvirt uses to identify the instance
+ GuestConfig& SetUUID(const std::string& uuid) {
+ uuid_ = uuid;
+ return *this;
+ }
+
+ // GetInstanceName returns name of this newly created instance.
+ std::string GetInstanceName() const;
+
+ // GetUSBSocketName returns name of the USB socket that will be used to
+ // forward access to USB gadget. This is for V1 of the USB bus.
+ std::string GetUSBV1SocketName() const {
+ return usb_v1_socket_name_;
+ }
+
+ GuestConfig& SetUSBV1SocketName(const std::string& source) {
+ usb_v1_socket_name_ = source;
+ return *this;
+ }
+
+ std::string GetKernelLogSocketName() const {
+ return kernel_log_socket_name_;
+ }
+
+ GuestConfig& SetKernelLogSocketName( const std::string& source) {
+ kernel_log_socket_name_ = source;
+ return *this;
+ }
+
+ // Build document as formatted XML string.
+ std::string Build() const;
+
+ private:
+ int id_;
+ int vcpus_;
+ int memory_mb_;
+
+ std::string kernel_name_;
+ std::string kernel_args_;
+ std::string initrd_name_;
+
+ std::string system_partition_path_;
+ std::string cache_partition_path_;
+ std::string data_partition_path_;
+ std::string vendor_partition_path_;
+ std::string usb_v1_socket_name_;
+ std::string kernel_log_socket_name_;
+
+ std::string ivshmem_socket_path_;
+ int ivshmem_vector_count_;
+
+ std::string mobile_bridge_name_;
+ std::string entropy_source_;
+
+ std::string uuid_;
+ bool disable_dac_security_;
+ bool disable_app_armor_security_;
+
+ GuestConfig(const GuestConfig&) = delete;
+ GuestConfig& operator=(const GuestConfig&) = delete;
+};
+
+} // namespace config
diff --git a/host/libs/config/host_config.cpp b/host/libs/config/host_config.cpp
new file mode 100644
index 0000000..5b74c67
--- /dev/null
+++ b/host/libs/config/host_config.cpp
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include "host/libs/config/host_config.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <string>
+#include <iomanip>
+#include <sstream>
+
+#include <gflags/gflags.h>
+
+const char vsoc_user_prefix[] = "vsoc-";
+
+int GetDefaultInstance() {
+ char* user = getenv("USER");
+ if (user && !memcmp(user, vsoc_user_prefix, sizeof(vsoc_user_prefix) - 1)) {
+ int temp = atoi(user + sizeof(vsoc_user_prefix) - 1);
+ if (temp > 0) {
+ return temp;
+ }
+ }
+ return 1;
+}
+
+DEFINE_string(domain, vsoc::GetDefaultShmClientSocketPath(),
+ "Path to the ivshmem client socket");
+DEFINE_int32(instance, GetDefaultInstance(),
+ "Instance number. Must be unique.");
+
+std::string vsoc::GetPerInstanceDefault(const char* prefix) {
+ std::ostringstream stream;
+ stream << prefix << std::setfill('0') << std::setw(2)
+ << GetDefaultInstance();
+ return stream.str();
+}
+
+std::string vsoc::GetDefaultPerInstanceDir() {
+ return vsoc::GetPerInstanceDefault("/var/run/cvd-");
+}
+
+std::string vsoc::GetDefaultPerInstancePath(const std::string& basename) {
+ std::ostringstream stream;
+ stream << GetDefaultPerInstanceDir() << "/" << basename;
+ return stream.str();
+}
+
+std::string vsoc::GetDefaultShmClientSocketPath() {
+ return vsoc::GetDefaultPerInstancePath("ivshmem_socket_client");
+}
+
+std::string vsoc::GetDomain() {
+ return FLAGS_domain;
+}
diff --git a/host/libs/config/host_config.h b/host/libs/config/host_config.h
new file mode 100644
index 0000000..8456270
--- /dev/null
+++ b/host/libs/config/host_config.h
@@ -0,0 +1,29 @@
+#pragma once
+
+/*
+ * Copyright (C) 2017 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.
+ */
+
+/* Host-specific values associated with RegionControl. */
+
+#include <string>
+
+namespace vsoc {
+std::string GetPerInstanceDefault(const char* prefix);
+std::string GetDefaultPerInstanceDir();
+std::string GetDefaultPerInstancePath(const std::string& basename);
+std::string GetDefaultShmClientSocketPath();
+std::string GetDomain();
+} // namespace vsoc
diff --git a/host/libs/ivserver/Android.bp b/host/libs/ivserver/Android.bp
new file mode 100644
index 0000000..c15c4b1
--- /dev/null
+++ b/host/libs/ivserver/Android.bp
@@ -0,0 +1,41 @@
+cc_library_host_static {
+ name: "libivserver",
+ srcs: [
+ "hald_client.cc",
+ "ivserver.cc",
+ "options.cc",
+ "qemu_client.cc",
+ "vsocsharedmem.cc",
+ ],
+ header_libs: [
+ "cuttlefish_glog",
+ ],
+ shared_libs: [
+ "libbase",
+ ],
+ static_libs: [
+ "libjsoncpp",
+ ],
+ defaults: ["cuttlefish_host_only"],
+}
+
+cc_test_host {
+ name: "hald_client_test",
+ srcs: [
+ "hald_client_test.cc",
+ ],
+ header_libs: [
+ "cuttlefish_glog",
+ ],
+ shared_libs: [
+ "libcuttlefish_fs",
+ "cuttlefish_auto_resources",
+ "libbase",
+ ],
+ static_libs: [
+ "libivserver",
+ "libjsoncpp",
+ "libgmock",
+ ],
+ defaults: ["cuttlefish_host_only"],
+}
diff --git a/host/libs/ivserver/README.md b/host/libs/ivserver/README.md
new file mode 100644
index 0000000..ea1d2c2
--- /dev/null
+++ b/host/libs/ivserver/README.md
@@ -0,0 +1,72 @@
+## Overview
+
+This is the native ivshmem-server implementation catering to the GCE CVD
+vSoC project.
+
+We are breaking from the general philosophy of ivshmem-server inter-vm
+communication.
+
+There is no concept of inter-vm communication. The server itself is meant to
+run on the L1 Guest (or L0 even). We will call this domain as 'host-side'. The
+following functions are envisoned:
+
+* Create the shared-memory window, listen for VM connection (and subsequent
+ disconnection). Note that the server can only accomodate one VM connection at
+ a time. This may need to be enforced as there may be mulitple VMs (but each
+ of them need a dedicated shared_memory and VM <--> server UNIX Domain
+ sockets).
+
+* Parse a JSON file describing memory layout and other information. Use
+ this information to initialize the shared memory.
+
+* Create two UNIX Domain sockets. One to communicate with QEMU and the other
+ to communicate with host clients.
+
+* For QEMU, speak the ivshmem protocol, i.e. pass a vmid, pass the shm fd
+ to the qemu VM along with event fds. One for host to guest signalling and the
+ other for guest to host signalling. Please see:
+ QEMU_SRC/docs/specs/ivshmem-spec.txt. The only twist is that the server
+ pretends to be another peer VM to comply to the ivshmem protocol.
+
+* For the client, speak the ad-hoc client protocol:
+
+ ivshmem_client <--> ivshmem_server handshake.
+
+ Client -> 'GET PROTOCOL_VER'
+ Server -> 'PROTOCOL_VER 0'
+ Client -> INFORM REGION_NAME_LEN: 0x0000000a
+ Client -> GET REGION: HW_COMPOSER
+ Server -> 0xffffffff(If region name not found)
+ Server -> 0xAABBC000 (region start offset)
+ Server -> 0xAABBC0000 (region end offset)
+ Server -> <Send cmsg with guest_to_host eventfd>
+ Server -> <Send cmsg with host_to_guest eventfd>
+ Server -> <Send cmsg with shmfd>
+
+ * This also launches QEMU with the appropriate parameters
+
+Building:
+ From this directory issue the following in the command line
+ `bazel build src:ivserver`
+
+Running:
+ Once the binary is built using bazel. Just run it from this directory
+ as the default options expect the JSON files under conf directory.
+ e.g.
+ `./bazel-bin/src/ivserver`
+
+ If you encounter either
+ * Error in creating shared_memory file: File exists
+ * Bind failed: Address already in use
+
+ Please run the following commands and retry launching.
+ `rm /dev/shm/ivshmem`
+ `rm /tmp/ivsh*`
+
+TODO:
+ * Refactor.
+ * Separate the JSON configuration into QEMU and mem-layout specific files.
+ * Conform to Google coding standards.
+ * Add some documentation on the default options.
+ * Fault Tolerance.
+
diff --git a/host/libs/ivserver/hald_client.cc b/host/libs/ivserver/hald_client.cc
new file mode 100644
index 0000000..6f2f6b8
--- /dev/null
+++ b/host/libs/ivserver/hald_client.cc
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#include "host/libs/ivserver/hald_client.h"
+
+#include <string>
+
+#include <glog/logging.h>
+
+namespace ivserver {
+namespace {
+// The protocol between host-clients and the ivserver could change.
+// Clients should verify what version they are talking to during the handshake.
+const uint32_t kHaldClientProtocolVersion = 0;
+} // anonymous namespace
+
+std::unique_ptr<HaldClient> HaldClient::New(const VSoCSharedMemory& shmem,
+ const cvd::SharedFD& clientfd) {
+ std::unique_ptr<HaldClient> res;
+
+ if (!clientfd->IsOpen()) {
+ LOG(WARNING) << "Invalid socket passed to HaldClient: "
+ << clientfd->StrError();
+ return res;
+ }
+
+ res.reset(new HaldClient(clientfd));
+ if (!res->PerformHandshake(shmem)) {
+ LOG(ERROR) << "HalD handshake failed. Dropping connection.";
+ res.reset();
+ }
+
+ return res;
+}
+
+HaldClient::HaldClient(const cvd::SharedFD& client_socket)
+ : client_socket_(client_socket) {}
+
+bool HaldClient::PerformHandshake(const VSoCSharedMemory& shared_mem) {
+ int rval =
+ client_socket_->Send(&kHaldClientProtocolVersion,
+ sizeof(kHaldClientProtocolVersion), MSG_NOSIGNAL);
+ if (rval != sizeof(kHaldClientProtocolVersion)) {
+ LOG(ERROR) << "failed to send protocol version: "
+ << client_socket_->StrError();
+ return false;
+ }
+
+ int16_t region_name_len;
+ if (client_socket_->Recv(®ion_name_len, sizeof(region_name_len),
+ MSG_NOSIGNAL) != sizeof(region_name_len)) {
+ LOG(ERROR) << "Error receiving region name length: "
+ << client_socket_->StrError();
+ return false;
+ }
+
+ if (region_name_len <= 0 ||
+ region_name_len > VSoCSharedMemory::kMaxRegionNameLength) {
+ LOG(ERROR) << "Invalid region length received: " << region_name_len;
+ return false;
+ }
+
+ std::vector<char> region_name_data(region_name_len);
+ rval = client_socket_->Recv(region_name_data.data(), region_name_len,
+ MSG_NOSIGNAL);
+ if (rval != region_name_len) {
+ LOG(ERROR) << "Incomplete region name length received. Want: "
+ << region_name_len << ", got: " << rval;
+ return false;
+ }
+
+ std::string region_name(region_name_data.begin(), region_name_data.end());
+ LOG(INFO) << "New HALD requesting region: " << region_name;
+
+ // Send Host, Guest and SharedMemory FDs associated with this region.
+ cvd::SharedFD guest_to_host_efd;
+ cvd::SharedFD host_to_guest_efd;
+
+ if (!shared_mem.GetEventFdPairForRegion(region_name, &guest_to_host_efd,
+ &host_to_guest_efd)) {
+ LOG(ERROR) << "Region " << region_name << " was not found.";
+ return false;
+ }
+
+ if (!guest_to_host_efd->IsOpen()) {
+ LOG(ERROR) << "Host channel is not open; last known error: "
+ << guest_to_host_efd->StrError();
+ return false;
+ }
+
+ if (!host_to_guest_efd->IsOpen()) {
+ LOG(ERROR) << "Guest channel is not open; last known error: "
+ << host_to_guest_efd->StrError();
+ return false;
+ }
+
+ // TODO(ender): delete this once no longer necessary. Currently, absence of
+ // payload makes RecvMsgAndFDs hang forever.
+ uint64_t control_data = 0;
+ struct iovec vec {
+ &control_data, sizeof(control_data)
+ };
+ cvd::InbandMessageHeader hdr{nullptr, 0, &vec, 1, 0};
+ cvd::SharedFD fds[3] = {guest_to_host_efd, host_to_guest_efd,
+ shared_mem.SharedMemFD()};
+ rval = client_socket_->SendMsgAndFDs<3>(hdr, MSG_NOSIGNAL, fds);
+ if (rval == -1) {
+ LOG(ERROR) << "failed to send Host FD: " << client_socket_->StrError();
+ return false;
+ }
+
+ LOG(INFO) << "HALD managing region: " << region_name << " connected.";
+ return true;
+}
+
+} // namespace ivserver
diff --git a/host/libs/ivserver/hald_client.h b/host/libs/ivserver/hald_client.h
new file mode 100644
index 0000000..99b5fee
--- /dev/null
+++ b/host/libs/ivserver/hald_client.h
@@ -0,0 +1,49 @@
+#pragma once
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include <inttypes.h>
+#include <unistd.h>
+#include <memory>
+
+#include "common/libs/fs/shared_fd.h"
+#include "host/libs/ivserver/vsocsharedmem.h"
+
+namespace ivserver {
+
+// Handles a HAL deamon client connection & handshake.
+class HaldClient final {
+ public:
+ static std::unique_ptr<HaldClient> New(const VSoCSharedMemory &shared_mem,
+ const cvd::SharedFD &client_fd);
+
+ private:
+ cvd::SharedFD client_socket_;
+
+ // Initialize new instance of HAL Client.
+ HaldClient(const cvd::SharedFD &client_fd);
+
+ // Perform handshake with HAL Client.
+ // If the region is not found approprate status (-1) is sent.
+ // Note that for every new client connected a unique ClientConnection object
+ // will be created and after the handshake it will be destroyed.
+ bool PerformHandshake(const VSoCSharedMemory &shared_mem);
+
+ HaldClient(const HaldClient &) = delete;
+ HaldClient &operator=(const HaldClient &) = delete;
+};
+
+} // namespace ivserver
diff --git a/host/libs/ivserver/hald_client_test.cc b/host/libs/ivserver/hald_client_test.cc
new file mode 100644
index 0000000..c566079
--- /dev/null
+++ b/host/libs/ivserver/hald_client_test.cc
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#include <stdlib.h>
+#include <memory>
+#include <string>
+#include <thread>
+
+#include <glog/logging.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "common/libs/fs/shared_fd.h"
+#include "host/libs/ivserver/hald_client.h"
+#include "host/libs/ivserver/vsocsharedmem_mock.h"
+
+using ::testing::DoAll;
+using ::testing::Return;
+using ::testing::ReturnRef;
+using ::testing::SetArgPointee;
+using ::testing::_;
+
+namespace ivserver {
+namespace test {
+
+class HaldClientTest : public ::testing::Test {
+ public:
+ void SetUp() override {
+ std::string socket_location;
+ ASSERT_TRUE(GetTempLocation(&socket_location))
+ << "Could not create temp file";
+ LOG(INFO) << "Temp file location: " << socket_location;
+
+ hald_server_socket_ = cvd::SharedFD::SocketLocalServer(
+ socket_location.c_str(), false, SOCK_STREAM, 0666);
+ test_socket_ = cvd::SharedFD::SocketLocalClient(socket_location.c_str(),
+ false, SOCK_STREAM);
+ hald_socket_ =
+ cvd::SharedFD::Accept(*hald_server_socket_, nullptr, nullptr);
+
+ EXPECT_TRUE(hald_server_socket_->IsOpen());
+ EXPECT_TRUE(test_socket_->IsOpen());
+ EXPECT_TRUE(hald_socket_->IsOpen());
+ }
+
+ bool GetTempLocation(std::string* target) {
+ char temp_location[] = "/tmp/hald-client-test-XXXXXX";
+ mktemp(temp_location);
+ *target = temp_location;
+ if (target->size()) {
+ cleanup_files_.emplace_back(*target);
+ return true;
+ }
+ return false;
+ }
+
+ void TearDown() override {
+ hald_socket_->Close();
+ test_socket_->Close();
+ hald_server_socket_->Close();
+
+ for (const std::string& file : cleanup_files_) {
+ unlink(file.c_str());
+ }
+ }
+
+ protected:
+ ::testing::NiceMock<VSoCSharedMemoryMock> vsoc_;
+
+ cvd::SharedFD hald_server_socket_;
+ cvd::SharedFD hald_socket_;
+ cvd::SharedFD test_socket_;
+
+ private:
+ std::vector<std::string> cleanup_files_;
+};
+
+TEST_F(HaldClientTest, HandshakeTerminatedByHald) {
+ std::thread thread(
+ [this]() {
+ auto client(HaldClient::New(vsoc_, hald_socket_));
+ EXPECT_FALSE(client)
+ << "Handshake should not complete when client terminates early.";
+ });
+
+ test_socket_->Close();
+ thread.join();
+}
+
+TEST_F(HaldClientTest, HandshakeTerminatedByInvalidRegionSize) {
+ uint16_t sizes[] = {0, VSoCSharedMemory::kMaxRegionNameLength + 1, 0xffff};
+
+ for (uint16_t size : sizes) {
+ std::thread thread([this, size]() {
+ auto client(HaldClient::New(vsoc_, hald_socket_));
+ EXPECT_FALSE(client) << "Handle should not be created when size is "
+ << size;
+ });
+
+ int32_t proto_version;
+ EXPECT_EQ(sizeof(proto_version),
+ test_socket_->Recv(&proto_version, sizeof(proto_version),
+ MSG_NOSIGNAL));
+
+ test_socket_->Send(&size, sizeof(size), MSG_NOSIGNAL);
+ thread.join();
+ }
+}
+
+TEST_F(HaldClientTest, FullSaneHandshake) {
+ std::string temp;
+ ASSERT_TRUE(GetTempLocation(&temp));
+ cvd::SharedFD host_fd(
+ cvd::SharedFD::Open(temp.c_str(), O_CREAT | O_RDWR, 0666));
+ EXPECT_TRUE(host_fd->IsOpen());
+
+ ASSERT_TRUE(GetTempLocation(&temp));
+ cvd::SharedFD guest_fd(
+ cvd::SharedFD::Open(temp.c_str(), O_CREAT | O_RDWR, 0666));
+ EXPECT_TRUE(guest_fd->IsOpen());
+
+ ASSERT_TRUE(GetTempLocation(&temp));
+ cvd::SharedFD shmem_fd(
+ cvd::SharedFD::Open(temp.c_str(), O_CREAT | O_RDWR, 0666));
+ EXPECT_TRUE(shmem_fd->IsOpen());
+
+ const std::string test_location("testing");
+ EXPECT_CALL(vsoc_, GetEventFdPairForRegion(test_location, _, _))
+ .WillOnce(DoAll(SetArgPointee<1>(host_fd), SetArgPointee<2>(guest_fd),
+ Return(true)));
+ EXPECT_CALL(vsoc_, SharedMemFD()).WillOnce(ReturnRef(shmem_fd));
+
+ std::thread thread([this]() {
+ auto client(HaldClient::New(vsoc_, hald_socket_));
+ EXPECT_TRUE(client);
+ });
+
+ int32_t proto_version;
+ EXPECT_EQ(
+ sizeof(proto_version),
+ test_socket_->Recv(&proto_version, sizeof(proto_version), MSG_NOSIGNAL));
+
+ uint16_t size = test_location.size();
+ EXPECT_EQ(sizeof(size),
+ test_socket_->Send(&size, sizeof(size), MSG_NOSIGNAL));
+ EXPECT_EQ(size, test_socket_->Send(test_location.data(), size, MSG_NOSIGNAL));
+
+ // TODO(ender): delete this once no longer necessary. Currently, absence of
+ // payload makes RecvMsgAndFDs hang forever.
+ uint64_t control_data;
+ struct iovec vec {
+ &control_data, sizeof(control_data)
+ };
+ cvd::InbandMessageHeader hdr{nullptr, 0, &vec, 1, 0};
+ cvd::SharedFD fds[3];
+
+ EXPECT_GT(test_socket_->RecvMsgAndFDs<3>(hdr, MSG_NOSIGNAL, &fds), 0);
+ EXPECT_TRUE(fds[0]->IsOpen());
+ EXPECT_TRUE(fds[1]->IsOpen());
+ EXPECT_TRUE(fds[2]->IsOpen());
+
+ thread.join();
+}
+
+} // namespace test
+} // namespace ivserver
diff --git a/host/libs/ivserver/ivserver.cc b/host/libs/ivserver/ivserver.cc
new file mode 100644
index 0000000..d966764
--- /dev/null
+++ b/host/libs/ivserver/ivserver.cc
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#include "host/libs/ivserver/ivserver.h"
+
+#include <sys/select.h>
+#include <algorithm>
+
+#include <glog/logging.h>
+
+#include "common/libs/fs/shared_select.h"
+#include "host/libs/ivserver/hald_client.h"
+#include "host/libs/ivserver/qemu_client.h"
+
+namespace ivserver {
+
+IVServer::IVServer(const IVServerOptions &options, const Json::Value &json_root)
+ : json_root_{json_root},
+ vsoc_shmem_(VSoCSharedMemory::New(options.shm_file_path, json_root_)) {
+ LOG_IF(WARNING, unlink(options.qemu_socket_path.c_str()) == 0)
+ << "Removed existing unix socket: " << options.qemu_socket_path
+ << ". We can't confirm yet whether another instance is running.";
+ qemu_channel_ = cvd::SharedFD::SocketLocalServer(
+ options.qemu_socket_path.c_str(), false, SOCK_STREAM, 0666);
+ LOG_IF(FATAL, !qemu_channel_->IsOpen())
+ << "Could not create QEmu channel: " << qemu_channel_->StrError();
+
+ LOG_IF(WARNING, unlink(options.client_socket_path.c_str()) == 0)
+ << "Removed existing unix socket: " << options.client_socket_path
+ << ". We can't confirm yet whether another instance is running.";
+ client_channel_ = cvd::SharedFD::SocketLocalServer(
+ options.client_socket_path.c_str(), false, SOCK_STREAM, 0666);
+ LOG_IF(FATAL, !client_channel_->IsOpen())
+ << "Could not create Client channel: " << client_channel_->StrError();
+}
+
+void IVServer::Serve() {
+ while (true) {
+ cvd::SharedFDSet rset;
+ rset.Set(qemu_channel_);
+ rset.Set(client_channel_);
+ cvd::Select(&rset, nullptr, nullptr, nullptr);
+
+ if (rset.IsSet(qemu_channel_)) {
+ HandleNewQemuConnection();
+ }
+
+ if (rset.IsSet(client_channel_)) {
+ HandleNewClientConnection();
+ }
+ }
+
+ LOG(FATAL) << "Control reached out of event loop";
+}
+
+void IVServer::HandleNewClientConnection() {
+ std::unique_ptr<HaldClient> res = HaldClient::New(
+ *vsoc_shmem_, cvd::SharedFD::Accept(*client_channel_, nullptr, nullptr));
+ if (!res) {
+ LOG(WARNING) << "Rejecting unsuccessful HALD connection.";
+ }
+}
+
+void IVServer::HandleNewQemuConnection() {
+ std::unique_ptr<QemuClient> res = QemuClient::New(
+ *vsoc_shmem_, cvd::SharedFD::Accept(*qemu_channel_, nullptr, nullptr));
+
+ if (!res) {
+ LOG(WARNING) << "Could not accept new QEmu client.";
+ }
+}
+
+} // namespace ivserver
diff --git a/host/libs/ivserver/ivserver.h b/host/libs/ivserver/ivserver.h
new file mode 100644
index 0000000..ad32d81
--- /dev/null
+++ b/host/libs/ivserver/ivserver.h
@@ -0,0 +1,48 @@
+#pragma once
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include <json/json.h>
+#include <memory>
+
+#include "common/libs/fs/shared_fd.h"
+#include "host/libs/ivserver/options.h"
+#include "host/libs/ivserver/vsocsharedmem.h"
+
+namespace ivserver {
+
+// This class is responsible for orchestrating the setup and then serving
+// new connections.
+class IVServer final {
+ public:
+ IVServer(const IVServerOptions &options, const Json::Value &json_root);
+ IVServer(const IVServer &) = delete;
+
+ // Serves incoming client and qemu connection.
+ // This method should never return.
+ void Serve();
+
+ private:
+ void HandleNewClientConnection();
+ void HandleNewQemuConnection();
+
+ const Json::Value &json_root_;
+ std::unique_ptr<VSoCSharedMemory> vsoc_shmem_;
+ cvd::SharedFD qemu_channel_;
+ cvd::SharedFD client_channel_;
+};
+
+} // namespace ivserver
diff --git a/host/libs/ivserver/options.cc b/host/libs/ivserver/options.cc
new file mode 100644
index 0000000..25f88dd
--- /dev/null
+++ b/host/libs/ivserver/options.cc
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#include "host/libs/ivserver/options.h"
+
+namespace ivserver {
+
+IVServerOptions::IVServerOptions(const std::string &mem_layout_conf,
+ const std::string &shm_file_path,
+ const std::string &qemu_socket_path,
+ const std::string &client_socket_path)
+ : memory_layout_conf_path(mem_layout_conf),
+ shm_file_path(shm_file_path),
+ qemu_socket_path(qemu_socket_path),
+ client_socket_path(client_socket_path) {}
+
+std::ostream &operator<<(std::ostream &out, const IVServerOptions &options) {
+ out << "\nmem_layout_conf_path: " << options.memory_layout_conf_path
+ << "\nshm_file: " << options.shm_file_path
+ << "\nqemu_socket_path: " << options.qemu_socket_path
+ << "\nclient_socket_path: " << options.client_socket_path << std::endl;
+
+ return out;
+}
+
+} // namespace ivserver
diff --git a/host/libs/ivserver/options.h b/host/libs/ivserver/options.h
new file mode 100644
index 0000000..53a51e2
--- /dev/null
+++ b/host/libs/ivserver/options.h
@@ -0,0 +1,50 @@
+#pragma once
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include <inttypes.h>
+#include <iostream>
+#include <string>
+
+namespace ivserver {
+
+const uint16_t kIVServerMajorVersion = 1;
+const uint16_t kIVServerMinorVersion = 0;
+const uint32_t kIVServerDefaultShmSizeInMiB = 4;
+const std::string kIVServerDefaultLayoutFile = "vsoc_mem.json";
+
+//
+// structure that contains the various options to start the server.
+//
+struct IVServerOptions final {
+ IVServerOptions(const std::string &mem_layout_conf,
+ const std::string &shm_file_path,
+ const std::string &qemu_socket_path,
+ const std::string &client_socket_path);
+
+ //
+ // We still need a friend here
+ //
+ friend std::ostream &operator<<(std::ostream &out,
+ const IVServerOptions &opts);
+
+ const std::string memory_layout_conf_path;
+ const std::string shm_file_path;
+ const std::string qemu_socket_path;
+ const std::string client_socket_path;
+};
+
+} // namespace ivserver
diff --git a/host/libs/ivserver/qemu_client.cc b/host/libs/ivserver/qemu_client.cc
new file mode 100644
index 0000000..2771e06
--- /dev/null
+++ b/host/libs/ivserver/qemu_client.cc
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#include "host/libs/ivserver/qemu_client.h"
+
+#include <glog/logging.h>
+
+namespace ivserver {
+
+std::unique_ptr<QemuClient> QemuClient::New(const VSoCSharedMemory& shmem,
+ const cvd::SharedFD& socket) {
+ std::unique_ptr<QemuClient> res;
+ if (!socket->IsOpen()) {
+ LOG(WARNING) << "Invalid socket passed to QemuClient: "
+ << socket->StrError();
+ return res;
+ }
+
+ res.reset(new QemuClient(std::move(socket)));
+ if (!res->PerformHandshake(shmem)) {
+ LOG(ERROR) << "Qemu handshake failed. Dropping connection.";
+ res.reset();
+ }
+
+ return res;
+}
+
+QemuClient::QemuClient(cvd::SharedFD socket) : client_socket_(socket) {}
+
+// Once the QemuClient object is constructed, invoking the following
+// method will perform the actual handshake with a QEMU instance.
+bool QemuClient::PerformHandshake(const VSoCSharedMemory& shmem) {
+ LOG(INFO) << "New QEmu client connected.";
+ // 1. The protocol version number, currently zero. The client should
+ // close the connection on receipt of versions it can't handle.
+ int64_t msg = QemuConstants::kIvshMemProtocolVersion;
+ int rval = client_socket_->Send(&msg, sizeof(msg), MSG_NOSIGNAL);
+ if (rval != sizeof(msg)) {
+ LOG(ERROR) << "Failed to send protocol version: "
+ << client_socket_->StrError();
+ return false;
+ }
+
+ // 2. The client's ID. This is unique among all clients of this server.
+ // IDs must be between 0 and 65535, because the Doorbell register
+ // provides only 16 bits for them.
+ msg = QemuConstants::kGuestID;
+ rval = client_socket_->Send(&msg, sizeof(msg), MSG_NOSIGNAL);
+ if (rval != sizeof(msg)) {
+ LOG(ERROR) << "Failed to send VM Id: " << client_socket_->StrError();
+ return false;
+ }
+
+ // 3. The number -1, accompanied by the file descriptor for the shared
+ // memory.
+ if (!SendSocketInfo(QemuFDMsg::kSharedMem, shmem.SharedMemFD())) {
+ LOG(ERROR) << "Failed to send Shared Memory socket: "
+ << client_socket_->StrError();
+ return false;
+ }
+
+ // 4. Connect notifications for existing other clients, if any. This is
+ // a peer ID (number between 0 and 65535 other than the client's ID),
+ // repeated N times. Each repetition is accompanied by one file
+ // descriptor. These are for interrupting the peer with that ID using
+ // vector 0,..,N-1, in order. If the client is configured for fewer
+ // vectors, it closes the extra file descriptors. If it is configured
+ // for more, the extra vectors remain unconnected.
+ for (const auto region_data : shmem.Regions()) {
+ if (!SendSocketInfo(QemuFDMsg::kHostSideHald, region_data.host_fd)) {
+ LOG(ERROR) << "Failed to send Host Side FD for region "
+ << region_data.values.device_name << ": " << client_socket_->StrError();
+ return false;
+ }
+ }
+
+ // 5. Interrupt setup. This is the client's own ID, repeated N times.
+ // Each repetition is accompanied by one file descriptor. These are
+ // for receiving interrupts from peers using vector 0,..,N-1, in
+ // order. If the client is configured for fewer vectors, it closes
+ // the extra file descriptors. If it is configured for more, the
+ // extra vectors remain unconnected.
+ for (const auto region_data : shmem.Regions()) {
+ if (!SendSocketInfo(QemuFDMsg::kGuestSideHal, region_data.guest_fd)) {
+ LOG(ERROR) << "Failed to send Guest Side FD for region "
+ << region_data.values.device_name << ": " << client_socket_->StrError();
+ return false;
+ }
+ }
+
+ LOG(INFO) << "QEmu handshake completed.";
+ return true;
+}
+
+bool QemuClient::SendSocketInfo(QemuFDMsg message,
+ const cvd::SharedFD& socket) {
+ struct iovec vec {
+ &message, sizeof(message)
+ };
+ cvd::InbandMessageHeader hdr{nullptr, 0, &vec, 1, 0};
+ cvd::SharedFD fds[] = {socket};
+ int rval = client_socket_->SendMsgAndFDs(hdr, 0, fds);
+ if (rval == -1) {
+ LOG(ERROR) << "failed to send shared_mem_fd: "
+ << client_socket_->StrError();
+ return false;
+ }
+ return true;
+}
+
+} // namespace ivserver
diff --git a/host/libs/ivserver/qemu_client.h b/host/libs/ivserver/qemu_client.h
new file mode 100644
index 0000000..658b42c
--- /dev/null
+++ b/host/libs/ivserver/qemu_client.h
@@ -0,0 +1,79 @@
+#pragma once
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include <memory>
+
+#include "common/libs/fs/shared_fd.h"
+#include "host/libs/ivserver/vsocsharedmem.h"
+
+namespace ivserver {
+
+// QemuClient manages individual QEmu connections using protocol specified
+// in documentation file distributed as part of QEmu 2.8 package under:
+// docs/specs/ivshmem-spec.txt
+// Alternatively, please point your browser to the following URL:
+// https://github.com/qemu/qemu/blob/stable-2.8/docs/specs/ivshmem-spec.txt
+class QemuClient final {
+ public:
+ static std::unique_ptr<QemuClient> New(const VSoCSharedMemory &shmem,
+ const cvd::SharedFD &connection);
+
+ cvd::SharedFD client_socket() const { return client_socket_; }
+
+ private:
+ enum QemuConstants : int64_t {
+ kIvshMemProtocolVersion = 0,
+ // HostID is in fact a Peer ID and can take multiple values, depending on
+ // how many subsystems we would like Guest to talk to.
+ kHostBaseID = 0,
+ // GuestID is a unique form of Peer ID (see above), that identifies newly
+ // created quest in IvSharedMem world.
+ kGuestID = 1024
+ };
+
+ static_assert(QemuConstants::kHostBaseID < QemuConstants::kGuestID,
+ "Guest and host should have different IDs");
+ // Type of QEmu FD messages.
+ // QEmu uses these messages to identify purpose of socket it is
+ // receiving.
+ enum class QemuFDMsg : int64_t {
+ // Represents SharedMemory FD.
+ kSharedMem = -1,
+ // Represents primary (and currently only) FD that is owned and managed by
+ // Host side.
+ kHostSideHald = QemuConstants::kHostBaseID,
+ // Represents FDs that are owned by Guest.
+ kGuestSideHal = QemuConstants::kGuestID,
+ };
+
+ cvd::SharedFD client_socket_;
+
+ // Initialize new instance of QemuClient.
+ QemuClient(cvd::SharedFD qemu_listener_socket);
+
+ // Once the QemuClient object is constructed, invoking the following
+ // method will perform the actual handshake with a QEMU instance.
+ bool PerformHandshake(const VSoCSharedMemory &shmem_fd);
+
+ // Send socket data to Qemu.
+ bool SendSocketInfo(QemuFDMsg message, const cvd::SharedFD &socket);
+
+ QemuClient(const QemuClient &) = delete;
+ QemuClient &operator=(const QemuClient &) = delete;
+};
+
+} // namespace ivserver
diff --git a/host/libs/ivserver/shm_layout.txt b/host/libs/ivserver/shm_layout.txt
new file mode 100644
index 0000000..39d66a7
--- /dev/null
+++ b/host/libs/ivserver/shm_layout.txt
@@ -0,0 +1,88 @@
+(Please use a smaller font and/or appropriate terminal width to see this line without wrapping)
+
+
+ REGION DESCRIPTORS
+ (starts at page 0)
+ +------------------------------+
+ | major_version: u16 |
+ +------------------------------+
+ | minor_version: u16 |
+ +------------------------------+
+ (size of shared memory) | size: u32 |
+ +------------------------------+
+ (number of regions) | region_count: u32 |
+ +------------------------------+
+ (offset of region descriptor data) | vsoc_region_desc_offset: u32 |o-----+
+ +------------------------------+ |
+ | | |
+ | .. .. .. | |
+ | | |
+ (region 0 descriptor start) +------------------------------+ <----+
+ | current_version: u16 |
+ +------------------------------+
+ | min_compatible_version: u16 |
+ +------------------------------+
+ (start offset of region 0) | region_begin_offset: u32 |o--------------------+
+ +------------------------------+ |
+ (end offset of region 0) | region_end_offset: u32 |o----------------------------------------------------------------------+
+ +------------------------------+ | |
+ | offset_of_region_data: u32 |o-----+ | |
+ +------------------------------+ | | |
+ | guest_to_host_signal_table | | | |
+ |______________________________| | | |
+ +----------------------------+ | | |
+ | num_nodes_lg2: u32 | | | |
+ +----------------------------+ | | |
+ |futex_uaddr_table_offset:u32|o------------------+ | |
+ +----------------------------+ | | | |
+ | intr_signaled_offset: u32 |o----------------+ | | |
+ +----------------------------+ | | | | |
+ +------------------------------+ | | | | |
+ | host_to_guest_signal_table | | | | | |
+ |______________________________| | | | | |
+ +----------------------------+ | | | | |
+ | num_nodes_lg2: u32 | | | | | |
+ +----------------------------+ | | | | |
+ |futex_uaddr_table_offset:u32|o--------------+ | | | |
+ +----------------------------+ | | | | \/ REGION AREA (page aligned) |
+ | intr_signaled_offset: u32 |o------|-----+ | | | +---------------------------------------+ |
+ +----------------------------+ | | | | | | region area before start of guest to | |
+ +------------------------------+ | | | | | | host signal table. | |
+ | device_name: char[16] | | | | | | | (currently does not exist) | |
+ (region 0 descriptor end) +------------------------------+ | | | | +->+---------------------------------------+ |
+ (region 1 descriptor start) | | | | | | | uaddr_offset_0 : u32 | |
+ | current_version: u16 | | | | | +---------------------------------------+ |
+ +------------------------------+ | | | | | uaddr_offset_1 : u32 | |
+ | min_compatible_version: u16 | | | | | +---------------------------------------+ |
+ +------------------------------+ | | | | | ... .. ... | |
+ | region_begin_offset: u32 | | | | | +---------------------------------------+ |
+ +------------------------------+ | | | | | uaddr_offset_(2^num_nodes_lg2 - 1):u32| |
+ | region_end_offset: u32 | | | | +--->+---------------------------------------+ |
+ +------------------------------+ | | | | (interrupt_signaled_area) : u32 | |
+ | offset_of_region_data: u32 | | | +----->+---------------------------------------+ |
+ +------------------------------+ | | | uaddr_offset_0 : u32 | |
+ | guest_to_host_signal_table | | | +---------------------------------------+ |
+ +------------------------------+ | | | ... .. ... | |
+ | host_to_guest_signal_table | | | +---------------------------------------+ |
+ +------------------------------+ | | | uaddr_offset_(2^num_nodes_lg2 - 1):u32| |
+ | device_name: char[16] | | +------->+---------------------------------------+ |
+ (region 1 descriptor end) +------------------------------+ | | (interrupt_signaled_area) : u32 | |
+ ... ... ... +------------->+---------------------------------------+ |
+ +------------------------------+ | | |
+ | current_version: u16 | | | |
+ +------------------------------+ | | |
+ | min_compatible_version: u16 | | region specific data | |
+ +------------------------------+ | (defined by region specific agreement | |
+ | region_begin_offset: u32 | | between HAL and host-side process) | |
+ +------------------------------+ | | |
+ | region_end_offset: u32 | | | |
+ +------------------------------+ | | |
+ | offset_of_region_data: u32 | +---------------------------------------+ <-------+
+ +------------------------------+
+ | guest_to_host_signal_table |
+ +------------------------------+
+ | host_to_guest_signal_table |
+ +------------------------------+
+ | device_name: char[16] |
+ +------------------------------+
+ END OF REGION DESCRIPTORS
diff --git a/host/libs/ivserver/vsocsharedmem.cc b/host/libs/ivserver/vsocsharedmem.cc
new file mode 100644
index 0000000..9714d23
--- /dev/null
+++ b/host/libs/ivserver/vsocsharedmem.cc
@@ -0,0 +1,354 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#include "host/libs/ivserver/vsocsharedmem.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/eventfd.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <algorithm>
+#include <tuple>
+
+#include <glog/logging.h>
+
+#include "uapi/vsoc_shm.h"
+
+namespace ivserver {
+namespace {
+
+static_assert(CURRENT_VSOC_LAYOUT_MAJOR_VERSION == 2,
+ "Region layout code must be updated");
+
+// Field names from the json file. These are declared so the compiler will
+// catch typos.
+const char g_vsoc_device_regions[] = "vsoc_device_regions";
+const char g_device_name_tag[] = "device_name";
+const char g_managed_by_tag[] = "managed_by";
+
+class RegionAllocator {
+ public:
+ static uint32_t PageSize() {
+ static long page_size = sysconf(_SC_PAGESIZE);
+ return page_size;
+ }
+
+ explicit RegionAllocator(const std::string &name, uint32_t max_size,
+ uint32_t offset = 0)
+ : name_{name}, max_size_{max_size}, offset_{offset} {}
+
+ uint32_t Allocate(uint32_t size, const char *usage, bool *error) {
+ if (size > (max_size_ - offset_)) {
+ *error = true;
+ LOG(ERROR) << name_ << ":"
+ << "allocation of " << size << "bytes for " << usage
+ << " will overflow memory region";
+ }
+
+ offset_ += size;
+ return (offset_ - size);
+ }
+
+ void PadTo(uint32_t size, bool *error) {
+ uint32_t padding = ((offset_ + (size - 1)) / size) * size - offset_;
+ Allocate(padding, "padding", error);
+ }
+
+ uint32_t AllocateRest(bool *error) {
+ return Allocate(max_size_ - offset_, "rest of region", error);
+ }
+
+ uint32_t GetOffset() const { return offset_; }
+
+ private:
+ std::string name_;
+ uint32_t max_size_;
+ uint32_t offset_;
+};
+
+class VSoCSharedMemoryImpl : public VSoCSharedMemory {
+ public:
+ VSoCSharedMemoryImpl(const vsoc_shm_layout_descriptor &header,
+ const std::map<std::string, size_t> &name_to_region_idx,
+ const std::vector<Region> ®ions,
+ const std::string &path);
+
+ bool GetEventFdPairForRegion(const std::string ®ion_name,
+ cvd::SharedFD *guest_to_host,
+ cvd::SharedFD *host_to_guest) const override;
+
+ const cvd::SharedFD &SharedMemFD() const override;
+
+ const std::vector<Region> &Regions() const override;
+
+ private:
+ void CreateLayout();
+
+ const vsoc_shm_layout_descriptor &header_;
+ cvd::SharedFD shared_mem_fd_;
+ const std::map<std::string, size_t> region_name_to_index_;
+ const std::vector<Region> region_data_;
+
+ VSoCSharedMemoryImpl(const VSoCSharedMemoryImpl &) = delete;
+ VSoCSharedMemoryImpl &operator=(const VSoCSharedMemoryImpl &other) = delete;
+};
+
+VSoCSharedMemoryImpl::VSoCSharedMemoryImpl(
+ const vsoc_shm_layout_descriptor &header,
+ const std::map<std::string, size_t> &name_to_region_idx,
+ const std::vector<Region> ®ions, const std::string &path)
+ : header_{header},
+ region_name_to_index_{name_to_region_idx},
+ region_data_{regions} {
+ // TODO(ender): Lock the file after creation and check lock status upon second
+ // execution attempt instead of throwing an error.
+ LOG_IF(WARNING, unlink(path.c_str()) == 0)
+ << "Removed existing instance of " << path
+ << ". We currently don't know if another instance of daemon is running";
+ shared_mem_fd_ = cvd::SharedFD::Open(path.c_str(), O_RDWR | O_CREAT | O_EXCL,
+ S_IRUSR | S_IWUSR);
+ LOG_IF(FATAL, !shared_mem_fd_->IsOpen())
+ << "Error in creating shared_memory file: " << shared_mem_fd_->StrError();
+
+ int truncate_res = shared_mem_fd_->Truncate(header_.size);
+ LOG_IF(FATAL, truncate_res == -1)
+ << "Error in sizing up the shared memory file: "
+ << shared_mem_fd_->StrError();
+ CreateLayout();
+}
+
+const cvd::SharedFD &VSoCSharedMemoryImpl::SharedMemFD() const {
+ return shared_mem_fd_;
+}
+
+const std::vector<VSoCSharedMemory::Region> &VSoCSharedMemoryImpl::Regions()
+ const {
+ return region_data_;
+}
+
+void VSoCSharedMemoryImpl::CreateLayout() {
+ void *mmap_addr = shared_mem_fd_->Mmap(0, header_.size,
+ PROT_READ | PROT_WRITE, MAP_SHARED, 0);
+ LOG_IF(FATAL, mmap_addr == MAP_FAILED)
+ << "Error mmaping file: " << strerror(errno);
+ *reinterpret_cast<vsoc_shm_layout_descriptor *>(mmap_addr) = header_;
+ auto region_dest = reinterpret_cast<vsoc_device_region *>(
+ reinterpret_cast<uintptr_t>(mmap_addr) + header_.vsoc_region_desc_offset);
+
+ for (const auto ®ion : region_data_) {
+ *region_dest++ = region.values;
+ }
+ munmap(mmap_addr, header_.size);
+}
+
+bool VSoCSharedMemoryImpl::GetEventFdPairForRegion(
+ const std::string ®ion_name, cvd::SharedFD *guest_to_host,
+ cvd::SharedFD *host_to_guest) const {
+ auto it = region_name_to_index_.find(region_name);
+ if (it == region_name_to_index_.end()) return false;
+
+ *guest_to_host = region_data_[it->second].host_fd;
+ *host_to_guest = region_data_[it->second].guest_fd;
+ return true;
+}
+
+template <typename T>
+void GetMandatoryUInt(const std::string ®ion_name,
+ const Json::Value &json_region, const char *field_name,
+ bool *error, T *dest) {
+ if (!json_region.isMember(field_name)) {
+ LOG(ERROR) << region_name << " missing " << field_name << " field";
+ *error = true;
+ return;
+ }
+ if (!json_region[field_name].isNumeric()) {
+ LOG(ERROR) << region_name << " " << field_name << " is not numeric";
+ *error = true;
+ return;
+ }
+ *dest = json_region[field_name].asUInt();
+}
+
+void JSONToSignalTable(const std::string ®ion_name,
+ const Json::Value &json_region, const char *table_name,
+ RegionAllocator *allocator, bool *error,
+ vsoc_signal_table_layout *dest) {
+ if (!json_region.isMember(table_name)) {
+ LOG(ERROR) << region_name << " has no " << table_name << " section";
+ *error = true;
+ return;
+ }
+ GetMandatoryUInt(region_name, json_region[table_name], "num_nodes_lg2", error,
+ &dest->num_nodes_lg2);
+ dest->futex_uaddr_table_offset = allocator->Allocate(
+ (1 << dest->num_nodes_lg2) * sizeof(uint32_t), "node table", error);
+ dest->interrupt_signalled_offset =
+ allocator->Allocate(sizeof(uint32_t), "signal word", error);
+}
+
+std::shared_ptr<VSoCSharedMemory::Region> JSONToRegion(
+ const std::string ®ion_name, const Json::Value &json_region,
+ bool *failed) {
+ std::shared_ptr<VSoCSharedMemory::Region> region(
+ new VSoCSharedMemory::Region);
+ GetMandatoryUInt(region_name, json_region, "current_version", failed,
+ ®ion->values.current_version);
+ GetMandatoryUInt(region_name, json_region, "min_compatible_version", failed,
+ ®ion->values.min_compatible_version);
+ GetMandatoryUInt(region_name, json_region, "region_size", failed,
+ ®ion->values.region_end_offset);
+ RegionAllocator allocator(region_name, region->values.region_end_offset);
+ JSONToSignalTable(region_name, json_region, "guest_to_host_signal_table",
+ &allocator, failed,
+ ®ion->values.guest_to_host_signal_table);
+ JSONToSignalTable(region_name, json_region, "host_to_guest_signal_table",
+ &allocator, failed,
+ ®ion->values.host_to_guest_signal_table);
+
+ region->values.offset_of_region_data = allocator.AllocateRest(failed);
+ return region;
+}
+
+} // anonymous namespace
+
+std::unique_ptr<VSoCSharedMemory> VSoCSharedMemory::New(
+ const std::string &path, const Json::Value &root) {
+ // This is so catastrophic that there isn't anything else to check.
+ if (!root.isMember(g_vsoc_device_regions)) {
+ LOG(ERROR) << "vsoc_device_regions section is absent";
+ return nullptr;
+ }
+ auto device_regions = root[g_vsoc_device_regions];
+ bool failed = false;
+ RegionAllocator shm_file("shared_memory_file", UINT32_MAX);
+ vsoc_shm_layout_descriptor header{};
+ header.major_version = CURRENT_VSOC_LAYOUT_MAJOR_VERSION;
+ header.minor_version = CURRENT_VSOC_LAYOUT_MINOR_VERSION;
+ // size is handled at N1
+ header.region_count = device_regions.size();
+ shm_file.Allocate(sizeof(header), "header", &failed);
+ header.vsoc_region_desc_offset =
+ shm_file.Allocate(sizeof(vsoc_device_region) * header.region_count,
+ "region descriptors", &failed);
+ // Align to a page boundary for the first region
+ shm_file.PadTo(RegionAllocator::PageSize(), &failed);
+
+ std::map<std::string, size_t> name_to_region_idx;
+ std::vector<Region> regions;
+ regions.reserve(header.region_count);
+ std::map<std::string, std::string> managed_by_references;
+
+ // Pass 1: Parse individual region structures validating all of the
+ // fields that can be validated without help.
+ for (const auto &json_region : device_regions) {
+ if (!json_region.isMember(g_device_name_tag)) {
+ LOG(ERROR) << g_device_name_tag << " is missing from region";
+ failed = true;
+ continue;
+ }
+ const std::string device_name = json_region[g_device_name_tag].asString();
+ if (name_to_region_idx.count(device_name)) {
+ LOG(ERROR) << device_name << " used for more than one region";
+ failed = true;
+ continue;
+ }
+ std::shared_ptr<Region> region =
+ JSONToRegion(device_name, json_region, &failed);
+ // Create one pair of eventfds for this region. Note that the guest to host
+ // eventfd is non-blocking, whereas the host to guest eventfd is blocking.
+ // This is in anticipation of blocking semantics for the host side locks.
+ region->host_fd = cvd::SharedFD::Event(0, EFD_NONBLOCK);
+ if (!region->host_fd->IsOpen()) {
+ failed = true;
+ LOG(ERROR) << "Failed to create host eventfd for " << device_name << ": "
+ << region->host_fd->StrError();
+ }
+ region->guest_fd = cvd::SharedFD::Event(0, EFD_NONBLOCK);
+ if (!region->guest_fd->IsOpen()) {
+ failed = true;
+ LOG(ERROR) << "Failed to create guest eventfd for " << device_name << ": "
+ << region->guest_fd->StrError();
+ }
+ region->values.region_begin_offset = shm_file.Allocate(
+ region->values.region_end_offset, device_name.c_str(), &failed);
+ shm_file.PadTo(RegionAllocator::PageSize(), &failed);
+ region->values.region_end_offset += region->values.region_begin_offset;
+ if (sizeof(region->values.device_name) - device_name.size() < 1) {
+ LOG(ERROR) << device_name << " is too long for a region name";
+ failed = true;
+ } else {
+ strcpy(region->values.device_name, device_name.c_str());
+ }
+
+ name_to_region_idx[device_name] = regions.size();
+ regions.push_back(*region);
+ // We will attempt to resolve this link in Pass 2
+ if (json_region.isMember(g_managed_by_tag)) {
+ managed_by_references[device_name] =
+ json_region[g_managed_by_tag].asString();
+ }
+ }
+
+ // Pass 2: Resolve the managed_by_references
+ for (const auto &it : managed_by_references) {
+ if (!name_to_region_idx.count(it.second)) {
+ LOG(ERROR) << it.first << " managed by missing region " << it.second;
+ failed = true;
+ continue;
+ }
+ regions[name_to_region_idx[it.first]].values.managed_by =
+ name_to_region_idx[it.second];
+ if (regions[name_to_region_idx[it.first]].values.managed_by ==
+ VSOC_REGION_WHOLE) {
+ LOG(ERROR)
+ << "Region '" << it.first << "' has owner " << it.second
+ << " with index "
+ << regions[name_to_region_idx[it.first]].values.managed_by
+ << " which is the default value for regions without an owner. Choose"
+ "a different region to be at index "
+ << regions[name_to_region_idx[it.first]].values.managed_by
+ << ", make sure the chosen region is NOT the owner of any other "
+ "region";
+ }
+ }
+ if (failed) {
+ return nullptr;
+ }
+ // Handles size (marker N1)
+ // The size must be a power of 2
+ size_t temp = shm_file.GetOffset();
+ size_t allocated_size = 0;
+ // Find the highest set bit
+ while(temp) {
+ allocated_size = temp;
+ // Clear a bit
+ temp = temp & (temp -1);
+ }
+ // If the size is already a power of 2 just use it. Otherswise use the
+ // next higher power of 2
+ if (allocated_size < shm_file.GetOffset()) {
+ allocated_size *= 2;
+ }
+ header.size = allocated_size;
+ return std::unique_ptr<VSoCSharedMemory>(
+ new VSoCSharedMemoryImpl(header, name_to_region_idx, regions, path));
+}
+
+} // namespace ivserver
diff --git a/host/libs/ivserver/vsocsharedmem.h b/host/libs/ivserver/vsocsharedmem.h
new file mode 100644
index 0000000..1edab05
--- /dev/null
+++ b/host/libs/ivserver/vsocsharedmem.h
@@ -0,0 +1,59 @@
+#pragma once
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include <inttypes.h>
+#include <json/json.h>
+#include <map>
+#include <memory>
+#include <string>
+
+#include "common/libs/fs/shared_fd.h"
+#include "uapi/vsoc_shm.h"
+
+namespace ivserver {
+
+class VSoCSharedMemory {
+ public:
+ // Region describes a VSoCSharedMem region.
+ struct Region {
+ vsoc_device_region values{};
+ cvd::SharedFD host_fd;
+ cvd::SharedFD guest_fd;
+ };
+
+ // Max name length of a memory region.
+ static constexpr int32_t kMaxRegionNameLength = sizeof(vsoc_device_name);
+
+ VSoCSharedMemory() = default;
+ virtual ~VSoCSharedMemory() = default;
+
+ static std::unique_ptr<VSoCSharedMemory> New(const std::string &name,
+ const Json::Value &json_root);
+
+ virtual bool GetEventFdPairForRegion(const std::string ®ion_name,
+ cvd::SharedFD *guest_to_host,
+ cvd::SharedFD *host_to_guest) const = 0;
+
+ virtual const cvd::SharedFD &SharedMemFD() const = 0;
+ virtual const std::vector<Region> &Regions() const = 0;
+
+ private:
+ VSoCSharedMemory(const VSoCSharedMemory &) = delete;
+ VSoCSharedMemory &operator=(const VSoCSharedMemory &other) = delete;
+};
+
+} // namespace ivserver
diff --git a/host/libs/ivserver/vsocsharedmem_mock.h b/host/libs/ivserver/vsocsharedmem_mock.h
new file mode 100644
index 0000000..5ce64ae
--- /dev/null
+++ b/host/libs/ivserver/vsocsharedmem_mock.h
@@ -0,0 +1,33 @@
+#pragma once
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include <gmock/gmock.h>
+
+#include "host/libs/ivserver/vsocsharedmem.h"
+
+namespace ivserver {
+namespace test {
+class VSoCSharedMemoryMock : public VSoCSharedMemory {
+ public:
+ MOCK_CONST_METHOD3(GetEventFdPairForRegion,
+ bool(const std::string&, cvd::SharedFD*, cvd::SharedFD*));
+ MOCK_CONST_METHOD0(SharedMemFD, const cvd::SharedFD&());
+ MOCK_CONST_METHOD0(Regions,
+ const std::vector<VSoCSharedMemory::Region>&());
+};
+} // namespace test
+} // namespace ivserver
diff --git a/host/libs/monitor/Android.bp b/host/libs/monitor/Android.bp
new file mode 100644
index 0000000..b062ed5
--- /dev/null
+++ b/host/libs/monitor/Android.bp
@@ -0,0 +1,18 @@
+cc_library_host_static {
+ name: "libmonitor",
+ srcs: [
+ "kernel_log_server.cpp",
+ ],
+ header_libs: [
+ "cuttlefish_glog",
+ ],
+ shared_libs: [
+ "libcuttlefish_fs",
+ "cuttlefish_auto_resources",
+ "libbase",
+ ],
+ static_libs: [
+ "libgflags",
+ ],
+ defaults: ["cuttlefish_host_only"],
+}
diff --git a/host/libs/monitor/kernel_log_server.cpp b/host/libs/monitor/kernel_log_server.cpp
new file mode 100644
index 0000000..e0f3ee2
--- /dev/null
+++ b/host/libs/monitor/kernel_log_server.cpp
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include "host/libs/monitor/kernel_log_server.h"
+
+#include <glog/logging.h>
+#include <netinet/in.h>
+#include "common/libs/fs/shared_select.h"
+
+using cvd::SharedFD;
+
+namespace {
+static const std::string kVirtualDeviceStages[] = {
+ "VIRTUAL_DEVICE_BOOT_STARTED",
+ "VIRTUAL_DEVICE_BOOT_COMPLETED",
+ "VIRTUAL_DEVICE_BOOT_FAILED",
+};
+} // namespace
+
+namespace monitor {
+KernelLogServer::KernelLogServer(const std::string& socket_name,
+ const std::string& log_name,
+ bool deprecated_boot_completed)
+ : name_(socket_name),
+ log_fd_(cvd::SharedFD::Open(
+ log_name.c_str(), O_CREAT | O_RDWR, 0666)),
+ deprecated_boot_completed_(deprecated_boot_completed) {}
+
+bool KernelLogServer::Init() { return CreateServerSocket(); }
+
+// Open new listening server socket.
+// Returns false, if listening socket could not be created.
+bool KernelLogServer::CreateServerSocket() {
+ LOG(INFO) << "Starting server socket: " << name_;
+
+ server_fd_ =
+ cvd::SharedFD::SocketLocalServer(name_.c_str(), false, SOCK_STREAM, 0666);
+ if (!server_fd_->IsOpen()) {
+ LOG(ERROR) << "Could not create socket: " << server_fd_->StrError();
+ return false;
+ }
+ return true;
+}
+
+void KernelLogServer::BeforeSelect(cvd::SharedFDSet* fd_read) const {
+ if (!client_fd_->IsOpen()) fd_read->Set(server_fd_);
+ if (client_fd_->IsOpen()) fd_read->Set(client_fd_);
+}
+
+void KernelLogServer::AfterSelect(const cvd::SharedFDSet& fd_read) {
+ if (fd_read.IsSet(server_fd_)) HandleIncomingConnection();
+
+ if (client_fd_->IsOpen() && fd_read.IsSet(client_fd_)) {
+ if (!HandleIncomingMessage()) {
+ client_fd_->Close();
+ }
+ }
+}
+
+// Accept new kernel log connection.
+void KernelLogServer::HandleIncomingConnection() {
+ if (client_fd_->IsOpen()) {
+ LOG(ERROR) << "Client already connected. No longer accepting connection.";
+ return;
+ }
+
+ client_fd_ = SharedFD::Accept(*server_fd_, nullptr, nullptr);
+ if (!client_fd_->IsOpen()) {
+ LOG(ERROR) << "Client connection failed: " << client_fd_->StrError();
+ return;
+ }
+}
+
+bool KernelLogServer::HandleIncomingMessage() {
+ const size_t buf_len = 256;
+ char buf[buf_len];
+ ssize_t ret = client_fd_->Read(buf, buf_len);
+ if (ret < 0) {
+ LOG(ERROR) << "Could not read from QEmu serial port: " << client_fd_->StrError();
+ return false;
+ }
+ if (ret == 0) return false;
+
+ // Detect VIRTUAL_DEVICE_BOOT_*
+ for (ssize_t i=0; i<ret; i++) {
+ if ('\n' == buf[i]) {
+ for (auto stage : kVirtualDeviceStages) {
+ if (std::string::npos != line_.find(stage)) {
+ LOG(INFO) << stage;
+ //TODO(b/69417553) Remove this when our clients have transitioned to the
+ // new boot completed
+ if (deprecated_boot_completed_) {
+ // Write to host kernel log
+ FILE* log = popen("/usr/bin/sudo /usr/bin/tee /dev/kmsg", "w");
+ fprintf(log, "%s\n", stage.c_str());
+ fclose(log);
+ }
+ }
+ }
+ line_.clear();
+ }
+ line_.append(1, buf[i]);
+ }
+
+ // Write the log to a file
+ if (log_fd_->Write(buf, ret) < 0) {
+ LOG(ERROR) << "Could not write kernel log to file: " << log_fd_->StrError();
+ return false;
+ }
+ return true;
+}
+
+} // namespace monitor
diff --git a/host/libs/monitor/kernel_log_server.h b/host/libs/monitor/kernel_log_server.h
new file mode 100644
index 0000000..d952d73
--- /dev/null
+++ b/host/libs/monitor/kernel_log_server.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#pragma once
+
+#include <string>
+
+#include "common/libs/fs/shared_fd.h"
+#include "common/libs/fs/shared_select.h"
+//#include "host/libs/monitor/kernel_log_client.h"
+
+namespace monitor {
+// KernelLogServer manages incoming kernel log connection from QEmu. Only accept
+// one connection.
+class KernelLogServer {
+ public:
+ KernelLogServer(const std::string& socket_name,
+ const std::string& log_name,
+ bool deprecated_boot_completed);
+
+ ~KernelLogServer() = default;
+
+ // Initialize this instance of Server.
+ // Returns true, if initialization was successful.
+ bool Init();
+
+ // BeforeSelect is Called right before Select() to populate interesting
+ // SharedFDs.
+ void BeforeSelect(cvd::SharedFDSet* fd_read) const;
+
+ // AfterSelect is Called right after Select() to detect and respond to changes
+ // on affected SharedFDs.
+ void AfterSelect(const cvd::SharedFDSet& fd_read);
+
+ private:
+ // Create kernel log server socket.
+ // Returns true, if socket was successfully created.
+ bool CreateServerSocket();
+
+ // Handle new client connection. Only accept one connection.
+ void HandleIncomingConnection();
+
+ // Respond to message from remote client.
+ // Returns false, if client disconnected.
+ bool HandleIncomingMessage();
+
+ std::string name_;
+ cvd::SharedFD server_fd_;
+ cvd::SharedFD client_fd_;
+ cvd::SharedFD log_fd_;
+ std::string line_;
+ bool deprecated_boot_completed_;
+
+ KernelLogServer(const KernelLogServer&) = delete;
+ KernelLogServer& operator=(const KernelLogServer&) = delete;
+};
+
+} // namespace monitor
diff --git a/host/libs/usbip/Android.bp b/host/libs/usbip/Android.bp
new file mode 100644
index 0000000..29f3778
--- /dev/null
+++ b/host/libs/usbip/Android.bp
@@ -0,0 +1,22 @@
+cc_library_host_static {
+ name: "libusbip",
+ srcs: [
+ "client.cpp",
+ "device_pool.cpp",
+ "messages.cpp",
+ "server.cpp",
+ "vhci_instrument.cpp",
+ ],
+ header_libs: [
+ "cuttlefish_glog",
+ ],
+ shared_libs: [
+ "libcuttlefish_fs",
+ "cuttlefish_auto_resources",
+ "libbase",
+ ],
+ static_libs: [
+ "libgflags",
+ ],
+ defaults: ["cuttlefish_host_only"],
+}
diff --git a/host/libs/usbip/README.md b/host/libs/usbip/README.md
new file mode 100644
index 0000000..e652352
--- /dev/null
+++ b/host/libs/usbip/README.md
@@ -0,0 +1,36 @@
+# USB/IP server library
+
+This folder contains set of classes and structures that constitute basic USB/IP
+server.
+
+Protocol used in this library is defined as part of
+[Linux kernel documentation](https://www.kernel.org/doc/Documentation/usb/usbip_protocol.txt).
+
+## Structure
+
+### [`vadb::usbip::Device`](./device.h)[](#Device)
+
+Structure describing individual device accessible over USB/IP protocol.
+
+### [`vadb::usbip::DevicePool`](./device_pool.h)[](#DevicePool)
+
+DevicePool holds a set of [Devices](#Device) that can be enumerated and
+accessed by clients of this Server.
+
+### [`vadb::usbip::Server`](./server.h)
+
+Purpose of this class is to start a new listening socket and accept incoming
+USB/IP connections & requests.
+
+### [`vadb::usbip::Client`](./client.h)
+
+Client class represents individual USB/IP connection. Client enables remote
+USB/IP client to enumerate and access devices registered in
+[DevicePool](#DevicePool).
+
+### [`USB/IP Messages`](./messages.h)
+
+This file contains structures and enum values defined by the USB/IP protocol.
+All definitions found there have been collected from
+[Linux kernel documentation](https://www.kernel.org/doc/Documentation/usb/usbip_protocol.txt)
+.
diff --git a/host/libs/usbip/client.cpp b/host/libs/usbip/client.cpp
new file mode 100644
index 0000000..7860f24
--- /dev/null
+++ b/host/libs/usbip/client.cpp
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#include "host/libs/usbip/client.h"
+
+#include <glog/logging.h>
+#include <iostream>
+
+#include "host/libs/usbip/device.h"
+#include "host/libs/usbip/messages.h"
+
+namespace vadb {
+namespace usbip {
+
+void Client::BeforeSelect(cvd::SharedFDSet* fd_read) const {
+ fd_read->Set(fd_);
+}
+
+bool Client::AfterSelect(const cvd::SharedFDSet& fd_read) {
+ if (fd_read.IsSet(fd_)) return HandleIncomingMessage();
+ return true;
+}
+
+// Handle incoming COMMAND.
+//
+// Read next CMD from client channel.
+// Returns false, if connection should be dropped.
+bool Client::HandleIncomingMessage() {
+ CmdHeader hdr;
+ if (!RecvUSBIPMsg(fd_, &hdr)) {
+ LOG(ERROR) << "Could not read command header: " << fd_->StrError();
+ return false;
+ }
+
+ // And the protocol, again.
+ switch (hdr.command) {
+ case kUsbIpCmdReqSubmit:
+ return HandleSubmitCmd(hdr);
+
+ case kUsbIpCmdReqUnlink:
+ return HandleUnlinkCmd(hdr);
+
+ default:
+ LOG(ERROR) << "Unsupported command requested: " << hdr.command;
+ return false;
+ }
+}
+
+// Handle incoming SUBMIT COMMAND.
+//
+// Execute command on specified USB device.
+// Returns false, if connection should be dropped.
+bool Client::HandleSubmitCmd(const CmdHeader& cmd) {
+ CmdReqSubmit req;
+ if (!RecvUSBIPMsg(fd_, &req)) {
+ LOG(ERROR) << "Could not read submit command: " << fd_->StrError();
+ return false;
+ }
+
+ uint32_t seq_num = cmd.seq_num;
+
+ // Reserve buffer for data in or out.
+ std::vector<uint8_t> payload;
+ int payload_length = req.transfer_buffer_length;
+ payload.resize(payload_length);
+
+ bool is_host_to_device = cmd.direction == kUsbIpDirectionOut;
+ // Control requests are quite easy to detect; if setup is all '0's, then we're
+ // doing a data transfer, otherwise it's a control transfer.
+ // We only check for cmd and type fields here, as combination 0/0 of these
+ // fields is already invalid (cmd == GET_STATUS, type = WRITE).
+ bool is_control_request = !(req.setup.cmd == 0 && req.setup.type == 0);
+
+ // Find requested device and execute command.
+ auto device = pool_.GetDevice({cmd.bus_num, cmd.dev_num});
+ if (device) {
+ // Read data to be sent to device, if specified.
+ if (is_host_to_device && payload_length) {
+ int32_t got = 0;
+ // Make sure we read everything.
+ while (got < payload.size()) {
+ auto read =
+ fd_->Recv(&payload[got], payload.size() - got, MSG_NOSIGNAL);
+ if (fd_->GetErrno() != 0) {
+ LOG(ERROR) << "Client disconnected: " << fd_->StrError();
+ return false;
+ } else if (!read) {
+ LOG(ERROR) << "Short read; client likely disconnected.";
+ return false;
+ }
+ got += read;
+ }
+ }
+
+ // If setup structure of request is initialized then we need to execute
+ // control transfer. Otherwise, this is a plain data exchange.
+ bool send_success = false;
+ if (is_control_request) {
+ send_success = device->handle_control_transfer(
+ req.setup, req.deadline_interval, std::move(payload),
+ [this, seq_num, is_host_to_device](bool is_success,
+ std::vector<uint8_t> data) {
+ HandleAsyncDataReady(seq_num, is_success, is_host_to_device,
+ std::move(data));
+ });
+ } else {
+ send_success = device->handle_data_transfer(
+ cmd.endpoint, is_host_to_device, req.deadline_interval,
+ std::move(payload),
+ [this, seq_num, is_host_to_device](bool is_success,
+ std::vector<uint8_t> data) {
+ HandleAsyncDataReady(seq_num, is_success, is_host_to_device,
+ std::move(data));
+ });
+ }
+
+ // Simply fail if couldn't execute command.
+ if (!send_success) {
+ HandleAsyncDataReady(seq_num, false, is_host_to_device,
+ std::vector<uint8_t>());
+ }
+ }
+ return true;
+}
+
+void Client::HandleAsyncDataReady(uint32_t seq_num, bool is_success,
+ bool is_host_to_device,
+ std::vector<uint8_t> data) {
+ // Response template.
+ // - in header, host doesn't care about anything else except for command type
+ // and sequence number.
+ // - in body, report status == !OK unless we completed everything
+ // successfully.
+ CmdHeader rephdr{};
+ rephdr.command = kUsbIpCmdRepSubmit;
+ rephdr.seq_num = seq_num;
+
+ CmdRepSubmit rep{};
+ rep.status = is_success ? 0 : 1;
+ rep.actual_length = data.size();
+
+ // Data out.
+ if (!SendUSBIPMsg(fd_, rephdr)) {
+ LOG(ERROR) << "Failed to send response header: " << fd_->StrError();
+ return;
+ }
+
+ if (!SendUSBIPMsg(fd_, rep)) {
+ LOG(ERROR) << "Failed to send response body: " << fd_->StrError();
+ return;
+ }
+
+ if (!is_host_to_device && data.size() > 0) {
+ if (fd_->Send(data.data(), data.size(), MSG_NOSIGNAL) != data.size()) {
+ LOG(ERROR) << "Failed to send response payload: " << fd_->StrError();
+ return;
+ }
+ }
+}
+
+// Handle incoming UNLINK COMMAND.
+//
+// Unlink removes command specified via seq_num from a list of commands to be
+// executed.
+// We don't schedule commands for execution, so technically every UNLINK will
+// come in late.
+// Returns false, if connection should be dropped.
+bool Client::HandleUnlinkCmd(const CmdHeader& cmd) {
+ CmdReqUnlink req;
+ if (!RecvUSBIPMsg(fd_, &req)) {
+ LOG(ERROR) << "Could not read unlink command: " << fd_->StrError();
+ return false;
+ }
+ LOG(INFO) << "Client requested to unlink previously submitted command: "
+ << req.seq_num;
+
+ CmdHeader rephdr{};
+ rephdr.command = kUsbIpCmdRepUnlink;
+ rephdr.seq_num = cmd.seq_num;
+
+ // Technically we do not schedule commands for execution, so we cannot
+ // de-queue commands, either. Indicate this by sending status != ok.
+ CmdRepUnlink rep;
+ rep.status = 1;
+
+ if (!SendUSBIPMsg(fd_, rephdr)) {
+ LOG(ERROR) << "Could not send unlink command header: " << fd_->StrError();
+ return false;
+ }
+
+ if (!SendUSBIPMsg(fd_, rep)) {
+ LOG(ERROR) << "Could not send unlink command data: " << fd_->StrError();
+ return false;
+ }
+ return true;
+}
+
+} // namespace usbip
+} // namespace vadb
diff --git a/host/libs/usbip/client.h b/host/libs/usbip/client.h
new file mode 100644
index 0000000..ae2cff2
--- /dev/null
+++ b/host/libs/usbip/client.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#pragma once
+
+#include "common/libs/fs/shared_fd.h"
+#include "common/libs/fs/shared_select.h"
+#include "host/libs/usbip/device_pool.h"
+#include "host/libs/usbip/messages.h"
+
+namespace vadb {
+namespace usbip {
+
+// Represents USB/IP client, or individual connection to our USB/IP server.
+// Multiple clients are allowed, even if practically we anticipate only one
+// connection at the time.
+class Client final {
+ public:
+ Client(const DevicePool& pool, const cvd::SharedFD& fd)
+ : pool_(pool), fd_(fd) {}
+
+ ~Client() {}
+
+ // BeforeSelect is Called right before Select() to populate interesting
+ // SharedFDs.
+ void BeforeSelect(cvd::SharedFDSet* fd_read) const;
+
+ // AfterSelect is Called right after Select() to detect and respond to changes
+ // on affected SharedFDs.
+ // Return value indicates whether this client is still valid.
+ bool AfterSelect(const cvd::SharedFDSet& fd_read);
+
+ private:
+ // Respond to message from remote client.
+ // Returns false, if client violated protocol or disconnected, indicating,
+ // that this instance should no longer be used.
+ bool HandleIncomingMessage();
+
+ // Execute command on USB device.
+ // Returns false, if connection should be dropped.
+ bool HandleSubmitCmd(const CmdHeader& hdr);
+
+ // HandleAsyncDataReady is called asynchronously once previously submitted
+ // data transfer (control or bulk) has completed (or failed).
+ void HandleAsyncDataReady(uint32_t seq_num, bool is_success,
+ bool is_host_to_device, std::vector<uint8_t> data);
+
+ // Unlink previously submitted message from device queue.
+ // Returns false, if connection should be dropped.
+ bool HandleUnlinkCmd(const CmdHeader& hdr);
+
+ const DevicePool& pool_;
+ cvd::SharedFD fd_;
+
+ Client(const Client&) = delete;
+ Client& operator=(const Client&) = delete;
+};
+
+} // namespace usbip
+} // namespace vadb
diff --git a/host/libs/usbip/device.h b/host/libs/usbip/device.h
new file mode 100644
index 0000000..f310c8e
--- /dev/null
+++ b/host/libs/usbip/device.h
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#pragma once
+
+#include <cstdint>
+#include <functional>
+#include <map>
+#include <memory>
+#include <vector>
+
+#include "host/libs/usbip/messages.h"
+
+namespace vadb {
+namespace usbip {
+
+// The device descriptor of a USB device represents a USB device that is
+// available for import.
+class Device {
+ public:
+ // AsyncTransferReadyCB specifies a signature of a function that will be
+ // called upon transfer completion (whether successful or failed). Parameters
+ // supplied to the function are:
+ // - operation status, indicated by boolean flag (true = success),
+ // - vector containing transferred data (and actual size).
+ using AsyncTransferReadyCB = std::function<void(bool, std::vector<uint8_t>)>;
+
+ // Interface provides minimal description of device's interface.
+ struct Interface {
+ uint8_t iface_class;
+ uint8_t iface_subclass;
+ uint8_t iface_protocol;
+ };
+
+ // vendor_id and product_id identify device manufacturer and type.
+ // dev_version describes device version (as BCD).
+ uint16_t vendor_id;
+ uint16_t product_id;
+ uint16_t dev_version;
+
+ // Class, Subclass and Protocol define device type.
+ uint8_t dev_class;
+ uint8_t dev_subclass;
+ uint8_t dev_protocol;
+
+ // Speed indicates device speed (see libusb_speed).
+ uint8_t speed;
+
+ // ConfigurationsCount and ConfigurationNumber describe total number of device
+ // configurations and currently activated device configuration.
+ size_t configurations_count;
+ size_t configuration_number;
+
+ // Interfaces returns a collection of device interfaces.
+ std::vector<Interface> interfaces;
+
+ // Attach request handler.
+ std::function<bool()> handle_attach;
+
+ // Device control request dispatcher.
+ std::function<bool(const CmdRequest& request, uint32_t deadline,
+ std::vector<uint8_t> data, AsyncTransferReadyCB callback)>
+ handle_control_transfer;
+
+ // Device data request dispatcher.
+ std::function<bool(uint8_t endpoint, bool is_host_to_device,
+ uint32_t deadline, std::vector<uint8_t> data,
+ AsyncTransferReadyCB callback)>
+ handle_data_transfer;
+};
+
+} // namespace usbip
+} // namespace vadb
diff --git a/host/libs/usbip/device_pool.cpp b/host/libs/usbip/device_pool.cpp
new file mode 100644
index 0000000..22f334b
--- /dev/null
+++ b/host/libs/usbip/device_pool.cpp
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include "host/libs/usbip/device_pool.h"
+
+#include <glog/logging.h>
+
+namespace vadb {
+namespace usbip {
+
+void DevicePool::AddDevice(BusDevNumber bdn, std::unique_ptr<Device> device) {
+ devices_[bdn] = std::move(device);
+}
+
+Device* DevicePool::GetDevice(BusDevNumber bus_id) const {
+ auto iter = devices_.find(bus_id);
+ if (iter == devices_.end()) return nullptr;
+ return iter->second.get();
+}
+
+} // namespace usbip
+} // namespace vadb
diff --git a/host/libs/usbip/device_pool.h b/host/libs/usbip/device_pool.h
new file mode 100644
index 0000000..1ba8527
--- /dev/null
+++ b/host/libs/usbip/device_pool.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#pragma once
+
+#include <map>
+#include <string>
+
+#include "host/libs/usbip/device.h"
+
+namespace vadb {
+namespace usbip {
+// Container for all virtual USB/IP devices.
+// Stores devices by virtual BUS ID.
+class DevicePool {
+ public:
+ // BusDevNumber is a pair uniquely identifying bus and device.
+ struct BusDevNumber {
+ uint16_t bus_number;
+ uint16_t dev_number;
+
+ bool operator<(BusDevNumber other) const {
+ return (bus_number << 16 | dev_number) <
+ (other.bus_number << 16 | other.dev_number);
+ }
+ };
+
+ // Internal container type.
+ using MapType = std::map<BusDevNumber, std::unique_ptr<Device>>;
+
+ DevicePool() = default;
+ virtual ~DevicePool() = default;
+
+ // Add new device associated with virtual BUS ID.
+ void AddDevice(BusDevNumber bus_id, std::unique_ptr<Device> device);
+
+ // Get device associated with supplied virtual bus/device number.
+ Device* GetDevice(BusDevNumber bus_dev_num) const;
+
+ // Get total number of USB/IP devices.
+ size_t Size() const { return devices_.size(); }
+
+ MapType::const_iterator begin() const { return devices_.cbegin(); }
+ MapType::const_iterator end() const { return devices_.cend(); }
+
+ private:
+ MapType devices_;
+
+ DevicePool(const DevicePool&) = delete;
+ DevicePool& operator=(const DevicePool&) = delete;
+};
+
+} // namespace usbip
+} // namespace vadb
diff --git a/host/libs/usbip/messages.cpp b/host/libs/usbip/messages.cpp
new file mode 100644
index 0000000..27af986
--- /dev/null
+++ b/host/libs/usbip/messages.cpp
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#include "host/libs/usbip/messages.h"
+
+#include <netinet/in.h>
+#include <iostream>
+
+#include <glog/logging.h>
+
+namespace vadb {
+namespace usbip {
+namespace {
+// Basic sanity checking.
+// We're using CmdHeader + CmdReq/Rep in case any of the fields is moved between
+// structures.
+constexpr int kUsbIpCmdLength = 48;
+
+static_assert(sizeof(CmdHeader) + sizeof(CmdReqSubmit) == kUsbIpCmdLength,
+ "USB/IP command + header must be exactly 48 bytes.");
+static_assert(sizeof(CmdHeader) + sizeof(CmdRepSubmit) == kUsbIpCmdLength,
+ "USB/IP command + header must be exactly 48 bytes.");
+static_assert(sizeof(CmdHeader) + sizeof(CmdReqUnlink) == kUsbIpCmdLength,
+ "USB/IP command + header must be exactly 48 bytes.");
+static_assert(sizeof(CmdHeader) + sizeof(CmdRepUnlink) == kUsbIpCmdLength,
+ "USB/IP command + header must be exactly 48 bytes.");
+} // namespace
+
+// NetToHost and HostToNet are used to reduce risk of copy/paste errors and to
+// provide uniform method of converting messages between different endian types.
+namespace internal {
+
+template <>
+void NetToHost(uint32_t* t) {
+ *t = ntohl(*t);
+}
+
+template <>
+void NetToHost(Command* t) {
+ *t = static_cast<Command>(ntohl(*t));
+}
+
+template <>
+void NetToHost(Direction* t) {
+ *t = static_cast<Direction>(ntohl(*t));
+}
+
+template <>
+void NetToHost(uint16_t* t) {
+ *t = ntohs(*t);
+}
+
+template <>
+void NetToHost(CmdHeader* t) {
+ NetToHost(&t->command);
+ NetToHost(&t->seq_num);
+ NetToHost(&t->bus_num);
+ NetToHost(&t->dev_num);
+ NetToHost(&t->direction);
+ NetToHost(&t->endpoint);
+}
+
+template <>
+void NetToHost(CmdReqSubmit* t) {
+ NetToHost(&t->transfer_flags);
+ NetToHost(&t->transfer_buffer_length);
+ NetToHost(&t->start_frame);
+ NetToHost(&t->number_of_packets);
+ NetToHost(&t->deadline_interval);
+}
+
+template <>
+void NetToHost(CmdReqUnlink* t) {
+ NetToHost(&t->seq_num);
+}
+
+template <>
+void HostToNet(uint32_t* t) {
+ *t = htonl(*t);
+}
+
+template <>
+void HostToNet(Command* t) {
+ *t = static_cast<Command>(htonl(*t));
+}
+
+template <>
+void HostToNet(Direction* t) {
+ *t = static_cast<Direction>(htonl(*t));
+}
+
+template <>
+void HostToNet(uint16_t* t) {
+ *t = htons(*t);
+}
+
+template <>
+void HostToNet(CmdHeader* t) {
+ HostToNet(&t->command);
+ HostToNet(&t->seq_num);
+ HostToNet(&t->bus_num);
+ HostToNet(&t->dev_num);
+ HostToNet(&t->direction);
+ HostToNet(&t->endpoint);
+}
+
+template <>
+void HostToNet(CmdRepSubmit* t) {
+ HostToNet(&t->status);
+ HostToNet(&t->actual_length);
+ HostToNet(&t->start_frame);
+ HostToNet(&t->number_of_packets);
+ HostToNet(&t->error_count);
+}
+
+template <>
+void HostToNet(CmdRepUnlink* t) {
+ HostToNet(&t->status);
+}
+
+} // namespace internal
+
+std::ostream& operator<<(std::ostream& out, const CmdHeader& header) {
+ out << "CmdHeader\n";
+ out << "\t\tcmd:\t" << header.command << '\n';
+ out << "\t\tseq#:\t" << header.seq_num << '\n';
+ out << "\t\tbus#:\t0x" << header.bus_num << '\n';
+ out << "\t\tdev#:\t0x" << header.dev_num << '\n';
+ out << "\t\tdir:\t" << (header.direction ? "in" : "out") << '\n';
+ out << "\t\tendpt:\t" << header.endpoint << "\n";
+ return out;
+}
+
+std::ostream& operator<<(std::ostream& out, const CmdRequest& setup) {
+ out << "Request\n";
+ out << "\t\t\ttype:\t" << std::hex << int(setup.type) << '\n';
+ out << "\t\t\treq:\t" << int(setup.cmd) << std::dec << '\n';
+ out << "\t\t\tval:\t" << setup.value << '\n';
+ out << "\t\t\tidx:\t" << setup.index << '\n';
+ out << "\t\t\tlen:\t" << setup.length << '\n';
+ return out;
+}
+
+std::ostream& operator<<(std::ostream& out, const CmdReqSubmit& submit) {
+ out << "CmdReqSubmit\n";
+ out << "\t\ttr_flg:\t" << std::hex << submit.transfer_flags << std::dec
+ << '\n';
+ out << "\t\ttr_len:\t" << submit.transfer_buffer_length << '\n';
+ out << "\t\tstart:\t" << submit.start_frame << '\n';
+ out << "\t\tpktcnt:\t" << submit.number_of_packets << '\n';
+ out << "\t\tttl:\t" << submit.deadline_interval << '\n';
+ out << "\t\tsetup:\t" << submit.setup << '\n';
+ return out;
+}
+
+std::ostream& operator<<(std::ostream& out, const CmdRepSubmit& submit) {
+ out << "CmdRepSubmit\n";
+ out << "\t\tstatus:\t" << submit.status << '\n';
+ out << "\t\tlen:\t" << submit.actual_length << '\n';
+ out << "\t\tstart:\t" << submit.start_frame << '\n';
+ out << "\t\tpktcnt:\t" << submit.number_of_packets << '\n';
+ out << "\t\terrors:\t" << submit.error_count << '\n';
+ out << "\t\tsetup:\t" << submit.setup << '\n';
+ return out;
+}
+
+std::ostream& operator<<(std::ostream& out, const CmdReqUnlink& unlink) {
+ out << "CmdReqUnlink\n";
+ out << "\t\tseq#:\t" << unlink.seq_num << '\n';
+ return out;
+}
+
+std::ostream& operator<<(std::ostream& out, const CmdRepUnlink& unlink) {
+ out << "CmdRepUnlink\n";
+ out << "\t\tstatus:\t" << unlink.status << '\n';
+ return out;
+}
+
+} // namespace usbip
+} // namespace vadb
diff --git a/host/libs/usbip/messages.h b/host/libs/usbip/messages.h
new file mode 100644
index 0000000..8941df1
--- /dev/null
+++ b/host/libs/usbip/messages.h
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#pragma once
+
+#include <glog/logging.h>
+#include <stdint.h>
+
+#include "common/libs/fs/shared_fd.h"
+
+// Requests and constants below are defined in kernel documentation file:
+// https://www.kernel.org/doc/Documentation/usb/usbip_protocol.txt
+namespace vadb {
+namespace usbip {
+namespace internal {
+// Rotate endianness of the data to match protocol.
+template <typename T>
+void HostToNet(T* data);
+template <typename T>
+void NetToHost(T* data);
+} // namespace internal
+
+// Send message to USB/IP client.
+// Accept data by value and modify it to match net endian locally.
+// Returns true, if message was sent successfully.
+template <typename T>
+bool SendUSBIPMsg(const cvd::SharedFD& fd, T data) {
+ internal::HostToNet(&data);
+ return fd->Send(&data, sizeof(T), MSG_NOSIGNAL) == sizeof(T);
+}
+
+// Receive message from USB/IP client.
+// After message is received, it's updated to match host endian.
+// Returns true, if message was received successfully.
+template <typename T>
+bool RecvUSBIPMsg(const cvd::SharedFD& fd, T* data) {
+ bool res = fd->Recv(data, sizeof(T), MSG_NOSIGNAL) == sizeof(T);
+ if (res) {
+ internal::NetToHost(data);
+ }
+ return res;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// COMMANDS
+////////////////////////////////////////////////////////////////////////////////
+
+// Command numbers. Commands are valid only once USB device is attached.
+enum Command : uint32_t {
+ kUsbIpCmdReqSubmit = 1, // Submit request
+ kUsbIpCmdReqUnlink = 2, // Unlink request
+ kUsbIpCmdRepSubmit = 3, // Submit response
+ kUsbIpCmdRepUnlink = 4, // Unlink response
+};
+
+// Direction of data flow.
+enum Direction : uint32_t {
+ kUsbIpDirectionOut = 0,
+ kUsbIpDirectionIn = 1,
+};
+
+// Setup structure is explained in great detail here:
+// - http://www.beyondlogic.org/usbnutshell/usb6.shtml
+// - http://www.usbmadesimple.co.uk/ums_4.htm
+struct CmdRequest {
+ uint8_t type;
+ uint8_t cmd;
+ uint16_t value;
+ uint16_t index;
+ uint16_t length;
+} __attribute__((packed));
+
+// CmdHeader precedes any command request or response body.
+struct CmdHeader {
+ Command command;
+ uint32_t seq_num;
+ uint16_t bus_num;
+ uint16_t dev_num;
+ Direction direction;
+ uint32_t endpoint; // valid values: 0-15
+} __attribute__((packed));
+
+// Command data for submitting an USB request.
+struct CmdReqSubmit {
+ uint32_t transfer_flags;
+ uint32_t transfer_buffer_length;
+ uint32_t start_frame;
+ uint32_t number_of_packets;
+ uint32_t deadline_interval;
+ CmdRequest setup;
+} __attribute__((packed));
+
+// Command response for submitting an USB request.
+struct CmdRepSubmit {
+ uint32_t status; // 0 = success.
+ uint32_t actual_length;
+ uint32_t start_frame;
+ uint32_t number_of_packets;
+ uint32_t error_count;
+ CmdRequest setup;
+} __attribute__((packed));
+
+// Unlink USB request.
+struct CmdReqUnlink {
+ uint32_t seq_num;
+ uint32_t reserved[6];
+} __attribute__((packed));
+
+// Unlink USB response.
+struct CmdRepUnlink {
+ uint32_t status;
+ uint32_t reserved[6];
+} __attribute__((packed));
+
+// Diagnostics.
+std::ostream& operator<<(std::ostream& out, const CmdHeader& header);
+std::ostream& operator<<(std::ostream& out, const CmdReqSubmit& data);
+std::ostream& operator<<(std::ostream& out, const CmdRepSubmit& data);
+std::ostream& operator<<(std::ostream& out, const CmdReqUnlink& data);
+std::ostream& operator<<(std::ostream& out, const CmdRepUnlink& data);
+
+} // namespace usbip
+} // namespace vadb
diff --git a/host/libs/usbip/server.cpp b/host/libs/usbip/server.cpp
new file mode 100644
index 0000000..bada417
--- /dev/null
+++ b/host/libs/usbip/server.cpp
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include "host/libs/usbip/server.h"
+
+#include <glog/logging.h>
+#include <netinet/in.h>
+#include "common/libs/fs/shared_select.h"
+
+using cvd::SharedFD;
+
+namespace vadb {
+namespace usbip {
+Server::Server(const std::string& name, const DevicePool& devices)
+ : name_{name}, device_pool_{devices} {}
+
+bool Server::Init() { return CreateServerSocket(); }
+
+// Open new listening server socket.
+// Returns false, if listening socket could not be created.
+bool Server::CreateServerSocket() {
+ LOG(INFO) << "Starting server socket: " << name_;
+
+ server_ = SharedFD::SocketLocalServer(name_.c_str(), true, SOCK_STREAM, 0700);
+ if (!server_->IsOpen()) {
+ LOG(ERROR) << "Could not create socket: " << server_->StrError();
+ return false;
+ }
+ return true;
+}
+
+void Server::BeforeSelect(cvd::SharedFDSet* fd_read) const {
+ fd_read->Set(server_);
+ for (const auto& client : clients_) client.BeforeSelect(fd_read);
+}
+
+void Server::AfterSelect(const cvd::SharedFDSet& fd_read) {
+ if (fd_read.IsSet(server_)) HandleIncomingConnection();
+
+ for (auto iter = clients_.begin(); iter != clients_.end();) {
+ if (!iter->AfterSelect(fd_read)) {
+ // If client conversation failed, hang up.
+ iter = clients_.erase(iter);
+ continue;
+ }
+ ++iter;
+ }
+}
+
+// Accept new USB/IP connection. Add it to client pool.
+void Server::HandleIncomingConnection() {
+ SharedFD client = SharedFD::Accept(*server_, nullptr, nullptr);
+ if (!client->IsOpen()) {
+ LOG(ERROR) << "Client connection failed: " << client->StrError();
+ return;
+ }
+
+ clients_.emplace_back(device_pool_, client);
+}
+} // namespace usbip
+} // namespace vadb
diff --git a/host/libs/usbip/server.h b/host/libs/usbip/server.h
new file mode 100644
index 0000000..ec47e43
--- /dev/null
+++ b/host/libs/usbip/server.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#pragma once
+
+#include <list>
+#include <string>
+
+#include "common/libs/fs/shared_fd.h"
+#include "host/libs/usbip/client.h"
+#include "host/libs/usbip/device_pool.h"
+
+namespace vadb {
+namespace usbip {
+
+class Server final {
+ public:
+ Server(const std::string& name, const DevicePool& device_pool);
+ ~Server() = default;
+
+ // Initialize this instance of Server.
+ // Returns true, if initialization was successful.
+ bool Init();
+
+ // BeforeSelect is Called right before Select() to populate interesting
+ // SharedFDs.
+ void BeforeSelect(cvd::SharedFDSet* fd_read) const;
+
+ // AfterSelect is Called right after Select() to detect and respond to changes
+ // on affected SharedFDs.
+ void AfterSelect(const cvd::SharedFDSet& fd_read);
+
+ private:
+ // Create USBIP server socket.
+ // Returns true, if socket was successfully created.
+ bool CreateServerSocket();
+
+ // Handle new client connection.
+ // New clients will be appended to clients_ list.
+ void HandleIncomingConnection();
+
+ std::string name_;
+ cvd::SharedFD server_;
+ std::list<Client> clients_;
+
+ const DevicePool& device_pool_;
+
+ Server(const Server&) = delete;
+ Server& operator=(const Server&) = delete;
+};
+
+} // namespace usbip
+} // namespace vadb
diff --git a/host/libs/usbip/vhci_instrument.cpp b/host/libs/usbip/vhci_instrument.cpp
new file mode 100644
index 0000000..179d6c5
--- /dev/null
+++ b/host/libs/usbip/vhci_instrument.cpp
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include <errno.h>
+#include <string.h>
+
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <sys/socket.h>
+
+#include <glog/logging.h>
+#include <fstream>
+#include <sstream>
+#include "common/libs/fs/shared_select.h"
+
+#include "common/libs/fs/shared_fd.h"
+#include "host/libs/usbip/vhci_instrument.h"
+
+namespace vadb {
+namespace usbip {
+namespace {
+// Device ID is specified as a concatenated pair of BUS and DEVICE id.
+// Since we only export one device and our server doesn't care much about
+// its number, we use the default value of BUS=1 and DEVICE=1.
+// This can be set to something else and should still work, as long as
+// numbers are valid in USB sense.
+constexpr uint32_t kDefaultDeviceID = (1 << 16) | 1;
+
+// Request Highspeed configuration. Superspeed isn't supported by vhci.
+// Supported configurations are:
+// 4 -> wireless
+// 3 -> highspeed
+// 2 -> full speed
+// 1 -> low speed
+// Please refer to the Kernel source tree in the following locations:
+// include/uapi/linux/usb/ch9.h
+// drivers/usb/usbip/vhci_sysfs.c
+constexpr uint32_t kDefaultDeviceSpeed = 3;
+
+// Subsystem and device type where VHCI driver is located.
+const char* const kVHCIPlatformPaths[] = {
+ "/sys/devices/platform/vhci_hcd",
+ "/sys/devices/platform/vhci_hcd.1",
+};
+
+// Control messages.
+// Attach tells thread to attach remote device.
+// Detach tells thread to detach remote device.
+using ControlMsgType = uint8_t;
+constexpr ControlMsgType kControlAttach = 'A';
+constexpr ControlMsgType kControlDetach = 'D';
+constexpr ControlMsgType kControlExit = 'E';
+
+// Used with EPOLL as epoll_data to determine event type.
+enum EpollEventType {
+ kControlEvent,
+ kVHCIEvent,
+};
+
+// Port status values deducted from /sys/devices/platform/vhci_hcd/status
+enum {
+ // kVHCIPortFree indicates the port is not currently in use.
+ kVHCIStatusPortFree = 4
+};
+} // anonymous namespace
+
+VHCIInstrument::VHCIInstrument(const std::string& name)
+ : name_(name) {}
+
+VHCIInstrument::~VHCIInstrument() {
+ control_write_end_->Write(&kControlExit, sizeof(kControlExit));
+ attach_thread_->join();
+}
+
+bool VHCIInstrument::Init() {
+ cvd::SharedFD::Pipe(&control_read_end_, &control_write_end_);
+
+ struct stat buf;
+ for (const auto* path : kVHCIPlatformPaths) {
+ if (stat(path, &buf) == 0) {
+ syspath_ = path;
+ break;
+ }
+ }
+
+ if (syspath_.empty()) {
+ LOG(ERROR) << "VHCI not available. Is the driver loaded?";
+ LOG(ERROR) << "Try: sudo modprobe vhci_hcd";
+ LOG(ERROR) << "The driver is part of linux-image-extra-`uname -r` package";
+ return false;
+ }
+
+ if (!FindFreePort()) {
+ LOG(ERROR) << "It appears all your VHCI ports are currently occupied.";
+ LOG(ERROR) << "New VHCI device cannot be registered unless one of the "
+ << "ports is freed.";
+ return false;
+ }
+
+ attach_thread_.reset(new std::thread([this]() { AttachThread(); }));
+ return true;
+}
+
+bool VHCIInstrument::FindFreePort() {
+ std::ifstream stat(syspath_ + "/status");
+ int port;
+ int status;
+ std::string everything_else;
+
+ if (!stat.is_open()) {
+ LOG(ERROR) << "Could not open usb-ip status file.";
+ return false;
+ }
+
+ // Skip past the header line.
+ std::getline(stat, everything_else);
+
+ while (stat.rdstate() == std::ios_base::goodbit) {
+ stat >> port >> status;
+ std::getline(stat, everything_else);
+ if (status == kVHCIStatusPortFree) {
+ port_ = port;
+ LOG(INFO) << "Using VHCI port " << port_;
+ return true;
+ }
+ }
+ return false;
+}
+
+void VHCIInstrument::TriggerAttach() {
+ control_write_end_->Write(&kControlAttach, sizeof(kControlAttach));
+}
+
+void VHCIInstrument::TriggerDetach() {
+ control_write_end_->Write(&kControlDetach, sizeof(kControlDetach));
+}
+
+void VHCIInstrument::AttachThread() {
+ cvd::SharedFD epoll = cvd::SharedFD::Epoll();
+ // Trigger attach upon start.
+ bool want_attach = true;
+ // Operation is pending on read.
+ bool is_pending = false;
+
+ epoll_event control_event;
+ control_event.events = EPOLLIN;
+ control_event.data.u64 = kControlEvent;
+ epoll_event vhci_event;
+ vhci_event.events = EPOLLRDHUP | EPOLLONESHOT;
+ vhci_event.data.u64 = kVHCIEvent;
+
+ epoll->EpollCtl(EPOLL_CTL_ADD, control_read_end_, &control_event);
+ while (true) {
+ if (vhci_socket_->IsOpen()) {
+ epoll->EpollCtl(EPOLL_CTL_ADD, vhci_socket_, &vhci_event);
+ }
+
+ epoll_event found_event{};
+ ControlMsgType request_type;
+
+ if (epoll->EpollWait(&found_event, 1, 1000)) {
+ switch (found_event.data.u64) {
+ case kControlEvent:
+ control_read_end_->Read(&request_type, sizeof(request_type));
+ is_pending = true;
+ want_attach = request_type == kControlAttach;
+ LOG(INFO) << (want_attach ? "Attach" : "Detach") << " triggered.";
+ break;
+ case kVHCIEvent:
+ vhci_socket_ = cvd::SharedFD();
+ // Only re-establish VHCI if it was already established before.
+ is_pending = want_attach;
+ // Do not immediately fall into attach cycle. It will likely complete
+ // before VHCI finishes deregistering this callback.
+ continue;
+ }
+ }
+
+ // Make an attempt to re-attach. If successful, clear pending attach flag.
+ if (is_pending) {
+ if (want_attach && Attach()) {
+ is_pending = false;
+ } else if (!want_attach && Detach()) {
+ is_pending = false;
+ } else {
+ LOG(INFO) << (want_attach ? "Attach" : "Detach") << " unsuccessful. "
+ << "Will re-try.";
+ sleep(1);
+ }
+ }
+ }
+}
+
+bool VHCIInstrument::Detach() {
+ std::stringstream result;
+ result << port_;
+ std::ofstream detach(syspath_ + "/detach");
+
+ if (!detach.is_open()) {
+ LOG(WARNING) << "Could not open VHCI detach file.";
+ return false;
+ }
+ detach << result.str();
+ return detach.rdstate() == std::ios_base::goodbit;
+}
+
+bool VHCIInstrument::Attach() {
+ if (!vhci_socket_->IsOpen()) {
+ vhci_socket_ =
+ cvd::SharedFD::SocketLocalClient(name_.c_str(), true, SOCK_STREAM);
+ if (!vhci_socket_->IsOpen()) return false;
+ }
+
+ int sys_fd = vhci_socket_->UNMANAGED_Dup();
+ bool success = false;
+
+ {
+ std::stringstream result;
+ result << port_ << ' ' << sys_fd << ' ' << kDefaultDeviceID << ' '
+ << kDefaultDeviceSpeed;
+ std::string path = syspath_ + "/attach";
+ std::ofstream attach(path);
+
+ if (!attach.is_open()) {
+ LOG(WARNING) << "Could not open VHCI attach file " << path << " ("
+ << strerror(errno) << ")";
+ close(sys_fd);
+ return false;
+ }
+ attach << result.str();
+
+ // It is unclear whether duplicate FD should remain open or not. There are
+ // cases supporting both assumptions, likely related to kernel version.
+ // Kernel 4.10 is having problems communicating with USB/IP server if the
+ // socket is closed after it's passed to kernel. It is a clear indication that
+ // the kernel requires the socket to be kept open.
+ success = attach.rdstate() == std::ios_base::goodbit;
+ // Make sure everything was written and flushed. This happens when we close
+ // the ofstream attach.
+ }
+
+ close(sys_fd);
+ return success;
+}
+
+} // namespace usbip
+} // namespace vadb
diff --git a/host/libs/usbip/vhci_instrument.h b/host/libs/usbip/vhci_instrument.h
new file mode 100644
index 0000000..ccb641e
--- /dev/null
+++ b/host/libs/usbip/vhci_instrument.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#pragma once
+
+#include <memory>
+#include <string>
+#include <thread>
+
+#include "common/libs/fs/shared_fd.h"
+
+namespace vadb {
+namespace usbip {
+// VHCIInstrument class configures VHCI-HCD on local kernel.
+class VHCIInstrument {
+ public:
+ VHCIInstrument(const std::string& name);
+ virtual ~VHCIInstrument();
+
+ // Init opens vhci-hcd driver and allocates port to which remote USB device
+ // will be attached.
+ // Returns false, if vhci-hcd driver could not be opened, or if no free port
+ // was found.
+ bool Init();
+
+ // TriggerAttach tells underlying thread to make attempt to re-attach USB
+ // device.
+ void TriggerAttach();
+
+ // TriggerDetach tells underlying thread to disconnect remote USB device.
+ void TriggerDetach();
+
+ private:
+ // Attach makes an attempt to configure VHCI to enable virtual USB device.
+ // Returns true, if configuration attempt was successful.
+ bool Attach();
+
+ // Detach disconnects virtual USB device.
+ // Returns true, if attempt was successful.
+ bool Detach();
+
+ // AttachThread is a background thread that responds to configuration
+ // requests.
+ void AttachThread();
+ bool FindFreePort();
+
+ private:
+ std::string name_;
+ std::unique_ptr<std::thread> attach_thread_;
+ std::string syspath_;
+ cvd::SharedFD control_write_end_;
+ cvd::SharedFD control_read_end_;
+ cvd::SharedFD vhci_socket_;
+ int port_;
+
+ VHCIInstrument(const VHCIInstrument& other) = delete;
+ VHCIInstrument& operator=(const VHCIInstrument& other) = delete;
+};
+} // namespace usbip
+} // namespace vadb
diff --git a/host/libs/vadb/Android.bp b/host/libs/vadb/Android.bp
new file mode 100644
index 0000000..48971df
--- /dev/null
+++ b/host/libs/vadb/Android.bp
@@ -0,0 +1,24 @@
+cc_library_host_static {
+ name: "libvadb",
+ srcs: [
+ "usb_cmd_attach.cpp",
+ "usb_cmd_control_transfer.cpp",
+ "usb_cmd_data_transfer.cpp",
+ "usb_cmd_device_list.cpp",
+ "usb_cmd_heartbeat.cpp",
+ "virtual_adb_client.cpp",
+ "virtual_adb_server.cpp",
+ ],
+ header_libs: [
+ "cuttlefish_glog",
+ ],
+ shared_libs: [
+ "libcuttlefish_fs",
+ "cuttlefish_auto_resources",
+ "libbase",
+ ],
+ static_libs: [
+ "libgflags",
+ ],
+ defaults: ["cuttlefish_host_only"],
+}
diff --git a/host/libs/vadb/README.md b/host/libs/vadb/README.md
new file mode 100644
index 0000000..432fa2e
--- /dev/null
+++ b/host/libs/vadb/README.md
@@ -0,0 +1,35 @@
+# Virtual ADB
+
+VirtualADB serves the purpose of making Cuttlefish device available locally as a
+USB device. VirtualADB uses USB/IP protocol to forward USB gadget from
+Cuttlefish to localhost.
+
+## Requirements
+
+To compile the VirtualADB package you need to install:
+
+```
+sudo apt-get install libudev-dev
+```
+
+VirtualADB requires `vhci-hcd` kernel module to be loaded. Module is part of
+kernel extra modules package:
+
+```
+sudo apt-get install linux-image-extra-`uname -r`
+```
+
+## Usage
+
+VirtualADB uses currently `virtio channel` to communicate with usb forwarder on
+`cuttlefish`. The tool instruments kernel to attach remote USB device directly.
+To do that, it requires super-user privileges - primarily because it's adding a
+new device to your system.
+
+To start VirtualADB simply execute:
+
+```
+sudo vadb /path/to/usb_forwarder_socket
+```
+
+where `usb_forwarder_socket` is the socket used by usb forwarder to communicate.
diff --git a/host/libs/vadb/usb_cmd.h b/host/libs/vadb/usb_cmd.h
new file mode 100644
index 0000000..7039fbe
--- /dev/null
+++ b/host/libs/vadb/usb_cmd.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#pragma once
+
+#include "common/libs/fs/shared_fd.h"
+#include "common/libs/usbforward/protocol.h"
+
+namespace vadb {
+// USBCommand is an abstraction of a proxied USB command.
+// Instances of this object all share the following life cycle:
+// 1) A specific instance (COMMAND) is being created.
+// 2) Instance owner (OWNER) sends RequestHeader.
+// 3) OWNER calls COMMAND.OnRequest() to send any relevant, additional
+// information.
+// 4) OWNER queues COMMAND until response arrives.
+//
+// At this point instance owner can process next command in queue. Then,
+// eventually:
+//
+// 5) OWNER receives matching ResponseHeader.
+// 6) OWNER calls COMMAND.OnResponse(), supplying FD that carries additional
+// data.
+// 7) OWNER dequeues and deletes COMMAND.
+class USBCommand {
+ public:
+ USBCommand() = default;
+ virtual ~USBCommand() = default;
+
+ // Command returns a specific usbforward command ID associated with this
+ // request.
+ virtual usb_forward::Command Command() = 0;
+
+ // OnRequest is called whenever additional data relevant to this command
+ // (other than RequestHeader) should be sent.
+ // Returns false, if communication with remote host failed (and should be
+ // terminated).
+ virtual bool OnRequest(const cvd::SharedFD& data) = 0;
+
+ // OnResponse is called whenever additional data relevant to this command
+ // (other than ResponseHeader) should be received.
+ // Returns false, if communication with remote host failed (and should be
+ // terminated).
+ virtual bool OnResponse(bool is_success, const cvd::SharedFD& data) = 0;
+
+ private:
+ USBCommand(const USBCommand& other) = delete;
+ USBCommand& operator=(const USBCommand& other) = delete;
+};
+
+} // namespace vadb
diff --git a/host/libs/vadb/usb_cmd_attach.cpp b/host/libs/vadb/usb_cmd_attach.cpp
new file mode 100644
index 0000000..02ade80
--- /dev/null
+++ b/host/libs/vadb/usb_cmd_attach.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#include <glog/logging.h>
+
+#include "common/libs/usbforward/protocol.h"
+#include "host/libs/vadb/usb_cmd_attach.h"
+
+namespace vadb {
+bool USBCmdAttach::OnRequest(const cvd::SharedFD& fd) {
+ if (fd->Write(&req_, sizeof(req_)) != sizeof(req_)) {
+ LOG(ERROR) << "Short write: " << fd->StrError();
+ return false;
+ }
+ return true;
+}
+
+bool USBCmdAttach::OnResponse(bool is_success, const cvd::SharedFD& data) {
+ if (!is_success) return false;
+ LOG(INFO) << "Attach successful.";
+ return true;
+}
+
+USBCmdAttach::USBCmdAttach(uint8_t bus_id, uint8_t dev_id) {
+ req_.bus_id = bus_id;
+ req_.dev_id = dev_id;
+}
+} // namespace vadb
diff --git a/host/libs/vadb/usb_cmd_attach.h b/host/libs/vadb/usb_cmd_attach.h
new file mode 100644
index 0000000..9cdd89e
--- /dev/null
+++ b/host/libs/vadb/usb_cmd_attach.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#pragma once
+
+#include "host/libs/vadb/usb_cmd.h"
+
+namespace vadb {
+// Request remote device attach (~open).
+class USBCmdAttach : public USBCommand {
+ public:
+ USBCmdAttach(uint8_t bus_id, uint8_t dev_id);
+ ~USBCmdAttach() override = default;
+
+ // Return usbforward command this instance is executing.
+ usb_forward::Command Command() override { return usb_forward::CmdAttach; }
+
+ // Send request body to the server.
+ // Return false, if communication failed.
+ bool OnRequest(const cvd::SharedFD& data) override;
+
+ // Receive response data from the server.
+ // Return false, if communication failed.
+ bool OnResponse(bool is_success, const cvd::SharedFD& data) override;
+
+ private:
+ usb_forward::AttachRequest req_;
+
+ USBCmdAttach(const USBCmdAttach& other) = delete;
+ USBCmdAttach& operator=(const USBCmdAttach& other) = delete;
+};
+} // namespace vadb
diff --git a/host/libs/vadb/usb_cmd_control_transfer.cpp b/host/libs/vadb/usb_cmd_control_transfer.cpp
new file mode 100644
index 0000000..643d416
--- /dev/null
+++ b/host/libs/vadb/usb_cmd_control_transfer.cpp
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#include <glog/logging.h>
+
+#include "host/libs/vadb/usb_cmd_control_transfer.h"
+
+namespace vadb {
+USBCmdControlTransfer::USBCmdControlTransfer(
+ uint8_t bus_id, uint8_t dev_id, uint8_t type, uint8_t request,
+ uint16_t value, uint16_t index, uint32_t timeout, std::vector<uint8_t> data,
+ usbip::Device::AsyncTransferReadyCB callback)
+ : data_(std::move(data)), callback_(std::move(callback)) {
+ req_.bus_id = bus_id;
+ req_.dev_id = dev_id;
+ req_.type = type;
+ req_.cmd = request;
+ req_.value = value;
+ req_.index = index;
+ req_.length = data_.size();
+ req_.timeout = timeout;
+}
+
+bool USBCmdControlTransfer::OnRequest(const cvd::SharedFD& fd) {
+ if (fd->Write(&req_, sizeof(req_)) != sizeof(req_)) {
+ LOG(ERROR) << "Short write: " << fd->StrError();
+ return false;
+ }
+
+ if ((req_.type & 0x80) == 0) {
+ if (data_.size() > 0) {
+ if (fd->Write(data_.data(), data_.size()) != data_.size()) {
+ LOG(ERROR) << "Short write: " << fd->StrError();
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+bool USBCmdControlTransfer::OnResponse(bool is_success,
+ const cvd::SharedFD& fd) {
+ if (!is_success) {
+ callback_(false, std::move(data_));
+ return true;
+ }
+
+ if (req_.type & 0x80) {
+ int32_t len;
+ if (fd->Read(&len, sizeof(len)) != sizeof(len)) {
+ LOG(ERROR) << "Short read: " << fd->StrError();
+ callback_(false, std::move(data_));
+ return false;
+ }
+
+ if (len > 0) {
+ data_.resize(len);
+ if (fd->Read(data_.data(), len) != len) {
+ LOG(ERROR) << "Short read: " << fd->StrError();
+ callback_(false, std::move(data_));
+ return false;
+ }
+ }
+ }
+
+ callback_(true, std::move(data_));
+ return true;
+}
+} // namespace vadb
diff --git a/host/libs/vadb/usb_cmd_control_transfer.h b/host/libs/vadb/usb_cmd_control_transfer.h
new file mode 100644
index 0000000..74607d0
--- /dev/null
+++ b/host/libs/vadb/usb_cmd_control_transfer.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#pragma once
+
+#include <memory>
+
+#include <stdint.h>
+
+#include "common/libs/usbforward/protocol.h"
+#include "host/libs/vadb/usb_cmd.h"
+#include "host/libs/usbip/device.h"
+
+namespace vadb {
+// Execute control transfer.
+class USBCmdControlTransfer : public USBCommand {
+ public:
+ USBCmdControlTransfer(uint8_t bus_id, uint8_t dev_id, uint8_t type,
+ uint8_t request, uint16_t value, uint16_t index,
+ uint32_t timeout, std::vector<uint8_t> data,
+ usbip::Device::AsyncTransferReadyCB callback);
+
+ ~USBCmdControlTransfer() override = default;
+
+ // Return usbforward command this instance is executing.
+ usb_forward::Command Command() override {
+ return usb_forward::CmdControlTransfer;
+ }
+
+ // Send request body to the server.
+ // Return false, if communication failed.
+ bool OnRequest(const cvd::SharedFD& data) override;
+
+ // Receive response data from the server.
+ // Return false, if communication failed.
+ bool OnResponse(bool is_success, const cvd::SharedFD& data) override;
+
+ private:
+ usb_forward::ControlTransfer req_;
+ std::vector<uint8_t> data_;
+ usbip::Device::AsyncTransferReadyCB callback_;
+
+ USBCmdControlTransfer(const USBCmdControlTransfer& other) = delete;
+ USBCmdControlTransfer& operator=(const USBCmdControlTransfer& other) = delete;
+};
+} // namespace vadb
diff --git a/host/libs/vadb/usb_cmd_data_transfer.cpp b/host/libs/vadb/usb_cmd_data_transfer.cpp
new file mode 100644
index 0000000..7503374
--- /dev/null
+++ b/host/libs/vadb/usb_cmd_data_transfer.cpp
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#include <glog/logging.h>
+
+#include "host/libs/vadb/usb_cmd_data_transfer.h"
+
+namespace vadb {
+USBCmdDataTransfer::USBCmdDataTransfer(
+ uint8_t bus_id, uint8_t dev_id, uint8_t endpoint, bool is_host_to_device,
+ uint32_t deadline, std::vector<uint8_t> data,
+ usbip::Device::AsyncTransferReadyCB callback)
+ : data_(std::move(data)), callback_(std::move(callback)) {
+ req_.bus_id = bus_id;
+ req_.dev_id = dev_id;
+ req_.endpoint_id = endpoint;
+ req_.is_host_to_device = is_host_to_device;
+ req_.length = data_.size();
+ req_.timeout = deadline;
+}
+
+bool USBCmdDataTransfer::OnRequest(const cvd::SharedFD& fd) {
+ if (fd->Write(&req_, sizeof(req_)) != sizeof(req_)) {
+ LOG(ERROR) << "Short write: " << fd->StrError();
+ return false;
+ }
+
+ if (req_.is_host_to_device && data_.size() > 0) {
+ if (fd->Write(data_.data(), data_.size()) != data_.size()) {
+ LOG(ERROR) << "Short write: " << fd->StrError();
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool USBCmdDataTransfer::OnResponse(bool is_success, const cvd::SharedFD& fd) {
+ if (!is_success) {
+ callback_(false, std::move(data_));
+ return true;
+ }
+
+ if (!req_.is_host_to_device) {
+ int32_t len;
+ if (fd->Read(&len, sizeof(len)) != sizeof(len)) {
+ LOG(ERROR) << "Short read: " << fd->StrError();
+ callback_(false, std::move(data_));
+ return false;
+ }
+
+ if (len > 0) {
+ data_.resize(len);
+ int32_t got = 0;
+ // Virtio sends data in 32k packets. We may have to do a few reads.
+ while (got < len) {
+ auto packetsize = fd->Read(&data_[got], len - got);
+ got += packetsize;
+
+ if (fd->GetErrno() != 0) {
+ // This could, technically, also be a disconnect.
+ LOG(ERROR) << "Read failed: " << fd->StrError();
+ return false;
+ } else if (packetsize == 0) {
+ LOG(ERROR) << "Short read; remote end disconnected.";
+ return false;
+ }
+ }
+ }
+ }
+
+ callback_(true, std::move(data_));
+ return true;
+}
+} // namespace vadb
diff --git a/host/libs/vadb/usb_cmd_data_transfer.h b/host/libs/vadb/usb_cmd_data_transfer.h
new file mode 100644
index 0000000..8cba30f
--- /dev/null
+++ b/host/libs/vadb/usb_cmd_data_transfer.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#pragma once
+
+#include <memory>
+#include <stdint.h>
+
+#include "common/libs/usbforward/protocol.h"
+#include "host/libs/vadb/usb_cmd.h"
+#include "host/libs/usbip/device.h"
+
+namespace vadb {
+// Execute control transfer.
+class USBCmdDataTransfer : public USBCommand {
+ public:
+ USBCmdDataTransfer(uint8_t bus_id, uint8_t dev_id, uint8_t endpoint,
+ bool is_host_to_device, uint32_t timeout,
+ std::vector<uint8_t> data,
+ usbip::Device::AsyncTransferReadyCB callback);
+
+ ~USBCmdDataTransfer() override = default;
+
+ // Return usbforward command this instance is executing.
+ usb_forward::Command Command() override {
+ return usb_forward::CmdDataTransfer;
+ }
+
+ // Send request body to the server.
+ // Return false, if communication failed.
+ bool OnRequest(const cvd::SharedFD& data) override;
+
+ // Receive response data from the server.
+ // Return false, if communication failed.
+ bool OnResponse(bool is_success, const cvd::SharedFD& data) override;
+
+ private:
+ usb_forward::DataTransfer req_;
+ std::vector<uint8_t> data_;
+ usbip::Device::AsyncTransferReadyCB callback_;
+
+ USBCmdDataTransfer(const USBCmdDataTransfer& other) = delete;
+ USBCmdDataTransfer& operator=(const USBCmdDataTransfer& other) = delete;
+};
+} // namespace vadb
diff --git a/host/libs/vadb/usb_cmd_device_list.cpp b/host/libs/vadb/usb_cmd_device_list.cpp
new file mode 100644
index 0000000..ce561de
--- /dev/null
+++ b/host/libs/vadb/usb_cmd_device_list.cpp
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#include <glog/logging.h>
+
+#include "host/libs/vadb/usb_cmd_device_list.h"
+
+namespace vadb {
+bool USBCmdDeviceList::OnRequest(const cvd::SharedFD& data) {
+ LOG(INFO) << "Requesting device list from Cuttlefish...";
+ // No action required.
+ return true;
+}
+
+bool USBCmdDeviceList::OnResponse(bool is_success, const cvd::SharedFD& fd) {
+ // This should never happen. If this command fails, something is very wrong.
+ if (!is_success) return false;
+
+ int32_t count;
+ if (fd->Read(&count, sizeof(count)) != sizeof(count)) {
+ LOG(ERROR) << "Short read: " << fd->StrError();
+ return false;
+ }
+
+ LOG(INFO) << "Device list completed with " << count << " devices.";
+
+ while (count-- > 0) {
+ usb_forward::DeviceInfo dev;
+ std::vector<usb_forward::InterfaceInfo> ifaces;
+
+ if (fd->Read(&dev, sizeof(dev)) != sizeof(dev)) {
+ LOG(ERROR) << "Short read: " << fd->StrError();
+ return false;
+ }
+
+ ifaces.resize(dev.num_interfaces);
+ if (fd->Read(ifaces.data(),
+ ifaces.size() * sizeof(usb_forward::InterfaceInfo)) !=
+ ifaces.size() * sizeof(usb_forward::InterfaceInfo)) {
+ LOG(ERROR) << "Short read: " << fd->StrError();
+ return false;
+ }
+
+ LOG(INFO) << "Found remote device 0x" << std::hex << dev.vendor_id << ":"
+ << dev.product_id;
+
+ on_device_discovered_(dev, ifaces);
+ }
+
+ return true;
+}
+} // namespace vadb
diff --git a/host/libs/vadb/usb_cmd_device_list.h b/host/libs/vadb/usb_cmd_device_list.h
new file mode 100644
index 0000000..2972ed2
--- /dev/null
+++ b/host/libs/vadb/usb_cmd_device_list.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#pragma once
+
+#include <functional>
+#include <vector>
+#include "common/libs/usbforward/protocol.h"
+#include "host/libs/vadb/usb_cmd.h"
+
+namespace vadb {
+// Request device list from remote host.
+class USBCmdDeviceList : public USBCommand {
+ public:
+ // DeviceDiscoveredCB is a callback function invoked for every new discovered
+ // device.
+ using DeviceDiscoveredCB =
+ std::function<void(const usb_forward::DeviceInfo&,
+ const std::vector<usb_forward::InterfaceInfo>&)>;
+
+ USBCmdDeviceList(DeviceDiscoveredCB cb)
+ : on_device_discovered_(std::move(cb)) {}
+
+ ~USBCmdDeviceList() override = default;
+
+ // Return usbforward command this instance is executing.
+ usb_forward::Command Command() override { return usb_forward::CmdDeviceList; }
+
+ // Send request body to the server.
+ // Return false, if communication failed.
+ bool OnRequest(const cvd::SharedFD& data) override;
+
+ // Receive response data from the server.
+ // Return false, if communication failed.
+ bool OnResponse(bool is_success, const cvd::SharedFD& data) override;
+
+ private:
+ DeviceDiscoveredCB on_device_discovered_;
+ USBCmdDeviceList(const USBCmdDeviceList& other) = delete;
+ USBCmdDeviceList& operator=(const USBCmdDeviceList& other) = delete;
+};
+} // namespace vadb
diff --git a/host/libs/vadb/usb_cmd_heartbeat.cpp b/host/libs/vadb/usb_cmd_heartbeat.cpp
new file mode 100644
index 0000000..59138bc
--- /dev/null
+++ b/host/libs/vadb/usb_cmd_heartbeat.cpp
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#include <glog/logging.h>
+
+#include "common/libs/usbforward/protocol.h"
+#include "host/libs/vadb/usb_cmd_heartbeat.h"
+
+namespace vadb {
+bool USBCmdHeartbeat::OnRequest(const cvd::SharedFD& fd) { return true; }
+
+bool USBCmdHeartbeat::OnResponse(bool is_success, const cvd::SharedFD& data) {
+ callback_(is_success);
+ return true;
+}
+
+USBCmdHeartbeat::USBCmdHeartbeat(USBCmdHeartbeat::HeartbeatResultCB callback)
+ : callback_(callback) {}
+} // namespace vadb
diff --git a/host/libs/vadb/usb_cmd_heartbeat.h b/host/libs/vadb/usb_cmd_heartbeat.h
new file mode 100644
index 0000000..e0bc20e
--- /dev/null
+++ b/host/libs/vadb/usb_cmd_heartbeat.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#pragma once
+
+#include "host/libs/vadb/usb_cmd.h"
+
+namespace vadb {
+// Request remote device attach (~open).
+class USBCmdHeartbeat : public USBCommand {
+ public:
+ // Heartbeat result callback receives a boolean argument indicating whether
+ // remote device is ready to be attached.
+ using HeartbeatResultCB = std::function<void(bool)>;
+
+ USBCmdHeartbeat(HeartbeatResultCB callback);
+ ~USBCmdHeartbeat() override = default;
+
+ // Return usbforward command this instance is executing.
+ usb_forward::Command Command() override { return usb_forward::CmdHeartbeat; }
+
+ // Send request body to the server.
+ // Return false, if communication failed.
+ bool OnRequest(const cvd::SharedFD& data) override;
+
+ // Receive response data from the server.
+ // Return false, if communication failed.
+ bool OnResponse(bool is_success, const cvd::SharedFD& data) override;
+
+ private:
+ HeartbeatResultCB callback_;
+
+ USBCmdHeartbeat(const USBCmdHeartbeat& other) = delete;
+ USBCmdHeartbeat& operator=(const USBCmdHeartbeat& other) = delete;
+};
+} // namespace vadb
diff --git a/host/libs/vadb/virtual_adb_client.cpp b/host/libs/vadb/virtual_adb_client.cpp
new file mode 100644
index 0000000..71a5c73
--- /dev/null
+++ b/host/libs/vadb/virtual_adb_client.cpp
@@ -0,0 +1,234 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#include <algorithm>
+#include <memory>
+#include <gflags/gflags.h>
+
+#include "common/libs/fs/shared_select.h"
+#include "host/libs/vadb/usb_cmd_attach.h"
+#include "host/libs/vadb/usb_cmd_control_transfer.h"
+#include "host/libs/vadb/usb_cmd_data_transfer.h"
+#include "host/libs/vadb/usb_cmd_device_list.h"
+#include "host/libs/vadb/usb_cmd_heartbeat.h"
+#include "host/libs/vadb/virtual_adb_client.h"
+
+DEFINE_bool(debug_adb_client, false, "Turn on verbose logging in the virtual_adb_client.cpp");
+
+#define VLOG(X) if (FLAGS_debug_adb_client) LOG(VERBOSE)
+
+namespace vadb {
+namespace {
+constexpr int kHeartbeatTimeoutSeconds = 3;
+} // namespace
+
+VirtualADBClient::VirtualADBClient(usbip::DevicePool* pool, cvd::SharedFD fd,
+ const std::string& usbip_socket_name)
+ : pool_{pool}, fd_{fd}, vhci_{usbip_socket_name} {
+ CHECK(vhci_.Init());
+ timer_ = cvd::SharedFD::TimerFD(CLOCK_MONOTONIC, 0);
+ SendHeartbeat();
+}
+
+void VirtualADBClient::RegisterDevice(
+ const usb_forward::DeviceInfo& dev,
+ const std::vector<usb_forward::InterfaceInfo>& ifaces) {
+ auto d = std::unique_ptr<usbip::Device>(new usbip::Device);
+ d->vendor_id = dev.vendor_id;
+ d->product_id = dev.product_id;
+ d->dev_version = dev.dev_version;
+ d->dev_class = dev.dev_class;
+ d->dev_subclass = dev.dev_subclass;
+ d->dev_protocol = dev.dev_protocol;
+ d->speed = dev.speed;
+ d->configurations_count = dev.num_configurations;
+ d->configuration_number = dev.cur_configuration;
+
+ for (const auto& iface : ifaces) {
+ d->interfaces.push_back(usbip::Device::Interface{
+ iface.if_class, iface.if_subclass, iface.if_protocol});
+ }
+
+ uint8_t bus_id = dev.bus_id;
+ uint8_t dev_id = dev.dev_id;
+
+ d->handle_attach = [this, bus_id, dev_id]() -> bool {
+ return HandleAttach(bus_id, dev_id);
+ };
+
+ d->handle_control_transfer =
+ [this, bus_id, dev_id](
+ const usbip::CmdRequest& r, uint32_t deadline,
+ std::vector<uint8_t> data,
+ usbip::Device::AsyncTransferReadyCB callback) -> bool {
+ return HandleDeviceControlRequest(bus_id, dev_id, r, deadline,
+ std::move(data), std::move(callback));
+ };
+
+ d->handle_data_transfer =
+ [this, bus_id, dev_id](
+ uint8_t endpoint, bool is_host_to_device, uint32_t deadline,
+ std::vector<uint8_t> data,
+ usbip::Device::AsyncTransferReadyCB callback) -> bool {
+ return HandleDeviceDataRequest(bus_id, dev_id, endpoint, is_host_to_device,
+ deadline, std::move(data),
+ std::move(callback));
+ };
+
+ pool_->AddDevice(usbip::DevicePool::BusDevNumber{bus_id, dev_id},
+ std::move(d));
+
+ // Attach this device.
+ HandleAttach(bus_id, dev_id);
+}
+
+bool VirtualADBClient::PopulateRemoteDevices() {
+ return ExecuteCommand(std::unique_ptr<USBCommand>(new USBCmdDeviceList(
+ [this](const usb_forward::DeviceInfo& info,
+ const std::vector<usb_forward::InterfaceInfo>& ifaces) {
+ RegisterDevice(info, ifaces);
+ })));
+}
+
+bool VirtualADBClient::HandleDeviceControlRequest(
+ uint8_t bus_id, uint8_t dev_id, const usbip::CmdRequest& r,
+ uint32_t timeout, std::vector<uint8_t> data,
+ usbip::Device::AsyncTransferReadyCB callback) {
+ return ExecuteCommand(std::unique_ptr<USBCommand>(new USBCmdControlTransfer(
+ bus_id, dev_id, r.type, r.cmd, r.value, r.index, timeout, std::move(data),
+ std::move(callback))));
+}
+
+bool VirtualADBClient::HandleDeviceDataRequest(
+ uint8_t bus_id, uint8_t dev_id, uint8_t endpoint, bool is_host_to_device,
+ uint32_t deadline, std::vector<uint8_t> data,
+ usbip::Device::AsyncTransferReadyCB callback) {
+ return ExecuteCommand(std::unique_ptr<USBCommand>(
+ new USBCmdDataTransfer(bus_id, dev_id, endpoint, is_host_to_device,
+ deadline, std::move(data), std::move(callback))));
+}
+
+bool VirtualADBClient::HandleAttach(uint8_t bus_id, uint8_t dev_id) {
+ return ExecuteCommand(
+ std::unique_ptr<USBCommand>(new USBCmdAttach(bus_id, dev_id)));
+}
+
+bool VirtualADBClient::SendHeartbeat() {
+ VLOG(1) << "Sending heartbeat...";
+ struct itimerspec spec {};
+ spec.it_value.tv_sec = kHeartbeatTimeoutSeconds;
+ timer_->TimerSet(0, &spec, nullptr);
+
+ heartbeat_tag_ = tag_;
+
+ return ExecuteCommand(std::unique_ptr<USBCommand>(
+ new USBCmdHeartbeat([this](bool success) { HandleHeartbeat(success); })));
+}
+
+void VirtualADBClient::HandleHeartbeat(bool is_ready) {
+ VLOG(1) << "Remote server status: " << is_ready;
+ if (is_ready && !is_remote_server_ready_) {
+ LOG(INFO) << "Remote server is now ready.";
+ PopulateRemoteDevices();
+ vhci_.TriggerAttach();
+ } else if (is_remote_server_ready_ && !is_ready) {
+ vhci_.TriggerDetach();
+ LOG(WARNING) << "Remote server connection lost.";
+ // It makes perfect sense to cancel all outstanding USB requests, as device
+ // is not going to answer any of these anyway.
+ for (const auto& pair : commands_) {
+ pair.second->OnResponse(false, fd_);
+ }
+ commands_.clear();
+ }
+ is_remote_server_ready_ = is_ready;
+}
+
+bool VirtualADBClient::HandleHeartbeatTimeout() {
+ uint64_t timer_result;
+ timer_->Read(&timer_result, sizeof(timer_result));
+
+ auto iter = commands_.find(heartbeat_tag_);
+ if (iter != commands_.end()) {
+ // Make sure to erase the value from list of commands prior to running
+ // callback. Particularly important for heartbeat, which cancels all
+ // outstanding USB commands (including self, if found), if device goes
+ // away (eg. reboots).
+ auto command = std::move(iter->second);
+ commands_.erase(iter);
+ command->OnResponse(false, fd_);
+ }
+
+ return SendHeartbeat();
+}
+
+bool VirtualADBClient::ExecuteCommand(std::unique_ptr<USBCommand> cmd) {
+ uint32_t this_tag = tag_;
+ tag_++;
+ usb_forward::RequestHeader hdr{cmd->Command(), this_tag};
+ if (fd_->Write(&hdr, sizeof(hdr)) != sizeof(hdr)) {
+ LOG(ERROR) << "Could not contact USB Forwarder: " << fd_->StrError();
+ return false;
+ }
+
+ if (!cmd->OnRequest(fd_)) return false;
+
+ commands_[this_tag] = std::move(cmd);
+ return true;
+}
+
+// BeforeSelect is Called right before Select() to populate interesting
+// SharedFDs.
+void VirtualADBClient::BeforeSelect(cvd::SharedFDSet* fd_read) const {
+ fd_read->Set(fd_);
+ fd_read->Set(timer_);
+}
+
+// AfterSelect is Called right after Select() to detect and respond to changes
+// on affected SharedFDs.
+// Return value indicates whether this client is still valid.
+bool VirtualADBClient::AfterSelect(const cvd::SharedFDSet& fd_read) {
+ if (fd_read.IsSet(timer_)) {
+ HandleHeartbeatTimeout();
+ }
+ if (fd_read.IsSet(fd_)) {
+ usb_forward::ResponseHeader rhdr;
+ if (fd_->Read(&rhdr, sizeof(rhdr)) != sizeof(rhdr)) {
+ LOG(ERROR) << "Could not read from USB Forwarder: " << fd_->StrError();
+ // TODO(ender): it is very likely the connection has been dropped by QEmu.
+ // Should we cancel all pending commands now?
+ return false;
+ }
+
+ auto iter = commands_.find(rhdr.tag);
+ if (iter == commands_.end()) {
+ // This is likely a late heartbeat response, but could very well be any of
+ // the remaining commands.
+ LOG(INFO) << "Received response for discarded tag " << rhdr.tag;
+ } else {
+ // Make sure to erase the value from list of commands prior to running
+ // callback. Particularly important for heartbeat, which cancels all
+ // outstanding USB commands (including self, if found), if device goes
+ // away (eg. reboots).
+ auto command = std::move(iter->second);
+ commands_.erase(iter);
+ command->OnResponse(rhdr.status == usb_forward::StatusSuccess, fd_);
+ }
+ }
+
+ return true;
+}
+
+} // namespace vadb
diff --git a/host/libs/vadb/virtual_adb_client.h b/host/libs/vadb/virtual_adb_client.h
new file mode 100644
index 0000000..d521c74
--- /dev/null
+++ b/host/libs/vadb/virtual_adb_client.h
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#pragma once
+
+#include <string>
+
+#include "common/libs/fs/shared_fd.h"
+#include "common/libs/fs/shared_select.h"
+#include "common/libs/usbforward/protocol.h"
+#include "host/libs/vadb/usb_cmd.h"
+#include "host/libs/usbip/device.h"
+#include "host/libs/usbip/device_pool.h"
+#include "host/libs/usbip/messages.h"
+#include "host/libs/usbip/vhci_instrument.h"
+
+namespace vadb {
+// VirtualADBClient is a companion class for USBForwarder, running on
+// Cuttlefish. VirtualADBClient collects list of available USB devices from
+// Cuttlefish and makes them available to USB/IP.
+//
+// Purpose of this class is to connect to USBForwarder and make access to
+// remote USB devices possible with help of USB/IP protocol.
+class VirtualADBClient {
+ public:
+ VirtualADBClient(usbip::DevicePool* pool, cvd::SharedFD fd,
+ const std::string& usbip_socket_name);
+
+ virtual ~VirtualADBClient() = default;
+
+ // Query remote server; populate available USB devices.
+ bool PopulateRemoteDevices();
+
+ // BeforeSelect is Called right before Select() to populate interesting
+ // SharedFDs.
+ void BeforeSelect(cvd::SharedFDSet* fd_read) const;
+
+ // AfterSelect is Called right after Select() to detect and respond to changes
+ // on affected SharedFDs.
+ // Return value indicates whether this client is still valid.
+ bool AfterSelect(const cvd::SharedFDSet& fd_read);
+
+ private:
+ // Register new device in a device pool.
+ void RegisterDevice(const usb_forward::DeviceInfo& dev,
+ const std::vector<usb_forward::InterfaceInfo>& ifaces);
+
+ // Request attach remote USB device.
+ bool HandleAttach(uint8_t bus_id, uint8_t dev_id);
+
+ // Execute control request on remote device.
+ bool HandleDeviceControlRequest(uint8_t bus_id, uint8_t dev_id,
+ const usbip::CmdRequest& r, uint32_t deadline,
+ std::vector<uint8_t> data,
+ usbip::Device::AsyncTransferReadyCB callback);
+
+ // Execute data request on remote device.
+ bool HandleDeviceDataRequest(uint8_t bus_id, uint8_t dev_id, uint8_t endpoint,
+ bool is_host_to_device, uint32_t deadline,
+ std::vector<uint8_t> data,
+ usbip::Device::AsyncTransferReadyCB callback);
+
+ // Send new heartbeat request and arm the heartbeat timer.
+ bool SendHeartbeat();
+
+ // Heartbeat handler receives response to heartbeat request.
+ // Supplied argument indicates, whether remote server is ready to export USB
+ // gadget.
+ void HandleHeartbeat(bool is_ready);
+
+ // Heartbeat timeout detects situation where heartbeat did not receive
+ // matching response. This could be a direct result of device reset.
+ bool HandleHeartbeatTimeout();
+
+ // ExecuteCommand creates command header and executes supplied USBCommand.
+ // If execution was successful, command will be stored internally until
+ // response arrives.
+ bool ExecuteCommand(std::unique_ptr<USBCommand> cmd);
+
+ usbip::DevicePool* pool_;
+ cvd::SharedFD fd_;
+ cvd::SharedFD timer_;
+ usbip::VHCIInstrument vhci_;
+ bool is_remote_server_ready_ = false;
+
+ uint32_t tag_ = 0;
+ // Assign an 'invalid' tag as previously sent heartbeat command. This will
+ // prevent heartbeat timeout handler from finding a command if none was sent.
+ uint32_t heartbeat_tag_ = ~0;
+ std::map<uint32_t, std::unique_ptr<USBCommand>> commands_;
+
+ VirtualADBClient(const VirtualADBClient& other) = delete;
+ VirtualADBClient& operator=(const VirtualADBClient& other) = delete;
+};
+
+} // namespace vadb
diff --git a/host/libs/vadb/virtual_adb_server.cpp b/host/libs/vadb/virtual_adb_server.cpp
new file mode 100644
index 0000000..63283e1
--- /dev/null
+++ b/host/libs/vadb/virtual_adb_server.cpp
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include "host/libs/vadb/virtual_adb_server.h"
+
+namespace vadb {
+
+bool VirtualADBServer::Init() {
+ LOG(INFO) << "Starting server socket: " << name_;
+
+ server_ =
+ cvd::SharedFD::SocketLocalServer(name_.c_str(), false, SOCK_STREAM, 0666);
+ if (!server_->IsOpen()) {
+ LOG(ERROR) << "Could not create socket: " << server_->StrError();
+ return false;
+ }
+ return true;
+}
+
+void VirtualADBServer::BeforeSelect(cvd::SharedFDSet* fd_read) const {
+ fd_read->Set(server_);
+ for (const auto& client : clients_) client.BeforeSelect(fd_read);
+}
+
+void VirtualADBServer::AfterSelect(const cvd::SharedFDSet& fd_read) {
+ if (fd_read.IsSet(server_)) HandleIncomingConnection();
+
+ for (auto iter = clients_.begin(); iter != clients_.end();) {
+ if (!iter->AfterSelect(fd_read)) {
+ // If client conversation failed, hang up.
+ iter = clients_.erase(iter);
+ continue;
+ }
+ ++iter;
+ }
+}
+
+// Accept new QEmu connection. Add it to client pool.
+// Typically we will have no more than one QEmu connection, but the nature
+// of server requires proper handling nonetheless.
+void VirtualADBServer::HandleIncomingConnection() {
+ cvd::SharedFD client = cvd::SharedFD::Accept(*server_, nullptr, nullptr);
+ if (!client->IsOpen()) {
+ LOG(ERROR) << "Client connection failed: " << client->StrError();
+ return;
+ }
+
+ clients_.emplace_back(&pool_, client, usbip_name_);
+}
+
+} // namespace vadb
diff --git a/host/libs/vadb/virtual_adb_server.h b/host/libs/vadb/virtual_adb_server.h
new file mode 100644
index 0000000..d679a5d
--- /dev/null
+++ b/host/libs/vadb/virtual_adb_server.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#pragma once
+
+#include <list>
+#include <string>
+
+#include "common/libs/fs/shared_fd.h"
+#include "host/libs/usbip/device_pool.h"
+#include "host/libs/vadb/virtual_adb_client.h"
+
+namespace vadb {
+// VirtualADBServer manages incoming VirtualUSB/ADB connections from QEmu.
+class VirtualADBServer {
+ public:
+ VirtualADBServer(const std::string& usb_socket_name,
+ const std::string& usbip_socket_name)
+ : name_(usb_socket_name), usbip_name_(usbip_socket_name) {}
+
+ ~VirtualADBServer() = default;
+
+ // Initialize this instance of Server.
+ // Returns true, if initialization was successful.
+ bool Init();
+
+ // Pool of USB devices available to export.
+ const usbip::DevicePool& Pool() const { return pool_; };
+
+ // BeforeSelect is Called right before Select() to populate interesting
+ // SharedFDs.
+ void BeforeSelect(cvd::SharedFDSet* fd_read) const;
+
+ // AfterSelect is Called right after Select() to detect and respond to changes
+ // on affected SharedFDs.
+ void AfterSelect(const cvd::SharedFDSet& fd_read);
+
+ private:
+ void HandleIncomingConnection();
+
+ usbip::DevicePool pool_;
+ std::string name_;
+ std::string usbip_name_;
+ cvd::SharedFD server_;
+ std::list<VirtualADBClient> clients_;
+
+ VirtualADBServer(const VirtualADBServer&) = delete;
+ VirtualADBServer& operator=(const VirtualADBServer&) = delete;
+};
+
+} // namespace vadb
diff --git a/host/vsoc/lib/gralloc_buffer_region_view.cpp b/host/vsoc/lib/gralloc_buffer_region_view.cpp
new file mode 100644
index 0000000..c2a8d87
--- /dev/null
+++ b/host/vsoc/lib/gralloc_buffer_region_view.cpp
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include "host/vsoc/lib/gralloc_buffer_region_view.h"
+
+#include <mutex>
+#include "glog/logging.h"
+
+using vsoc::gralloc::GrallocBufferRegionView;
+
+// static
+GrallocBufferRegionView* GrallocBufferRegionView::GetInstance(const char* domain) {
+ static std::mutex mutex;
+ static std::map<std::string, GrallocBufferRegionView*> gralloc_regions;
+
+ std::lock_guard<std::mutex> guard(mutex);
+ if (!gralloc_regions.count(domain)) {
+ gralloc_regions[domain] = new GrallocBufferRegionView(domain);
+ }
+ return gralloc_regions[domain];
+}
+
+uint8_t* GrallocBufferRegionView::OffsetToBufferPtr(vsoc_reg_off_t offset) {
+ if (offset <= control_->region_desc().offset_of_region_data ||
+ offset >= control_->region_size()) {
+ LOG(FATAL)
+ << "Attempted to access a gralloc buffer outside region data, offset: "
+ << offset;
+ return nullptr;
+ }
+ return region_offset_to_pointer<uint8_t>(offset);
+}
+
+GrallocBufferRegionView::GrallocBufferRegionView(const char* domain) {
+ is_open_ = Open(domain);
+}
diff --git a/host/vsoc/lib/gralloc_buffer_region_view.h b/host/vsoc/lib/gralloc_buffer_region_view.h
new file mode 100644
index 0000000..f2d10b4
--- /dev/null
+++ b/host/vsoc/lib/gralloc_buffer_region_view.h
@@ -0,0 +1,46 @@
+#pragma once
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include "common/vsoc/lib/typed_region_view.h"
+#include "common/vsoc/shm/gralloc_layout.h"
+
+#include <string>
+
+namespace vsoc {
+namespace gralloc {
+
+// Allows access to the gralloc buffer region from host side. It needs to be a
+// different class than the one on guest side because of the required
+// interactions with the kernel on the guest.
+// Initially this class only returns a pointer to a buffer in memory given a
+// region offset, which is enough for now since it's only used by the hwcomposer
+// (which gets all other information from the guest side hwcomposer) and by the
+// VNC server (which uses only the frame buffer and gets the information it
+// needs from the framebuffer region).
+class GrallocBufferRegionView
+ : vsoc::TypedRegionView<vsoc::layout::gralloc::GrallocBufferLayout> {
+ public:
+ static GrallocBufferRegionView* GetInstance(const char* domain);
+
+ uint8_t* OffsetToBufferPtr(vsoc_reg_off_t offset);
+ protected:
+ GrallocBufferRegionView(const char* domain);
+ bool is_open_{};
+};
+
+}
+}
diff --git a/host/vsoc/lib/host_lock.cpp b/host/vsoc/lib/host_lock.cpp
new file mode 100644
index 0000000..44c20b5
--- /dev/null
+++ b/host/vsoc/lib/host_lock.cpp
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#include "common/vsoc/shm/lock.h"
+
+#include "sys/types.h"
+
+#include "common/vsoc/lib/compat.h"
+#include "common/vsoc/lib/single_sided_signal.h"
+
+namespace vsoc {
+namespace layout {
+
+void HostLock::Lock() {
+ uint32_t tid = gettid();
+ uint32_t expected_value;
+ uint32_t* uaddr = reinterpret_cast<uint32_t*>(&lock_uint32_);
+
+ while (1) {
+ if (TryLock(tid, &expected_value)) {
+ return;
+ }
+ SingleSidedSignal::AwaitSignal(expected_value, uaddr);
+ }
+}
+
+void HostLock::Unlock() {
+ Sides sides_to_signal = UnlockCommon(gettid());
+ if (sides_to_signal.value_ != Sides::NoSides) {
+ SingleSidedSignal::Signal(&lock_uint32_);
+ }
+}
+
+} // namespace layout
+} // namespace vsoc
diff --git a/host/vsoc/lib/host_region_e2e_test.cpp b/host/vsoc/lib/host_region_e2e_test.cpp
new file mode 100644
index 0000000..d4f9e29
--- /dev/null
+++ b/host/vsoc/lib/host_region_e2e_test.cpp
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+/*
+ * End-to-end test to ensure that mapping of vsoc regions works on the host.
+ */
+
+#include <gtest/gtest.h>
+#include "common/vsoc/lib/e2e_test_region_view.h"
+#include "host/libs/config/host_config.h"
+
+// Here is a summary of the two regions interrupt and write test:
+// 1. Write our strings to the first region
+// 2. Ensure that our peer hasn't signalled the second region. That would
+// indicate that it didn't wait for our interrupt.
+// 3. Send the interrupt on the first region
+// 4. Wait for our peer's interrupt on the first region
+// 5. Confirm that we can see our peer's writes in the first region
+// 6. Initialize our strings in the second region
+// 7. Send an interrupt on the second region to our peer
+// 8. Wait for our peer's interrupt on the second region
+// 9. Confirm that we can see our peer's writes in the second region
+// 10. Repeat the process for signaling.
+// 11. Confirm that no interrupt is pending in the first region
+// 12. Confirm that no interrupt is pending in the second region
+
+template <typename View>
+void SetHostStrings(View* in) {
+ size_t num_data = in->string_size();
+ EXPECT_LE(2, num_data);
+ for (size_t i = 0; i < num_data; ++i) {
+ EXPECT_TRUE(!in->host_string(i)[0] ||
+ !strcmp(in->host_string(i), View::Layout::host_pattern));
+ in->set_host_string(i, View::Layout::host_pattern);
+ EXPECT_STREQ(in->host_string(i), View::Layout::host_pattern);
+ }
+}
+
+template <typename View>
+void CheckPeerStrings(View* in) {
+ size_t num_data = in->string_size();
+ EXPECT_LE(2, num_data);
+ for (size_t i = 0; i < num_data; ++i) {
+ EXPECT_STREQ(View::Layout::guest_pattern, in->guest_string(i));
+ }
+}
+
+TEST(RegionTest, PeerTests) {
+ vsoc::E2EPrimaryRegionView primary;
+ ASSERT_TRUE(primary.Open(vsoc::GetDomain().c_str()));
+ vsoc::E2ESecondaryRegionView secondary;
+ ASSERT_TRUE(secondary.Open(vsoc::GetDomain().c_str()));
+ LOG(INFO) << "Regions are open";
+ SetHostStrings(&primary);
+ EXPECT_FALSE(secondary.HasIncomingInterrupt());
+ EXPECT_TRUE(primary.MaybeInterruptPeer());
+ LOG(INFO) << "Waiting for first interrupt from peer";
+ primary.WaitForInterrupt();
+ LOG(INFO) << "First interrupt received";
+ CheckPeerStrings(&primary);
+ SetHostStrings(&secondary);
+ EXPECT_TRUE(secondary.MaybeInterruptPeer());
+ LOG(INFO) << "Waiting for second interrupt from peer";
+ secondary.WaitForInterrupt();
+ LOG(INFO) << "Second interrupt received";
+ CheckPeerStrings(&secondary);
+
+ // Test signals
+ EXPECT_FALSE(secondary.HasIncomingInterrupt());
+ LOG(INFO) << "Verified no early second signal";
+ vsoc::layout::Sides side;
+ side.value_ = vsoc::layout::Sides::Peer;
+ primary.SendSignal(side, &primary.data()->host_to_guest_signal);
+ LOG(INFO) << "Signal sent. Waiting for first signal from peer";
+ primary.WaitForInterrupt();
+ int count = 0; // counts the number of signals received.
+ primary.ProcessSignalsFromPeer([&primary, &count](uint32_t* uaddr){
+ ++count;
+ EXPECT_TRUE(uaddr == &primary.data()->guest_to_host_signal);
+ });
+ EXPECT_TRUE(count == 1);
+ LOG(INFO) << "Signal received on primary region";
+ secondary.SendSignal(side, &secondary.data()->host_to_guest_signal);
+ LOG(INFO) << "Signal sent. Waiting for second signal from peer";
+ secondary.WaitForInterrupt();
+ count = 0;
+ secondary.ProcessSignalsFromPeer([&secondary, &count](uint32_t* uaddr){
+ ++count;
+ EXPECT_TRUE(uaddr == &secondary.data()->guest_to_host_signal);
+ });
+ EXPECT_TRUE(count == 1);
+ LOG(INFO) << "Signal received on secondary region";
+
+ EXPECT_FALSE(primary.HasIncomingInterrupt());
+ EXPECT_FALSE(secondary.HasIncomingInterrupt());
+}
+
+TEST(RegionTest, MissingRegionCausesDeath) {
+ vsoc::E2EUnfindableRegionView test;
+ EXPECT_DEATH(test.Open(vsoc::GetDomain().c_str()), ".*");
+}
+
+int main(int argc, char** argv) {
+ testing::InitGoogleTest(&argc, argv);
+ int rval = RUN_ALL_TESTS();
+ if (!rval) {
+ vsoc::E2EPrimaryRegionView region;
+ region.Open(vsoc::GetDomain().c_str());
+ region.host_status(vsoc::layout::e2e_test::E2E_MEMORY_FILLED);
+ }
+ return rval;
+}
diff --git a/host/vsoc/lib/region_control.cpp b/host/vsoc/lib/region_control.cpp
new file mode 100644
index 0000000..1ad94a5
--- /dev/null
+++ b/host/vsoc/lib/region_control.cpp
@@ -0,0 +1,246 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#include "common/vsoc/lib/region_view.h"
+
+#define LOG_TAG "vsoc: region_host"
+
+#include <stdio.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <sys/socket.h>
+#include <sys/syscall.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <iomanip>
+#include <sstream>
+#include <thread>
+#include <vector>
+
+#include <glog/logging.h>
+
+#include "common/libs/fs/shared_fd.h"
+#include "common/libs/fs/shared_select.h"
+
+using cvd::SharedFD;
+
+namespace {
+
+class HostRegionControl : public vsoc::RegionControl {
+ public:
+ HostRegionControl(const char* region_name,
+ const SharedFD& incoming_interrupt_fd,
+ const SharedFD& outgoing_interrupt_fd,
+ const SharedFD& shared_memory_fd)
+ : region_name_{region_name},
+ incoming_interrupt_fd_{incoming_interrupt_fd},
+ outgoing_interrupt_fd_{outgoing_interrupt_fd},
+ shared_memory_fd_{shared_memory_fd} {}
+
+ int CreateFdScopedPermission(const char* managed_region_name,
+ vsoc_reg_off_t owner_offset, uint32_t owned_val,
+ vsoc_reg_off_t begin_offset,
+ vsoc_reg_off_t end_offset) override {
+ return -1;
+ }
+
+ bool InitializeRegion();
+
+ virtual bool InterruptPeer() override {
+ uint64_t one = 1;
+ ssize_t rval = outgoing_interrupt_fd_->Write(&one, sizeof(one));
+ if (rval != sizeof(one)) {
+ LOG(FATAL) << __FUNCTION__ << ": rval (" << rval << ") != sizeof(one))";
+ return false;
+ }
+ return true;
+ }
+
+ // Wake the local signal table scanner. Primarily used during shutdown
+ virtual void InterruptSelf() override {
+ uint64_t one = 1;
+ ssize_t rval = incoming_interrupt_fd_->Write(&one, sizeof(one));
+ if (rval != sizeof(one)) {
+ LOG(FATAL) << __FUNCTION__ << ": rval (" << rval << ") != sizeof(one))";
+ }
+ }
+
+ virtual void WaitForInterrupt() override {
+ // Check then act isn't a problem here: the other side does
+ // the following things in exactly this order:
+ // 1. exchanges 1 with interrupt_signalled
+ // 2. if interrupt_signalled was 0 it increments the eventfd
+ // eventfd increments are persistent, so if interrupt_signalled was set
+ // back to 1 while we are going to sleep the sleep will return
+ // immediately.
+ uint64_t missed{};
+ cvd::SharedFDSet readset;
+ readset.Set(incoming_interrupt_fd_);
+ cvd::Select(&readset, NULL, NULL, NULL);
+ ssize_t rval = incoming_interrupt_fd_->Read(&missed, sizeof(missed));
+ if (rval != sizeof(missed)) {
+ LOG(FATAL) << __FUNCTION__ << ": rval (" << rval
+ << ") != sizeof(missed))";
+ }
+ if (!missed) {
+ LOG(FATAL) << __FUNCTION__ << ": woke with 0 interrupts";
+ }
+ }
+
+ virtual void* Map() override {
+ if (region_base_) {
+ return region_base_;
+ }
+ // Now actually map the region
+ region_base_ =
+ shared_memory_fd_->Mmap(0, region_size(), PROT_READ | PROT_WRITE,
+ MAP_SHARED, region_desc_.region_begin_offset);
+ if (region_base_ == MAP_FAILED) {
+ LOG(FATAL) << "mmap failed for offset "
+ << region_desc_.region_begin_offset << " ("
+ << shared_memory_fd_->StrError() << ")";
+ region_base_ = nullptr;
+ }
+ return region_base_;
+ }
+
+ protected:
+ const char* region_name_{};
+ cvd::SharedFD incoming_interrupt_fd_;
+ cvd::SharedFD outgoing_interrupt_fd_;
+ cvd::SharedFD shared_memory_fd_;
+};
+
+// Default path to the ivshmem_server socket. This can vary when we're
+// launching multiple CVDs.
+constexpr int kMaxSupportedProtocolVersion = 0;
+
+bool HostRegionControl::InitializeRegion() {
+ size_t region_name_len = strlen(region_name_);
+ if (region_name_len >= sizeof(vsoc_device_name)) {
+ LOG(FATAL) << "Region name length (" << region_name_len << ") not < "
+ << sizeof(vsoc_device_name);
+ return false;
+ }
+ vsoc_shm_layout_descriptor layout;
+ ssize_t rval = shared_memory_fd_->Pread(&layout, sizeof(layout), 0);
+ if (rval != sizeof(layout)) {
+ LOG(FATAL) << "Unable to read layout, rval=" << rval << " ("
+ << shared_memory_fd_->StrError() << ")";
+ return false;
+ }
+ if (layout.major_version != CURRENT_VSOC_LAYOUT_MAJOR_VERSION) {
+ LOG(FATAL) << "Incompatible major version: saw " << layout.major_version
+ << " wanted " << CURRENT_VSOC_LAYOUT_MAJOR_VERSION;
+ }
+ std::vector<vsoc_device_region> descriptors;
+ descriptors.resize(layout.region_count);
+ ssize_t wanted = sizeof(vsoc_device_region) * layout.region_count;
+ rval = shared_memory_fd_->Pread(descriptors.data(), wanted,
+ layout.vsoc_region_desc_offset);
+ if (rval != wanted) {
+ LOG(FATAL) << "Unable to read region descriptors, rval=" << rval << " ("
+ << shared_memory_fd_->StrError() << ")";
+ return false;
+ }
+ for (const auto& desc : descriptors) {
+ if (!strcmp(region_name_, desc.device_name)) {
+ region_desc_ = desc;
+ return true;
+ }
+ }
+
+ std::ostringstream buf;
+ for (const auto& desc : descriptors) {
+ buf << " " << desc.device_name;
+ }
+ LOG(FATAL) << "Region name of " << region_name_
+ << " not found among:" << buf.str();
+ return false;
+}
+} // namespace
+
+std::shared_ptr<vsoc::RegionControl> vsoc::RegionControl::Open(
+ const char* region_name, const char* domain) {
+ AutoFreeBuffer msg;
+
+ SharedFD region_server =
+ SharedFD::SocketLocalClient(domain, false, SOCK_STREAM);
+ if (!region_server->IsOpen()) {
+ LOG(FATAL) << "Could not contact ivshmem_server ("
+ << region_server->StrError() << ")";
+ return nullptr;
+ }
+
+ // Check server protocol version.
+ uint32_t protocol_version;
+ ssize_t bytes = region_server->Recv(&protocol_version,
+ sizeof(protocol_version), MSG_NOSIGNAL);
+ if (bytes != sizeof(protocol_version)) {
+ LOG(FATAL) << "Failed to recv protocol version; res=" << bytes << " ("
+ << region_server->StrError() << ")";
+ return nullptr;
+ }
+
+ if (protocol_version > kMaxSupportedProtocolVersion) {
+ LOG(FATAL) << "Unsupported protocol version " << protocol_version
+ << "; max supported version is " << kMaxSupportedProtocolVersion;
+ return nullptr;
+ }
+
+ // Send requested region.
+ int16_t size = strlen(region_name);
+ bytes = region_server->Send(&size, sizeof(size), MSG_NOSIGNAL);
+ if (bytes != sizeof(size)) {
+ LOG(FATAL) << "Failed to send region name length; res=" << bytes << " ("
+ << region_server->StrError() << ")";
+ return nullptr;
+ }
+
+ bytes = region_server->Send(region_name, size, MSG_NOSIGNAL);
+ if (bytes != size) {
+ LOG(FATAL) << "Failed to send region name; res=" << bytes << " ("
+ << region_server->StrError() << ")";
+ return nullptr;
+ }
+
+ // Receive control sockets.
+ uint64_t control_data;
+ struct iovec iov {
+ &control_data, sizeof(control_data)
+ };
+ cvd::InbandMessageHeader hdr{};
+ hdr.msg_iov = &iov;
+ hdr.msg_iovlen = 1;
+ SharedFD fds[3];
+ bytes = region_server->RecvMsgAndFDs(hdr, 0, &fds);
+ if (bytes != sizeof(control_data)) {
+ LOG(FATAL) << "Failed to complete handshake; res=" << bytes << " ("
+ << region_server->StrError() << ")";
+ return nullptr;
+ }
+ HostRegionControl* rval =
+ new HostRegionControl(region_name, fds[0], fds[1], fds[2]);
+ if (!rval) {
+ return nullptr;
+ }
+ // Search for the region header
+ if (!rval->InitializeRegion()) {
+ // We already logged, so we can just bail out.
+ return nullptr;
+ }
+ return std::shared_ptr<RegionControl>(rval);
+}
diff --git a/host/vsoc/lib/region_view.cpp b/host/vsoc/lib/region_view.cpp
new file mode 100644
index 0000000..975b7cd
--- /dev/null
+++ b/host/vsoc/lib/region_view.cpp
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#include "common/vsoc/lib/region_view.h"
+#include "common/vsoc/lib/region_control.h"
+
+const vsoc_signal_table_layout& vsoc::RegionView::incoming_signal_table() {
+ return control_->region_desc().guest_to_host_signal_table;
+}
+
+// Returns a pointer to the table that will be used to post signals
+const vsoc_signal_table_layout& vsoc::RegionView::outgoing_signal_table() {
+ return control_->region_desc().host_to_guest_signal_table;
+}
diff --git a/tools/bazel.rc b/tools/bazel.rc
new file mode 100644
index 0000000..6b32331
--- /dev/null
+++ b/tools/bazel.rc
@@ -0,0 +1,2 @@
+test --client_env=CC=/usr/bin/clang-3.8 --color=yes --test_output all
+build --client_env=CC=/usr/bin/clang-3.8 --color=yes