From 96f206c2131bf62c5dcbe6b90236b42ff653031b Mon Sep 17 00:00:00 2001 From: Matthew Date: Mon, 10 Jul 2023 21:49:15 +0800 Subject: [PATCH] =?UTF-8?q?=E8=A3=85=E7=BD=AE=E7=AB=AFAPP=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/AndroidManifest.xml | 11 +- app/src/main/cpp/CMakeLists.txt | 2 + app/src/main/cpp/MicroPhoto.cpp | 136 ++++++- app/src/main/cpp/PhoneDevice.cpp | 208 +++++++++-- app/src/main/cpp/PhoneDevice.h | 41 ++- app/src/main/cpp/TerminalDevice.cpp | 21 +- .../microphoto/FloatingWindow.java | 3 + .../xinyingpower/microphoto/MainActivity.java | 26 ++ .../microphoto/MicroPhotoService.java | 335 ++++++++++++++---- 9 files changed, 646 insertions(+), 137 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b196c098..7e08658b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -45,7 +45,7 @@ - + @@ -69,11 +69,11 @@ android:name="android.permission.SET_TIME_ZONE" tools:ignore="ProtectedPermissions" /> - - + + @@ -82,10 +82,9 @@ - - + + - GetEnv((void**)env, JNI_VERSION_1_6); + if (get_env_result == JNI_EDETACHED) + { + if (vm->AttachCurrentThread(env, NULL) == JNI_OK) { + did_attach_thread = true; + } else { + // Failed to attach thread. Throw an exception if you want to. + } + } else if (get_env_result == JNI_EVERSION) + { + // Unsupported JNI version. Throw an exception if you want to. + } + return did_attach_thread; +} + + // #include "client/linux/handler/exception_handler.h" // #include "client/linux/handler/minidump_descriptor.h" @@ -85,7 +107,7 @@ Java_com_xinyingpower_microphoto_MicroPhotoService_init( // const string& appPath, const string& termId, const string& server, unsigned short port, const string& bindIp CTermClient& service = CTermClient::GetService(); // CTerminalDevice* device = new CTerminalDevice(vm, pThis); - CPhoneDevice* device = new CPhoneDevice(); + CPhoneDevice* device = new CPhoneDevice(vm, pThis); bool res = service.InitService(appPathStr, cmdidStr, ipStr, (unsigned short)port, "", device); env->ReleaseStringUTFChars(appPath, appPathStr); @@ -95,11 +117,16 @@ Java_com_xinyingpower_microphoto_MicroPhotoService_init( return res ? JNI_TRUE : JNI_FALSE; } - extern "C" JNIEXPORT jboolean JNICALL Java_com_xinyingpower_microphoto_MicroPhotoService_takePhoto( JNIEnv* env, - jobject pThis, jint channel, jint preset, jstring path, jstring fileName) { + jobject pThis, jint channel, jint preset, jstring path, jstring fileName, jboolean sendToCma) { + + CTerminal* pTerminal = CTermClient::GetService().GetTerminal(); + if (pTerminal == NULL) + { + return JNI_FALSE; + } if (channel < 1 || channel > 0xFF) { @@ -107,20 +134,47 @@ Java_com_xinyingpower_microphoto_MicroPhotoService_takePhoto( } unsigned char id = (unsigned char)channel - 1; + // env->ReleaseStringUTFChars(fileName, fileNameStr); + // env->ReleaseStringUTFChars(path, pathStr); - Camera2Reader *camera = new Camera2Reader(id); - const char *pathStr = env->GetStringUTFChars(path, 0); - const char *fileNameStr = env->GetStringUTFChars(fileName, 0); + pTerminal->TakeAndSendPhoto(channel, preset, 0); - camera->Open(pathStr, fileNameStr); - env->ReleaseStringUTFChars(fileName, fileNameStr); - env->ReleaseStringUTFChars(path, pathStr); + return JNI_TRUE; +} - camera->start(); + +extern "C" JNIEXPORT jboolean JNICALL +Java_com_xinyingpower_microphoto_MicroPhotoService_sendHeartbeat( + JNIEnv* env, + jobject pThis) { + + CTerminal* pTerminal = CTermClient::GetService().GetTerminal(); + if (pTerminal == NULL) + { + return JNI_FALSE; + } + + pTerminal->SendHeartbeat(); return JNI_TRUE; } + +extern "C" JNIEXPORT jboolean JNICALL +Java_com_xinyingpower_microphoto_MicroPhotoService_fireTimeout( + JNIEnv* env, + jobject pThis, jlong uid) { + + IDevice* dev = CTermClient::GetService().GetDevice(); + if (dev == NULL) + { + return JNI_FALSE; + } + + CPhoneDevice* phoneDevice = (CPhoneDevice *)dev; + return phoneDevice->FireTimer((IDevice::timer_uid_t)uid) ? JNI_TRUE : JNI_FALSE; +} + extern "C" JNIEXPORT jboolean JNICALL Java_com_xinyingpower_microphoto_MicroPhotoService_uninit( JNIEnv* env, @@ -142,3 +196,65 @@ Java_com_xinyingpower_microphoto_MicroPhotoService_getHeartbeatDuration( return 60000; } +extern "C" JNIEXPORT jlongArray JNICALL +Java_com_xinyingpower_microphoto_MicroPhotoService_getPhotoTimeData( + JNIEnv* env, + jobject pThis) { + + CTerminal* pTerminal = CTermClient::GetService().GetTerminal(); + if (pTerminal == NULL) + { + return NULL; + } + + map> photoTime; + if (!pTerminal->GetPhotoTime(photoTime) || photoTime.empty()) + { + return NULL; + } + + size_t numberOfData = photoTime.size() * photoTime.begin()->second.size(); + if (numberOfData == 0) + { + return NULL; + } + + vector dataArray; + dataArray.reserve(numberOfData); + + unsigned long val = 0; + jint channel = 0; + for (map>::const_iterator it = photoTime.cbegin(); it != photoTime.cend(); ++it) + { + if (it->second.empty()) + { + continue; + } + + channel = (jint)((unsigned short)it->first); + // dataArray.push_back(channel); + + // val = (jint)it->second.size(); + // dataArray.push_back(val); + for (vector::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); + dataArray.push_back((jlong)val); + } + } + + std::sort(dataArray.begin(), dataArray.end()); + + jlongArray data = env->NewLongArray(dataArray.size()); + if (data == NULL) { + return NULL; + } + + env->SetLongArrayRegion(data, 0, dataArray.size(), &dataArray[0]); + + return data; +} diff --git a/app/src/main/cpp/PhoneDevice.cpp b/app/src/main/cpp/PhoneDevice.cpp index 94c06f82..289f8600 100644 --- a/app/src/main/cpp/PhoneDevice.cpp +++ b/app/src/main/cpp/PhoneDevice.cpp @@ -19,6 +19,7 @@ #define LOG_TAG "CameraTestHelpers" #include "PhoneDevice.h" +#include "TermClient.h" #include @@ -26,36 +27,129 @@ #define ALOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__) #define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) -CPhoneDevice::CPhoneDevice() +extern bool GetJniEnv(JavaVM *vm, JNIEnv **env); + +CPhoneDevice::CPhoneDevice(JavaVM* vm, jobject service) { + m_vm = vm; + JNIEnv* env = NULL; + bool attached = GetJniEnv(m_vm, &env); + m_javaService = env->NewGlobalRef(service); + + jclass classService = env->GetObjectClass(m_javaService); + mRegisterTimerMid = env->GetMethodID(classService, "registerTimer", "(JI)Z"); + mUnregisterTimerMid = env->GetMethodID(classService, "unregisterTimer", "(J)Z"); + + env->DeleteLocalRef(classService); + + if (attached) + { + vm->DetachCurrentThread(); + } + + m_timerUidFeed = time(NULL); } CPhoneDevice::~CPhoneDevice() { - + JNIEnv* env = NULL; + bool attached = GetJniEnv(m_vm, &env); + env->DeleteGlobalRef(m_javaService); + if (attached) + { + m_vm->DetachCurrentThread(); + } + m_javaService = NULL; } +void CPhoneDevice::SetListener(IListener* listener) +{ + m_listener = listener; +} IDevice::timer_uid_t CPhoneDevice::registerTimer(unsigned int timerType, unsigned int timeout) { + 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; + jboolean ret = JNI_FALSE; + bool attached = GetJniEnv(m_vm, &env); + if (attached) + { + ret = env->CallBooleanMethod(m_javaService, mRegisterTimerMid, (jlong)uid, (jint)timeout); + 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) { - return true; + JNIEnv* env = NULL; + jboolean ret = JNI_FALSE; + bool attached = GetJniEnv(m_vm, &env); + if (attached) + { + ret = env->CallBooleanMethod(m_javaService, mUnregisterTimerMid, (jlong)uid); + m_vm->DetachCurrentThread(); + } + + if (ret == JNI_TRUE) + { + mTimers.erase(uid); + return true; + } + return false; } -bool CPhoneDevice::onTimeout(IDevice::timer_uid_t uid, unsigned int timerType, unsigned int times) +bool CPhoneDevice::FireTimer(timer_uid_t uid) { + std::map::iterator it = mTimers.find(uid); + if (it == mTimers.end()) + { + return false; + } + + unsigned long timerType = it->second & 0xFFFFFFFF; + unsigned long times = (it->second & 0xFFFFFFFF00000000) >> 32; + times++; + + if (timerType != 100) + { + int aa = 0; + } + it->second = timerType | (times << 32); + + if (m_listener == NULL) + { + return false; + } + + m_listener->OnTimeout(uid, timerType, times); + return true; } +IDevice::timer_uid_t CPhoneDevice::RegisterHeartbeat(unsigned int timerType, unsigned int timeout) +{ + return registerTimer(timerType, timeout); +} -bool CPhoneDevice::TakePhoto(unsigned char channel, unsigned char preset, const string& path, bool photo) +bool CPhoneDevice::TakePhoto(const IDevice::PHOTO_INFO& photoInfo, const string& path) { - int cameraId = (int)channel - 1; + LOGI("TAKE_PHOTO: CH=%u PR=%u\n", (unsigned int)photoInfo.channel, (unsigned int)photoInfo.preset); + mPhotoInfo = photoInfo; + mPath = path; + + int cameraId = (int)photoInfo.channel - 1; ACameraIdList *cameraIdList = NULL; ACameraMetadata *cameraMetadata = NULL; @@ -67,16 +161,19 @@ bool CPhoneDevice::TakePhoto(unsigned char channel, unsigned char preset, const camera_status = ACameraManager_getCameraIdList(cameraManager, &cameraIdList); if (camera_status != ACAMERA_OK) { LOGI("Failed to get camera id list (reason: %d)\n", camera_status); + TakePhotoCb(false, photoInfo, path, 0); return false; } if (cameraIdList->numCameras < 1 ) { LOGI("No camera device detected.\n"); + TakePhotoCb(false, photoInfo, path, 0); return false; } if (cameraIdList->numCameras <= cameraId ) { LOGI("No required camera device %d detected.\n", cameraId); + TakePhotoCb(false, photoInfo, path, 0); return false; } @@ -125,6 +222,7 @@ bool CPhoneDevice::TakePhoto(unsigned char channel, unsigned char preset, const if (status != AMEDIA_OK) { LOGI("AImageReader_new error\n"); + TakePhotoCb(false, photoInfo, path, 0); return false; } @@ -139,6 +237,7 @@ bool CPhoneDevice::TakePhoto(unsigned char channel, unsigned char preset, const if (status != AMEDIA_OK) { LOGI("AImageReader_getWindow error\n"); + TakePhotoCb(false, photoInfo, path, 0); return false; } @@ -157,7 +256,6 @@ bool CPhoneDevice::TakePhoto(unsigned char channel, unsigned char preset, const ACameraCaptureSession_capture(captureSession, NULL, 1, &captureRequest, NULL); LOGI("Surface is prepared in here.\n"); - return true; } @@ -175,31 +273,28 @@ ACameraCaptureSession_stateCallbacks* CPhoneDevice::GetSessionListener() void CPhoneDevice::ImageCallback(AImageReader *reader) { - int32_t format; AImage *image = nullptr; media_status_t status = AImageReader_acquireNextImage(reader, &image); - LOGI("ImageCallback\n"); if (status == AMEDIA_OK && image) { - LOGI("ImageCallback\n"); + bool res = WriteFile(image); AImage_delete(image); + // delete pThis; + + TakePhotoCb(res, mPhotoInfo, mPath, time(NULL)); } } void CPhoneDevice::OnImageCallback(void *ctx, AImageReader *reader) { CPhoneDevice* pThis = reinterpret_cast(ctx); - AImage *image = nullptr; - media_status_t status = AImageReader_acquireNextImage(reader, &image); - if (status == AMEDIA_OK && image) + if (pThis != NULL) { - WriteFile(pThis, image); - AImage_delete(image); - - // delete pThis; + pThis->ImageCallback(reader); } } -bool CPhoneDevice::WriteFile(CPhoneDevice* pThis, AImage *image) + +bool CPhoneDevice::WriteFile(AImage *image) { int planeCount = 0; media_status_t status = AImage_getNumberOfPlanes(image, &planeCount); @@ -215,7 +310,7 @@ bool CPhoneDevice::WriteFile(CPhoneDevice* pThis, AImage *image) int len = 0; AImage_getPlaneData(image, 0, &data, &len); - std::string path = pThis->GetFileName(); + std::string path = GetFileName(); bool res = false; FILE *file = fopen(path.c_str(), "wb"); @@ -225,6 +320,8 @@ bool CPhoneDevice::WriteFile(CPhoneDevice* pThis, AImage *image) fclose(file); LOGE("Capture: %s", path.c_str()); + + res = true; } else @@ -236,16 +333,87 @@ bool CPhoneDevice::WriteFile(CPhoneDevice* pThis, AImage *image) return res; } +bool CPhoneDevice::WriteFile(CPhoneDevice* pThis, AImage *image) +{ + return pThis->WriteFile(image); +} + std::string CPhoneDevice::GetFileName() const { return mPath; } +/* +bool CPhoneDevice::SendBroadcastMessage(String16 action, int value) +{ + TM_INFO_LOG("sendBroadcastMessage(): Action: %s, Value: %d ", action.string(), value); + sp sm = defaultServiceManager(); + sp am = sm->getService(String16("activity")); + if (am != NULL) { + Parcel data, reply; + data.writeInterfaceToken(String16("android.app.IActivityManager")); + data.writeStrongBinder(NULL); + // intent begin + data.writeString16(action); // action + data.writeInt32(0); // URI data type + data.writeString16(NULL, 0); // type + data.writeInt32(0); // flags + data.writeString16(NULL, 0); // package name + data.writeString16(NULL, 0); // component name + data.writeInt32(0); // source bound - size + data.writeInt32(0); // categories - size + data.writeInt32(0); // selector - size + data.writeInt32(0); // clipData - size + data.writeInt32(-2); // contentUserHint: -2 -> UserHandle.USER_CURRENT + data.writeInt32(-1); // bundle extras length + data.writeInt32(0x4C444E42); // 'B' 'N' 'D' 'L' + int oldPos = data.dataPosition(); + data.writeInt32(1); // size + // data.writeInt32(0); // VAL_STRING, need to remove because of analyze common intent + data.writeString16(String16("type")); + data.writeInt32(1); // VAL_INTEGER + data.writeInt32(value); + int newPos = data.dataPosition(); + data.setDataPosition(oldPos - 8); + data.writeInt32(newPos - oldPos); // refill bundle extras length + data.setDataPosition(newPos); + // intent end + data.writeString16(NULL, 0); // resolvedType + data.writeStrongBinder(NULL); // resultTo + data.writeInt32(0); // resultCode + data.writeString16(NULL, 0); // resultData + data.writeInt32(-1); // resultExtras + data.writeString16(NULL, 0); // permission + data.writeInt32(0); // appOp + data.writeInt32(-1); // option + data.writeInt32(1); // serialized: != 0 -> ordered + data.writeInt32(0); // sticky + data.writeInt32(-2); // userId: -2 -> UserHandle.USER_CURRENT + + status_t ret = am->transact(IBinder::FIRST_CALL_TRANSACTION + 13, data, + &reply); // BROADCAST_INTENT_TRANSACTION + if (ret == NO_ERROR) { + int exceptionCode = reply.readExceptionCode(); + if (exceptionCode) { + TM_INFO_LOG("sendBroadcastMessage(%s) caught exception %d\n", + action.string(), exceptionCode); + return false; + } + } else { + return false; + } + } else { + TM_INFO_LOG("getService() couldn't find activity service!\n"); + return false; + } + return true; +} + */ void CPhoneDevice::camera_device_on_disconnected(void *context, ACameraDevice *device) { LOGI("Camera(id: %s) is diconnected.\n", ACameraDevice_getId(device)); CPhoneDevice* pThis = (CPhoneDevice*)context; - delete pThis; + // delete pThis; } void CPhoneDevice::camera_device_on_error(void *context, ACameraDevice *device, int error) diff --git a/app/src/main/cpp/PhoneDevice.h b/app/src/main/cpp/PhoneDevice.h index f0ef8c6d..78bab0e2 100644 --- a/app/src/main/cpp/PhoneDevice.h +++ b/app/src/main/cpp/PhoneDevice.h @@ -9,6 +9,9 @@ #include #include +#include +#include + #include #include #include @@ -25,19 +28,23 @@ class CPhoneDevice : public IDevice { public: - CPhoneDevice(); - ~CPhoneDevice(); + CPhoneDevice(JavaVM* vm, jobject service); + virtual ~CPhoneDevice(); - virtual bool TakePhoto(unsigned char channel, unsigned char preset, const string& path, bool photo); + virtual void SetListener(IListener* listener); + 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); virtual bool unregisterTimer(timer_uid_t uid); - virtual bool onTimeout(timer_uid_t uid, unsigned int timerType, unsigned int times); + virtual bool FireTimer(timer_uid_t uid); protected: ACameraCaptureSession_stateCallbacks *GetSessionListener(); std::string GetFileName() const; + bool SendBroadcastMessage(std::string action, int value); + 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); @@ -46,11 +53,35 @@ protected: void ImageCallback(AImageReader *reader); static void OnImageCallback(void *ctx, AImageReader *reader); - static bool WriteFile(CPhoneDevice* pThis, AImage *image); + bool WriteFile(AImage *image); + static bool WriteFile(CPhoneDevice* pThis, AImage *image); + + inline bool TakePhotoCb(bool res, const IDevice::PHOTO_INFO& photoInfo, const string& path, time_t photoTime) + { + if (m_listener != NULL) + { + return m_listener->OnPhotoTaken(res, photoInfo, path, photoTime); + } + + return false; + } + + protected: + JavaVM* m_vm; + jobject m_javaService; + + jmethodID mRegisterTimerMid; + jmethodID mUnregisterTimerMid; + std::string mPath; + IDevice::PHOTO_INFO mPhotoInfo; + IListener* m_listener; + + atomic_ulong m_timerUidFeed; + std::map mTimers; AImageReader *mAImageReader; ANativeWindow *theNativeWindow; diff --git a/app/src/main/cpp/TerminalDevice.cpp b/app/src/main/cpp/TerminalDevice.cpp index fef0ded0..19d91fb2 100644 --- a/app/src/main/cpp/TerminalDevice.cpp +++ b/app/src/main/cpp/TerminalDevice.cpp @@ -4,26 +4,7 @@ typedef jbyteArray (*TakePhotoFunc)(int, int, int, int); -bool GetJniEnv(JavaVM *vm, JNIEnv **env) -{ - bool did_attach_thread = false; - *env = nullptr; - // Check if the current thread is attached to the VM - auto get_env_result = vm->GetEnv((void**)env, JNI_VERSION_1_6); - if (get_env_result == JNI_EDETACHED) - { - if (vm->AttachCurrentThread(env, NULL) == JNI_OK) { - did_attach_thread = true; - } else { - // Failed to attach thread. Throw an exception if you want to. - } - } else if (get_env_result == JNI_EVERSION) - { - // Unsupported JNI version. Throw an exception if you want to. - } - return did_attach_thread; -} - +extern bool GetJniEnv(JavaVM *vm, JNIEnv **env); CTerminalDevice::CTerminalDevice(JavaVM* vm, jobject service) { diff --git a/app/src/main/java/com/xinyingpower/microphoto/FloatingWindow.java b/app/src/main/java/com/xinyingpower/microphoto/FloatingWindow.java index 987878b9..85762f3c 100644 --- a/app/src/main/java/com/xinyingpower/microphoto/FloatingWindow.java +++ b/app/src/main/java/com/xinyingpower/microphoto/FloatingWindow.java @@ -58,6 +58,7 @@ public class FloatingWindow extends Service { WindowManager.LayoutParams mWindowsParams; private void moveView() { + /* DisplayMetrics metrics = mContext.getResources().getDisplayMetrics(); int width = (int) (metrics.widthPixels * 1f); int height = (int) (metrics.heightPixels * 1f); @@ -122,6 +123,8 @@ public class FloatingWindow extends Service { return false; } }); + + */ } private boolean isViewInBounds(View view, int x, int y) { diff --git a/app/src/main/java/com/xinyingpower/microphoto/MainActivity.java b/app/src/main/java/com/xinyingpower/microphoto/MainActivity.java index 80614f11..8b96b449 100644 --- a/app/src/main/java/com/xinyingpower/microphoto/MainActivity.java +++ b/app/src/main/java/com/xinyingpower/microphoto/MainActivity.java @@ -7,6 +7,7 @@ import android.graphics.SurfaceTexture; import android.hardware.Camera; 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; @@ -87,6 +88,31 @@ public class MainActivity extends AppCompatActivity { }); + this.binding.stop.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + + Intent intent = new Intent(MainActivity.this, MicroPhotoService.class); + intent.setAction(MicroPhotoService.ACTION_STOP); + + MainActivity.this.stopService(intent); + + binding.start.setEnabled(true); + binding.stop.setEnabled(false); + } + + + }); + + Handler handler=new Handler(); + Runnable runnable=new Runnable(){ + @Override + public void run() { + binding.start.performClick(); + } + }; + handler.postDelayed(runnable, 2000); + } protected void takePhoto() diff --git a/app/src/main/java/com/xinyingpower/microphoto/MicroPhotoService.java b/app/src/main/java/com/xinyingpower/microphoto/MicroPhotoService.java index 62d22cb4..68ed89e8 100644 --- a/app/src/main/java/com/xinyingpower/microphoto/MicroPhotoService.java +++ b/app/src/main/java/com/xinyingpower/microphoto/MicroPhotoService.java @@ -30,8 +30,18 @@ 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; +import java.util.HashMap; +import java.util.List; +import java.util.Map; public class MicroPhotoService extends Service { @@ -55,6 +65,19 @@ public class MicroPhotoService extends Service { private static String ACTION_HEARTBEAT = "ACT_HB"; private static String ACTION_TAKE_PHOTO = "ACT_TP"; + private static String ACTION_TIMEOUT = "ACT_TIMEOUT"; + + private static String EXTRA_PARAM_CHANNEL = "Channel"; + private static String EXTRA_PARAM_SCHEDULES = "Schedules"; + private static String EXTRA_PARAM_SCHEDULE = "Schedule_"; + private static String EXTRA_PARAM_PRESET = "Preset"; + private static String EXTRA_PARAM_TIME = "Time"; + // private static String EXTRA_PARAM_FILENAME = "FileName"; + + private static String EXTRA_PARAM_TIMER_UID = "TimerUid"; + // private static String EXTRA_PARAM_TIMER_TYPE = "TimerType"; + private static String EXTRA_PARAM_TIMEOUT = "Timeout"; + private final static String FOREGROUND_CHANNEL_ID = "foreground_channel_id"; @@ -63,20 +86,15 @@ public class MicroPhotoService extends Service { public static final int NOT_CONNECTED = 0; } - private static String mDesc = ""; - private AlarmManager mAlarmManager; private NotificationManager mNotificationManager; - private Handler handler; - private int count = 0; + private int mHeartbeatDuration = 300000; // 5m: 5 * 60 * 1000 + private long mNextHeartbeatTime = 0; + private Map mTimers = new HashMap<>(); private static int stateService = STATE_SERVICE.NOT_CONNECTED; - - public MicroPhotoService() { - - } @Override @@ -92,7 +110,31 @@ public class MicroPhotoService extends Service { mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); stateService = STATE_SERVICE.NOT_CONNECTED; + alarmReceiver = new AlarmReceiver(this); + // 注册广播接受者 + IntentFilter intentFilter = new IntentFilter(ACTION_HEARTBEAT); + intentFilter.addAction(ACTION_TAKE_PHOTO); + intentFilter.addAction(ACTION_TIMEOUT); + registerReceiver( alarmReceiver, intentFilter); + + AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE); + + while (true) { + AlarmManager.AlarmClockInfo aci = alarmManager.getNextAlarmClock(); + if (aci == null) { + break; + } + + if (aci.getShowIntent().isBroadcast()) { + // alarmManager.cancel(aci.getShowIntent()); + } + + + } + // alarmManager.cancel(); + boolean res = false; + /* res = DSCameraManager.getInstace().init(); ChannelPicParam picParam = new ChannelPicParam(); picParam.setColor(1); @@ -100,8 +142,13 @@ public class MicroPhotoService extends Service { picParam.setHeight(1080); picParam.setCompress_radio(40); DSCameraManager.getInstace().setPicParam(1, picParam); + */ // Environment.getExternalStoragePublicDirectory(String) - File path = getApplicationContext().getFilesDir(); + File path = new File(Environment.getExternalStorageDirectory(), "com.xyp.mp/"); + if (!path.exists()) { + path.mkdirs(); + } + // File path = getApplicationContext().getFilesDir(); String appPath = path.getAbsolutePath(); Log.i("XYMP", "AppPath=" + appPath); String ip = "180.166.218.222"; @@ -109,14 +156,203 @@ public class MicroPhotoService extends Service { String cmdid = "XYDEV100230100012"; init(appPath, ip, port, cmdid); - alarmReceiver = new AlarmReceiver(this); + // registerHeartbeatTimer(getHeartbeatDuration()); - // 注册广播接受者 - IntentFilter intentFilter = new IntentFilter(ACTION_HEARTBEAT); - intentFilter.addAction(ACTION_TAKE_PHOTO); - registerReceiver( alarmReceiver, intentFilter); + Date date = new Date(); + long nowTs = date.getTime() / 1000; + date.setHours(0); + date.setMinutes(0); + date.setSeconds(0); + long startTime = date.getTime() / 1000; + long baseTime = nowTs - startTime; + + registerCaptureSchedule(startTime, baseTime); + + // registerPhotoTimer(); + } + + + public static class AlarmReceiver extends BroadcastReceiver { + + private MicroPhotoService mService; + public AlarmReceiver(MicroPhotoService service) { + mService = service; + } + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if(TextUtils.equals(ACTION_HEARTBEAT, action)){ + Log.i(TAG, "receiver ACTION=" + action); + mService.sendHeartbeat(); + mService.registerHeartbeatTimer(); + } + else if(TextUtils.equals(ACTION_TAKE_PHOTO, action)) { + long ts = intent.getLongExtra(EXTRA_PARAM_TIME, 0); + int cnt = intent.getIntExtra(EXTRA_PARAM_SCHEDULES, 0); + if (cnt > 0) { + 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); + + mService.takePhoto(channel, preset, mService.buildPhotoDir(channel), mService.buildPhotoFileName(channel, preset, ts), true); + } + } + + // Register Next Photo Timer + Date date = new Date(); + long nowTs = date.getTime() / 1000; + date.setHours(0); + date.setMinutes(0); + date.setSeconds(0); + long startTime = date.getTime() / 1000; + long baseTime = nowTs - startTime; + + mService.registerCaptureSchedule(startTime, baseTime); + } + else if(TextUtils.equals(ACTION_TIMEOUT, action)) { + long uid = intent.getLongExtra(EXTRA_PARAM_TIMER_UID, 0); + Log.i(TAG, "Timeout:" + uid); + mService.fireTimeout(uid); + + int timeout = intent.getIntExtra(EXTRA_PARAM_TIMEOUT, 0); + Long uidObj = Long.valueOf(uid); + PendingIntent pendingIntent = mService.mTimers.get(uidObj); + + if (pendingIntent != null) { + mService.registerTimer(pendingIntent, uid, timeout); + } + } + } + } + + private void updateHeartbeatDuration(int duration) { + mHeartbeatDuration = duration; + } + private void registerHeartbeatTimer() { + + // 创建延迟意图 + Intent alarmIntent = new Intent(); + alarmIntent.setAction(ACTION_HEARTBEAT); + PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, alarmIntent, 0); + + AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE); + alarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + mHeartbeatDuration, pendingIntent); + + mNextHeartbeatTime = System.currentTimeMillis() + mHeartbeatDuration; + // alarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime() + timeout, pendingIntent); + } + private void registerPhotoTimer(int channel, int preset, long ts, long timeout, List schedules) { + + // 创建延迟意图 + Intent alarmIntent = new Intent(); + alarmIntent.setAction(ACTION_TAKE_PHOTO); + int cnt = schedules.size(); + alarmIntent.putExtra(EXTRA_PARAM_SCHEDULES, cnt); + for (int idx = 0; idx < cnt; idx++) { + alarmIntent.putExtra(EXTRA_PARAM_SCHEDULE + idx, schedules.get(idx).longValue()); + } + + alarmIntent.putExtra(EXTRA_PARAM_TIME, ts); + + PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, alarmIntent, PendingIntent.FLAG_UPDATE_CURRENT); + + AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE); + + alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + timeout, pendingIntent); + } - registerHeartbeatTimer(getHeartbeatDuration()); + // private HashMap mTimers = new HashMap(); + + public boolean registerTimer(long uid, int timeout) { + + // 创建延迟意图 + Intent alarmIntent = new Intent(); + alarmIntent.setAction(ACTION_TIMEOUT); + alarmIntent.putExtra(EXTRA_PARAM_TIMER_UID, uid); + alarmIntent.putExtra(EXTRA_PARAM_TIMEOUT, timeout); + + PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, alarmIntent, PendingIntent.FLAG_UPDATE_CURRENT); + + mTimers.put(Long.valueOf(uid), pendingIntent); + return registerTimer(pendingIntent, uid, timeout); + } + + public boolean registerTimer(PendingIntent pendingIntent, long uid, int timeout) { + + AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE); + + alarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + timeout, pendingIntent); + + Log.i(TAG, "RegTimer:" + uid + " timeout=" + timeout); + return true; + } + + public boolean unregisterTimer(long uid) { + + Long uidObj = Long.valueOf(uid); + PendingIntent pendingIntent = mTimers.get(uidObj); + if (pendingIntent != null) { + AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE); + + alarmManager.cancel(pendingIntent); + + mTimers.remove(uidObj); + Log.i(TAG, "UnregTimer:" + uid); + } + + return true; + } + private boolean registerCaptureSchedule(long startTime, long baseTime) { + + long[] photoTimeData = getPhotoTimeData(); + if (photoTimeData == null) { + return false; + } + + int cnt = photoTimeData.length; + int idx = 0; + short channel = 0; + short preset = 0; + int ts = 0; + long val = 0L; + // int maxDuration = mHeartbeatDuration * 2 / 1000; + int maxDuration = 35 * 60 * 1000 + 1000; + int currentTs = 0; + List schedules = new ArrayList<>(); + while (true) { + val = photoTimeData[idx++]; + ts = (int)(val & 0xFFFFFFFFL); + + if (ts < baseTime) { + continue; + } + + if ((ts - baseTime) > maxDuration) { + break; + } + + if (currentTs == 0) { + currentTs = ts; + channel = (short)((val & 0xFF0000000000L) >> 40); + preset = (short)((val & 0xFF00000000L) >> 32); + + schedules.add(Long.valueOf(val)); + } else if (ts > currentTs) { + break; + } else { + schedules.add(Long.valueOf(val)); + } + } + + if (!schedules.isEmpty()) { + Date date = new Date((startTime + ts) * 1000); + Log.d(TAG, "Register Photo Time: " + date.toString() + " BaseTime=" + baseTime + " ts=" + ts + " startTime=" + startTime); + + registerPhotoTimer(channel, preset, currentTs, (currentTs - baseTime) * 1000, schedules); + } + + return true; } @Override @@ -151,7 +387,6 @@ public class MicroPhotoService extends Service { return START_NOT_STICKY; } - private void connect() { // after 10 seconds its connected new android.os.Handler().postDelayed( @@ -273,8 +508,8 @@ public class MicroPhotoService extends Service { super.onDestroy(); } - private String buildPhotoDir(int channel) { - File path = new File(Environment.getExternalStorageDirectory(), "com.xinyingpower.com/photos/"); + public String buildPhotoDir(int channel) { + File path = new File(Environment.getExternalStorageDirectory(), "com.xyp.mp/photos/"); if (!path.exists() && !path.mkdirs()) { return null; @@ -286,74 +521,22 @@ public class MicroPhotoService extends Service { return p; } - private String buildPhotoFileName(int channel, int preset) { + private String buildPhotoFileName(int channel, int preset, long ts) { - SimpleDateFormat dateFormat = new SimpleDateFormat("yyyymmddhhmmss"); - String date = dateFormat.format(new Date()); + String date = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")); String photoFile = "img_" + Integer.toString(channel) + "_" + Integer.toHexString(preset).toUpperCase() + "_" + date + ".jpg"; return photoFile; } - public static class AlarmReceiver extends BroadcastReceiver { - - private MicroPhotoService mService; - public AlarmReceiver(MicroPhotoService service) { - mService = service; - } - public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - if(TextUtils.equals(ACTION_HEARTBEAT, action)){ - Log.i(TAG, "receiver ACTION=" + action); - mService.registerHeartbeatTimer(mService.getHeartbeatDuration()); - } - } - } - - - private BroadcastReceiver receiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - // 获取广播事件 - String action = intent.getAction(); - if(TextUtils.equals(ACTION_HEARTBEAT, action)){ - Log.i(TAG, "receiver ACTION"); - registerHeartbeatTimer(getHeartbeatDuration()); - } - } - }; - - private void registerHeartbeatTimer(long timeout){ - - // 创建延迟意图 - Intent alarmIntent = new Intent(); - alarmIntent.setAction(ACTION_HEARTBEAT); - PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, alarmIntent, 0); - - AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE); - alarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + timeout, pendingIntent); - - // alarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime() + timeout, pendingIntent); - } - - private void registerPhotoTimer(long ts){ - - // 创建延迟意图 - Intent alarmIntent = new Intent(); - alarmIntent.setAction(ACTION_TAKE_PHOTO); - PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, alarmIntent, 0); - - AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE); - alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, ts, pendingIntent); - } protected native boolean init(String appPath, String ip, int port, String cmdid); - protected native long getHeartbeatDuration(); - - protected native boolean takePhoto(int channel, int preset, String path, String fileName); + protected native long[] getPhotoTimeData(); + protected native boolean takePhoto(int channel, int preset, String path, String fileName, boolean sendToCma); + protected native boolean sendHeartbeat(); + protected native boolean fireTimeout(long uid); protected native boolean uninit(); - protected long mHandler = 0; private AlarmReceiver alarmReceiver = null;