You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
564 lines
19 KiB
564 lines
19 KiB
// Copyright 2019 The Android Open Source Project
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT 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-common/address_space_device.h"
|
|
#include "host-common/AddressSpaceService.h"
|
|
#include "host-common/address_space_graphics.h"
|
|
#ifndef AEMU_MIN
|
|
#include "host-common/address_space_host_media.h"
|
|
#endif
|
|
#include "host-common/address_space_host_memory_allocator.h"
|
|
#include "host-common/address_space_shared_slots_host_memory_allocator.h"
|
|
#include "host-common/vm_operations.h"
|
|
|
|
#include "base/Lock.h"
|
|
|
|
#include <map>
|
|
#include <unordered_map>
|
|
#include <memory>
|
|
|
|
using android::base::AutoLock;
|
|
using android::base::Lock;
|
|
using android::base::Stream;
|
|
using android::emulation::asg::AddressSpaceGraphicsContext;
|
|
|
|
using namespace android::emulation;
|
|
|
|
#define AS_DEVICE_DEBUG 0
|
|
|
|
#if AS_DEVICE_DEBUG
|
|
#define AS_DEVICE_DPRINT(fmt,...) fprintf(stderr, "%s:%d " fmt "\n", __func__, __LINE__, ##__VA_ARGS__);
|
|
#else
|
|
#define AS_DEVICE_DPRINT(fmt,...)
|
|
#endif
|
|
|
|
const QAndroidVmOperations* sVmOps = nullptr;
|
|
|
|
namespace {
|
|
|
|
class AddressSpaceDeviceState {
|
|
public:
|
|
AddressSpaceDeviceState() = default;
|
|
~AddressSpaceDeviceState() = default;
|
|
|
|
uint32_t genHandle() {
|
|
AutoLock lock(mContextsLock);
|
|
|
|
auto res = mHandleIndex;
|
|
|
|
if (!res) {
|
|
++res;
|
|
mHandleIndex += 2;
|
|
} else {
|
|
++mHandleIndex;
|
|
}
|
|
|
|
AS_DEVICE_DPRINT("new handle: %u", res);
|
|
return res;
|
|
}
|
|
|
|
void destroyHandle(uint32_t handle) {
|
|
AS_DEVICE_DPRINT("erase handle: %u", handle);
|
|
AutoLock lock(mContextsLock);
|
|
mContexts.erase(handle);
|
|
}
|
|
|
|
void tellPingInfo(uint32_t handle, uint64_t gpa) {
|
|
AutoLock lock(mContextsLock);
|
|
auto& contextDesc = mContexts[handle];
|
|
contextDesc.pingInfo =
|
|
(AddressSpaceDevicePingInfo*)
|
|
sVmOps->physicalMemoryGetAddr(gpa);
|
|
contextDesc.pingInfoGpa = gpa;
|
|
AS_DEVICE_DPRINT("Ping info: gpa 0x%llx @ %p\n", (unsigned long long)gpa,
|
|
contextDesc.pingInfo);
|
|
}
|
|
|
|
void ping(uint32_t handle) {
|
|
AutoLock lock(mContextsLock);
|
|
auto& contextDesc = mContexts[handle];
|
|
AddressSpaceDevicePingInfo* pingInfo = contextDesc.pingInfo;
|
|
|
|
const uint64_t phys_addr = pingInfo->phys_addr;
|
|
|
|
AS_DEVICE_DPRINT(
|
|
"handle %u data 0x%llx -> %p size %llu meta 0x%llx\n", handle,
|
|
(unsigned long long)phys_addr,
|
|
sVmOps->physicalMemoryGetAddr(phys_addr),
|
|
(unsigned long long)pingInfo->size, (unsigned long long)pingInfo->metadata);
|
|
|
|
AddressSpaceDeviceContext *device_context = contextDesc.device_context.get();
|
|
if (device_context) {
|
|
device_context->perform(pingInfo);
|
|
} else {
|
|
// The first ioctl establishes the device type
|
|
const AddressSpaceDeviceType device_type =
|
|
static_cast<AddressSpaceDeviceType>(pingInfo->metadata);
|
|
|
|
contextDesc.device_context = buildAddressSpaceDeviceContext(device_type, phys_addr, false);
|
|
pingInfo->metadata = contextDesc.device_context ? 0 : -1;
|
|
}
|
|
}
|
|
|
|
void pingAtHva(uint32_t handle, AddressSpaceDevicePingInfo* pingInfo) {
|
|
AutoLock lock(mContextsLock);
|
|
auto& contextDesc = mContexts[handle];
|
|
|
|
const uint64_t phys_addr = pingInfo->phys_addr;
|
|
|
|
AS_DEVICE_DPRINT(
|
|
"handle %u data 0x%llx -> %p size %llu meta 0x%llx\n", handle,
|
|
(unsigned long long)phys_addr,
|
|
sVmOps->physicalMemoryGetAddr(phys_addr),
|
|
(unsigned long long)pingInfo->size, (unsigned long long)pingInfo->metadata);
|
|
|
|
AddressSpaceDeviceContext *device_context = contextDesc.device_context.get();
|
|
if (device_context) {
|
|
device_context->perform(pingInfo);
|
|
} else {
|
|
// The first ioctl establishes the device type
|
|
const AddressSpaceDeviceType device_type =
|
|
static_cast<AddressSpaceDeviceType>(pingInfo->metadata);
|
|
|
|
contextDesc.device_context = buildAddressSpaceDeviceContext(device_type, phys_addr, false);
|
|
pingInfo->metadata = contextDesc.device_context ? 0 : -1;
|
|
}
|
|
}
|
|
|
|
void registerDeallocationCallback(uint64_t gpa, void* context, address_space_device_deallocation_callback_t func) {
|
|
AutoLock lock(mContextsLock);
|
|
auto& currentCallbacks = mDeallocationCallbacks[gpa];
|
|
|
|
DeallocationCallbackEntry entry = {
|
|
context,
|
|
func,
|
|
};
|
|
|
|
currentCallbacks.push_back(entry);
|
|
}
|
|
|
|
void runDeallocationCallbacks(uint64_t gpa) {
|
|
AutoLock lock(mContextsLock);
|
|
|
|
auto it = mDeallocationCallbacks.find(gpa);
|
|
if (it == mDeallocationCallbacks.end()) return;
|
|
|
|
auto& callbacks = it->second;
|
|
|
|
for (auto& entry: callbacks) {
|
|
entry.func(entry.context, gpa);
|
|
}
|
|
|
|
mDeallocationCallbacks.erase(gpa);
|
|
}
|
|
|
|
AddressSpaceDeviceContext* handleToContext(uint32_t handle) {
|
|
AutoLock lock(mContextsLock);
|
|
if (mContexts.find(handle) == mContexts.end()) return nullptr;
|
|
|
|
auto& contextDesc = mContexts[handle];
|
|
return contextDesc.device_context.get();
|
|
}
|
|
|
|
uint64_t hostmemRegister(uint64_t hva, uint64_t size, uint32_t register_fixed, uint64_t fixed_id) {
|
|
return sVmOps->hostmemRegister(hva, size, register_fixed, fixed_id);
|
|
}
|
|
|
|
void hostmemUnregister(uint64_t id) {
|
|
sVmOps->hostmemUnregister(id);
|
|
}
|
|
|
|
void save(Stream* stream) const {
|
|
// Pre-save
|
|
for (const auto &kv : mContexts) {
|
|
const AddressSpaceContextDescription &desc = kv.second;
|
|
const AddressSpaceDeviceContext *device_context = desc.device_context.get();
|
|
if (device_context) {
|
|
device_context->preSave();
|
|
}
|
|
}
|
|
|
|
AddressSpaceGraphicsContext::globalStatePreSave();
|
|
|
|
// Save
|
|
AddressSpaceSharedSlotsHostMemoryAllocatorContext::globalStateSave(stream);
|
|
AddressSpaceGraphicsContext::globalStateSave(stream);
|
|
|
|
stream->putBe32(mHandleIndex);
|
|
stream->putBe32(mContexts.size());
|
|
|
|
for (const auto &kv : mContexts) {
|
|
const uint32_t handle = kv.first;
|
|
const AddressSpaceContextDescription &desc = kv.second;
|
|
const AddressSpaceDeviceContext *device_context = desc.device_context.get();
|
|
|
|
stream->putBe32(handle);
|
|
stream->putBe64(desc.pingInfoGpa);
|
|
|
|
if (device_context) {
|
|
stream->putByte(1);
|
|
stream->putBe32(device_context->getDeviceType());
|
|
device_context->save(stream);
|
|
} else {
|
|
stream->putByte(0);
|
|
}
|
|
}
|
|
|
|
// Post save
|
|
|
|
AddressSpaceGraphicsContext::globalStatePostSave();
|
|
|
|
for (const auto &kv : mContexts) {
|
|
const AddressSpaceContextDescription &desc = kv.second;
|
|
const AddressSpaceDeviceContext *device_context = desc.device_context.get();
|
|
if (device_context) {
|
|
device_context->postSave();
|
|
}
|
|
}
|
|
}
|
|
|
|
bool load(Stream* stream) {
|
|
// First destroy all contexts, because
|
|
// this can be done while an emulator is running
|
|
clear();
|
|
|
|
if (!AddressSpaceSharedSlotsHostMemoryAllocatorContext::globalStateLoad(
|
|
stream,
|
|
get_address_space_device_control_ops(),
|
|
get_address_space_device_hw_funcs())) {
|
|
return false;
|
|
}
|
|
|
|
asg::AddressSpaceGraphicsContext::init(get_address_space_device_control_ops());
|
|
|
|
if (!AddressSpaceGraphicsContext::globalStateLoad(
|
|
stream)) {
|
|
return false;
|
|
}
|
|
|
|
const uint32_t handleIndex = stream->getBe32();
|
|
const size_t size = stream->getBe32();
|
|
|
|
Contexts contexts;
|
|
for (size_t i = 0; i < size; ++i) {
|
|
const uint32_t handle = stream->getBe32();
|
|
const uint64_t pingInfoGpa = stream->getBe64();
|
|
|
|
std::unique_ptr<AddressSpaceDeviceContext> context;
|
|
switch (stream->getByte()) {
|
|
case 0:
|
|
break;
|
|
|
|
case 1: {
|
|
const auto device_type =
|
|
static_cast<AddressSpaceDeviceType>(stream->getBe32());
|
|
context = buildAddressSpaceDeviceContext(device_type, pingInfoGpa, true);
|
|
if (!context || !context->load(stream)) {
|
|
return false;
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
auto &desc = contexts[handle];
|
|
desc.pingInfoGpa = pingInfoGpa;
|
|
if (desc.pingInfoGpa == ~0ULL) {
|
|
fprintf(stderr, "%s: warning: restoring hva-only ping\n", __func__);
|
|
} else {
|
|
desc.pingInfo = (AddressSpaceDevicePingInfo*)
|
|
sVmOps->physicalMemoryGetAddr(pingInfoGpa);
|
|
}
|
|
desc.device_context = std::move(context);
|
|
}
|
|
|
|
{
|
|
AutoLock lock(mContextsLock);
|
|
mHandleIndex = handleIndex;
|
|
mContexts = std::move(contexts);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void clear() {
|
|
AutoLock lock(mContextsLock);
|
|
mContexts.clear();
|
|
AddressSpaceSharedSlotsHostMemoryAllocatorContext::globalStateClear();
|
|
auto it = mMemoryMappings.begin();
|
|
std::vector<std::pair<uint64_t, uint64_t>> gpasSizesToErase;
|
|
for (auto it: mMemoryMappings) {
|
|
auto gpa = it.first;
|
|
auto size = it.second.second;
|
|
gpasSizesToErase.push_back({gpa, size});
|
|
}
|
|
for (const auto& gpaSize : gpasSizesToErase) {
|
|
removeMemoryMappingLocked(gpaSize.first, gpaSize.second);
|
|
}
|
|
mMemoryMappings.clear();
|
|
}
|
|
|
|
bool addMemoryMapping(uint64_t gpa, void *ptr, uint64_t size) {
|
|
AutoLock lock(mMemoryMappingsLock);
|
|
return addMemoryMappingLocked(gpa, ptr, size);
|
|
}
|
|
|
|
bool removeMemoryMapping(uint64_t gpa, uint64_t size) {
|
|
AutoLock lock(mMemoryMappingsLock);
|
|
return removeMemoryMappingLocked(gpa, size);
|
|
}
|
|
|
|
void *getHostPtr(uint64_t gpa) const {
|
|
AutoLock lock(mMemoryMappingsLock);
|
|
return getHostPtrLocked(gpa);
|
|
}
|
|
|
|
private:
|
|
mutable Lock mContextsLock;
|
|
uint32_t mHandleIndex = 1;
|
|
typedef std::unordered_map<uint32_t, AddressSpaceContextDescription> Contexts;
|
|
Contexts mContexts;
|
|
|
|
std::unique_ptr<AddressSpaceDeviceContext>
|
|
buildAddressSpaceDeviceContext(const AddressSpaceDeviceType device_type,
|
|
const uint64_t phys_addr,
|
|
bool fromSnapshot) {
|
|
typedef std::unique_ptr<AddressSpaceDeviceContext> DeviceContextPtr;
|
|
|
|
switch (device_type) {
|
|
case AddressSpaceDeviceType::Graphics:
|
|
asg::AddressSpaceGraphicsContext::init(get_address_space_device_control_ops());
|
|
return DeviceContextPtr(new asg::AddressSpaceGraphicsContext(false /* not virtio */, fromSnapshot));
|
|
#ifndef AEMU_MIN
|
|
case AddressSpaceDeviceType::Media:
|
|
AS_DEVICE_DPRINT("allocating media context");
|
|
return DeviceContextPtr(new AddressSpaceHostMediaContext(phys_addr, get_address_space_device_control_ops(), fromSnapshot));
|
|
#endif
|
|
case AddressSpaceDeviceType::Sensors:
|
|
return nullptr;
|
|
case AddressSpaceDeviceType::Power:
|
|
return nullptr;
|
|
case AddressSpaceDeviceType::GenericPipe:
|
|
return nullptr;
|
|
case AddressSpaceDeviceType::HostMemoryAllocator:
|
|
return DeviceContextPtr(new AddressSpaceHostMemoryAllocatorContext(
|
|
get_address_space_device_control_ops()));
|
|
case AddressSpaceDeviceType::SharedSlotsHostMemoryAllocator:
|
|
return DeviceContextPtr(new AddressSpaceSharedSlotsHostMemoryAllocatorContext(
|
|
get_address_space_device_control_ops(),
|
|
get_address_space_device_hw_funcs()));
|
|
|
|
case AddressSpaceDeviceType::VirtioGpuGraphics:
|
|
asg::AddressSpaceGraphicsContext::init(get_address_space_device_control_ops());
|
|
return DeviceContextPtr(new asg::AddressSpaceGraphicsContext(true /* is virtio */, fromSnapshot));
|
|
|
|
default:
|
|
AS_DEVICE_DPRINT("Bad device type");
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
bool addMemoryMappingLocked(uint64_t gpa, void *ptr, uint64_t size) {
|
|
if (mMemoryMappings.insert({gpa, {ptr, size}}).second) {
|
|
sVmOps->mapUserBackedRam(gpa, ptr, size);
|
|
return true;
|
|
} else {
|
|
fprintf(stderr, "%s: failed: hva %p -> gpa [0x%llx 0x%llx]\n", __func__,
|
|
ptr,
|
|
(unsigned long long)gpa,
|
|
(unsigned long long)size);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool removeMemoryMappingLocked(uint64_t gpa, uint64_t size) {
|
|
if (mMemoryMappings.erase(gpa) > 0) {
|
|
sVmOps->unmapUserBackedRam(gpa, size);
|
|
return true;
|
|
} else {
|
|
fprintf(stderr, "%s: failed: gpa [0x%llx 0x%llx]\n", __func__,
|
|
(unsigned long long)gpa,
|
|
(unsigned long long)size);
|
|
*(uint32_t*)(123) = 12;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void *getHostPtrLocked(uint64_t gpa) const {
|
|
auto i = mMemoryMappings.lower_bound(gpa); // i->first >= gpa (or i==end)
|
|
if ((i != mMemoryMappings.end()) && (i->first == gpa)) {
|
|
return i->second.first; // gpa is exactly the beginning of the range
|
|
} else if (i == mMemoryMappings.begin()) {
|
|
return nullptr; // can't '--i', see below
|
|
} else {
|
|
--i;
|
|
|
|
if ((i->first + i->second.second) > gpa) {
|
|
// move the host ptr by +(gpa-base)
|
|
return static_cast<char *>(i->second.first) + (gpa - i->first);
|
|
} else {
|
|
return nullptr; // the range does not cover gpa
|
|
}
|
|
}
|
|
}
|
|
|
|
mutable Lock mMemoryMappingsLock;
|
|
std::map<uint64_t, std::pair<void *, uint64_t>> mMemoryMappings; // do not save/load
|
|
|
|
struct DeallocationCallbackEntry {
|
|
void* context;
|
|
address_space_device_deallocation_callback_t func;
|
|
};
|
|
|
|
std::map<uint64_t, std::vector<DeallocationCallbackEntry>> mDeallocationCallbacks; // do not save/load, users re-register on load
|
|
};
|
|
|
|
static AddressSpaceDeviceState* sAddressSpaceDeviceState() {
|
|
static AddressSpaceDeviceState* s = new AddressSpaceDeviceState;
|
|
return s;
|
|
}
|
|
|
|
static uint32_t sAddressSpaceDeviceGenHandle() {
|
|
return sAddressSpaceDeviceState()->genHandle();
|
|
}
|
|
|
|
static void sAddressSpaceDeviceDestroyHandle(uint32_t handle) {
|
|
sAddressSpaceDeviceState()->destroyHandle(handle);
|
|
}
|
|
|
|
static void sAddressSpaceDeviceTellPingInfo(uint32_t handle, uint64_t gpa) {
|
|
sAddressSpaceDeviceState()->tellPingInfo(handle, gpa);
|
|
}
|
|
|
|
static void sAddressSpaceDevicePing(uint32_t handle) {
|
|
sAddressSpaceDeviceState()->ping(handle);
|
|
}
|
|
|
|
int sAddressSpaceDeviceAddMemoryMapping(uint64_t gpa, void *ptr, uint64_t size) {
|
|
return sAddressSpaceDeviceState()->addMemoryMapping(gpa, ptr, size) ? 1 : 0;
|
|
}
|
|
|
|
int sAddressSpaceDeviceRemoveMemoryMapping(uint64_t gpa, void *ptr, uint64_t size) {
|
|
(void)ptr; // TODO(lfy): remove arg
|
|
return sAddressSpaceDeviceState()->removeMemoryMapping(gpa, size) ? 1 : 0;
|
|
}
|
|
|
|
void* sAddressSpaceDeviceGetHostPtr(uint64_t gpa) {
|
|
return sAddressSpaceDeviceState()->getHostPtr(gpa);
|
|
}
|
|
|
|
static void* sAddressSpaceHandleToContext(uint32_t handle) {
|
|
return (void*)(sAddressSpaceDeviceState()->handleToContext(handle));
|
|
}
|
|
|
|
static void sAddressSpaceDeviceClear() {
|
|
sAddressSpaceDeviceState()->clear();
|
|
}
|
|
|
|
static uint64_t sAddressSpaceDeviceHostmemRegister(uint64_t hva, uint64_t size, uint32_t register_fixed, uint64_t fixed_id) {
|
|
return sAddressSpaceDeviceState()->hostmemRegister(hva, size, register_fixed, fixed_id);
|
|
}
|
|
|
|
static void sAddressSpaceDeviceHostmemUnregister(uint64_t id) {
|
|
sAddressSpaceDeviceState()->hostmemUnregister(id);
|
|
}
|
|
|
|
static void sAddressSpaceDevicePingAtHva(uint32_t handle, void* hva) {
|
|
sAddressSpaceDeviceState()->pingAtHva(
|
|
handle, (AddressSpaceDevicePingInfo*)hva);
|
|
}
|
|
|
|
static void sAddressSpaceDeviceRegisterDeallocationCallback(
|
|
void* context, uint64_t gpa, address_space_device_deallocation_callback_t func) {
|
|
sAddressSpaceDeviceState()->registerDeallocationCallback(gpa, context, func);
|
|
}
|
|
|
|
static void sAddressSpaceDeviceRunDeallocationCallbacks(uint64_t gpa) {
|
|
sAddressSpaceDeviceState()->runDeallocationCallbacks(gpa);
|
|
}
|
|
|
|
static const struct AddressSpaceHwFuncs* sAddressSpaceDeviceControlGetHwFuncs() {
|
|
return get_address_space_device_hw_funcs();
|
|
}
|
|
|
|
|
|
} // namespace
|
|
|
|
extern "C" {
|
|
|
|
static struct address_space_device_control_ops sAddressSpaceDeviceOps = {
|
|
&sAddressSpaceDeviceGenHandle, // gen_handle
|
|
&sAddressSpaceDeviceDestroyHandle, // destroy_handle
|
|
&sAddressSpaceDeviceTellPingInfo, // tell_ping_info
|
|
&sAddressSpaceDevicePing, // ping
|
|
&sAddressSpaceDeviceAddMemoryMapping, // add_memory_mapping
|
|
&sAddressSpaceDeviceRemoveMemoryMapping, // remove_memory_mapping
|
|
&sAddressSpaceDeviceGetHostPtr, // get_host_ptr
|
|
&sAddressSpaceHandleToContext, // handle_to_context
|
|
&sAddressSpaceDeviceClear, // clear
|
|
&sAddressSpaceDeviceHostmemRegister, // hostmem register
|
|
&sAddressSpaceDeviceHostmemUnregister, // hostmem unregister
|
|
&sAddressSpaceDevicePingAtHva, // ping_at_hva
|
|
&sAddressSpaceDeviceRegisterDeallocationCallback, // register_deallocation_callback
|
|
&sAddressSpaceDeviceRunDeallocationCallbacks, // run_deallocation_callbacks
|
|
&sAddressSpaceDeviceControlGetHwFuncs, // control_get_hw_funcs
|
|
};
|
|
|
|
struct address_space_device_control_ops* get_address_space_device_control_ops(void) {
|
|
return &sAddressSpaceDeviceOps;
|
|
}
|
|
|
|
static const struct AddressSpaceHwFuncs* sAddressSpaceHwFuncs = nullptr;
|
|
|
|
const struct AddressSpaceHwFuncs* address_space_set_hw_funcs(
|
|
const AddressSpaceHwFuncs* hwFuncs) {
|
|
const AddressSpaceHwFuncs* result = sAddressSpaceHwFuncs;
|
|
sAddressSpaceHwFuncs = hwFuncs;
|
|
return result;
|
|
}
|
|
|
|
const struct AddressSpaceHwFuncs* get_address_space_device_hw_funcs(void) {
|
|
return sAddressSpaceHwFuncs;
|
|
}
|
|
|
|
void address_space_set_vm_operations(const QAndroidVmOperations* vmops) {
|
|
sVmOps = vmops;
|
|
}
|
|
|
|
} // extern "C"
|
|
|
|
namespace android {
|
|
namespace emulation {
|
|
|
|
void goldfish_address_space_set_vm_operations(const QAndroidVmOperations* vmops) {
|
|
sVmOps = vmops;
|
|
}
|
|
|
|
const QAndroidVmOperations* goldfish_address_space_get_vm_operations() {
|
|
return sVmOps;
|
|
}
|
|
|
|
int goldfish_address_space_memory_state_load(android::base::Stream *stream) {
|
|
return sAddressSpaceDeviceState()->load(stream) ? 0 : 1;
|
|
}
|
|
|
|
int goldfish_address_space_memory_state_save(android::base::Stream *stream) {
|
|
sAddressSpaceDeviceState()->save(stream);
|
|
return 0;
|
|
}
|
|
|
|
} // namespace emulation
|
|
} // namespace android
|