#include "TerminalDevice.h" /* * Copyright 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT 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_TAG "CameraTestHelpers" #include #include "PhoneDevice.h" #include #include #include #include #include // #include // #include #include #include #include #include #include #include #include namespace fs = std::filesystem; extern bool GetJniEnv(JavaVM *vm, JNIEnv **env, bool& didAttachThread); #define WAKELOCK_NAME "NDK_WK_" // This value is 2 ^ 18 - 1, and is used to clamp the RGB values before their // ranges // are normalized to eight bits. static const int kMaxChannelValue = 262143; static long getFreeMemoryImpl(const char* const sums[], const size_t sumsLen[], size_t num) { int fd = open("/proc/meminfo", O_RDONLY | O_CLOEXEC); if (fd < 0) { ALOGW("Unable to open /proc/meminfo"); return -1; } char buffer[2048]; const int len = read(fd, buffer, sizeof(buffer)-1); close(fd); if (len < 0) { ALOGW("Unable to read /proc/meminfo"); return -1; } buffer[len] = 0; size_t numFound = 0; jlong mem = 0; char* p = buffer; while (*p && numFound < num) { int i = 0; while (sums[i]) { if (strncmp(p, sums[i], sumsLen[i]) == 0) { p += sumsLen[i]; while (*p == ' ') p++; char* num = p; while (*p >= '0' && *p <= '9') p++; if (*p != 0) { *p = 0; p++; if (*p == 0) p--; } mem += atoll(num) * 1024; numFound++; break; } i++; } p++; } return numFound > 0 ? mem : -1; } static jlong android_os_Process_getFreeMemory() { static const char* const sums[] = { "MemFree:", "Cached:", NULL }; static const size_t sumsLen[] = { strlen("MemFree:"), strlen("Cached:"), 0 }; return getFreeMemoryImpl(sums, sumsLen, 2); } static jlong android_os_Process_getTotalMemory() { static const char* const sums[] = { "MemTotal:", NULL }; static const size_t sumsLen[] = { strlen("MemTotal:"), 0 }; return getFreeMemoryImpl(sums, sumsLen, 1); } static inline uint32_t YUV2RGB(int nY, int nU, int nV) { nY -= 16; nU -= 128; nV -= 128; if (nY < 0) nY = 0; // This is the floating point equivalent. We do the conversion in integer // because some Android devices do not have floating point in hardware. // nR = (int)(1.164 * nY + 1.596 * nV); // nG = (int)(1.164 * nY - 0.813 * nV - 0.391 * nU); // nB = (int)(1.164 * nY + 2.018 * nU); int nR = (int)(1192 * nY + 1634 * nV); int nG = (int)(1192 * nY - 833 * nV - 400 * nU); int nB = (int)(1192 * nY + 2066 * nU); nR = std::min(kMaxChannelValue, std::max(0, nR)); nG = std::min(kMaxChannelValue, std::max(0, nG)); nB = std::min(kMaxChannelValue, std::max(0, nB)); nR = (nR >> 10) & 0xff; nG = (nG >> 10) & 0xff; nB = (nB >> 10) & 0xff; return 0xff000000 | (nR << 16) | (nG << 8) | nB; } CPhoneDevice::CPhoneCamera::CPhoneCamera(CPhoneDevice* dev, int32_t width, int32_t height) : NdkCamera(width, height), m_dev(dev) { } bool CPhoneDevice::CPhoneCamera::on_image(const cv::Mat& rgb) { if (m_dev != NULL) { return m_dev->OnImageReady(rgb); } return false; } void CPhoneDevice::CPhoneCamera::on_error(const std::string& msg) { if (m_dev != NULL) { m_dev->onError(msg); } } CPhoneDevice::CPhoneDevice(JavaVM* vm, jobject service) { mCamera = NULL; m_listener = NULL; mHeartbeatStartTime = 0; mHeartbeatDuration = 0; m_javaService = NULL; m_vm = vm; JNIEnv* env = NULL; bool didAttachThread = false; bool res = GetJniEnv(m_vm, &env, didAttachThread); if (!res) { ALOGE("Failed to get JNI Env"); } m_javaService = env->NewGlobalRef(service); jclass classService = env->GetObjectClass(m_javaService); mRegisterTimerMid = env->GetMethodID(classService, "registerTimer", "(JIJ)Z"); mRegisterHeartbeatMid = env->GetMethodID(classService, "registerHeartbeatTimer", "(I)V"); mUnregisterTimerMid = env->GetMethodID(classService, "unregisterTimer", "(J)Z"); mUpdateTimeMid = env->GetMethodID(classService, "updateTime", "(J)Z"); mRequestWakelockMid = env->GetMethodID(classService, "requestWakelock", "(Ljava/lang/String;J)V"); mReleaseWakelockMid = env->GetMethodID(classService, "releaseWakelock", "(Ljava/lang/String;)V"); mGetSystemInfoMid = env->GetMethodID(classService, "getSystemInfo", "()Ljava/lang/String;"); mRebootMid = env->GetMethodID(classService, "reboot", "(I)V"); mEnableGpsMid = env->GetMethodID(classService, "enableGps", "(Z)V"); mRequestPositionMid = env->GetMethodID(classService, "requestPosition", "()Z"); env->DeleteLocalRef(classService); if (didAttachThread) { vm->DetachCurrentThread(); } m_timerUidFeed = time(NULL) * 1000; m_wakelockIdFeed = (unsigned long)m_timerUidFeed; } CPhoneDevice::~CPhoneDevice() { JNIEnv* env = NULL; bool didAttachThread = false; bool res = GetJniEnv(m_vm, &env, didAttachThread); if (!res) { ALOGE("Failed to get JNI Env"); } env->DeleteGlobalRef(m_javaService); if (didAttachThread) { m_vm->DetachCurrentThread(); } m_javaService = NULL; } void CPhoneDevice::SetListener(IListener* listener) { m_listener = listener; } bool CPhoneDevice::UpdateTime(time_t ts) { JNIEnv* env = NULL; jboolean ret = JNI_FALSE; bool didAttachThread = false; bool res = GetJniEnv(m_vm, &env, didAttachThread); if (!res) { ALOGE("Failed to get JNI Env"); } jlong timeInMillis = ((jlong)ts) * 1000; ret = env->CallBooleanMethod(m_javaService, mUpdateTimeMid, timeInMillis); if (didAttachThread) { m_vm->DetachCurrentThread(); } return (ret == JNI_TRUE); } bool CPhoneDevice::UpdateSchedules() { return true; } bool CPhoneDevice::QuerySystemProperties(std::map& properties) { char value[PROP_VALUE_MAX] = { 0 }; std::map powerInfo; for (std::map::iterator it = properties.begin(); it != properties.end(); ++it) { if (it->first == PROP_EQUIP_NAME) { __system_property_get("ro.product.name", value); it->second = value; } else if (it->first == PROP_MODEL) { __system_property_get("ro.product.model", value); it->second = value; } else if (it->first == PROP_BS_MANU) { __system_property_get("ro.product.manufacturer", value); it->second = value; } else if (it->first == PROP_VERSION) { __system_property_get("ro.build.version.release", value); it->second = value; } else if (it->first == PROP_PROD_DATE) { __system_property_get("ro.build.date.utc", value); it->second = value; } else if (it->first == PROP_SN) { __system_property_get("ro.serialno", value); it->second = value; } else if (it->first == PROP_IMEI) { __system_property_get("phone.imei", value); it->second = value; } else if (it->first == PROP_OPERATION_TEMP) { it->second = QueryCpuTemperature(); } else if (it->first == PROP_BS_ID) { it->second = "SHXY"; } else if (it->first == PROP_FREE_ROM) { fs::space_info si = fs::space("/data"); it->second = std::to_string(si.available); } else if (it->first == PROP_TOTAL_ROM) { fs::space_info si = fs::space("/data"); it->second = std::to_string(si.capacity); } else if (startsWith(it->first, PROP_JAVA_PREFIX)) { if (powerInfo.empty()) { QueryPowerInfo(powerInfo); } auto it2 = powerInfo.find(it->first); if (it2 != powerInfo.cend()) { it->second = it2->second; } } } // __system_property_get("ro.telephony.default_network", value); return true; } std::string CPhoneDevice::QueryCpuTemperature() { // /sys/devices/virtual/thermal/thermal_zone0/temp std::vector data; if (readFile("/sys/devices/virtual/thermal/thermal_zone0/temp", data) && !data.empty()) { data.push_back(0); int temp = atoi((const char*)(&data[0])); return std::to_string(temp / 1000); } return ""; } void CPhoneDevice::QueryPowerInfo(std::map& powerInfo) { JNIEnv* env = NULL; jboolean ret = JNI_FALSE; bool didAttachThread = false; bool res = GetJniEnv(m_vm, &env, didAttachThread); if (!res) { ALOGE("Failed to get JNI Env"); } jobject jobj = env->CallObjectMethod(m_javaService, mGetSystemInfoMid); std::string str = jstring2string(env, (jstring)jobj); if (didAttachThread) { m_vm->DetachCurrentThread(); } if (!str.empty()) { std::map queries = parseQuery(str); powerInfo.swap(queries); } } bool CPhoneDevice::GetNextScheduleItem(uint32_t tsBasedZero, uint32_t scheduleTime, vector& items) { return false; } bool CPhoneDevice::Reboot(int resetType) { JNIEnv* env = NULL; bool didAttachThread = false; bool res = GetJniEnv(m_vm, &env, didAttachThread); if (!res) { ALOGE("Failed to get JNI Env"); } env->CallVoidMethod(m_javaService, mRebootMid, resetType); if (didAttachThread) { m_vm->DetachCurrentThread(); } return true; } bool CPhoneDevice::EnableGPS(bool enabled) { JNIEnv* env = NULL; bool didAttachThread = false; bool res = GetJniEnv(m_vm, &env, didAttachThread); if (!res) { ALOGE("Failed to get JNI Env"); return false; } jboolean jenabled = enabled ? JNI_TRUE : JNI_FALSE; env->CallVoidMethod(m_javaService, mEnableGpsMid, jenabled); if (didAttachThread) { m_vm->DetachCurrentThread(); } return true; } bool CPhoneDevice::RequestPosition() { JNIEnv* env = NULL; bool didAttachThread = false; bool res = GetJniEnv(m_vm, &env, didAttachThread); if (!res) { ALOGE("Failed to get JNI Env"); return false; } jboolean ret = env->CallBooleanMethod(m_javaService, mRequestPositionMid); if (didAttachThread) { m_vm->DetachCurrentThread(); } return (ret == JNI_TRUE); } IDevice::timer_uid_t CPhoneDevice::RegisterTimer(unsigned int timerType, unsigned int timeout, unsigned long times/* = 0*/) { IDevice::timer_uid_t uid = m_timerUidFeed.fetch_add(1); ALOGI("NDK RegTimer: uid=%lld Type=%u timeout=%u", uid, timerType, timeout); JNIEnv* env = NULL; bool didAttachThread = false; bool res = GetJniEnv(m_vm, &env, didAttachThread); if (!res) { ALOGE("Failed to get JNI Env"); return 0; } jboolean ret = env->CallBooleanMethod(m_javaService, mRegisterTimerMid, (jlong)uid, (jint)timeout, (jlong)times); if (didAttachThread) { m_vm->DetachCurrentThread(); } if (ret == JNI_TRUE) { unsigned long val = timerType; mTimers.insert(mTimers.end(), std::pair(uid, val)); return uid; } return 0; } bool CPhoneDevice::UnregisterTimer(IDevice::timer_uid_t uid) { jboolean ret = JNI_FALSE; JNIEnv* env = NULL; bool didAttachThread = false; bool res = GetJniEnv(m_vm, &env, didAttachThread); if (!res) { ALOGE("Failed to get JNI Env"); return false; } ret = env->CallBooleanMethod(m_javaService, mUnregisterTimerMid, (jlong)uid); if (didAttachThread) { m_vm->DetachCurrentThread(); } if (ret == JNI_TRUE) { mTimers.erase(uid); return true; } return false; } unsigned long CPhoneDevice::RequestWakelock(unsigned long timeout) { unsigned long wakelockId = m_wakelockIdFeed.fetch_add(1); std::string name = WAKELOCK_NAME; name += to_string(wakelockId); // ALOGI("RequestWakelock=%lld",wakelockId); jboolean ret = JNI_FALSE; JNIEnv* env = NULL; bool didAttachThread = false; bool res = GetJniEnv(m_vm, &env, didAttachThread); if (!res) { ALOGE("Failed to get JNI Env"); return 0; } jstring jname = env->NewStringUTF(name.c_str()); jlong jtimeout = (jlong)timeout; env->CallVoidMethod(m_javaService, mRequestWakelockMid, jname, jtimeout); // env->ReleaseStringUTFChars(jname, name.c_str()); env->DeleteLocalRef(jname); if (didAttachThread) { m_vm->DetachCurrentThread(); } return wakelockId; } bool CPhoneDevice::ReleaseWakelock(unsigned long wakelock) { // ALOGI("ReleaseWakelock=%lld", wakelock); std::string name = WAKELOCK_NAME; name += to_string(wakelock); jboolean ret = JNI_FALSE; JNIEnv* env = NULL; bool didAttachThread = false; bool res = GetJniEnv(m_vm, &env, didAttachThread); if (!res) { ALOGE("Failed to get JNI Env"); return false; } jstring jname = env->NewStringUTF(name.c_str()); env->CallVoidMethod(m_javaService, mReleaseWakelockMid, jname); env->DeleteLocalRef(jname); // env->ReleaseStringUTFChars(jname, name.c_str()); if (didAttachThread) { m_vm->DetachCurrentThread(); } return true; } bool CPhoneDevice::FireTimer(timer_uid_t uid, unsigned long times) { std::map::iterator it = mTimers.find(uid); if (it == mTimers.end()) { return false; } unsigned long timerType = it->second & 0xFFFFFFFF; unsigned long ntimes = (it->second & 0xFFFFFFFF00000000) >> 32; ntimes++; if (timerType != 100) { int aa = 0; } it->second = timerType | (ntimes << 32); if (m_listener == NULL) { return false; } m_listener->OnTimeout(uid, timerType, ntimes); return true; } IDevice::timer_uid_t CPhoneDevice::RegisterHeartbeat(unsigned int timerType, unsigned int timeout) { mHeartbeatStartTime = time(NULL); mHeartbeatDuration = timeout; IDevice::timer_uid_t uid = m_timerUidFeed.fetch_add(1); jboolean ret = JNI_FALSE; JNIEnv* env = NULL; bool didAttachThread = false; bool res = GetJniEnv(m_vm, &env, didAttachThread); if (!res) { ALOGE("Failed to get JNI Env"); return 0; } if (didAttachThread) { env->CallVoidMethod(m_javaService, mRegisterHeartbeatMid, (jint)timeout); m_vm->DetachCurrentThread(); } return uid; } bool CPhoneDevice::TakePhoto(const IDevice::PHOTO_INFO& photoInfo, const vector& osds, const std::string& path) { if (mCamera != NULL) { delete mCamera; mCamera = NULL; } ALOGI("TAKE_PHOTO: CH=%u PR=%u\n", (unsigned int)photoInfo.channel, (unsigned int)photoInfo.preset); mPhotoInfo = photoInfo; mPath = path; mOsds = osds; mCamera = new CPhoneCamera(this, photoInfo.width, photoInfo.height); if (mCamera->open(to_string(photoInfo.channel - 1).c_str()) == 0) { } else { delete mCamera; mCamera = NULL; return false; } return true; } bool CPhoneDevice::CloseCamera() { if (mCamera != NULL) { auto camera = mCamera; mCamera = NULL; camera->close(); delete camera; } return true; } void CPhoneDevice::CloseCamera2(CPhoneDevice::CPhoneCamera* camera) { std::this_thread::sleep_for(std::chrono::milliseconds(16)); camera->close(); delete camera; } bool CPhoneDevice::OnImageReady(const cv::Mat& mat) { mPhotoInfo.photoTime = time(NULL); int baseline = 0; cv::Size textSize, textSize2; double fontScale = 1; // base 1024 double height = mat.size().height; double width = mat.size().width; double ratio = std::min(height / 1024, width / 1920); fontScale = fontScale * ratio; int thickness2 = 1 * ratio; if (thickness2 == 0) thickness2 = 1; int thickness1 = thickness2 + 1; cv::Scalar scalar1(0, 0, 0); // black cv::Scalar scalar2(255, 255, 255); // white cv::Point pt1, pt2; for (vector::const_iterator it = mOsds.cbegin(); it != mOsds.cend(); ++it) { textSize = cv::getTextSize(it->text, cv::FONT_HERSHEY_COMPLEX, fontScale, thickness1, &baseline); textSize2 = cv::getTextSize(it->text, cv::FONT_HERSHEY_COMPLEX, fontScale, thickness2, &baseline); ALOGE("putText: w1=%d w2=%d tn=%d/%d", textSize.width, textSize2.width, thickness1, thickness2); if (it->alignment == OSD_ALIGNMENT_TOP_LEFT) { pt1.x = it->x * ratio; pt1.y = it->y * ratio + textSize.height; } else if (it->alignment == OSD_ALIGNMENT_TOP_RIGHT) { pt1.x = width - textSize.width - it->x * ratio; pt1.y= it->y * ratio + textSize.height; } else if (it->alignment == OSD_ALIGNMENT_BOTTOM_RIGHT) { pt1.x = width - textSize.width - it->x * ratio; pt1.y = height - it->y * ratio; } else if (it->alignment == OSD_ALIGNMENT_BOTTOM_LEFT) { pt1.x = it->x * ratio; pt1.y = height - it->y * ratio; } cv::putText(mat, it->text, pt1, cv::FONT_HERSHEY_COMPLEX, fontScale, scalar1, thickness1,cv::LINE_AA); cv::putText(mat, it->text, pt1, cv::FONT_HERSHEY_COMPLEX, fontScale, scalar2, thickness2,cv::LINE_AA); } vector compression_params; compression_params.push_back(cv::IMWRITE_JPEG_QUALITY); compression_params.push_back(mPhotoInfo.quality); bool res = false; std::string fullPath = mPath + CTerminal::BuildPhotoFileName(mPhotoInfo); if (!std::filesystem::exists(std::filesystem::path(fullPath))) { bool res = cv::imwrite(fullPath.c_str(), mat, compression_params); if (!res) { ALOGE("Failed to write photo: %s", fullPath.c_str()); } else { ALOGI("Succeeded to write photo: %s", fullPath.c_str()); } TakePhotoCb(res, mPhotoInfo, fullPath, time(NULL)); } else { ALOGI("Photo file exists: %s", mPath.c_str()); } CPhoneCamera* pCamera = mCamera; mCamera = NULL; std::thread closeThread(&CPhoneDevice::CloseCamera2, pCamera); closeThread.detach(); return res; } void CPhoneDevice::onError(const std::string& msg) { // XFLOG(XFLOG_SEVERITY_ERROR, "Failed to Take Photo: %s", msg.c_str()); ALOGE("Failed to Take Photo: %s", msg.c_str()); CPhoneCamera* pCamera = mCamera; mCamera = NULL; TakePhotoCb(false, mPhotoInfo, mPath, 0); std::thread closeThread(&CPhoneDevice::CloseCamera2, pCamera); closeThread.detach(); } std::string CPhoneDevice::GetFileName() const { return mPath; } void CPhoneDevice::UpdatePosition(double lon, double lat, time_t ts) { if (m_listener != NULL) { return m_listener->OnPositionDataArrived(lon, lat, ts); } }