From db99318e284e8a13c580cad1e1d3afca95db49b7 Mon Sep 17 00:00:00 2001 From: BlueMatthew Date: Thu, 21 Dec 2023 14:20:02 +0800 Subject: [PATCH] =?UTF-8?q?=E9=A1=B9=E7=9B=AE=E7=BB=93=E6=9E=84=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E5=92=8CAI=E8=AF=86=E5=88=AB=E7=9A=84=E6=94=AF?= =?UTF-8?q?=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/cpp/MicroPhoto.cpp | 2 +- app/src/main/cpp/PhoneDevice.cpp | 17 +- app/src/main/cpp/PhoneDevice.h | 13 +- app/src/main/cpp/PhoneDevice2.h | 3 +- app/src/main/cpp/ncnn/yolov5ncnn.cpp | 35 +-- app/src/main/cpp/ncnn/yolov5ncnn.h | 26 +-- .../java/com/xypower/mpapp/AppMaster.java | 3 +- .../com/xypower/mpapp/ChannelActivity.java | 6 +- .../java/com/xypower/mpapp/MainActivity.java | 16 +- .../com/xypower/mpapp/MicroPhotoService.java | 85 ++----- .../main/res/layout-land/activity_main.xml | 7 +- app/src/main/res/values-night/themes.xml | 1 + app/src/main/res/values/themes.xml | 2 +- common/build.gradle | 2 +- .../com/xypower/common/InetAddressUtils.java | 28 +++ .../com/xypower/common/MicroPhotoContext.java | 23 ++ mpmaster/build.gradle | 1 + mpmaster/libs/devapi.aar | Bin 0 -> 42083 bytes mpmaster/src/main/AndroidManifest.xml | 9 +- .../java/com/xypower/mpmaster/AppMaster.java | 211 ++++++++++++++++++ .../xypower/mpmaster/MicroPhotoService.java | 16 ++ 21 files changed, 386 insertions(+), 120 deletions(-) create mode 100644 common/src/main/java/com/xypower/common/InetAddressUtils.java create mode 100644 mpmaster/libs/devapi.aar create mode 100644 mpmaster/src/main/java/com/xypower/mpmaster/AppMaster.java create mode 100644 mpmaster/src/main/java/com/xypower/mpmaster/MicroPhotoService.java diff --git a/app/src/main/cpp/MicroPhoto.cpp b/app/src/main/cpp/MicroPhoto.cpp index dea570fa..2853725e 100644 --- a/app/src/main/cpp/MicroPhoto.cpp +++ b/app/src/main/cpp/MicroPhoto.cpp @@ -223,7 +223,7 @@ Java_com_xypower_mpapp_MicroPhotoService_init( extern "C" JNIEXPORT jboolean JNICALL Java_com_xypower_mpapp_MicroPhotoService_notifyToTakePhoto( JNIEnv* env, - jobject pThis, jlong handler, jint channel, jint preset, jlong scheduleTime, jstring path, jstring fileName, jboolean sendToCma) { + jobject pThis, jlong handler, jint channel, jint preset, jlong scheduleTime, jboolean sendToCma) { if (channel < 1 || channel > 0xFF) { diff --git a/app/src/main/cpp/PhoneDevice.cpp b/app/src/main/cpp/PhoneDevice.cpp index 3411f3c9..a933095e 100644 --- a/app/src/main/cpp/PhoneDevice.cpp +++ b/app/src/main/cpp/PhoneDevice.cpp @@ -21,6 +21,7 @@ #include "PhoneDevice.h" #include #include +#include #include "ncnn/yolov5ncnn.h" #include @@ -242,7 +243,15 @@ void CPhoneDevice::SetRecognizationCfg(const IDevice::CFG_RECOGNIZATION* pRecogn ncnn_init(); std::string paramFile = m_appPath + (APP_PATH_RECOG_PARAM); std::string binFile = m_appPath + (APP_PATH_RECOG_BIN); - YoloV5Ncnn_Init(paramFile, binFile); + bool res = YoloV5Ncnn_Init(paramFile, binFile); + if (res) + { + XYLOG(XYLOG_SEVERITY_INFO, "Succeeded to Init NCNN"); + } + else + { + XYLOG(XYLOG_SEVERITY_ERROR, "Failed to Init NCNN"); + } } m_pRecognizationCfg = pRecognizationCfg; @@ -738,11 +747,11 @@ bool CPhoneDevice::OnImageReady(cv::Mat& mat) // cv::Rect rc(0, 0, mat.cols, mat.rows); // cv::rectangle (mat, rc, cv::Scalar(255, 255, 255), cv::FILLED); + std::vector objs; if ((m_pRecognizationCfg != NULL) && (m_pRecognizationCfg->enabled != 0) && (mPhotoInfo.recognization != 0)) { // visualize(ncnnPath.c_str(), in); - std::vector objs; #ifdef _DEBUG double startTime = ncnn::get_current_time(); #endif // _DEBUG @@ -771,7 +780,7 @@ bool CPhoneDevice::OnImageReady(cv::Mat& mat) }; #endif cv::Scalar borderColor(m_pRecognizationCfg->borderColor & 0xFF, (m_pRecognizationCfg->borderColor & 0xFF00) >> 8, (m_pRecognizationCfg->borderColor & 0xFF0000) >> 16); - for (std::vector::const_iterator it = objs.cbegin(); it != objs.cend(); ++it) + for (std::vector::const_iterator it = objs.cbegin(); it != objs.cend(); ++it) { if (it->label >= m_pRecognizationCfg->items.size()) { @@ -860,7 +869,7 @@ bool CPhoneDevice::OnImageReady(cv::Mat& mat) { ALOGI("Succeeded to write photo: %s", fullPath.c_str()); } - TakePhotoCb(res, mPhotoInfo, fullPath, time(NULL)); + TakePhotoCb(res, mPhotoInfo, fullPath, time(NULL), objs); } else { diff --git a/app/src/main/cpp/PhoneDevice.h b/app/src/main/cpp/PhoneDevice.h index 7bc42b4d..083874fc 100644 --- a/app/src/main/cpp/PhoneDevice.h +++ b/app/src/main/cpp/PhoneDevice.h @@ -193,11 +193,22 @@ protected: bool SendBroadcastMessage(std::string action, int value); // bool MatchCaptureSizeRequest(ACameraManager *cameraManager, const char *selectedCameraId, unsigned int width, unsigned int height, uint32_t cameraOrientation_, + inline bool TakePhotoCb(bool res, const IDevice::PHOTO_INFO& photoInfo, const string& path, time_t photoTime, const std::vector& objects) const + { + if (m_listener != NULL) + { + return m_listener->OnPhotoTaken(res, photoInfo, path, photoTime, objects); + } + + return false; + } + inline bool TakePhotoCb(bool res, const IDevice::PHOTO_INFO& photoInfo, const string& path, time_t photoTime) const { if (m_listener != NULL) { - return m_listener->OnPhotoTaken(res, photoInfo, path, photoTime); + std::vector objects; + return m_listener->OnPhotoTaken(res, photoInfo, path, photoTime, objects); } return false; diff --git a/app/src/main/cpp/PhoneDevice2.h b/app/src/main/cpp/PhoneDevice2.h index dc9cfe43..6daffbc1 100644 --- a/app/src/main/cpp/PhoneDevice2.h +++ b/app/src/main/cpp/PhoneDevice2.h @@ -67,7 +67,8 @@ protected: { if (m_listener != NULL) { - return m_listener->OnPhotoTaken(res, photoInfo, path, photoTime); + std::vector objects; + return m_listener->OnPhotoTaken(res, photoInfo, path, photoTime, objects); } return false; diff --git a/app/src/main/cpp/ncnn/yolov5ncnn.cpp b/app/src/main/cpp/ncnn/yolov5ncnn.cpp index 75882087..ca06cb05 100644 --- a/app/src/main/cpp/ncnn/yolov5ncnn.cpp +++ b/app/src/main/cpp/ncnn/yolov5ncnn.cpp @@ -9,7 +9,7 @@ ncnn::Net yolov5; DEFINE_LAYER_CREATOR(YoloV5Focus) -void qsort_descent_inplace(std::vector& faceobjects, int left, int right) +void qsort_descent_inplace(std::vector& faceobjects, int left, int right) { int i = left; int j = right; @@ -46,7 +46,7 @@ void qsort_descent_inplace(std::vector& faceobjects, int left, int right } } -void qsort_descent_inplace(std::vector& faceobjects) +void qsort_descent_inplace(std::vector& faceobjects) { if (faceobjects.empty()) return; @@ -54,7 +54,7 @@ void qsort_descent_inplace(std::vector& faceobjects) qsort_descent_inplace(faceobjects, 0, faceobjects.size() - 1); } -void nms_sorted_bboxes(const std::vector& faceobjects, std::vector& picked, float nms_threshold) +void nms_sorted_bboxes(const std::vector& faceobjects, std::vector& picked, float nms_threshold) { picked.clear(); @@ -68,12 +68,12 @@ void nms_sorted_bboxes(const std::vector& faceobjects, std::vector& for (int i = 0; i < n; i++) { - const Object& a = faceobjects[i]; + const IDevice::RECOG_OBJECT& a = faceobjects[i]; int keep = 1; for (int j = 0; j < (int)picked.size(); j++) { - const Object& b = faceobjects[picked[j]]; + const IDevice::RECOG_OBJECT& b = faceobjects[picked[j]]; // intersection over union float inter_area = intersection_area(a, b); @@ -88,7 +88,7 @@ void nms_sorted_bboxes(const std::vector& faceobjects, std::vector& } } -void generate_proposals(const ncnn::Mat& anchors, int stride, const ncnn::Mat& in_pad, const ncnn::Mat& feat_blob, float prob_threshold, std::vector& objects) +void generate_proposals(const ncnn::Mat& anchors, int stride, const ncnn::Mat& in_pad, const ncnn::Mat& feat_blob, float prob_threshold, std::vector& objects) { const int num_grid = feat_blob.h; @@ -162,7 +162,7 @@ void generate_proposals(const ncnn::Mat& anchors, int stride, const ncnn::Mat& i float x1 = pb_cx + pb_w * 0.5f; float y1 = pb_cy + pb_h * 0.5f; - Object obj; + IDevice::RECOG_OBJECT obj; obj.x = x0; obj.y = y0; obj.w = x1 - x0; @@ -177,6 +177,7 @@ void generate_proposals(const ncnn::Mat& anchors, int stride, const ncnn::Mat& i } } + // public native boolean Init(AssetManager mgr); bool YoloV5Ncnn_Init(const std::string& paramFile, const std::string& binFile) { @@ -221,7 +222,7 @@ bool YoloV5Ncnn_Init(const std::string& paramFile, const std::string& binFile) } // public native Obj[] Detect(Bitmap bitmap, boolean use_gpu); -bool YoloV5NcnnDetect( ncnn::Mat& mat, bool use_gpu, std::vector& objects) +bool YoloV5NcnnDetect( ncnn::Mat& mat, bool use_gpu, std::vector& objects) { if (use_gpu && ncnn::get_gpu_count() == 0) { @@ -282,7 +283,7 @@ bool YoloV5NcnnDetect( ncnn::Mat& mat, bool use_gpu, std::vector& object ex.input("images", in_pad); - std::vector proposals; + std::vector proposals; // anchor setting from yolov5/models/yolov5s.yaml @@ -299,7 +300,7 @@ bool YoloV5NcnnDetect( ncnn::Mat& mat, bool use_gpu, std::vector& object anchors[4] = 33.f; anchors[5] = 23.f; - std::vector objects8; + std::vector objects8; generate_proposals(anchors, 8, in_pad, out, prob_threshold, objects8); proposals.insert(proposals.end(), objects8.begin(), objects8.end()); @@ -318,7 +319,7 @@ bool YoloV5NcnnDetect( ncnn::Mat& mat, bool use_gpu, std::vector& object anchors[4] = 59.f; anchors[5] = 119.f; - std::vector objects16; + std::vector objects16; generate_proposals(anchors, 16, in_pad, out, prob_threshold, objects16); proposals.insert(proposals.end(), objects16.begin(), objects16.end()); @@ -337,7 +338,7 @@ bool YoloV5NcnnDetect( ncnn::Mat& mat, bool use_gpu, std::vector& object anchors[4] = 373.f; anchors[5] = 326.f; - std::vector objects32; + std::vector objects32; generate_proposals(anchors, 32, in_pad, out, prob_threshold, objects32); proposals.insert(proposals.end(), objects32.begin(), objects32.end()); @@ -413,7 +414,7 @@ bool YoloV5NcnnDetect( ncnn::Mat& mat, bool use_gpu, std::vector& object return true; } -bool YoloV5NcnnDetect( cv::Mat& mat, bool use_gpu, std::vector& objects) +bool YoloV5NcnnDetect( cv::Mat& mat, bool use_gpu, std::vector& objects) { if (use_gpu && ncnn::get_gpu_count() == 0) { @@ -472,7 +473,7 @@ bool YoloV5NcnnDetect( cv::Mat& mat, bool use_gpu, std::vector& objects) ex.input("images", in_pad); - std::vector proposals; + std::vector proposals; // anchor setting from yolov5/models/yolov5s.yaml @@ -489,7 +490,7 @@ bool YoloV5NcnnDetect( cv::Mat& mat, bool use_gpu, std::vector& objects) anchors[4] = 33.f; anchors[5] = 23.f; - std::vector objects8; + std::vector objects8; generate_proposals(anchors, 8, in_pad, out, prob_threshold, objects8); proposals.insert(proposals.end(), objects8.begin(), objects8.end()); @@ -508,7 +509,7 @@ bool YoloV5NcnnDetect( cv::Mat& mat, bool use_gpu, std::vector& objects) anchors[4] = 59.f; anchors[5] = 119.f; - std::vector objects16; + std::vector objects16; generate_proposals(anchors, 16, in_pad, out, prob_threshold, objects16); proposals.insert(proposals.end(), objects16.begin(), objects16.end()); @@ -527,7 +528,7 @@ bool YoloV5NcnnDetect( cv::Mat& mat, bool use_gpu, std::vector& objects) anchors[4] = 373.f; anchors[5] = 326.f; - std::vector objects32; + std::vector objects32; generate_proposals(anchors, 32, in_pad, out, prob_threshold, objects32); proposals.insert(proposals.end(), objects32.begin(), objects32.end()); diff --git a/app/src/main/cpp/ncnn/yolov5ncnn.h b/app/src/main/cpp/ncnn/yolov5ncnn.h index bc9167fc..7fa27d86 100644 --- a/app/src/main/cpp/ncnn/yolov5ncnn.h +++ b/app/src/main/cpp/ncnn/yolov5ncnn.h @@ -14,6 +14,8 @@ #include +#include + extern ncnn::UnlockedPoolAllocator g_blob_pool_allocator; extern ncnn::PoolAllocator g_workspace_pool_allocator; @@ -65,17 +67,7 @@ public: } }; -struct Object -{ - float x; - float y; - float w; - float h; - int label; - float prob; -}; - -static inline float intersection_area(const Object& a, const Object& b) +static inline float intersection_area(const IDevice::RECOG_OBJECT& a, const IDevice::RECOG_OBJECT& b) { if (a.x > b.x + b.w || a.x + a.w < b.x || a.y > b.y + b.h || a.y + a.h < b.y) { @@ -89,18 +81,18 @@ static inline float intersection_area(const Object& a, const Object& b) return inter_width * inter_height; } -void qsort_descent_inplace(std::vector& faceobjects, int left, int right); +void qsort_descent_inplace(std::vector& faceobjects, int left, int right); -void qsort_descent_inplace(std::vector& faceobjects); +void qsort_descent_inplace(std::vector& faceobjects); -void nms_sorted_bboxes(const std::vector& faceobjects, std::vector& picked, float nms_threshold); +void nms_sorted_bboxes(const std::vector& faceobjects, std::vector& picked, float nms_threshold); static inline float sigmoid(float x) { return static_cast(1.f / (1.f + exp(-x))); } -void generate_proposals(const ncnn::Mat& anchors, int stride, const ncnn::Mat& in_pad, const ncnn::Mat& feat_blob, float prob_threshold, std::vector& objects); +void generate_proposals(const ncnn::Mat& anchors, int stride, const ncnn::Mat& in_pad, const ncnn::Mat& feat_blob, float prob_threshold, std::vector& objects); inline void ncnn_init() { @@ -116,5 +108,5 @@ inline void ncnn_uninit() bool YoloV5Ncnn_Init(const std::string& paramFile, const std::string& binFile); // public native Obj[] Detect(Bitmap bitmap, boolean use_gpu); -bool YoloV5NcnnDetect( ncnn::Mat& mat, bool use_gpu, std::vector& objects); -bool YoloV5NcnnDetect( cv::Mat& mat, bool use_gpu, std::vector& objects); \ No newline at end of file +bool YoloV5NcnnDetect( ncnn::Mat& mat, bool use_gpu, std::vector& objects); +bool YoloV5NcnnDetect( cv::Mat& mat, bool use_gpu, std::vector& objects); \ No newline at end of file diff --git a/app/src/main/java/com/xypower/mpapp/AppMaster.java b/app/src/main/java/com/xypower/mpapp/AppMaster.java index 7c975b4a..fba9bed5 100644 --- a/app/src/main/java/com/xypower/mpapp/AppMaster.java +++ b/app/src/main/java/com/xypower/mpapp/AppMaster.java @@ -20,6 +20,7 @@ import android.util.Pair; import com.dev.devapi.api.SysApi; import com.xypower.common.FileDownloader; +import com.xypower.common.MicroPhotoContext; import org.json.JSONObject; @@ -119,7 +120,7 @@ public class AppMaster { private void upgradeApp(String url) { FileDownloader dl = new FileDownloader(); - File path = new File(MicroPhotoService.buildAppDir(mService.getApplicationContext()), "packages"); + File path = new File(MicroPhotoContext.buildAppDir(mService.getApplicationContext()), "packages"); if (!path.exists()) { path.mkdirs(); } diff --git a/app/src/main/java/com/xypower/mpapp/ChannelActivity.java b/app/src/main/java/com/xypower/mpapp/ChannelActivity.java index 99469c87..f013be84 100644 --- a/app/src/main/java/com/xypower/mpapp/ChannelActivity.java +++ b/app/src/main/java/com/xypower/mpapp/ChannelActivity.java @@ -8,8 +8,8 @@ import android.text.TextUtils; import android.view.MenuItem; import android.view.View; import android.view.inputmethod.InputMethodManager; -import androidx.appcompat.app.ActionBar; +import com.xypower.common.MicroPhotoContext; import com.xypower.mpapp.databinding.ActivityChannelBinding; import org.json.JSONException; @@ -99,7 +99,7 @@ public class ChannelActivity extends AppCompatActivity { binding.exposuretime.setText("0"); binding.sensitivity.setText("0"); - String appPath = MicroPhotoService.buildAppDir(getApplicationContext()); + String appPath = MicroPhotoContext.buildAppDir(getApplicationContext()); InputStreamReader inputStreamReader = null; BufferedReader bufferedReader = null; @@ -169,7 +169,7 @@ public class ChannelActivity extends AppCompatActivity { private void saveChannelParams(int channel) { JSONObject jsonObject = null; - String appPath = MicroPhotoService.buildAppDir(this.getApplicationContext()); + String appPath = MicroPhotoContext.buildAppDir(this.getApplicationContext()); InputStreamReader inputStreamReader = null; BufferedReader bufferedReader = null; File dataPath = new File(appPath + "data/channels/"); diff --git a/app/src/main/java/com/xypower/mpapp/MainActivity.java b/app/src/main/java/com/xypower/mpapp/MainActivity.java index 18160ca0..9c8030c2 100644 --- a/app/src/main/java/com/xypower/mpapp/MainActivity.java +++ b/app/src/main/java/com/xypower/mpapp/MainActivity.java @@ -4,13 +4,11 @@ import android.Manifest; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; import android.graphics.SurfaceTexture; import android.hardware.Camera; import android.location.Location; import android.location.LocationListener; import android.location.LocationManager; -import android.net.Uri; import android.os.Build; import android.os.Environment; import android.os.FileObserver; @@ -18,13 +16,13 @@ import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.Messenger; +import android.os.StrictMode; import android.os.SystemClock; import androidx.annotation.NonNull; import androidx.appcompat.app.ActionBar; import androidx.core.app.ActivityCompat; import androidx.appcompat.app.AppCompatActivity; -import androidx.core.content.FileProvider; import android.os.Bundle; import android.telephony.SubscriptionManager; @@ -38,6 +36,7 @@ import android.view.WindowManager; import android.widget.Toast; import com.dowse.camera.client.DSCameraManager; +import com.xypower.common.MicroPhotoContext; import com.xypower.mpapp.databinding.ActivityMainBinding; import com.xypower.mpapp.utils.RandomReader; //import com.xinyingpower.microphoto.request.INettyMessageListener; @@ -54,8 +53,6 @@ import java.io.UnsupportedEncodingException; import java.lang.reflect.Method; import java.text.SimpleDateFormat; import java.util.Date; -import java.util.List; -import java.util.TimeZone; import org.json.JSONException; import org.json.JSONObject; @@ -209,6 +206,9 @@ public class MainActivity extends AppCompatActivity { int width = defaultDisplay.getWidth(); int height = defaultDisplay.getHeight(); + StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build(); + StrictMode.setThreadPolicy(policy); + Intent intent = getIntent(); final int noDelay = intent.getIntExtra("noDelay", 0); int rebootFlag = intent.getIntExtra("reboot", 0); @@ -491,7 +491,7 @@ public class MainActivity extends AppCompatActivity { // call the superclass method first super.onStart(); - String logFilePath = MicroPhotoService.buildAppDir(this.getApplicationContext()); + String logFilePath = MicroPhotoContext.buildAppDir(this.getApplicationContext()); logFilePath += "logs/log.txt"; mLogFileObserver = new LogFileObserver(logFilePath); @@ -557,7 +557,7 @@ public class MainActivity extends AppCompatActivity { AppConfig appConfig = new AppConfig(); - String appPath = MicroPhotoService.buildAppDir(context); + String appPath = MicroPhotoContext.buildAppDir(context); InputStreamReader inputStreamReader = null; BufferedReader bufferedReader = null; @@ -606,7 +606,7 @@ public class MainActivity extends AppCompatActivity { private void saveAppConfig(String cmdid, String server, int port, int protocol, int networkProtocol) { - String appPath = MicroPhotoService.buildAppDir(this.getApplicationContext()); + String appPath = MicroPhotoContext.buildAppDir(this.getApplicationContext()); InputStreamReader inputStreamReader = null; BufferedReader bufferedReader = null; OutputStreamWriter outputStreamWriter = null; diff --git a/app/src/main/java/com/xypower/mpapp/MicroPhotoService.java b/app/src/main/java/com/xypower/mpapp/MicroPhotoService.java index 24fea552..b3cae42a 100644 --- a/app/src/main/java/com/xypower/mpapp/MicroPhotoService.java +++ b/app/src/main/java/com/xypower/mpapp/MicroPhotoService.java @@ -22,7 +22,6 @@ import android.net.wifi.WifiManager; import android.os.BatteryManager; import android.os.Build; import android.os.Bundle; -import android.os.Environment; import android.os.Handler; import android.os.IBinder; import android.os.Message; @@ -45,9 +44,12 @@ import android.widget.Toast; import com.dev.devapi.api.SysApi; import com.xypower.common.FileDownloader; +import com.xypower.common.InetAddressUtils; +import com.xypower.common.MicroPhotoContext; import java.io.File; import java.lang.reflect.Method; +import java.net.InetAddress; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.ArrayList; @@ -255,7 +257,7 @@ public class MicroPhotoService extends Service { int preset = (int) ((val & 0xFF00L) >> 8); Log.i(TAG, "PhotoTimer Fired: CH=" + channel + " PR=" + preset); - mService.notifyToTakePhoto(mService.mNativeHandle, channel, preset, ts, mService.buildPhotoDir(mService.getApplicationContext(), channel), mService.buildPhotoFileName(channel, preset, ts), true); + mService.notifyToTakePhoto(mService.mNativeHandle, channel, preset, ts, true); } } @@ -277,7 +279,7 @@ public class MicroPhotoService extends Service { long ts = System.currentTimeMillis() / 1000; Log.i(TAG, "Take Photo CH=" + channel + " PR=" + preset + " Mannually"); - mService.notifyToTakePhoto(mService.mNativeHandle, channel, preset, ts, mService.buildPhotoDir(mService.getApplicationContext(), channel), mService.buildPhotoFileName(channel, preset, ts), photoOrVideo); + mService.notifyToTakePhoto(mService.mNativeHandle, channel, preset, ts, photoOrVideo); } else if (TextUtils.equals(ACTION_TIMEOUT, action)) { long uid = intent.getLongExtra(EXTRA_PARAM_TIMER_UID, 0); long expectedTimes = intent.getLongExtra(EXTRA_PARAM_TIMES, 0); @@ -524,18 +526,31 @@ public class MicroPhotoService extends Service { mMessenger = intent.getParcelableExtra("messenger"); } - String appPath = buildAppDir(this.getApplicationContext()); + String appPath = MicroPhotoContext.buildAppDir(this.getApplicationContext()); - String ip = intent.getStringExtra("server"); + String server = intent.getStringExtra("server"); int port = intent.getIntExtra("port", 0); String cmdid = intent.getStringExtra("cmdid"); int protocol = intent.getIntExtra("protocol", 0); int networkProtocol = intent.getIntExtra("networkProtocol", 0); - Log.i(TAG, "AppPath=" + appPath + " Server=" + ip + ":" + port + " cmdid=" + cmdid + " Protocol=" + protocol + " Network=" + networkProtocol); + if (!InetAddressUtils.isIPv4Address(server) && !InetAddressUtils.isIPv6Address(server)) { + // It is a domain + InetAddress addr = null; + try { + addr = InetAddress.getByName(server); + } catch (Exception e) { + e.printStackTrace(); + } + if (addr != null) { + server = addr.getHostAddress(); + } + } + Log.i(TAG, "AppPath=" + appPath + " Server=" + server + ":" + port + " cmdid=" + cmdid + " Protocol=" + protocol + " Network=" + networkProtocol); MicroPhotoService service = MicroPhotoService.this; - service.mNativeHandle = init(appPath, ip, port, cmdid, protocol, networkProtocol); + + service.mNativeHandle = init(appPath, server, port, cmdid, protocol, networkProtocol); if (service.mNativeHandle !=0) { service.mCmdid = cmdid; @@ -676,58 +691,6 @@ public class MicroPhotoService extends Service { return notificationBuilder.build(); } - public static String buildAppDir(Context contxt) { - - /* - File[] paths = contxt.getExternalFilesDirs(null); - - if (paths == null || paths.length == 0) { - return null; - } - - File path = paths[0]; - */ - - File path = new File(Environment.getExternalStorageDirectory(), contxt.getPackageName() + "/"); - - if (!path.exists() && !path.mkdirs()) { - return null; - } - String p = path.getAbsolutePath(); - if (!p.endsWith(File.separator)) { - p += File.separator; - } - return p; - } - - public static String buildPhotoDir(Context contxt, int channel) { - // File path = new File(Environment.getExternalStorageDirectory(), "com.xinyingpower.mp/photos/"); - - String appDir = buildAppDir(contxt); - if (appDir == null) { - return null; - } - - File path = new File(appDir, "photos/"); - - if (!path.exists() && !path.mkdirs()) { - return null; - } - String p = path.getAbsolutePath(); - if (!p.endsWith(File.separator)) { - p += File.separator; - } - return p; - } - - private String buildPhotoFileName(int channel, int preset, long ts) { - - LocalDateTime nowDT = LocalDateTime.now(); - String date = nowDT.format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss.S")); - String photoFile = "img_" + Integer.toString(channel) + "_" + Integer.toHexString(preset).toUpperCase() + "_" + date + ".jpg"; - return photoFile; - } - public boolean updateTime(long timeInMillis) { try { SysApi.setSystemTime(getApplicationContext(), timeInMillis); @@ -773,7 +736,7 @@ public class MicroPhotoService extends Service { public void downloadAndInstall(final String url) { final Context context = getApplicationContext(); - final String tempPath = buildAppDir(context) + File.separator + "tmp"; + final String tempPath = MicroPhotoContext.buildAppDir(context) + File.separator + "tmp"; File file = new File(tempPath); file.mkdirs(); final String filePath = tempPath + File.separator + "mp.apk"; @@ -919,7 +882,7 @@ cellSignalStrengthGsm.getDbm(); protected native long getHeartbeatDuration(long handler); protected native long[] getPhotoTimeData(long handler); // protected native long[] getNextScheduleItem(long handler); - protected native boolean notifyToTakePhoto(long handler, int channel, int preset, long scheduleTime, String path, String fileName, boolean sendToCma); + protected native boolean notifyToTakePhoto(long handler, int channel, int preset, long scheduleTime, boolean sendToCma); protected native boolean sendHeartbeat(long handler); protected native boolean fireTimeout(long handler, long uid, long times); protected native void updatePosition(long handler, double lon, double lat, long ts); diff --git a/app/src/main/res/layout-land/activity_main.xml b/app/src/main/res/layout-land/activity_main.xml index ecf21d72..5b6de180 100644 --- a/app/src/main/res/layout-land/activity_main.xml +++ b/app/src/main/res/layout-land/activity_main.xml @@ -25,7 +25,8 @@ android:layout_marginLeft="4dp" android:layout_marginTop="8dp" android:ems="10" - android:inputType="none" + android:maxLines="1" + android:inputType="text" android:imeOptions="actionDone" android:singleLine="true" android:text="XY-ANDROIDSIM-001" @@ -58,7 +59,8 @@ android:layout_height="wrap_content" android:layout_marginTop="4dp" android:ems="10" - android:inputType="none" + android:maxLines="1" + android:inputType="text" android:imeOptions="actionDone" android:text="47.96.238.157" app:layout_constraintStart_toStartOf="@+id/cmdid" @@ -71,6 +73,7 @@ android:ems="10" android:inputType="none|number" android:imeOptions="actionDone" + android:maxLines="1" android:text="6891" app:layout_constraintBottom_toBottomOf="@+id/server" app:layout_constraintLeft_toRightOf="@+id/server" diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml index 900228d6..dbed5641 100644 --- a/app/src/main/res/values-night/themes.xml +++ b/app/src/main/res/values-night/themes.xml @@ -5,6 +5,7 @@ @color/purple_200 @color/purple_700 @color/teal_200 + 12sp \ No newline at end of file diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index 7af1342b..9f40e5b4 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -5,7 +5,7 @@ @color/purple_500 @color/purple_700 @color/teal_200 - 14sp + 12sp \ No newline at end of file diff --git a/common/build.gradle b/common/build.gradle index db159a66..b9a578f6 100644 --- a/common/build.gradle +++ b/common/build.gradle @@ -32,5 +32,5 @@ dependencies { testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.3' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' - implementation files('libs/devapi.aar') + compileOnly files('libs/devapi.aar') } \ No newline at end of file diff --git a/common/src/main/java/com/xypower/common/InetAddressUtils.java b/common/src/main/java/com/xypower/common/InetAddressUtils.java new file mode 100644 index 00000000..3cf0bd46 --- /dev/null +++ b/common/src/main/java/com/xypower/common/InetAddressUtils.java @@ -0,0 +1,28 @@ +package com.xypower.common; + +import java.util.regex.Pattern; + +public class InetAddressUtils { + // String regex = "^((25[0-5]|(2[0-4]|1\\d|[1-9]|)\\d)\\.?\\b){4}$"; + private static final Pattern IPV4_PATTERN = Pattern.compile("^(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}$"); + + private static final Pattern IPV6_STD_PATTERN = Pattern.compile("^(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$"); + + private static final Pattern IPV6_HEX_COMPRESSED_PATTERN = Pattern.compile("^((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)::((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)$"); + + public static boolean isIPv4Address(final String input) { + return IPV4_PATTERN.matcher(input).matches(); + } + + public static boolean isIPv6StdAddress(final String input) { + return IPV6_STD_PATTERN.matcher(input).matches(); + } + + public static boolean isIPv6HexCompressedAddress(final String input) { + return IPV6_HEX_COMPRESSED_PATTERN.matcher(input).matches(); + } + + public static boolean isIPv6Address(final String input) { + return isIPv6StdAddress(input) || isIPv6HexCompressedAddress(input); + } +} diff --git a/common/src/main/java/com/xypower/common/MicroPhotoContext.java b/common/src/main/java/com/xypower/common/MicroPhotoContext.java index edbe7dad..31b6ffdc 100644 --- a/common/src/main/java/com/xypower/common/MicroPhotoContext.java +++ b/common/src/main/java/com/xypower/common/MicroPhotoContext.java @@ -1,4 +1,27 @@ package com.xypower.common; +import android.content.Context; +import android.os.Environment; + +import java.io.File; + public class MicroPhotoContext { + + public static final String PACKAGE_NAME_MPAPP = "com.xypower.mpapp"; + public static final String PACKAGE_NAME_MPMASTER = "com.xypower.mpmaster"; + public final static String MASTER_URL = "http://180.166.218.222:40101/?cmdid="; + + public static String buildAppDir(Context contxt) { + + File path = new File(Environment.getExternalStorageDirectory(), contxt.getPackageName() + "/"); + + if (!path.exists() && !path.mkdirs()) { + return null; + } + String p = path.getAbsolutePath(); + if (!p.endsWith(File.separator)) { + p += File.separator; + } + return p; + } } diff --git a/mpmaster/build.gradle b/mpmaster/build.gradle index 8cd8ab9a..f2f1953f 100644 --- a/mpmaster/build.gradle +++ b/mpmaster/build.gradle @@ -36,4 +36,5 @@ dependencies { testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.3' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' + implementation files('libs/devapi.aar') } \ No newline at end of file diff --git a/mpmaster/libs/devapi.aar b/mpmaster/libs/devapi.aar new file mode 100644 index 0000000000000000000000000000000000000000..743280f499aded8ae4bd64e02ec48098986c7fae GIT binary patch literal 42083 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

fW*Ffgc%`gNZ1m1G3b&K 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 Lmo_i>PwRgHkI~xP literal 0 HcmV?d00001 diff --git a/mpmaster/src/main/AndroidManifest.xml b/mpmaster/src/main/AndroidManifest.xml index a59fbf69..2d6bde42 100644 --- a/mpmaster/src/main/AndroidManifest.xml +++ b/mpmaster/src/main/AndroidManifest.xml @@ -7,15 +7,20 @@ + + @@ -29,7 +34,7 @@ + android:exported="true"> diff --git a/mpmaster/src/main/java/com/xypower/mpmaster/AppMaster.java b/mpmaster/src/main/java/com/xypower/mpmaster/AppMaster.java new file mode 100644 index 00000000..9f50428f --- /dev/null +++ b/mpmaster/src/main/java/com/xypower/mpmaster/AppMaster.java @@ -0,0 +1,211 @@ +package com.xypower.mpmaster; + +import android.content.Context; +import android.os.PowerManager; +import android.text.TextUtils; +import android.util.Pair; + +import com.dev.devapi.api.SysApi; +import com.xypower.common.FileDownloader; +import com.xypower.common.MicroPhotoContext; + +import org.json.JSONObject; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.List; + +public class AppMaster { + + private MicroPhotoService mService; + private String mCmdid; + private PowerManager.WakeLock mWakelock; + + public AppMaster(MicroPhotoService service, String cmdid) { + + PowerManager powerManager = (PowerManager) service.getSystemService(Context.POWER_SERVICE); + mWakelock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE, "com.xinyingpower.microphoto:Upgrader"); + + mService = service; + mCmdid = cmdid; + } + + @Override + protected void finalize() { + try { + if (mWakelock != null) { + mWakelock.release(); + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + mWakelock = null; + } + mService = null; + } + + public void start() { + + new Thread(new Runnable() { + @Override + public void run() { + HttpURLConnection httpURLConnection = null; + InputStream inputStream = null; + + try { + String url = MicroPhotoContext.MASTER_URL + URLEncoder.encode(mCmdid, "UTF-8"); + URL mUrl = new URL(url); + httpURLConnection = (HttpURLConnection) mUrl.openConnection(); + httpURLConnection.setConnectTimeout(15000); + httpURLConnection.setReadTimeout(15000); + httpURLConnection.setRequestMethod("POST"); + // httpURLConnection.setRequestProperty("Connection", "Keep-Alive"); + httpURLConnection.setDoInput(true); + httpURLConnection.setDoOutput(true); + + List> postParams = new ArrayList<>(); + postParams.add(new Pair("id", mCmdid)); + // postParams(httpURLConnection.getOutputStream(), postParams); + buildParams(httpURLConnection.getOutputStream()); + httpURLConnection.connect(); + inputStream = httpURLConnection.getInputStream(); + + int responseCode = httpURLConnection.getResponseCode(); + + if (responseCode == HttpURLConnection.HTTP_OK) { + //在子线程中不能操作UI线程,通过handler在UI线程中进行操作 + // handler.sendEmptyMessage(0x00); + String response = convertStreamToString(inputStream); + process(response); + } + } catch (Exception e) { + e.printStackTrace(); + } + + } + }).start(); + } + + private void process(String content) { + if (TextUtils.isEmpty(content)) { + return; + } + + try { + JSONObject jsonObject = new JSONObject(content); + int isUpgrade = jsonObject.optInt("isUpgrade", 0); + String url = jsonObject.optString("url", null); + + if (isUpgrade == 1 && !TextUtils.isEmpty(url)) { + upgradeApp(url); + } + + } catch (Exception e) { + e.printStackTrace(); + } + + } + + private void upgradeApp(String url) { + + FileDownloader dl = new FileDownloader(); + File path = new File(MicroPhotoContext.buildAppDir(mService.getApplicationContext()), "packages"); + if (!path.exists()) { + path.mkdirs(); + } + + File file = new File(path, "app.apk"); + if (file.exists()) { + file.delete(); + } + String apkPath = file.getAbsolutePath(); + if (dl.download(url, apkPath)) { + Context context = mService.getApplicationContext(); + SysApi.installApk(context, apkPath, context.getPackageName(), true); + } + } + + private void buildParams(OutputStream output) { + BufferedWriter bufferedWriter = null; + try { + JSONObject jsonObject = new JSONObject(); + jsonObject.put("id", mCmdid); + + bufferedWriter = new BufferedWriter(new OutputStreamWriter(output, "UTF-8")); + bufferedWriter.write(jsonObject.toString()); + } catch (Exception e) { + e.printStackTrace(); + } finally { + try { + if (bufferedWriter != null) { + bufferedWriter.close(); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + public static void postParams(OutputStream output, List> paramsList) { + + BufferedWriter bufferedWriter = null; + try { + StringBuilder stringBuilder = new StringBuilder(); + for (Pair pair : paramsList) { + if (!TextUtils.isEmpty(stringBuilder)) { + stringBuilder.append("&"); + } + stringBuilder.append(URLEncoder.encode(pair.first, "UTF-8")); + stringBuilder.append("="); + stringBuilder.append(URLEncoder.encode(pair.second, "UTF-8")); + bufferedWriter = new BufferedWriter(new OutputStreamWriter(output, "UTF-8")); + bufferedWriter.write(stringBuilder.toString()); + } + + } catch (Exception e) { + e.printStackTrace(); + } finally { + try { + if (bufferedWriter != null) { + bufferedWriter.close(); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + private String convertStreamToString(InputStream inputStream) { + BufferedReader bufferedReader = null; + StringBuffer stringBuffer = new StringBuffer(); + String line; + try { + bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); + while ((line = bufferedReader.readLine()) != null) { + stringBuffer.append(line).append("\n"); + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + try { + if (bufferedReader != null) { + bufferedReader.close(); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + return stringBuffer.toString(); + } + + +} diff --git a/mpmaster/src/main/java/com/xypower/mpmaster/MicroPhotoService.java b/mpmaster/src/main/java/com/xypower/mpmaster/MicroPhotoService.java new file mode 100644 index 00000000..aff437a5 --- /dev/null +++ b/mpmaster/src/main/java/com/xypower/mpmaster/MicroPhotoService.java @@ -0,0 +1,16 @@ +package com.xypower.mpmaster; + +import android.app.Service; +import android.content.Intent; +import android.os.IBinder; + +public class MicroPhotoService extends Service { + public MicroPhotoService() { + } + + @Override + public IBinder onBind(Intent intent) { + // TODO: Return the communication channel to the service. + throw new UnsupportedOperationException("Not yet implemented"); + } +} \ No newline at end of file