diff --git a/app/build.gradle b/app/build.gradle index 4d13d131..d15b4363 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -2,22 +2,28 @@ plugins { id 'com.android.application' } +def AppVersionName = "1.0.0" +def AppVersionCode = ((1 * 100 + 1) * 100 + 0) * 10 + 0 + android { namespace 'com.xinyingpower.microphoto' compileSdk 33 defaultConfig { applicationId "com.xinyingpower.microphoto" - minSdk 26 - targetSdk 33 - versionCode 1 - versionName "1.0" + minSdk 28 + //noinspection ExpiredTargetSdkVersion + targetSdk 28 + versionCode AppVersionCode + versionName AppVersionName - testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' externalNativeBuild { cmake { - cppFlags '-std=c++17' + cppFlags '-std=c++17 -frtti -fexceptions -Wno-error=format-security' abiFilters 'arm64-v8a' + // setAbiFilters(['arm64-v8a']) + arguments "-DOpenCV_DIR=" + opencvsdk + "/sdk/native/jni" } } } @@ -48,19 +54,23 @@ android { buildFeatures { viewBinding true } + + ndkVersion '25.1.8937393' } dependencies { - implementation 'com.android.support:appcompat-v7:28.0.0' - implementation 'com.android.support.constraint:constraint-layout:2.0.4' + // implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation 'androidx.appcompat:appcompat:1.0.0' + implementation 'androidx.constraintlayout:constraintlayout:1.1.3' testImplementation 'junit:junit:4.13.2' - androidTestImplementation 'com.android.support.test:runner:1.0.2' - androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' + androidTestImplementation 'androidx.test.ext:junit:1.1.1' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0' implementation 'com.google.code.gson:gson:2.10.1' implementation 'com.googlecode.mp4parser:isoparser:1.1.21' // implementation 'com.tencent.mars:mars-core:1.2.5' // implementation 'com.tencent:mmkv-static:1.3.0' + implementation project(path: ':opencv') } \ No newline at end of file diff --git a/app/src/androidTest/java/com/xinyingpower/microphoto/ExampleInstrumentedTest.java b/app/src/androidTest/java/com/xinyingpower/microphoto/ExampleInstrumentedTest.java index 014b4a5e..dc0f7e6d 100644 --- a/app/src/androidTest/java/com/xinyingpower/microphoto/ExampleInstrumentedTest.java +++ b/app/src/androidTest/java/com/xinyingpower/microphoto/ExampleInstrumentedTest.java @@ -1,8 +1,8 @@ package com.xinyingpower.microphoto; import android.content.Context; -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 7e08658b..4c4f74f6 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,93 +2,34 @@ - - - - - + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + - + + + tools:targetApi="28"> - - - + ::const_iterator it2 = it->second.cbegin(); it2 != it->second.cend(); ++it2) { - val = ((unsigned long)channel << 40); - // preset - val |= ((unsigned long)((*it2) & 0xFF)) << 32; // time - val |= (unsigned long)(((*it2) & 0xFFFFFF00) >> 8); + val = ((unsigned long)((*it2) & 0xFFFFFF00)) << 24; + // channel + val |= ((unsigned long)channel) << 16; + // preset + val |= ((unsigned long)((*it2) & 0xFF)) << 8; + dataArray.push_back((jlong)val); } } diff --git a/app/src/main/cpp/PhoneDevice.cpp b/app/src/main/cpp/PhoneDevice.cpp index 289f8600..73b14c6f 100644 --- a/app/src/main/cpp/PhoneDevice.cpp +++ b/app/src/main/cpp/PhoneDevice.cpp @@ -21,14 +21,61 @@ #include "PhoneDevice.h" #include "TermClient.h" +#include +#include +#include +#include +#include + +#include +#include +#include + #include #define ALOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) #define ALOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__) #define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) +#define ASSERT(cond, fmt, ...) \ + if (!(cond)) { \ + __android_log_assert(#cond, LOG_TAG, fmt, ##__VA_ARGS__); \ + } extern bool GetJniEnv(JavaVM *vm, JNIEnv **env); + +// 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 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::CPhoneDevice(JavaVM* vm, jobject service) { m_vm = vm; @@ -38,6 +85,7 @@ CPhoneDevice::CPhoneDevice(JavaVM* vm, jobject service) jclass classService = env->GetObjectClass(m_javaService); mRegisterTimerMid = env->GetMethodID(classService, "registerTimer", "(JI)Z"); + mRegisterHeartbeatMid = env->GetMethodID(classService, "registerHeartbeatTimer", "(I)V"); mUnregisterTimerMid = env->GetMethodID(classService, "unregisterTimer", "(J)Z"); env->DeleteLocalRef(classService); @@ -48,6 +96,7 @@ CPhoneDevice::CPhoneDevice(JavaVM* vm, jobject service) } m_timerUidFeed = time(NULL); + presentRotation_ = 0; } CPhoneDevice::~CPhoneDevice() @@ -67,6 +116,11 @@ void CPhoneDevice::SetListener(IListener* listener) m_listener = listener; } +bool CPhoneDevice::Reboot() +{ + return false; +} + IDevice::timer_uid_t CPhoneDevice::registerTimer(unsigned int timerType, unsigned int timeout) { IDevice::timer_uid_t uid = m_timerUidFeed.fetch_add(1); @@ -140,7 +194,18 @@ bool CPhoneDevice::FireTimer(timer_uid_t uid) IDevice::timer_uid_t CPhoneDevice::RegisterHeartbeat(unsigned int timerType, unsigned int timeout) { - return registerTimer(timerType, timeout); + IDevice::timer_uid_t uid = m_timerUidFeed.fetch_add(1); + + JNIEnv* env = NULL; + jboolean ret = JNI_FALSE; + bool attached = GetJniEnv(m_vm, &env); + if (attached) + { + env->CallVoidMethod(m_javaService, mRegisterHeartbeatMid, (jint)timeout); + m_vm->DetachCurrentThread(); + } + + return uid; } bool CPhoneDevice::TakePhoto(const IDevice::PHOTO_INFO& photoInfo, const string& path) @@ -149,6 +214,10 @@ bool CPhoneDevice::TakePhoto(const IDevice::PHOTO_INFO& photoInfo, const string& mPhotoInfo = photoInfo; mPath = path; + LOGE("Image Buffer Size: %d", photoInfo.width * photoInfo.height * 4); + imageBuffer_ = (uint8_t*)malloc(photoInfo.width * photoInfo.height * 4); + ASSERT(imageBuffer_ != nullptr, "Failed to allocate imageBuffer_"); + int cameraId = (int)photoInfo.channel - 1; ACameraIdList *cameraIdList = NULL; @@ -199,7 +268,7 @@ bool CPhoneDevice::TakePhoto(const IDevice::PHOTO_INFO& photoInfo, const string& LOGI("Failed to open camera device (id: %s)\n", selectedCameraId); } - camera_status = ACameraDevice_createCaptureRequest(cameraDevice, TEMPLATE_PREVIEW, + camera_status = ACameraDevice_createCaptureRequest(cameraDevice, TEMPLATE_STILL_CAPTURE/*TEMPLATE_PREVIEW*/, &captureRequest); if (camera_status != ACAMERA_OK) { @@ -218,7 +287,7 @@ bool CPhoneDevice::TakePhoto(const IDevice::PHOTO_INFO& photoInfo, const string& media_status_t status; // status = AImageReader_new(1920, 1080, AIMAGE_FORMAT_YUV_420_888, 5, &mAImageReader); - status = AImageReader_new(1920, 1080, AIMAGE_FORMAT_JPEG, 5, &mAImageReader); + status = AImageReader_new(photoInfo.width, photoInfo.height, AIMAGE_FORMAT_YUV_420_888/*AIMAGE_FORMAT_JPEG*/, 5, &mAImageReader); if (status != AMEDIA_OK) { LOGI("AImageReader_new error\n"); @@ -273,11 +342,65 @@ ACameraCaptureSession_stateCallbacks* CPhoneDevice::GetSessionListener() void CPhoneDevice::ImageCallback(AImageReader *reader) { + bool res = false; AImage *image = nullptr; media_status_t status = AImageReader_acquireNextImage(reader, &image); if (status == AMEDIA_OK && image) { - bool res = WriteFile(image); + AImageCropRect srcRect; + AImage_getCropRect(image, &srcRect); + int32_t width = srcRect.right - srcRect.left; + int32_t height = srcRect.bottom - srcRect.top; + + uint8_t *yPixel = nullptr; + uint8_t *uPixel = nullptr; + uint8_t *vPixel = nullptr; + + int32_t yLen = 0; + int32_t uLen = 0; + int32_t vLen = 0; + + cv::Mat _yuv_rgb_img, _yuv_gray_img; + + AImage_getPlaneData(image, 0, &yPixel, &yLen); + AImage_getPlaneData(image, 1, &uPixel, &uLen); + AImage_getPlaneData(image, 2, &vPixel, &vLen); + + uint8_t * data = new uint8_t[yLen + vLen + uLen]; + memcpy(data, yPixel, yLen); + memcpy(data+yLen, vPixel, vLen); + memcpy(data+yLen+vLen, uPixel, uLen); + + cv::Mat mYUV = cv::Mat(height * 1.5, width, CV_8UC1, data); + + cv::cvtColor(mYUV, _yuv_rgb_img, cv::COLOR_YUV2RGB_NV21, 3); + + // cv::Mat mYUV = cv::Mat(height, yStride, CV_8UC4, data); + + cv::cvtColor(mYUV, _yuv_rgb_img, cv::COLOR_YUV2RGB_NV21, 3); + + cv::rotate(_yuv_rgb_img, _yuv_rgb_img, cv::ROTATE_90_CLOCKWISE); + + // cv::Mat mat = cv::Mat(buffer.height, buffer.stride, CV_8UC4, buffer.bits); + + const char *str = "OSD"; + putText(_yuv_rgb_img, str, cv::Point(50, 50), cv::FONT_HERSHEY_COMPLEX, 1, cv::Scalar(0, 0, 0), 4,cv::LINE_AA); + putText(_yuv_rgb_img, str, cv::Point(50, 50), cv::FONT_HERSHEY_COMPLEX, 1, cv::Scalar(255, 255, 255), 2,cv::LINE_AA); + + vector compression_params; + compression_params.push_back(cv::IMWRITE_JPEG_QUALITY); + compression_params.push_back(75); + + res = cv::imwrite(mPath.c_str(), _yuv_rgb_img, compression_params); + + // ANativeWindow_unlockAndPost(theNativeWindow); + + if (res) + { + int aa = 0; + } + + // bool res = WriteFile(image); AImage_delete(image); // delete pThis; @@ -319,7 +442,7 @@ bool CPhoneDevice::WriteFile(AImage *image) fwrite(data, 1, len, file); fclose(file); - LOGE("Capture: %s", path.c_str()); + LOGI("Capture: %s", path.c_str()); res = true; @@ -342,6 +465,220 @@ std::string CPhoneDevice::GetFileName() const { return mPath; } + +/** + * Convert yuv image inside AImage into ANativeWindow_Buffer + * ANativeWindow_Buffer format is guaranteed to be + * WINDOW_FORMAT_RGBX_8888 + * WINDOW_FORMAT_RGBA_8888 + * @param buf a {@link ANativeWindow_Buffer } instance, destination of + * image conversion + * @param image a {@link AImage} instance, source of image conversion. + * it will be deleted via {@link AImage_delete} + */ +bool CPhoneDevice::DisplayImage(ANativeWindow_Buffer *buf, AImage *image) { + ASSERT(buf->format == WINDOW_FORMAT_RGBX_8888 || + buf->format == WINDOW_FORMAT_RGBA_8888, + "Not supported buffer format"); + + int32_t srcFormat = -1; + AImage_getFormat(image, &srcFormat); + ASSERT(AIMAGE_FORMAT_YUV_420_888 == srcFormat, "Failed to get format"); + int32_t srcPlanes = 0; + AImage_getNumberOfPlanes(image, &srcPlanes); + ASSERT(srcPlanes == 3, "Is not 3 planes"); + + switch (presentRotation_) { + case 0: + PresentImage(buf, image); + break; + case 90: + PresentImage90(buf, image); + break; + case 180: + PresentImage180(buf, image); + break; + case 270: + PresentImage270(buf, image); + break; + default: + ASSERT(0, "NOT recognized display rotation: %d", presentRotation_); + } + + AImage_delete(image); + image = nullptr; + + return true; +} + + +/* + * PresentImage() + * Converting yuv to RGB + * No rotation: (x,y) --> (x, y) + * Refer to: + * https://mathbits.com/MathBits/TISection/Geometry/Transformations2.htm + */ +void CPhoneDevice::PresentImage(ANativeWindow_Buffer *buf, AImage *image) { + AImageCropRect srcRect; + AImage_getCropRect(image, &srcRect); + + AImage_getPlaneRowStride(image, 0, &yStride); + AImage_getPlaneRowStride(image, 1, &uvStride); + yPixel = imageBuffer_; + AImage_getPlaneData(image, 0, &yPixel, &yLen); + vPixel = imageBuffer_ + yLen; + AImage_getPlaneData(image, 1, &vPixel, &vLen); + uPixel = imageBuffer_ + yLen + vLen; + AImage_getPlaneData(image, 2, &uPixel, &uLen); + AImage_getPlanePixelStride(image, 1, &uvPixelStride); + + int32_t height = std::min(buf->height, (srcRect.bottom - srcRect.top)); + int32_t width = std::min(buf->width, (srcRect.right - srcRect.left)); + + uint32_t *out = static_cast(buf->bits); + + for (int32_t y = 0; y < height; y++) { + const uint8_t *pY = yPixel + yStride * (y + srcRect.top) + srcRect.left; + + int32_t uv_row_start = uvStride * ((y + srcRect.top) >> 1); + const uint8_t *pU = uPixel + uv_row_start + (srcRect.left >> 1); + const uint8_t *pV = vPixel + uv_row_start + (srcRect.left >> 1); + + for (int32_t x = 0; x < width; x++) { + const int32_t uv_offset = (x >> 1) * uvPixelStride; + out[x] = YUV2RGB(pY[x], pU[uv_offset], pV[uv_offset]); + } + out += buf->stride; + } +} + +/* + * PresentImage90() + * Converting YUV to RGB + * Rotation image anti-clockwise 90 degree -- (x, y) --> (-y, x) + */ +void CPhoneDevice::PresentImage90(ANativeWindow_Buffer *buf, AImage *image) { + AImageCropRect srcRect; + AImage_getCropRect(image, &srcRect); + + AImage_getPlaneRowStride(image, 0, &yStride); + AImage_getPlaneRowStride(image, 1, &uvStride); + yPixel = imageBuffer_; + AImage_getPlaneData(image, 0, &yPixel, &yLen); + vPixel = imageBuffer_ + yLen; + AImage_getPlaneData(image, 1, &vPixel, &vLen); + uPixel = imageBuffer_ + yLen + vLen; + AImage_getPlaneData(image, 2, &uPixel, &uLen); + AImage_getPlanePixelStride(image, 1, &uvPixelStride); + + int32_t height = std::min(buf->width, (srcRect.bottom - srcRect.top)); + int32_t width = std::min(buf->height, (srcRect.right - srcRect.left)); + + uint32_t *out = static_cast(buf->bits); + out += height - 1; + for (int32_t y = 0; y < height; y++) { + const uint8_t *pY = yPixel + yStride * (y + srcRect.top) + srcRect.left; + + int32_t uv_row_start = uvStride * ((y + srcRect.top) >> 1); + const uint8_t *pU = uPixel + uv_row_start + (srcRect.left >> 1); + const uint8_t *pV = vPixel + uv_row_start + (srcRect.left >> 1); + + for (int32_t x = 0; x < width; x++) { + const int32_t uv_offset = (x >> 1) * uvPixelStride; + // [x, y]--> [-y, x] + int testb = pU[uv_offset]; + int testc = pV[uv_offset]; + int testA = pY[x]; + out[x * buf->stride] = YUV2RGB(testA, testb, testc); + } + out -= 1; // move to the next column + } +} + +/* + * PresentImage180() + * Converting yuv to RGB + * Rotate image 180 degree: (x, y) --> (-x, -y) + */ +void CPhoneDevice::PresentImage180(ANativeWindow_Buffer *buf, AImage *image) { + AImageCropRect srcRect; + AImage_getCropRect(image, &srcRect); + + AImage_getPlaneRowStride(image, 0, &yStride); + AImage_getPlaneRowStride(image, 1, &uvStride); + yPixel = imageBuffer_; + AImage_getPlaneData(image, 0, &yPixel, &yLen); + vPixel = imageBuffer_ + yLen; + AImage_getPlaneData(image, 1, &vPixel, &vLen); + uPixel = imageBuffer_ + yLen + vLen; + AImage_getPlaneData(image, 2, &uPixel, &uLen); + AImage_getPlanePixelStride(image, 1, &uvPixelStride); + + int32_t height = std::min(buf->height, (srcRect.bottom - srcRect.top)); + int32_t width = std::min(buf->width, (srcRect.right - srcRect.left)); + + uint32_t *out = static_cast(buf->bits); + out += (height - 1) * buf->stride; + for (int32_t y = 0; y < height; y++) { + const uint8_t *pY = yPixel + yStride * (y + srcRect.top) + srcRect.left; + + int32_t uv_row_start = uvStride * ((y + srcRect.top) >> 1); + const uint8_t *pU = uPixel + uv_row_start + (srcRect.left >> 1); + const uint8_t *pV = vPixel + uv_row_start + (srcRect.left >> 1); + + for (int32_t x = 0; x < width; x++) { + const int32_t uv_offset = (x >> 1) * uvPixelStride; + // mirror image since we are using front camera + out[width - 1 - x] = YUV2RGB(pY[x], pU[uv_offset], pV[uv_offset]); + // out[x] = YUV2RGB(pY[x], pU[uv_offset], pV[uv_offset]); + } + out -= buf->stride; + } +} + +/* + * PresentImage270() + * Converting image from YUV to RGB + * Rotate Image counter-clockwise 270 degree: (x, y) --> (y, x) + */ +void CPhoneDevice::PresentImage270(ANativeWindow_Buffer *buf, AImage *image) { + AImageCropRect srcRect; + AImage_getCropRect(image, &srcRect); + + AImage_getPlaneRowStride(image, 0, &yStride); + AImage_getPlaneRowStride(image, 1, &uvStride); + yPixel = imageBuffer_; + AImage_getPlaneData(image, 0, &yPixel, &yLen); + vPixel = imageBuffer_ + yLen; + AImage_getPlaneData(image, 1, &vPixel, &vLen); + uPixel = imageBuffer_ + yLen + vLen; + AImage_getPlaneData(image, 2, &uPixel, &uLen); + AImage_getPlanePixelStride(image, 1, &uvPixelStride); + + int32_t height = std::min(buf->width, (srcRect.bottom - srcRect.top)); + int32_t width = std::min(buf->height, (srcRect.right - srcRect.left)); + + uint32_t *out = static_cast(buf->bits); + for (int32_t y = 0; y < height; y++) { + const uint8_t *pY = yPixel + yStride * (y + srcRect.top) + srcRect.left; + + int32_t uv_row_start = uvStride * ((y + srcRect.top) >> 1); + const uint8_t *pU = uPixel + uv_row_start + (srcRect.left >> 1); + const uint8_t *pV = vPixel + uv_row_start + (srcRect.left >> 1); + + for (int32_t x = 0; x < width; x++) { + const int32_t uv_offset = (x >> 1) * uvPixelStride; + int testb = pU[uv_offset]; + int testc = pV[uv_offset]; + int testA = pY[x]; + out[(width - 1 - x) * buf->stride] = + YUV2RGB(testA, testb, testc); + } + out += 1; // move to the next column + } +} + /* bool CPhoneDevice::SendBroadcastMessage(String16 action, int value) { diff --git a/app/src/main/cpp/PhoneDevice.h b/app/src/main/cpp/PhoneDevice.h index 78bab0e2..3fccf9a8 100644 --- a/app/src/main/cpp/PhoneDevice.h +++ b/app/src/main/cpp/PhoneDevice.h @@ -32,6 +32,7 @@ public: virtual ~CPhoneDevice(); virtual void SetListener(IListener* listener); + virtual bool Reboot(); virtual timer_uid_t RegisterHeartbeat(unsigned int timerType, unsigned int timeout); virtual bool TakePhoto(const IDevice::PHOTO_INFO& photoInfo, const string& path); virtual timer_uid_t registerTimer(unsigned int timerType, unsigned int timeout); @@ -45,6 +46,13 @@ protected: bool SendBroadcastMessage(std::string action, int value); + bool DisplayImage(ANativeWindow_Buffer* buf, AImage* image); + + void PresentImage(ANativeWindow_Buffer* buf, AImage* image); + void PresentImage90(ANativeWindow_Buffer* buf, AImage* image); + void PresentImage180(ANativeWindow_Buffer* buf, AImage* image); + void PresentImage270(ANativeWindow_Buffer* buf, AImage* image); + static void camera_device_on_disconnected(void *context, ACameraDevice *device); static void camera_device_on_error(void *context, ACameraDevice *device, int error); static void capture_session_on_ready(void *context, ACameraCaptureSession *session); @@ -74,6 +82,7 @@ protected: jobject m_javaService; jmethodID mRegisterTimerMid; + jmethodID mRegisterHeartbeatMid; jmethodID mUnregisterTimerMid; std::string mPath; @@ -95,6 +104,18 @@ protected: ACameraDevice_StateCallbacks deviceStateCallbacks; ACameraCaptureSession_stateCallbacks captureSessionStateCallbacks; + int32_t presentRotation_; + + int32_t imageHeight_; + int32_t imageWidth_; + + uint8_t* imageBuffer_; + int32_t yStride, uvStride; + uint8_t *yPixel, *uPixel, *vPixel; + int32_t yLen, uLen, vLen; + int32_t uvPixelStride; + + }; diff --git a/app/src/main/java/com/xinyingpower/microphoto/MainActivity.java b/app/src/main/java/com/xinyingpower/microphoto/MainActivity.java index 8b96b449..7fda1372 100644 --- a/app/src/main/java/com/xinyingpower/microphoto/MainActivity.java +++ b/app/src/main/java/com/xinyingpower/microphoto/MainActivity.java @@ -9,8 +9,8 @@ import android.os.Build; import android.os.Environment; import android.os.Handler; import android.os.SystemClock; -import android.support.v4.app.ActivityCompat; -import android.support.v7.app.AppCompatActivity; +import androidx.core.app.ActivityCompat; +import androidx.appcompat.app.AppCompatActivity; import android.os.Bundle; import android.view.View; @@ -111,7 +111,7 @@ public class MainActivity extends AppCompatActivity { binding.start.performClick(); } }; - handler.postDelayed(runnable, 2000); + handler.postDelayed(runnable, 1000); } diff --git a/app/src/main/java/com/xinyingpower/microphoto/MicroPhotoService.java b/app/src/main/java/com/xinyingpower/microphoto/MicroPhotoService.java index 68ed89e8..b5107c7e 100644 --- a/app/src/main/java/com/xinyingpower/microphoto/MicroPhotoService.java +++ b/app/src/main/java/com/xinyingpower/microphoto/MicroPhotoService.java @@ -10,32 +10,20 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.graphics.BitmapFactory; -import android.graphics.SurfaceTexture; -import android.hardware.Camera; -import android.os.Build; import android.os.Environment; -import android.os.Handler; import android.os.IBinder; import android.os.SystemClock; -import android.provider.SyncStateContract; -import android.support.v4.app.NotificationCompat; + +import androidx.core.app.NotificationCompat; import android.text.TextUtils; import android.util.Log; import android.widget.RemoteViews; import android.widget.Toast; -import com.dowse.base.param.pic.ChannelPicParam; import com.dowse.camera.client.DSCameraManager; import java.io.File; -import java.io.FileOutputStream; -import java.sql.Timestamp; -import java.text.SimpleDateFormat; -import java.time.LocalDate; import java.time.LocalDateTime; -import java.time.LocalTime; -import java.time.ZoneOffset; import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.Date; @@ -89,7 +77,7 @@ public class MicroPhotoService extends Service { private AlarmManager mAlarmManager; private NotificationManager mNotificationManager; - private int mHeartbeatDuration = 300000; // 5m: 5 * 60 * 1000 + private int mHeartbeatDuration = 0; // 5m: 5 * 60 * 1000 private long mNextHeartbeatTime = 0; private Map mTimers = new HashMap<>(); @@ -192,8 +180,8 @@ public class MicroPhotoService extends Service { for (int idx = 0; idx < cnt; idx++) { long val = intent.getLongExtra(EXTRA_PARAM_SCHEDULE + idx, 0); - int channel = (int)((val & 0xFF0000000000L) >> 40); - int preset = (int)((val & 0xFF00000000L) >> 32); + int channel = (int)((val & 0xFF0000L) >> 16); + int preset = (int)((val & 0xFF00L) >> 8); mService.takePhoto(channel, preset, mService.buildPhotoDir(channel), mService.buildPhotoFileName(channel, preset, ts), true); } @@ -226,8 +214,13 @@ public class MicroPhotoService extends Service { } } - private void updateHeartbeatDuration(int duration) { + private void registerHeartbeatTimer(int duration) { + int orgHeartbeatDuration = mHeartbeatDuration; mHeartbeatDuration = duration; + if (orgHeartbeatDuration == 0) { + registerHeartbeatTimer(); + } + } private void registerHeartbeatTimer() { @@ -249,8 +242,10 @@ public class MicroPhotoService extends Service { alarmIntent.setAction(ACTION_TAKE_PHOTO); int cnt = schedules.size(); alarmIntent.putExtra(EXTRA_PARAM_SCHEDULES, cnt); + String channelStr = ""; for (int idx = 0; idx < cnt; idx++) { alarmIntent.putExtra(EXTRA_PARAM_SCHEDULE + idx, schedules.get(idx).longValue()); + channelStr += schedules.get(idx).toString() + " "; } alarmIntent.putExtra(EXTRA_PARAM_TIME, ts); @@ -259,6 +254,10 @@ public class MicroPhotoService extends Service { AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE); + long currentTimeMillis = System.currentTimeMillis(); + Date date = new Date(currentTimeMillis + timeout); + Log.d(TAG, "Register Photo Timer: " + date.toString() + " currentTimeMillis=" + currentTimeMillis + " timeout=" + timeout + " Channels=" + channelStr); + alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + timeout, pendingIntent); } @@ -311,7 +310,6 @@ public class MicroPhotoService extends Service { } int cnt = photoTimeData.length; - int idx = 0; short channel = 0; short preset = 0; int ts = 0; @@ -320,9 +318,9 @@ public class MicroPhotoService extends Service { int maxDuration = 35 * 60 * 1000 + 1000; int currentTs = 0; List schedules = new ArrayList<>(); - while (true) { - val = photoTimeData[idx++]; - ts = (int)(val & 0xFFFFFFFFL); + for (int idx = 0; idx < cnt; idx++) { + val = photoTimeData[idx]; + ts = (int)((val & 0x00FFFFFF00000000L) >> 32); if (ts < baseTime) { continue; @@ -334,8 +332,8 @@ public class MicroPhotoService extends Service { if (currentTs == 0) { currentTs = ts; - channel = (short)((val & 0xFF0000000000L) >> 40); - preset = (short)((val & 0xFF00000000L) >> 32); + channel = (short)((val & 0xFF0000L) >> 16); + preset = (short)((val & 0xFF00L) >> 8); schedules.add(Long.valueOf(val)); } else if (ts > currentTs) { @@ -346,8 +344,9 @@ public class MicroPhotoService extends Service { } if (!schedules.isEmpty()) { - Date date = new Date((startTime + ts) * 1000); - Log.d(TAG, "Register Photo Time: " + date.toString() + " BaseTime=" + baseTime + " ts=" + ts + " startTime=" + startTime); + long expectedTs = startTime + ts; + Date date = new Date(expectedTs * 1000); + // Log.d(TAG, "Register Photo Time: " + date.toString() + " BaseTime=" + baseTime + " ts=" + ts + " startTime=" + startTime + " expectedTs=" + expectedTs); registerPhotoTimer(channel, preset, currentTs, (currentTs - baseTime) * 1000, schedules); } @@ -390,14 +389,14 @@ public class MicroPhotoService extends Service { private void connect() { // after 10 seconds its connected new android.os.Handler().postDelayed( - new Runnable() { - public void run() { - Log.d(TAG, "Bluetooth Low Energy device is connected!!"); - Toast.makeText(getApplicationContext(),"Connected!",Toast.LENGTH_SHORT).show(); - stateService = STATE_SERVICE.CONNECTED; - startForeground(NOTIFICATION_ID_FOREGROUND_SERVICE, prepareNotification()); - } - }, 10000); + new Runnable() { + public void run() { + Log.d(TAG, "Bluetooth Low Energy device is connected!!"); + Toast.makeText(getApplicationContext(),"Connected!",Toast.LENGTH_SHORT).show(); + stateService = STATE_SERVICE.CONNECTED; + startForeground(NOTIFICATION_ID_FOREGROUND_SERVICE, prepareNotification()); + } + }, 10000); } diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index ec85746b..f8192bd4 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,5 +1,5 @@ - - \ No newline at end of file + \ No newline at end of file diff --git a/build.gradle b/build.gradle index f05eacfd..c3dce6c5 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,18 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. +buildscript { + ext.kotlin_version = '1.6.10' + + dependencies { + classpath 'com.android.tools.build:gradle:7.2.1' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + plugins { id 'com.android.application' version '7.4.2' apply false id 'com.android.library' version '7.4.2' apply false -} \ No newline at end of file +} + + diff --git a/gradle.properties b/gradle.properties index 71f9eed7..e5653060 100644 --- a/gradle.properties +++ b/gradle.properties @@ -14,4 +14,8 @@ org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 # Enables namespacing of each library's R class so that its R class includes only the # resources declared in the library itself and none from the library's dependencies, # thereby reducing the size of the R class for that library -android.nonTransitiveRClass=true \ No newline at end of file +android.nonTransitiveRClass=true +android.useAndroidX=true +android.enableJetifier=true + +opencvsdk=D:/Workspace/deps/OpenCV-android-sdk \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index 5eaf0449..01e1bb1b 100644 --- a/settings.gradle +++ b/settings.gradle @@ -14,3 +14,6 @@ dependencyResolutionManagement { } rootProject.name = "MicroPhoto" include ':app' + +include ':opencv' +project(':opencv').projectDir = new File(opencvsdk + '/sdk') \ No newline at end of file