From 4a984f16098f56653d5bd7b9de4e20f57b32449c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A7=9C=E5=A4=A9=E5=AE=87?= <jiangtianyu@wmdigit.com> Date: Tue, 24 Sep 2024 10:07:49 +0800 Subject: [PATCH] =?UTF-8?q?feat(v1.0.2):=20=E5=AE=8C=E5=96=84AIDL=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle | 4 +- app/src/main/AndroidManifest.xml | 9 +- .../wmdigit/cateringdetect/Application.java | 3 + .../wmdigit/common/constants/ErrorCode.java | 26 ++ .../com/wmdigit/common/model/ProductsDTO.java | 52 ++- core/src/main/cpp/videopipe_rk3568.cpp | 18 +- .../java/com/wmdigit/core/CoreModule.java | 5 +- .../com/wmdigit/core/hnsw/HnswRepository.java | 20 + ...eManager.java => VideoPipeRepository.java} | 12 +- .../data/database/dao/ProductsDao.java | 8 + .../repository/ProductsRepository.java | 8 + .../demo/ui/fragment/DemoHomeFragment.java | 2 - .../viewmodel/DataLearningViewModel.java | 13 +- service-sdk/.gitignore | 1 + service-sdk/build.gradle | 54 +++ .../consumer-rules.pro | 0 service-sdk/proguard-rules.pro | 21 + .../service/ExampleInstrumentedTest.java | 26 ++ service-sdk/src/main/AndroidManifest.xml | 4 + .../com/wmdigit/common/model/ProductsDTO.aidl | 3 + .../wmdigit/service/ICateringInterface.aidl | 30 ++ .../wmdigit/service/IOnDetectionListener.aidl | 7 + .../com/wmdigit/service/IOnInitListener.aidl | 6 + .../service/aidl/model/DetectResult.aidl | 2 + .../com/wmdigit/common/model/ProductsDTO.java | 138 +++++++ .../main/java/com/wmdigit/service/WmSdk.java | 297 ++++++++++++++ .../com/wmdigit/service/WmSdkInterface.java | 120 ++++++ .../service/aidl/model/DetectResult.java | 175 ++++++++ .../listener/OnServiceConnectListener.java | 26 ++ .../com/wmdigit/service/ExampleUnitTest.java | 17 + service/build.gradle | 11 + service/src/main/AndroidManifest.xml | 18 + .../com/wmdigit/common/model/ProductsDTO.aidl | 3 + .../wmdigit/service/ICateringInterface.aidl | 7 +- .../wmdigit/service/IOnDetectionListener.aidl | 2 +- .../com/wmdigit/service/ServiceModule.java | 35 ++ .../service/aidl/CateringInterfaceImpl.java | 374 +++++++++++++++++- .../wmdigit/service/aidl/CateringService.java | 68 +++- .../service/aidl/model/DetectResult.java | 77 +++- settings.gradle | 1 + 40 files changed, 1638 insertions(+), 65 deletions(-) create mode 100644 common/src/main/java/com/wmdigit/common/constants/ErrorCode.java rename core/src/main/java/com/wmdigit/core/videopipe/{VideoPipeManager.java => VideoPipeRepository.java} (93%) create mode 100644 service-sdk/.gitignore create mode 100644 service-sdk/build.gradle rename service/src/main/java/com/wmdigit/service/aidl/model/CMakeLists.txt => service-sdk/consumer-rules.pro (100%) create mode 100644 service-sdk/proguard-rules.pro create mode 100644 service-sdk/src/androidTest/java/com/wmdigit/service/ExampleInstrumentedTest.java create mode 100644 service-sdk/src/main/AndroidManifest.xml create mode 100644 service-sdk/src/main/aidl/com/wmdigit/common/model/ProductsDTO.aidl create mode 100644 service-sdk/src/main/aidl/com/wmdigit/service/ICateringInterface.aidl create mode 100644 service-sdk/src/main/aidl/com/wmdigit/service/IOnDetectionListener.aidl create mode 100644 service-sdk/src/main/aidl/com/wmdigit/service/IOnInitListener.aidl create mode 100644 service-sdk/src/main/aidl/com/wmdigit/service/aidl/model/DetectResult.aidl create mode 100644 service-sdk/src/main/java/com/wmdigit/common/model/ProductsDTO.java create mode 100644 service-sdk/src/main/java/com/wmdigit/service/WmSdk.java create mode 100644 service-sdk/src/main/java/com/wmdigit/service/WmSdkInterface.java create mode 100644 service-sdk/src/main/java/com/wmdigit/service/aidl/model/DetectResult.java create mode 100644 service-sdk/src/main/java/com/wmdigit/service/listener/OnServiceConnectListener.java create mode 100644 service-sdk/src/test/java/com/wmdigit/service/ExampleUnitTest.java create mode 100644 service/src/main/aidl/com/wmdigit/common/model/ProductsDTO.aidl create mode 100644 service/src/main/java/com/wmdigit/service/ServiceModule.java diff --git a/app/build.gradle b/app/build.gradle index ea078b8..6072dcd 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -59,8 +59,8 @@ android { //如果åˆå¹¶ä¸èƒ½è§£å†³é—®é¢˜å°±é€‰æ‹©å…¶ä¸ä¸€ä¸ª merge 'META-INF/proguard/androidx-annotations.pro' merge 'META-INF/proguard/coroutines.pro' -// merge 'lib/arm64-v8a/libc++_shared.so' -// merge 'lib/armeabi-v7a/libc++_shared.so' + merge 'lib/arm64-v8a/libc++_shared.so' + merge 'lib/armeabi-v7a/libc++_shared.so' pickFirst 'lib/x86/libc++_shared.so' pickFirst 'lib/x86_64/libc++_shared.so' pickFirst 'lib/arm64-v8a/libc++_shared.so' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e9d315d..081eff2 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -12,10 +12,17 @@ <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" tools:ignore="ScopedStorage" /> - <!--æ‘„åƒå¤´ç›¸å…³æƒé™--> <uses-feature android:name="android.hardware.camera.any" /> <uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.RECORD_AUDIO" /> + <uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> + <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" /> + <uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> + <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> + <uses-permission android:name="android.permission.SYSTEM_OVERLAY_WINDOW" /> + <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" /> + <uses-permission android:name="android.permission.START_ACTIVITIES_FROM_BACKGROUND" /> + <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" /> <application android:name=".Application" diff --git a/app/src/main/java/com/wmdigit/cateringdetect/Application.java b/app/src/main/java/com/wmdigit/cateringdetect/Application.java index 6439a01..678f919 100644 --- a/app/src/main/java/com/wmdigit/cateringdetect/Application.java +++ b/app/src/main/java/com/wmdigit/cateringdetect/Application.java @@ -5,6 +5,7 @@ import com.wmdigit.NetworkModule; import com.wmdigit.common.CommonModule; import com.wmdigit.core.CoreModule; import com.wmdigit.data.LocalDataModule; +import com.wmdigit.service.ServiceModule; /** * @author dizi @@ -45,5 +46,7 @@ public class Application extends android.app.Application { CoreModule.init(this); // åˆå§‹åŒ–ç½‘ç»œæ¨¡å— NetworkModule.init(this); + // åˆå§‹åŒ–æœåŠ¡æ¨¡å— + ServiceModule.init(this); } } diff --git a/common/src/main/java/com/wmdigit/common/constants/ErrorCode.java b/common/src/main/java/com/wmdigit/common/constants/ErrorCode.java new file mode 100644 index 0000000..8a92d54 --- /dev/null +++ b/common/src/main/java/com/wmdigit/common/constants/ErrorCode.java @@ -0,0 +1,26 @@ +package com.wmdigit.common.constants; + +/** + * 错误ç + * @author dizi + */ +public class ErrorCode { + // æˆåŠŸçš„é”™è¯¯ç + public static final int ERROR_CODE_SUCCESS = 0; + + // æ‘„åƒå¤´ä¸å¯ç”¨çš„错误ç + public static final int ERROR_CODE_CAMERA_NOT_AVAILABLE = 2000; + + // 未激活的错误ç + public static final int ERROR_CODE_NOT_ACTIVATED = 2001; + + // å¦ä¹ æ•°æ®åˆå§‹åŒ–未完æˆçš„错误ç + public static final int ERROR_CODE_LEARNING_DATA_INIT_NOT_COMPLETED = 2002; + + // æ‘„åƒå¤´æœªè£å‰ªçš„错误ç + public static final int ERROR_CODE_CAMERA_NOT_CROPPED = 2003; + + // åŒºåŸŸå†…æ— å¯¹è±¡çš„é”™è¯¯ç + public static final int ERROR_CODE_NO_OBJECT_IN_AREA = 2004; + +} diff --git a/common/src/main/java/com/wmdigit/common/model/ProductsDTO.java b/common/src/main/java/com/wmdigit/common/model/ProductsDTO.java index 401e601..4654300 100644 --- a/common/src/main/java/com/wmdigit/common/model/ProductsDTO.java +++ b/common/src/main/java/com/wmdigit/common/model/ProductsDTO.java @@ -1,12 +1,15 @@ package com.wmdigit.common.model; +import android.os.Parcel; +import android.os.Parcelable; + import androidx.annotation.NonNull; /** * 商å“DTO * @author dizi */ -public class ProductsDTO { +public class ProductsDTO implements Parcelable { /** * å“å @@ -40,6 +43,22 @@ public class ProductsDTO { this.onSale = onSale; } + protected ProductsDTO(Parcel in) { + readFromParcel(in); + } + + public static final Creator<ProductsDTO> CREATOR = new Creator<ProductsDTO>() { + @Override + public ProductsDTO createFromParcel(Parcel in) { + return new ProductsDTO(in); + } + + @Override + public ProductsDTO[] newArray(int size) { + return new ProductsDTO[size]; + } + }; + @NonNull @Override public String toString() { @@ -85,4 +104,35 @@ public class ProductsDTO { public void setUnitPrice(String unitPrice) { this.unitPrice = unitPrice; } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeString(productName); + dest.writeString(productCode); + dest.writeString(productMnemonicCode); + dest.writeString(unitPrice); + if (onSale == null) { + dest.writeByte((byte) 0); + } else { + dest.writeByte((byte) 1); + dest.writeInt(onSale); + } + } + + protected void readFromParcel(Parcel in){ + productName = in.readString(); + productCode = in.readString(); + productMnemonicCode = in.readString(); + unitPrice = in.readString(); + if (in.readByte() == 0) { + onSale = null; + } else { + onSale = in.readInt(); + } + } } diff --git a/core/src/main/cpp/videopipe_rk3568.cpp b/core/src/main/cpp/videopipe_rk3568.cpp index 6df6803..5ac233e 100644 --- a/core/src/main/cpp/videopipe_rk3568.cpp +++ b/core/src/main/cpp/videopipe_rk3568.cpp @@ -11,7 +11,7 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) { return JNI_ERR; } - java_class_video_pipe_manager = (jclass)env->NewGlobalRef(env->FindClass("com/wmdigit/core/videopipe/VideoPipeManager")); + java_class_video_pipe_manager = (jclass)env->NewGlobalRef(env->FindClass("com/wmdigit/core/videopipe/VideoPipeRepository")); return JNI_VERSION_1_6; } @@ -21,9 +21,9 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { */ extern "C" JNIEXPORT jint JNICALL -Java_com_wmdigit_core_videopipe_VideoPipeManager_init(JNIEnv *env, jobject thiz, - jstring modelBackBonePath, - jstring modelC3dPath) { +Java_com_wmdigit_core_videopipe_VideoPipeRepository_init(JNIEnv *env, jobject thiz, + jstring modelBackBonePath, + jstring modelC3dPath) { int size_back_bone, size_c3d; unsigned char* data_back_bone = read_file_byte(env, modelBackBonePath, size_back_bone); unsigned char* data_c3d = read_file_byte(env, modelC3dPath, size_c3d); @@ -50,8 +50,8 @@ Java_com_wmdigit_core_videopipe_VideoPipeManager_init(JNIEnv *env, jobject thiz, */ extern "C" JNIEXPORT jint JNICALL -Java_com_wmdigit_core_videopipe_VideoPipeManager_setMarkMat(JNIEnv *env, jobject thiz, - jobject bitmap) { +Java_com_wmdigit_core_videopipe_VideoPipeRepository_setMarkMat(JNIEnv *env, jobject thiz, + jobject bitmap) { // Bitmap转mat cv::Mat mat = convert_bitmap_to_mat(env, bitmap); // RGBA转RGB @@ -63,8 +63,8 @@ Java_com_wmdigit_core_videopipe_VideoPipeManager_setMarkMat(JNIEnv *env, jobject extern "C" JNIEXPORT void JNICALL -Java_com_wmdigit_core_videopipe_VideoPipeManager_feedFrame(JNIEnv *env, jobject thiz, - jobject bitmap) { +Java_com_wmdigit_core_videopipe_VideoPipeRepository_feedFrame(JNIEnv *env, jobject thiz, + jobject bitmap) { // Bitmap转mat cv::Mat mat = convert_bitmap_to_mat(env, bitmap); // rgba转rgb @@ -114,7 +114,7 @@ void feed_frame_callback(int state){ jmethodID j_method_instance = env->GetStaticMethodID( java_class_video_pipe_manager, "getInstance", - "()Lcom/wmdigit/core/videopipe/VideoPipeManager;" + "()Lcom/wmdigit/core/videopipe/VideoPipeRepository;" ); jobject instance=env->CallStaticObjectMethod(java_class_video_pipe_manager, j_method_instance); jmethodID j_method_in_out; diff --git a/core/src/main/java/com/wmdigit/core/CoreModule.java b/core/src/main/java/com/wmdigit/core/CoreModule.java index 065409e..c82fd71 100644 --- a/core/src/main/java/com/wmdigit/core/CoreModule.java +++ b/core/src/main/java/com/wmdigit/core/CoreModule.java @@ -3,10 +3,9 @@ package com.wmdigit.core; import android.content.Context; import com.wmdigit.core.catering.TargetDetectionRepository; -import com.wmdigit.core.hnsw.Hnsw; import com.wmdigit.core.hnsw.HnswRepository; import com.wmdigit.core.opencv.OpencvRepository; -import com.wmdigit.core.videopipe.VideoPipeManager; +import com.wmdigit.core.videopipe.VideoPipeRepository; /** * Core模å—åˆå§‹åŒ–ç±» @@ -35,7 +34,7 @@ public class CoreModule { // åˆå§‹åŒ–ç›®æ ‡æ£€æµ‹ TargetDetectionRepository.getInstance().initTargetDetection(); // åˆå§‹åŒ–è§†é¢‘æµ - VideoPipeManager.getInstance().initVideoPipe(); + VideoPipeRepository.getInstance().initVideoPipe(); // åˆå§‹åŒ–索引库 HnswRepository.getInstance().init(); } diff --git a/core/src/main/java/com/wmdigit/core/hnsw/HnswRepository.java b/core/src/main/java/com/wmdigit/core/hnsw/HnswRepository.java index d5d3102..883450d 100644 --- a/core/src/main/java/com/wmdigit/core/hnsw/HnswRepository.java +++ b/core/src/main/java/com/wmdigit/core/hnsw/HnswRepository.java @@ -42,6 +42,7 @@ public class HnswRepository { * 记录åˆå§‹åŒ–å®Œæˆæƒ…况 */ private boolean initComplete = false; + private OnHnswInitListener listener; public HnswRepository() { hnsw = new Hnsw(); @@ -63,6 +64,9 @@ public class HnswRepository { .observeOn(AndroidSchedulers.mainThread()) .subscribe(o -> { initComplete = true; + if (listener != null){ + listener.onInitSuccess(); + } }); compositeDisposable.add(disposable); } @@ -157,7 +161,23 @@ public class HnswRepository { hnsw.initHnsw(); } + public void setListener(OnHnswInitListener listener) { + this.listener = listener; + if (initComplete && listener != null){ + listener.onInitSuccess(); + } + } + + public boolean isInitComplete() { + return initComplete; + } + public void close(){ compositeDisposable.clear(); + listener = null; + } + + public static interface OnHnswInitListener{ + void onInitSuccess(); } } diff --git a/core/src/main/java/com/wmdigit/core/videopipe/VideoPipeManager.java b/core/src/main/java/com/wmdigit/core/videopipe/VideoPipeRepository.java similarity index 93% rename from core/src/main/java/com/wmdigit/core/videopipe/VideoPipeManager.java rename to core/src/main/java/com/wmdigit/core/videopipe/VideoPipeRepository.java index fafc31b..4babce7 100644 --- a/core/src/main/java/com/wmdigit/core/videopipe/VideoPipeManager.java +++ b/core/src/main/java/com/wmdigit/core/videopipe/VideoPipeRepository.java @@ -11,13 +11,11 @@ import com.wmdigit.data.mmkv.repository.CropLocalRepository; import java.io.File; import java.io.IOException; -import io.reactivex.disposables.Disposable; - /** * 视频æµç®¡ç†ç±» * @author dizi */ -public class VideoPipeManager { +public class VideoPipeRepository { static { System.loadLibrary("video_pipe"); } @@ -42,13 +40,13 @@ public class VideoPipeManager { */ private native void feedFrame(Bitmap bitmap); - private static VideoPipeManager instance; + private static VideoPipeRepository instance; - public static VideoPipeManager getInstance() { + public static VideoPipeRepository getInstance() { if (instance == null){ - synchronized (VideoPipeManager.class){ + synchronized (VideoPipeRepository.class){ if (instance == null){ - instance = new VideoPipeManager(); + instance = new VideoPipeRepository(); } } } diff --git a/data-local/src/main/java/com/wmdigit/data/database/dao/ProductsDao.java b/data-local/src/main/java/com/wmdigit/data/database/dao/ProductsDao.java index 94ca5c2..5d1d8e7 100644 --- a/data-local/src/main/java/com/wmdigit/data/database/dao/ProductsDao.java +++ b/data-local/src/main/java/com/wmdigit/data/database/dao/ProductsDao.java @@ -30,6 +30,13 @@ public interface ProductsDao { @Query("SELECT * FROM Products") List<ProductsPO> getAll(); + /** + * æŸ¥è¯¢å•†å“æ€»æ•° + * @return + */ + @Query("SELECT COUNT(*) FROM Products") + int getCount(); + /** * æ ¹æ®å…³é”®è¯èŽ·å–æ€»æ•° * @param keywords @@ -55,4 +62,5 @@ public interface ProductsDao { */ @Query("SELECT * FROM Products WHERE productCode = :productCode") ProductsPO getByProductCode(String productCode); + } diff --git a/data-local/src/main/java/com/wmdigit/data/database/repository/ProductsRepository.java b/data-local/src/main/java/com/wmdigit/data/database/repository/ProductsRepository.java index 6686489..64c212c 100644 --- a/data-local/src/main/java/com/wmdigit/data/database/repository/ProductsRepository.java +++ b/data-local/src/main/java/com/wmdigit/data/database/repository/ProductsRepository.java @@ -59,6 +59,14 @@ public class ProductsRepository { return getProductsDao().getCountByKeywords(keywords); } + /** + * æŸ¥è¯¢å•†å“æ€»æ•° + * @return + */ + public int queryCount(){ + return getProductsDao().getCount(); + } + /** * æ ¹æ®å…³é”®è¯æŸ¥è¯¢ * @param keywords diff --git a/module-demo/src/main/java/com/wmdigit/cateringdetect/demo/ui/fragment/DemoHomeFragment.java b/module-demo/src/main/java/com/wmdigit/cateringdetect/demo/ui/fragment/DemoHomeFragment.java index fc3b8ba..4042ef8 100644 --- a/module-demo/src/main/java/com/wmdigit/cateringdetect/demo/ui/fragment/DemoHomeFragment.java +++ b/module-demo/src/main/java/com/wmdigit/cateringdetect/demo/ui/fragment/DemoHomeFragment.java @@ -7,14 +7,12 @@ import androidx.recyclerview.widget.GridLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager; import com.wmdigit.camera.CameraxController; -import com.wmdigit.camera.listener.OnImageAnalyzeListener; import com.wmdigit.cateringdetect.demo.R; import com.wmdigit.cateringdetect.demo.databinding.FragmentDemoHomeBinding; import com.wmdigit.cateringdetect.demo.ui.adapter.DemoImagesAdapter; import com.wmdigit.cateringdetect.demo.ui.adapter.ShoppingCartAdapter; import com.wmdigit.cateringdetect.demo.ui.viewmodel.DemoHomeViewModel; import com.wmdigit.common.base.mvvm.BaseMvvmFragment; -import com.wmdigit.core.videopipe.VideoPipeManager; /** diff --git a/module-setting/src/main/java/com/wmdigit/setting/viewmodel/DataLearningViewModel.java b/module-setting/src/main/java/com/wmdigit/setting/viewmodel/DataLearningViewModel.java index a2b997d..5194334 100644 --- a/module-setting/src/main/java/com/wmdigit/setting/viewmodel/DataLearningViewModel.java +++ b/module-setting/src/main/java/com/wmdigit/setting/viewmodel/DataLearningViewModel.java @@ -18,13 +18,12 @@ import com.wmdigit.core.catering.TargetDetectionRepository; import com.wmdigit.core.catering.model.TargetDetectResult; import com.wmdigit.core.hnsw.HnswRepository; import com.wmdigit.core.opencv.OpencvRepository; -import com.wmdigit.core.videopipe.VideoPipeManager; +import com.wmdigit.core.videopipe.VideoPipeRepository; import com.wmdigit.data.database.entity.ProductsPO; import com.wmdigit.data.database.mapper.ProductsMapper; import com.wmdigit.data.database.repository.FeaturesRepository; import com.wmdigit.data.database.repository.ProductsRepository; import com.wmdigit.data.mmkv.repository.CropLocalRepository; -import com.wmdigit.setting.R; import java.util.ArrayList; import java.util.List; @@ -78,7 +77,7 @@ public class DataLearningViewModel extends BaseViewModel { /** * 视频æµå›žè°ƒ */ - private final VideoPipeManager.OnVideoPipeEventListener onVideoPipeEventListener = new VideoPipeManager.OnVideoPipeEventListener() { + private final VideoPipeRepository.OnVideoPipeEventListener onVideoPipeEventListener = new VideoPipeRepository.OnVideoPipeEventListener() { @Override public void onObjectIn() { onObjectInEvent(); @@ -103,7 +102,7 @@ public class DataLearningViewModel extends BaseViewModel { keywords.postValue(""); cameraOpened.postValue(false); // 设置视频æµå›žè°ƒ - VideoPipeManager.getInstance().setListener(onVideoPipeEventListener); + VideoPipeRepository.getInstance().setListener(onVideoPipeEventListener); } /** @@ -127,11 +126,11 @@ public class DataLearningViewModel extends BaseViewModel { // 处ç†Bitmap if (frameCount > 15){ // å–‚ç»™è§†é¢‘æµ - VideoPipeManager.getInstance().processImage(bitmapCopy, true); + VideoPipeRepository.getInstance().processImage(bitmapCopy, true); } else if (frameCount == 15){ // 视频æµè®¾ç½®ç©ºç›˜èƒŒæ™¯ - VideoPipeManager.getInstance().setEmptyImage(bitmapCopy, true); + VideoPipeRepository.getInstance().setEmptyImage(bitmapCopy, true); frameCount++; } else{ @@ -427,7 +426,7 @@ public class DataLearningViewModel extends BaseViewModel { protected void onCleared() { super.onCleared(); // 注销视频æµå›žè°ƒ - VideoPipeManager.getInstance().setListener(null); + VideoPipeRepository.getInstance().setListener(null); // 释放计划任务 if (scheduler != null && !scheduler.isShutdown()){ scheduler.shutdown(); diff --git a/service-sdk/.gitignore b/service-sdk/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/service-sdk/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/service-sdk/build.gradle b/service-sdk/build.gradle new file mode 100644 index 0000000..a8a940d --- /dev/null +++ b/service-sdk/build.gradle @@ -0,0 +1,54 @@ +plugins { + id 'com.android.library' +} + +android { + namespace 'com.wmdigit.service' + compileSdk 33 + + defaultConfig { + minSdk 24 + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles "consumer-rules.pro" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + buildFeatures{ + aidl true + } +} + +dependencies { + + implementation 'androidx.appcompat:appcompat:1.6.1' + implementation 'com.google.android.material:material:1.9.0' + testImplementation 'junit:junit:4.13.2' + androidTestImplementation 'androidx.test.ext:junit:1.2.1' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1' +} + +task makeJar(type: Jar) { + //指定生æˆçš„jarå +// baseName ('WmCateringService_v1.0.2_sdk') + archiveBaseName="WmCateringService_v1.0.2_sdk" + //从哪里打包classæ–‡ä»¶ï¼Œæ ¹æ®ä½ çš„AS版本会所有ä¸åŒ + //但是一定è¦èƒ½åœ¨æ¤è·¯å¾„下å¯ä»¥æ‰¾å¾—到自己写的类 + //å¦‚æžœä½ å°è£…çš„jaråŒ…ç”¨èµ·æ¥æœ‰é—®é¢˜ï¼Œå¾ˆå¯èƒ½æ˜¯æ¤å¤„出错 + from('build/intermediates/javac/debug/classes/') + //去掉ä¸éœ€è¦æ‰“包的目录和文件 + exclude('test/','BuildConfig.class','R.class') + //去掉R$开头的文件 + exclude{ it.name.startsWith('R$') } +} + +makeJar.dependsOn(build) \ No newline at end of file diff --git a/service/src/main/java/com/wmdigit/service/aidl/model/CMakeLists.txt b/service-sdk/consumer-rules.pro similarity index 100% rename from service/src/main/java/com/wmdigit/service/aidl/model/CMakeLists.txt rename to service-sdk/consumer-rules.pro diff --git a/service-sdk/proguard-rules.pro b/service-sdk/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/service-sdk/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/service-sdk/src/androidTest/java/com/wmdigit/service/ExampleInstrumentedTest.java b/service-sdk/src/androidTest/java/com/wmdigit/service/ExampleInstrumentedTest.java new file mode 100644 index 0000000..cb1b1d2 --- /dev/null +++ b/service-sdk/src/androidTest/java/com/wmdigit/service/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package com.wmdigit.service; + +import android.content.Context; + +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see <a href="http://d.android.com/tools/testing">Testing documentation</a> + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + assertEquals("com.wmdigit.service.test", appContext.getPackageName()); + } +} \ No newline at end of file diff --git a/service-sdk/src/main/AndroidManifest.xml b/service-sdk/src/main/AndroidManifest.xml new file mode 100644 index 0000000..a5918e6 --- /dev/null +++ b/service-sdk/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android"> + +</manifest> \ No newline at end of file diff --git a/service-sdk/src/main/aidl/com/wmdigit/common/model/ProductsDTO.aidl b/service-sdk/src/main/aidl/com/wmdigit/common/model/ProductsDTO.aidl new file mode 100644 index 0000000..c21dc01 --- /dev/null +++ b/service-sdk/src/main/aidl/com/wmdigit/common/model/ProductsDTO.aidl @@ -0,0 +1,3 @@ +package com.wmdigit.common.model; + +parcelable ProductsDTO; \ No newline at end of file diff --git a/service-sdk/src/main/aidl/com/wmdigit/service/ICateringInterface.aidl b/service-sdk/src/main/aidl/com/wmdigit/service/ICateringInterface.aidl new file mode 100644 index 0000000..64f4d7a --- /dev/null +++ b/service-sdk/src/main/aidl/com/wmdigit/service/ICateringInterface.aidl @@ -0,0 +1,30 @@ +package com.wmdigit.service; + +import com.wmdigit.service.IOnInitListener; +import com.wmdigit.service.IOnDetectionListener; +import com.wmdigit.service.aidl.model.DetectResult; +import com.wmdigit.common.model.ProductsDTO; + + +interface ICateringInterface { + + void init(IOnInitListener listener); + + void importProducts(in List<ProductsDTO> products); + + void registerDetectionListener(boolean generateBitmap, IOnDetectionListener listener); + + void unregisterDetectionListener(); + + DetectResult autoDetect(boolean generateBitmap); + + boolean checkActivation(); + + boolean checkCameraCrop(); + + boolean checkLearningDataInited(); + + void openSettingPage(); + + void resetCameraBackground(); +} \ No newline at end of file diff --git a/service-sdk/src/main/aidl/com/wmdigit/service/IOnDetectionListener.aidl b/service-sdk/src/main/aidl/com/wmdigit/service/IOnDetectionListener.aidl new file mode 100644 index 0000000..80b87e8 --- /dev/null +++ b/service-sdk/src/main/aidl/com/wmdigit/service/IOnDetectionListener.aidl @@ -0,0 +1,7 @@ +package com.wmdigit.service; + +import com.wmdigit.service.aidl.model.DetectResult; + +interface IOnDetectionListener { + void onDetected(in DetectResult result); +} \ No newline at end of file diff --git a/service-sdk/src/main/aidl/com/wmdigit/service/IOnInitListener.aidl b/service-sdk/src/main/aidl/com/wmdigit/service/IOnInitListener.aidl new file mode 100644 index 0000000..c208326 --- /dev/null +++ b/service-sdk/src/main/aidl/com/wmdigit/service/IOnInitListener.aidl @@ -0,0 +1,6 @@ +package com.wmdigit.service; + + +interface IOnInitListener { + void onComplete(); +} \ No newline at end of file diff --git a/service-sdk/src/main/aidl/com/wmdigit/service/aidl/model/DetectResult.aidl b/service-sdk/src/main/aidl/com/wmdigit/service/aidl/model/DetectResult.aidl new file mode 100644 index 0000000..2a8d44f --- /dev/null +++ b/service-sdk/src/main/aidl/com/wmdigit/service/aidl/model/DetectResult.aidl @@ -0,0 +1,2 @@ +package com.wmdigit.service.aidl.model; +parcelable DetectResult; diff --git a/service-sdk/src/main/java/com/wmdigit/common/model/ProductsDTO.java b/service-sdk/src/main/java/com/wmdigit/common/model/ProductsDTO.java new file mode 100644 index 0000000..4654300 --- /dev/null +++ b/service-sdk/src/main/java/com/wmdigit/common/model/ProductsDTO.java @@ -0,0 +1,138 @@ +package com.wmdigit.common.model; + +import android.os.Parcel; +import android.os.Parcelable; + +import androidx.annotation.NonNull; + +/** + * 商å“DTO + * @author dizi + */ +public class ProductsDTO implements Parcelable { + + /** + * å“å + */ + private String productName; + /** + * 商å“代ç + */ + private String productCode; + /** + * 商å“助记ç + */ + private String productMnemonicCode; + /** + * 商å“å•价(元/份) + */ + private String unitPrice; + /** + * 在售状æ€ï¼Œ1:在售 2:下架 + */ + private Integer onSale; + + public ProductsDTO() { + } + + public ProductsDTO(String productName, String productCode, String productMnemonicCode, String unitPrice, Integer onSale) { + this.productName = productName; + this.productCode = productCode; + this.productMnemonicCode = productMnemonicCode; + this.unitPrice = unitPrice; + this.onSale = onSale; + } + + protected ProductsDTO(Parcel in) { + readFromParcel(in); + } + + public static final Creator<ProductsDTO> CREATOR = new Creator<ProductsDTO>() { + @Override + public ProductsDTO createFromParcel(Parcel in) { + return new ProductsDTO(in); + } + + @Override + public ProductsDTO[] newArray(int size) { + return new ProductsDTO[size]; + } + }; + + @NonNull + @Override + public String toString() { + return super.toString(); + } + + public String getProductName() { + return productName; + } + + public void setProductName(String productName) { + this.productName = productName; + } + + public String getProductCode() { + return productCode; + } + + public void setProductCode(String productCode) { + this.productCode = productCode; + } + + public String getProductMnemonicCode() { + return productMnemonicCode; + } + + public void setProductMnemonicCode(String productMnemonicCode) { + this.productMnemonicCode = productMnemonicCode; + } + + public String getUnitPrice() { + return unitPrice; + } + + public Integer getOnSale() { + return onSale; + } + + public void setOnSale(Integer onSale) { + this.onSale = onSale; + } + + public void setUnitPrice(String unitPrice) { + this.unitPrice = unitPrice; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeString(productName); + dest.writeString(productCode); + dest.writeString(productMnemonicCode); + dest.writeString(unitPrice); + if (onSale == null) { + dest.writeByte((byte) 0); + } else { + dest.writeByte((byte) 1); + dest.writeInt(onSale); + } + } + + protected void readFromParcel(Parcel in){ + productName = in.readString(); + productCode = in.readString(); + productMnemonicCode = in.readString(); + unitPrice = in.readString(); + if (in.readByte() == 0) { + onSale = null; + } else { + onSale = in.readInt(); + } + } +} diff --git a/service-sdk/src/main/java/com/wmdigit/service/WmSdk.java b/service-sdk/src/main/java/com/wmdigit/service/WmSdk.java new file mode 100644 index 0000000..c63ce63 --- /dev/null +++ b/service-sdk/src/main/java/com/wmdigit/service/WmSdk.java @@ -0,0 +1,297 @@ +package com.wmdigit.service; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.content.pm.PackageManager; +import android.os.IBinder; +import android.os.RemoteException; + +import com.wmdigit.common.model.ProductsDTO; +import com.wmdigit.service.aidl.model.DetectResult; +import com.wmdigit.service.listener.OnServiceConnectListener; + +import java.util.List; + +/** + * WmSdk类,实现了WmSdkInterface接å£ï¼Œæä¾›äº†ä¸ŽæœåŠ¡ç«¯è¿›è¡Œé€šä¿¡çš„åŠŸèƒ½ + * @author dizi + */ +public class WmSdk implements WmSdkInterface{ + + /** + * æœåŠ¡åŒ…å,用于绑定æœåŠ¡ + */ + private static final String SERVICE_PACKAGE = "com.wmdigit.cateringdetect"; + /** + * æœåŠ¡åŠ¨ä½œï¼Œç”¨äºŽç»‘å®šæœåŠ¡ + */ + private static final String SERVICE_ACTION = "com.wmdigit.service"; + /** + * WmSdkçš„å•例实例 + */ + private static WmSdk instance; + /** + * 获å–WmSdkçš„å•例实例 + * @return WmSdk的实例 + */ + public static WmSdk getInstance(){ + if (instance == null){ + synchronized (WmSdk.class){ + if (instance == null){ + instance = new WmSdk(); + } + } + } + return instance; + } + + /** + * 应用程åºä¸Šä¸‹æ–‡ + */ + private Context context; + /** + * AIDL接å£å¯¹è±¡ + */ + private ICateringInterface aidlInterface; + /** + * æœåŠ¡è¿žæŽ¥ç›‘å¬å™¨ + */ + private OnServiceConnectListener onServiceConnectListener; + + private boolean isServiceConnected = false; + + /** + * DeathRecipient用于处ç†aidl接å£å¯¹è±¡çš„æ»äº¡æ¶ˆæ¯ + */ + private final IBinder.DeathRecipient deathRecipient = new IBinder.DeathRecipient() { + @Override + public void binderDied() { + if (aidlInterface != null) { + aidlInterface.asBinder().unlinkToDeath(this, 0); + } + } + }; + + /** + * ServiceConnection对象,用于处ç†ä¸ŽæœåŠ¡çš„è¿žæŽ¥å’Œæ–开连接 + */ + private ServiceConnection serviceConnection = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + isServiceConnected = true; + aidlInterface = ICateringInterface.Stub.asInterface(service); + // 注册DeathRecipient + try { + aidlInterface.asBinder().linkToDeath(deathRecipient, 0); + } catch (RemoteException e) { + e.printStackTrace(); + } + if (onServiceConnectListener != null){ + onServiceConnectListener.onConnected(name, service); + } + } + + @Override + public void onServiceDisconnected(ComponentName name) { + isServiceConnected = false; + if (onServiceConnectListener != null){ + onServiceConnectListener.onDisconnected(name); + } + } + }; + + /** + * 绑定æœåŠ¡ + * @param context 应用程åºä¸Šä¸‹æ–‡ + * @param listener æœåŠ¡è¿žæŽ¥ç›‘å¬å™¨ï¼Œç”¨äºŽå¤„ç†æœåŠ¡è¿žæŽ¥çŠ¶æ€çš„å˜åŒ– + */ + @Override + public void bindService(Context context, OnServiceConnectListener listener) { + this.context = context; + this.onServiceConnectListener = listener; + Intent intent = new Intent(); + intent.setPackage(SERVICE_PACKAGE); + intent.setAction(SERVICE_ACTION); + context.bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE); + } + + /** + * 解绑æœåŠ¡ + */ + @Override + public void unbindService() { + if (serviceConnection != null){ + context.unbindService(serviceConnection); + } + } + + /** + * åˆå§‹åŒ–SDK + * @param listener åˆå§‹åŒ–监å¬å™¨ï¼Œç”¨äºŽå¤„ç†åˆå§‹åŒ–过程ä¸çš„回调 + */ + @Override + public void init(IOnInitListener listener) { + try { + aidlInterface.init(listener); + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * å¯¼å…¥äº§å“æ•°æ® + * @param products äº§å“æ•°æ®åˆ—表 + */ + @Override + public void importProducts(List<ProductsDTO> products) { + try { + aidlInterface.importProducts(products); + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * 注册检测监å¬å™¨ + * @param generateBitmap 是å¦ç”Ÿæˆä½å›¾ + * @param listener 检测监å¬å™¨ï¼Œç”¨äºŽå¤„ç†æ£€æµ‹è¿‡ç¨‹ä¸çš„回调 + */ + @Override + public void registerDetectionListener(boolean generateBitmap, IOnDetectionListener listener) { + try { + aidlInterface.registerDetectionListener(generateBitmap, listener); + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * å–æ¶ˆæ³¨å†Œæ£€æµ‹ç›‘å¬å™¨ + */ + @Override + public void unregisterDetectionListener() { + try { + aidlInterface.unregisterDetectionListener(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * 自动检测 + * @param generateBitmap 是å¦ç”Ÿæˆä½å›¾ + * @return 检测结果 + */ + @Override + public DetectResult autoDetect(boolean generateBitmap) { + DetectResult detectResult = null; + try { + detectResult = aidlInterface.autoDetect(generateBitmap); + } catch (Exception e){ + e.printStackTrace(); + } + return detectResult; + } + + /** + * 检查æœåŠ¡åº”ç”¨æ˜¯å¦å·²å®‰è£… + * + * @param context Android上下文,用于访问包管ç†å™¨ä»¥æŸ¥è¯¢åº”ç”¨å®‰è£…çŠ¶æ€ + * @return 如果æœåŠ¡åº”ç”¨å·²å®‰è£…ï¼Œåˆ™è¿”å›žtrueï¼›å¦åˆ™è¿”回false + */ + @Override + public boolean checkServiceAppInstalled(Context context) { + try{ + // å°è¯•èŽ·å–æœåŠ¡åº”ç”¨çš„åŒ…ä¿¡æ¯ + context.getPackageManager().getPackageInfo(SERVICE_PACKAGE, 0); + } + catch (PackageManager.NameNotFoundException e){ + // 如果未找到包,则æœåŠ¡åº”ç”¨æœªå®‰è£… + return false; + } + // 如果æˆåŠŸèŽ·å–到包信æ¯ï¼Œåˆ™æœåŠ¡åº”ç”¨å·²å®‰è£… + return true; + } + + /** + * 检查与æœåŠ¡çš„è¿žæŽ¥çŠ¶æ€ + * + * @return 如果æœåŠ¡è¿žæŽ¥å·²å»ºç«‹ï¼Œåˆ™è¿”å›žtrueï¼›å¦åˆ™è¿”回false + */ + @Override + public boolean checkServiceConnected() { + // 返回当å‰çš„æœåŠ¡è¿žæŽ¥çŠ¶æ€ + return isServiceConnected; + } + /** + * æ£€æŸ¥æ¿€æ´»çŠ¶æ€ + * @return 激活状æ€ï¼Œtrue表示已激活,false表示未激活 + */ + @Override + public boolean checkActivation() { + boolean result = false; + try { + result = aidlInterface.checkActivation(); + } catch (Exception e) { + e.printStackTrace(); + } + return result; + } + + /** + * 检查相机è£å‰ªåŠŸèƒ½ + * @return 相机è£å‰ªåŠŸèƒ½çŠ¶æ€ï¼Œtrue表示支æŒï¼Œfalseè¡¨ç¤ºä¸æ”¯æŒ + */ + @Override + public boolean checkCameraCrop() { + boolean result = false; + try{ + result = aidlInterface.checkCameraCrop(); + } catch (Exception e){ + e.printStackTrace(); + } + return result; + } + + /** + * 检查å¦ä¹ æ•°æ®æ˜¯å¦å·²åˆå§‹åŒ– + * @return å¦ä¹ æ•°æ®åˆå§‹åŒ–状æ€ï¼Œtrue表示已åˆå§‹åŒ–,false表示未åˆå§‹åŒ– + */ + @Override + public boolean checkLearningDataInitCompleted() { + boolean result = false; + try{ + result = aidlInterface.checkLearningDataInited(); + } catch (Exception e){ + e.printStackTrace(); + } + return result; + } + + /** + * æ‰“å¼€è®¾ç½®é¡µé¢ + */ + @Override + public void openSettingPage() { + try { + aidlInterface.openSettingPage(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * é‡ç½®ç›¸æœºèƒŒæ™¯ + */ + @Override + public void resetCameraBackground() { + try { + aidlInterface.resetCameraBackground(); + } catch (Exception e) { + e.printStackTrace(); + } + } +} + diff --git a/service-sdk/src/main/java/com/wmdigit/service/WmSdkInterface.java b/service-sdk/src/main/java/com/wmdigit/service/WmSdkInterface.java new file mode 100644 index 0000000..c4c8aac --- /dev/null +++ b/service-sdk/src/main/java/com/wmdigit/service/WmSdkInterface.java @@ -0,0 +1,120 @@ +package com.wmdigit.service; + +import android.content.Context; + +import com.wmdigit.common.model.ProductsDTO; +import com.wmdigit.service.aidl.model.DetectResult; +import com.wmdigit.service.listener.OnServiceConnectListener; + +import java.util.List; + +/** + * WmSdkç•Œé¢æŽ¥å£ï¼Œå®šä¹‰äº†ä¸€ç³»åˆ—与æœåŠ¡äº¤äº’çš„æ–¹æ³• + */ +public interface WmSdkInterface { + + /** + * 绑定æœåŠ¡ + * + * @param context 上下文,用于绑定æœåŠ¡ + * @param listener æœåŠ¡è¿žæŽ¥ç›‘å¬å™¨ï¼Œå¤„ç†æœåŠ¡è¿žæŽ¥çŠ¶æ€ + */ + void bindService(Context context, OnServiceConnectListener listener); + + /** + * 解绑æœåŠ¡ + * + * è°ƒç”¨æ¤æ–¹æ³•å‰åº”ç¡®ä¿æœåŠ¡å·²ç»‘å®š + */ + void unbindService(); + + /** + * åˆå§‹åŒ–SDK + * + * @param listener åˆå§‹åŒ–监å¬å™¨ï¼Œå¤„ç†åˆå§‹åŒ–结果 + */ + void init(IOnInitListener listener); + + /** + * 导入产å“ä¿¡æ¯ + * + * @param products äº§å“æ•°æ®ä¼ 输对象列表,包å«éœ€è¦å¯¼å…¥çš„产å“ä¿¡æ¯ + */ + void importProducts(List<ProductsDTO> products); + + /** + * 注册检测监å¬å™¨ + * + * @param generateBitmap 是å¦ç”Ÿæˆä½å›¾ + * @param listener 检测事件监å¬å™¨ï¼Œå¤„ç†æ£€æµ‹ç»“æžœ + */ + void registerDetectionListener(boolean generateBitmap, IOnDetectionListener listener); + + /** + * å–æ¶ˆæ³¨å†Œæ£€æµ‹ç›‘å¬å™¨ + * + * è°ƒç”¨æ¤æ–¹æ³•å‰åº”ç¡®ä¿æ£€æµ‹ç›‘å¬å™¨å·²æ³¨å†Œ + */ + void unregisterDetectionListener(); + + /** + * 自动检测 + * + * @param generateBitmap 是å¦ç”Ÿæˆä½å›¾ + * @return 检测结果 + */ + DetectResult autoDetect(boolean generateBitmap); + + + /** + * 检查æœåŠ¡åº”ç”¨ç¨‹åºæ˜¯å¦å·²å®‰è£… + * + * @param context Android应用程åºçš„上下文,用于访问包管ç†å™¨ç‰ç³»ç»ŸæœåŠ¡ + * @return 如果æœåŠ¡åº”ç”¨ç¨‹åºå·²å®‰è£…返回true,å¦åˆ™è¿”回false + */ + boolean checkServiceAppInstalled(Context context); + + /** + * 检查æœåŠ¡æ˜¯å¦å·²è¿žæŽ¥ + * + * @return 如果æœåŠ¡å·²è¿žæŽ¥è¿”å›žtrue,å¦åˆ™è¿”回false + */ + boolean checkServiceConnected(); + + /** + * èŽ·å–æ¿€æ´»çŠ¶æ€ + * + * @return 激活状æ€ï¼Œtrue表示已激活,false表示未激活 + */ + boolean checkActivation(); + + /** + * èŽ·å–æ‘„åƒå¤´è£å‰ªçŠ¶æ€ + * + * @return è£å‰ªçжæ€ï¼Œtrue表示开å¯è£å‰ªï¼Œfalse表示关é—è£å‰ª + */ + boolean checkCameraCrop(); + + /** + * 获å–å¦ä¹ æ•°æ®åˆå§‹åŒ–çŠ¶æ€ + * + * @return åˆå§‹åŒ–状æ€ï¼Œtrue表示已åˆå§‹åŒ–,false表示未åˆå§‹åŒ– + */ + boolean checkLearningDataInitCompleted(); + + /** + * æ‰“å¼€è®¾ç½®é¡µé¢ + * + * æ¤æ–¹æ³•用于引导用户至应用的设置页é¢è¿›è¡Œç›¸å…³é…ç½® + */ + void openSettingPage(); + + /** + * é‡ç½®æ‘„åƒå¤´èƒŒæ™¯ + * + * è°ƒç”¨æ¤æ–¹æ³•å¯ä»¥é‡ç½®æ‘„åƒå¤´çš„èƒŒæ™¯è‡³é»˜è®¤çŠ¶æ€ + */ + void resetCameraBackground(); + +} + diff --git a/service-sdk/src/main/java/com/wmdigit/service/aidl/model/DetectResult.java b/service-sdk/src/main/java/com/wmdigit/service/aidl/model/DetectResult.java new file mode 100644 index 0000000..6b11f93 --- /dev/null +++ b/service-sdk/src/main/java/com/wmdigit/service/aidl/model/DetectResult.java @@ -0,0 +1,175 @@ +package com.wmdigit.service.aidl.model; + +import android.graphics.Bitmap; +import android.os.Parcel; +import android.os.Parcelable; + +import androidx.annotation.NonNull; + +import java.util.ArrayList; +import java.util.List; + +/** + * DetectResult类,表示检测的结果,实现了Parcelable接å£ï¼Œç”¨äºŽç»„ä»¶é—´æ•°æ®ä¼ 递 + * @author dizi + */ +public class DetectResult implements Parcelable { + + /** + * 识别结果的状æ€ç ,默认为0 + */ + private int code = 0; + /** + * 图åƒä½å›¾ + */ + private Bitmap bitmap; + /** + * 识别到的产å“ç¼–ç 列表 + */ + private List<String> productCodes; + + public DetectResult() { + this.bitmap = null; + this.productCodes = new ArrayList<>(); + } + + /** + * æž„é€ å‡½æ•°ï¼Œåˆå§‹åŒ–检测结果对象 + * + * @param code 识别结果的状æ€ç + * @param bitmap 识别的图åƒä½å›¾ + * @param productCodes 识别到的产å“ç¼–ç 列表 + */ + public DetectResult(int code, Bitmap bitmap, List<String> productCodes) { + this.code = code; + this.bitmap = bitmap; + this.productCodes = productCodes != null ? new ArrayList<>(productCodes) : new ArrayList<>(); + } + + /** + * æž„é€ å‡½æ•°ï¼Œç”¨äºŽä»ŽParcelä¸è¯»å–æ•°æ®å¹¶é‡å»ºå¯¹è±¡ + * + * @param in ç”¨äºŽè¯»å–æ•°æ®çš„Parcel对象 + */ + protected DetectResult(Parcel in) { + readFromParcel(in); + } + + /** + * 创建DetectResult对象的Creator实例 + * + * @return DetectResult对象的Creator实例 + */ + public static final Creator<DetectResult> CREATOR = new Creator<DetectResult>() { + @Override + public DetectResult createFromParcel(Parcel in) { + // 从指定的Parcelä¸è¯»å–æ•°æ®å¹¶åˆ›å»ºDetectResult对象 + return new DetectResult(in); + } + + @Override + public DetectResult[] newArray(int size) { + // 创建并返回DetectResult对象的数组 + return new DetectResult[size]; + } + }; + + /** + * æè¿°å†…容的类型,返回0表示没有需è¦ç‰¹æ®Šå¤„ç†çš„内容 + * + * @return int类型,表示内容的类型 + */ + @Override + public int describeContents() { + return 0; + } + + /** + * 将当å‰å¯¹è±¡çš„æ•°æ®å†™å…¥åˆ°Parcelä¸ + * æ¤æ–¹æ³•用于对象的åºåˆ—化,将对象的状æ€è½¬æ¢ä¸ºå¯ä»¥åœ¨è¿›ç¨‹é—´ä¼ è¾“çš„å½¢å¼ + * + * @param dest 用于写入数æ®çš„Parcel对象 + * @param flags 写入æ“ä½œçš„æ ‡å¿—ï¼Œç›®å‰æœªä½¿ç”¨ä½†ä¿ç•™ä»¥å…¼å®¹æ€§ + */ + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(code); + // 先写入productCodesåˆ—è¡¨çš„å¤§å° + dest.writeInt(productCodes.size()); + // 使用StringBuilder将所有产å“ç¼–ç æ‹¼æŽ¥æˆä¸€ä¸ªå—符串 + StringBuilder sb = new StringBuilder(); + for (String productCode : productCodes) { + sb.append(productCode).append(","); + } + dest.writeString(sb.toString()); + // 检查bitmap是å¦ä¸ºç©ºï¼Œå¹¶æ ¹æ®æƒ…å†µå†™å…¥æ ‡å¿—å’Œbitmapæ•°æ® + if (bitmap == null) { + dest.writeInt(0); + } else { + dest.writeInt(1); + dest.writeParcelable(bitmap, flags); + } + } + + /** + * 从Parcelä¸è¯»å–æ•°æ®ï¼Œæ¢å¤å½“å‰å¯¹è±¡ + * æ¤æ–¹æ³•用于对象的ååºåˆ—化,从Parcelæ•°æ®ä¸æ¢å¤å¯¹è±¡çš„çŠ¶æ€ + * + * @param in åŒ…å«æ•°æ®çš„Parcel对象 + */ + public void readFromParcel(Parcel in) { + code = in.readInt(); + // 先读å–productCodesåˆ—è¡¨çš„å¤§å° + int size = in.readInt(); + // æ ¹æ®è¯»å–的大å°åˆå§‹åŒ–productCodes列表 + String productCodesStr = in.readString(); + productCodes = new ArrayList<>(); + if (productCodesStr != null && !productCodesStr.isEmpty()) { + String[] codes = productCodesStr.split(","); + for (String code : codes) { + if (!code.isEmpty()) { + productCodes.add(code); + } + } + } + // 读å–bitmap是å¦å˜åœ¨æ ‡å¿— + int hasBitmap = in.readInt(); + // æ ¹æ®æ ‡å¿—æ¢å¤bitmap,如果ä¸å˜åœ¨åˆ™ç½®ä¸ºnull + if (hasBitmap == 0) { + bitmap = null; + } else { + // 如果å˜åœ¨ï¼Œåˆ™è¯»å–å¹¶æ¢å¤bitmap对象 + try { + bitmap = in.readParcelable(Bitmap.class.getClassLoader()); + } catch (Exception e) { + // 异常处ç†ï¼Œç¡®ä¿ç¨‹åºä¸ä¼šå´©æºƒ + bitmap = null; + e.printStackTrace(); + } + } + } + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public Bitmap getBitmap() { + return bitmap; + } + + public void setBitmap(Bitmap bitmap) { + this.bitmap = bitmap; + } + + public List<String> getProductCodes() { + return productCodes; + } + + public void setProductCodes(List<String> productCodes) { + this.productCodes = productCodes != null ? new ArrayList<>(productCodes) : new ArrayList<>(); + } +} diff --git a/service-sdk/src/main/java/com/wmdigit/service/listener/OnServiceConnectListener.java b/service-sdk/src/main/java/com/wmdigit/service/listener/OnServiceConnectListener.java new file mode 100644 index 0000000..4de8877 --- /dev/null +++ b/service-sdk/src/main/java/com/wmdigit/service/listener/OnServiceConnectListener.java @@ -0,0 +1,26 @@ +package com.wmdigit.service.listener; + +import android.content.ComponentName; +import android.os.IBinder; + +/** + * æœåŠ¡è¿žæŽ¥ç›‘å¬å™¨æŽ¥å£ï¼Œç”¨äºŽç›‘嬿œåŠ¡çš„è¿žæŽ¥å’Œæ–开事件 + */ +public interface OnServiceConnectListener { + + /** + * 当æœåŠ¡è¿žæŽ¥æˆåŠŸæ—¶è°ƒç”¨ + * + * @param name æœåŠ¡çš„ç»„ä»¶åç§° + * @param service æœåŠ¡çš„Binder对象,用于与æœåŠ¡é€šä¿¡ + */ + void onConnected(ComponentName name, IBinder service); + + /** + * 当æœåŠ¡æ–开连接时调用,例如æœåŠ¡è¿›ç¨‹ç»“æŸæˆ–主动æ–开连接 + * + * @param name æœåŠ¡çš„ç»„ä»¶åç§° + */ + void onDisconnected(ComponentName name); + +} diff --git a/service-sdk/src/test/java/com/wmdigit/service/ExampleUnitTest.java b/service-sdk/src/test/java/com/wmdigit/service/ExampleUnitTest.java new file mode 100644 index 0000000..9b845de --- /dev/null +++ b/service-sdk/src/test/java/com/wmdigit/service/ExampleUnitTest.java @@ -0,0 +1,17 @@ +package com.wmdigit.service; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * Example local unit test, which will execute on the development machine (host). + * + * @see <a href="http://d.android.com/tools/testing">Testing documentation</a> + */ +public class ExampleUnitTest { + @Test + public void addition_isCorrect() { + assertEquals(4, 2 + 2); + } +} \ No newline at end of file diff --git a/service/build.gradle b/service/build.gradle index 51c68d5..1f86bd3 100644 --- a/service/build.gradle +++ b/service/build.gradle @@ -11,6 +11,12 @@ android { testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" consumerProguardFiles "consumer-rules.pro" + + javaCompileOptions { + annotationProcessorOptions { + arguments = [ AROUTER_MODULE_NAME : project.getName() ] + } + } } buildTypes { @@ -37,4 +43,9 @@ dependencies { implementation project(path: ':common') implementation project(path: ':core') implementation project(path: ':data-local') + implementation project(path: ':camera') + implementation project(path: ':module-setting') + + implementation 'com.alibaba:arouter-api:1.5.2' + annotationProcessor 'com.alibaba:arouter-compiler:1.5.2' } \ No newline at end of file diff --git a/service/src/main/AndroidManifest.xml b/service/src/main/AndroidManifest.xml index a5918e6..d08bc0f 100644 --- a/service/src/main/AndroidManifest.xml +++ b/service/src/main/AndroidManifest.xml @@ -1,4 +1,22 @@ <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android"> + <application + android:allowBackup="true" + android:icon="@mipmap/ic_launcher" + android:roundIcon="@mipmap/ic_launcher_round" + android:supportsRtl="true" + android:theme="@style/Theme.CateringDetect"> + <service android:name=".aidl.CateringService" + android:enabled="true" + android:exported="true" + android:foregroundServiceType="camera|location"> + + <intent-filter android:priority="1000"> + <action android:name="com.wmdigit.service" /> + <category android:name="android.intent.category.DEFAULT" /> + </intent-filter> + + </service> + </application> </manifest> \ No newline at end of file diff --git a/service/src/main/aidl/com/wmdigit/common/model/ProductsDTO.aidl b/service/src/main/aidl/com/wmdigit/common/model/ProductsDTO.aidl new file mode 100644 index 0000000..c21dc01 --- /dev/null +++ b/service/src/main/aidl/com/wmdigit/common/model/ProductsDTO.aidl @@ -0,0 +1,3 @@ +package com.wmdigit.common.model; + +parcelable ProductsDTO; \ No newline at end of file diff --git a/service/src/main/aidl/com/wmdigit/service/ICateringInterface.aidl b/service/src/main/aidl/com/wmdigit/service/ICateringInterface.aidl index 5f8a9ff..64f4d7a 100644 --- a/service/src/main/aidl/com/wmdigit/service/ICateringInterface.aidl +++ b/service/src/main/aidl/com/wmdigit/service/ICateringInterface.aidl @@ -3,17 +3,20 @@ package com.wmdigit.service; import com.wmdigit.service.IOnInitListener; import com.wmdigit.service.IOnDetectionListener; import com.wmdigit.service.aidl.model.DetectResult; +import com.wmdigit.common.model.ProductsDTO; interface ICateringInterface { void init(IOnInitListener listener); - void registerDetectionListener(IOnDetectionListener listener); + void importProducts(in List<ProductsDTO> products); + + void registerDetectionListener(boolean generateBitmap, IOnDetectionListener listener); void unregisterDetectionListener(); - DetectResult autoDetect(); + DetectResult autoDetect(boolean generateBitmap); boolean checkActivation(); diff --git a/service/src/main/aidl/com/wmdigit/service/IOnDetectionListener.aidl b/service/src/main/aidl/com/wmdigit/service/IOnDetectionListener.aidl index acaca72..80b87e8 100644 --- a/service/src/main/aidl/com/wmdigit/service/IOnDetectionListener.aidl +++ b/service/src/main/aidl/com/wmdigit/service/IOnDetectionListener.aidl @@ -3,5 +3,5 @@ package com.wmdigit.service; import com.wmdigit.service.aidl.model.DetectResult; interface IOnDetectionListener { - void onDetected(out DetectResult result); + void onDetected(in DetectResult result); } \ No newline at end of file diff --git a/service/src/main/java/com/wmdigit/service/ServiceModule.java b/service/src/main/java/com/wmdigit/service/ServiceModule.java new file mode 100644 index 0000000..4f0906b --- /dev/null +++ b/service/src/main/java/com/wmdigit/service/ServiceModule.java @@ -0,0 +1,35 @@ +package com.wmdigit.service; + +import android.content.Context; + +/** + * ServiceModule类作为应用程åºä¸Šä¸‹æ–‡çš„å˜å‚¨å’Œæä¾›è€…,主è¦ç”¨äºŽåœ¨åº”用的å„ä¸ªéƒ¨åˆ†ä¹‹é—´å…±äº«ä¸Šä¸‹æ–‡ä¿¡æ¯ + */ +public class ServiceModule { + + /** + * ä¿å˜åº”用程åºçš„å…¨å±€ä¸Šä¸‹æ–‡ï¼Œå…¨å±€ä¸Šä¸‹æ–‡åœ¨æ•´ä¸ªåº”ç”¨ä¸æ˜¯å”¯ä¸€çš„ + */ + private static Context appContext; + + /** + * åˆå§‹åŒ–ServiceModule,设置应用程åºä¸Šä¸‹æ–‡ + * + * @param context 应用程åºä¸Šä¸‹æ–‡ï¼Œç”¨äºŽåˆå§‹åŒ–appContext + */ + public static void init(Context context){ + if (appContext == null){ + appContext = context; + } + } + + /** + * 获å–应用程åºçš„全局上下文 + * + * @return 返回ä¿å˜çš„应用程åºå…¨å±€ä¸Šä¸‹æ–‡ï¼Œå¦‚果未åˆå§‹åŒ–则返回null + */ + public static Context getAppContext() { + return appContext; + } +} + diff --git a/service/src/main/java/com/wmdigit/service/aidl/CateringInterfaceImpl.java b/service/src/main/java/com/wmdigit/service/aidl/CateringInterfaceImpl.java index a8ed17a..79d39b6 100644 --- a/service/src/main/java/com/wmdigit/service/aidl/CateringInterfaceImpl.java +++ b/service/src/main/java/com/wmdigit/service/aidl/CateringInterfaceImpl.java @@ -1,65 +1,417 @@ package com.wmdigit.service.aidl; +import android.content.Intent; +import android.graphics.Bitmap; import android.os.RemoteException; +import androidx.lifecycle.LifecycleOwner; + +import com.alibaba.android.arouter.launcher.ARouter; +import com.elvishew.xlog.XLog; +import com.wmdigit.camera.CameraxController; +import com.wmdigit.camera.listener.OnImageAnalyzeListener; +import com.wmdigit.common.constants.ErrorCode; +import com.wmdigit.common.constants.RouteConstant; +import com.wmdigit.common.model.ProductsDTO; +import com.wmdigit.common.utils.ParcelHelper; +import com.wmdigit.core.catering.TargetDetectionRepository; +import com.wmdigit.core.catering.model.TargetDetectResult; +import com.wmdigit.core.hnsw.HnswRepository; +import com.wmdigit.core.opencv.OpencvRepository; +import com.wmdigit.core.videopipe.VideoPipeRepository; +import com.wmdigit.data.database.entity.ProductsPO; +import com.wmdigit.data.database.repository.ProductsRepository; +import com.wmdigit.data.mmkv.repository.CropLocalRepository; +import com.wmdigit.data.mmkv.repository.UserLocalRepository; import com.wmdigit.service.ICateringInterface; import com.wmdigit.service.IOnDetectionListener; import com.wmdigit.service.IOnInitListener; +import com.wmdigit.service.ServiceModule; import com.wmdigit.service.aidl.model.DetectResult; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + /** * é¤é¥®AIDLæœåŠ¡å®žçŽ°ç±» * @author dizi */ public class CateringInterfaceImpl extends ICateringInterface.Stub{ + /** + * 声明一个生命周期所有者,用于管ç†ç»„件的生命周期 + */ + private LifecycleOwner lifecycleOwner; /** - * åˆå§‹åŒ– - * @param listener - * @throws RemoteException + * 声明一个检测监å¬å™¨æŽ¥å£å®žä¾‹ï¼Œç”¨äºŽå¤„ç†æ£€æµ‹äº‹ä»¶ + */ + private IOnDetectionListener onDetectionListener; + + /** + * æž„é€ å‡½æ•° + * @param lifecycleOwner 生命周期所有者,用于管ç†CateringInterfaceImpl实例的生命周期 + */ + public CateringInterfaceImpl(LifecycleOwner lifecycleOwner) { + this.lifecycleOwner = lifecycleOwner; + } + + /** + * åˆå§‹åŒ–æŽ¥å£ + * @param listener åˆå§‹åŒ–完æˆåŽçš„回调监å¬å™¨ï¼Œå½“åˆå§‹åŒ–å®Œæˆæ—¶ä¼šè¢«è°ƒç”¨ + * @throws RemoteException 如果远程过程调用失败,会抛出æ¤å¼‚常 */ @Override public void init(IOnInitListener listener) throws RemoteException { + XLog.i("调用接å£ï¼šinit"); + // 设置åˆå§‹åŒ–完æˆçš„监å¬å™¨ + if (listener != null){ + HnswRepository.getInstance().setListener(() -> { + try { + listener.onComplete(); + } catch (RemoteException e) { + XLog.e(e.toString()); + } + }); + } + // 如果相机没有打开,则设置相机的帧间隔并绑定生命周期所有者 + if (!CameraxController.getInstance(ServiceModule.getAppContext()).isOpen()) { + CameraxController.getInstance().setFrameInterval(2).setLifecycleOwner(lifecycleOwner).apply(); + } + } + /** + * 导入产å“ä¿¡æ¯åˆ°æ•°æ®åº“ + * 该方法用于接收一组产å“ä¿¡æ¯å¯¹è±¡ï¼Œå¹¶å°†å®ƒä»¬æ‰¹é‡æ’入到数æ®åº“ä¸ + * 在执行æ’å…¥å‰ï¼Œä¼šæ£€æŸ¥ä¼ 入的产å“ä¿¡æ¯åˆ—表是å¦ä¸ºnull,以é¿å…空指针异常 + * 如果产å“ä¿¡æ¯åˆ—表为nullï¼Œåˆ™æ–¹æ³•ç›´æŽ¥è¿”å›žï¼Œä¸æ‰§è¡Œä»»ä½•æ“作 + * å¦åˆ™ï¼Œå°†ä½¿ç”¨äº§å“仓库(ProductsRepositoryï¼‰çš„å®žä¾‹æ¥æ‰§è¡Œæ‰¹é‡æ’å…¥æ“作 + * + * @param products 待导入的产å“ä¿¡æ¯åˆ—表,æ¯ä¸ªProductsDTO对象包å«ä¸€ä¸ªäº§å“çš„æ‰€æœ‰ç›¸å…³ä¿¡æ¯ + * @throws RemoteException 如果与远程系统通信时å‘生错误 + */ + @Override + public void importProducts(List<ProductsDTO> products) throws RemoteException { + XLog.i("调用接å£ï¼šimportProducts"); + if (products == null){ + return; + } + ProductsRepository.getInstance().insertBatch(products); + XLog.i("æ’入商哿•°æ®å®Œæˆï¼Œæ•°æ®åº“商哿•°ï¼š%s", ProductsRepository.getInstance().queryCount()); } + /** + * 注册检测监å¬å™¨ + * @param generateBitmap æ ‡å¿—ä½ï¼ŒæŒ‡ç¤ºæ˜¯å¦ç”Ÿæˆä½å›¾ + * @param listener 检测事件的监å¬å™¨ï¼Œå½“有检测事件时会被调用 + * @throws RemoteException 如果远程过程调用失败,会抛出æ¤å¼‚常 + */ @Override - public void registerDetectionListener(IOnDetectionListener listener) throws RemoteException { + public void registerDetectionListener(boolean generateBitmap, IOnDetectionListener listener) throws RemoteException { + XLog.i("调用接å£ï¼šregisterDetectionListener"); + // 设置图åƒåˆ†æžç›‘å¬å™¨ + CameraxController.getInstance(ServiceModule.getAppContext()).setOnImageAnalyzeListener(this::analyzeImage); + // 设置视频管é“事件监å¬å™¨ + VideoPipeRepository.getInstance().setListener(new VideoPipeRepository.OnVideoPipeEventListener() { + @Override + public void onObjectIn() { + onObjectInEvent(generateBitmap); + } + @Override + public void onObjectOut() { + onObjectOutEvent(); + } + }); + // ä¿å˜æ£€æµ‹ç›‘å¬å™¨å®žä¾‹ + this.onDetectionListener = listener; } + + /** + * å–æ¶ˆæ³¨å†Œæ£€æµ‹ç›‘å¬å™¨ + * æ¤æ–¹æ³•ä¼šå–æ¶ˆæ³¨å†Œç›¸å…³çš„æ£€æµ‹ç›‘å¬å™¨ï¼Œå¹¶é‡Šæ”¾ç›¸å…³èµ„æº + * @throws RemoteException 如果远程调用失败 + */ @Override public void unregisterDetectionListener() throws RemoteException { - + XLog.i("调用接å£ï¼šunregisterDetectionListener"); + this.onDetectionListener = null; + CameraxController.getInstance().setOnImageAnalyzeListener(null); + VideoPipeRepository.getInstance().setListener(null); + if (scheduler != null && !scheduler.isShutdown()){ + scheduler.shutdown(); + } + if (currentTask != null && !currentTask.isCancelled()){ + currentTask.cancel(false); + } } + /** + * 自动检测方法 + * æ¤æ–¹æ³•用于自动检测,å¯ä»¥æ ¹æ®éœ€è¦ç”Ÿæˆä½å›¾ + * @param generateBitmap 是å¦ç”Ÿæˆä½å›¾ + * @return DetectResult 检测结果 + * @throws RemoteException 如果远程调用失败 + */ @Override - public DetectResult autoDetect() throws RemoteException { - return null; + public DetectResult autoDetect(boolean generateBitmap) throws RemoteException { + int code = checkDetectValid(); + if (code != ErrorCode.ERROR_CODE_SUCCESS){ + return new DetectResult(code, null, new ArrayList<>()); + } + return captureAndProcessImage(bitmapCopy, generateBitmap); } + /** + * æ£€æŸ¥æ¿€æ´»çŠ¶æ€ + * æ¤æ–¹æ³•用于检查当å‰ç”¨æˆ·æ˜¯å¦å·²æ¿€æ´» + * @return boolean 用户是å¦å·²æ¿€æ´» + * @throws RemoteException 如果远程调用失败 + */ @Override public boolean checkActivation() throws RemoteException { - return false; + return UserLocalRepository.getInstance().getIsActivation(); } + /** + * 检查相机è£å‰ªçŠ¶æ€ + * æ¤æ–¹æ³•用于检查相机是å¦å·²ç»è¿‡è£å‰ª + * @return boolean 相机是å¦å·²ç»è¿‡è£å‰ª + * @throws RemoteException 如果远程调用失败 + */ @Override public boolean checkCameraCrop() throws RemoteException { - return false; + return CropLocalRepository.getInstance().getHasCropped(); } + /** + * 检查å¦ä¹ æ•°æ®åˆå§‹åŒ–çŠ¶æ€ + * æ¤æ–¹æ³•用于检查å¦ä¹ æ•°æ®æ˜¯å¦å·²ç»åˆå§‹åŒ–å®Œæˆ + * @return boolean å¦ä¹ æ•°æ®æ˜¯å¦å·²ç»åˆå§‹åŒ–å®Œæˆ + * @throws RemoteException 如果远程调用失败 + */ @Override public boolean checkLearningDataInited() throws RemoteException { - return false; + return HnswRepository.getInstance().isInitComplete(); } + /** + * æ‰“å¼€è®¾ç½®é¡µé¢ + * 该方法通过ARouterå¯¼èˆªåˆ°è®¾ç½®é¡µé¢ + * 使用ARouter进行页é¢è·³è½¬å¯ä»¥å®žçŽ°æ›´çµæ´»çš„页é¢è·¯ç”±ç®¡ç† + */ @Override public void openSettingPage() throws RemoteException { - + XLog.i("调用接å£ï¼šopenSettingPage"); + ARouter.getInstance().build(RouteConstant.ROUTE_SETTING).navigation(); } + /** + * é‡ç½®æ‘„åƒå¤´èƒŒæ™¯ + * 该方法用于é‡ç½®æ‘„åƒå¤´èƒŒæ™¯çš„ç›¸å…³å‚æ•° + * 通过é‡ç½®frameCount的值,å¯ä»¥æ¢å¤é»˜è®¤çš„æ‘„åƒå¤´èƒŒæ™¯è¡Œä¸º + */ @Override public void resetCameraBackground() throws RemoteException { + XLog.i("调用接å£ï¼šresetCameraBackground"); + frameCount = 10; + } + + /** + * 帧计数器,用于跟踪已处ç†çš„帧数 + */ + private int frameCount = 0; + /** + * bitmapCopy 用于å˜å‚¨ bitmap 的副本,以便在分æžè¿‡ç¨‹ä¸ä¸ä¼šä¿®æ”¹åŽŸå§‹å›¾åƒ + */ + private Bitmap bitmapCopy; + + /** + * 分æžå›¾åƒæ–¹æ³• + * + * 本方法的目的是分æžç»™å®šçš„ Bitmap å¯¹è±¡ï¼Œå¹¶æ ¹æ®å½“å‰å¸§è®¡æ•°ï¼ˆframeCount)执行ä¸åŒçš„æ“ä½œ + * 当 frameCount 超过 15 时,将 bitmapCopy å‘é€ç»™ VideoPipeRepository 进行图åƒå¤„ç† + * 当 frameCount ç‰äºŽ 15 时,将 bitmapCopy 设置为 VideoPipeRepository çš„ç©ºå›¾åƒæŒ‡ç¤ºï¼Œå¹¶å¢žåŠ frameCount + * å¦åˆ™ï¼Œåªå¢žåŠ frameCount + * + * @param bitmap è¦åˆ†æžçš„ Bitmap 对象 + */ + private void analyzeImage(Bitmap bitmap) { + // 创建 bitmap 的副本,以é¿å…修改原始 bitmap + bitmapCopy = ParcelHelper.copy(bitmap); + // æ ¹æ®å¸§è®¡æ•°æ‰§è¡Œä¸åŒçš„æ“ä½œ + if (frameCount > 15) { + // 如果帧计数超过 15,将副本å‘é€åˆ° VideoPipeRepository è¿›è¡Œå¤„ç† + VideoPipeRepository.getInstance().processImage(bitmapCopy, true); + } else if (frameCount == 15) { + // 如果帧计数ç‰äºŽ 15,将副本设置为 VideoPipeRepository 的空图åƒï¼Œå¹¶å¢žåŠ å¸§è®¡æ•° + VideoPipeRepository.getInstance().setEmptyImage(bitmapCopy, true); + frameCount++; + } else { + // å¦åˆ™ï¼Œåªå¢žåŠ å¸§è®¡æ•° + frameCount++; + } + } + + /** + * 定义å˜é‡ä»¥æŒ‡ç¤ºæ˜¯å¦æœ‰ç‰©ä½“进入指定区域 + */ + private boolean objectInArea; + /** + * 创建一个å•线程的定时任务调度器 + */ + private ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); + /** + * 用于å˜å‚¨å½“剿‰§è¡Œçš„定时任务 + */ + private ScheduledFuture<?> currentTask; + /** + * å®šä¹‰ä¸€ä¸ªå¯¹è±¡ä½œä¸ºå¤„ç†æ£€æµ‹ç»“æžœçš„é” + */ + private final Object detectResultLock = new Object(); + + /** + * 处ç†ç‰©ä½“进入事件 + * å½“ç‰©ä½“è¿›å…¥æŒ‡å®šåŒºåŸŸæ—¶ï¼Œæ¤æ–¹æ³•å°†è¢«è°ƒç”¨è¿›è¡Œå¤„ç† + * + * @param generateBitmap 指示是å¦ç”Ÿæˆä½å›¾çš„布尔值,用于控制是å¦ç”Ÿæˆæ£€æµ‹å›¾åƒ + */ + private void onObjectInEvent(boolean generateBitmap){ + // è®¾ç½®æ ‡å¿—è¡¨ç¤ºæœ‰ç‰©ä½“è¿›å…¥åŒºåŸŸ + objectInArea = true; + int code = checkDetectValid(); + if (code != ErrorCode.ERROR_CODE_SUCCESS){ + if (onDetectionListener != null){ + try { + onDetectionListener.onDetected(new DetectResult(code, null, new ArrayList<>())); + } catch (RemoteException e) { + XLog.e(e.toString()); + } + } + return; + } + // å¦‚æžœå½“å‰æœ‰æœªå®Œæˆçš„å®šæ—¶ä»»åŠ¡ï¼Œå–æ¶ˆè¯¥ä»»åŠ¡ + if (currentTask != null && !currentTask.isDone()){ + currentTask.cancel(false); + } + // æ£€æŸ¥çº¿ç¨‹æ± æ˜¯å¦å…³é— + if (scheduler == null || scheduler.isShutdown()){ + scheduler = Executors.newSingleThreadScheduledExecutor(); + } + // 安排一个新的定时任务,在2ç§’åŽæ‰§è¡Œç‰©ä½“检测 + currentTask = scheduler.schedule(() -> { + // æ‰§è¡Œå›¾åƒæ•获和处ç†ï¼ŒèŽ·å–æ£€æµ‹ç»“æžœ + DetectResult detectResult = captureAndProcessImage(null, generateBitmap); + // 如果å˜åœ¨æ£€æµ‹ç»“果监å¬å™¨ï¼Œè°ƒç”¨å…¶onDetected方法 + if (onDetectionListener != null){ + try { + onDetectionListener.onDetected(detectResult); + } catch (RemoteException e) { + XLog.e(e.toString()); + } + } + }, 2, TimeUnit.SECONDS); + } + + /** + * 处ç†ç‰©ä½“离开事件 + * å½“ç‰©ä½“ç¦»å¼€æŒ‡å®šåŒºåŸŸæ—¶ï¼Œæ¤æ–¹æ³•å°†è¢«è°ƒç”¨è¿›è¡Œå¤„ç† + */ + private void onObjectOutEvent(){ + // è®¾ç½®æ ‡å¿—è¡¨ç¤ºç‰©ä½“å·²ç¦»å¼€åŒºåŸŸ + objectInArea = false; + // 如果当å‰ä»»åŠ¡æœªè¢«å–æ¶ˆï¼Œå–消当å‰ä»»åŠ¡ + if (currentTask != null && !currentTask.isCancelled()){ + currentTask.cancel(false); + } + } + + /** + * æ•获并处ç†å›¾åƒä»¥è¿›è¡Œå¯¹è±¡æ£€æµ‹ + * + * æ¤æ–¹æ³•接å—一个Bitmap对象和一个布尔值,指示是å¦ç”Ÿæˆå¤„ç†åŽçš„Bitmap + * å®ƒé¦–å…ˆæ£€æŸ¥æ£€æµ‹åŒºåŸŸä¸æ˜¯å¦æœ‰å¯¹è±¡ï¼Œå¦‚果没有,立å³è¿”回错误代ç + * å¦‚æžœæœ‰å¯¹è±¡ï¼Œå®ƒå°†æ ¹æ®ä¼ 入的Bitmap和一系列检测逻辑æ¥å¤„ç†å›¾åƒ + * 最åŽï¼Œæ ¹æ®è¾“入傿•°å†³å®šæ˜¯å¦ç”Ÿæˆå¹¶è¿”回处ç†åŽçš„Bitmap + * + * @param bitmap 用于检测的Bitmap对象,å¯ä»¥ä¸ºnull,如果为null,将使用备用Bitmap + * @param generateBitmap 布尔值,指示是å¦ç”Ÿæˆå¹¶è¿”回处ç†åŽçš„Bitmap + * @return DetectResult åŒ…å«æ£€æµ‹ç»“果和å¯èƒ½çš„处ç†åŽBitmap的对象 + */ + private DetectResult captureAndProcessImage(Bitmap bitmap, boolean generateBitmap){ + synchronized (detectResultLock) { + DetectResult detectResult = new DetectResult(); + // å¦‚æžœæ£€æµ‹åŒºåŸŸä¸æ²¡æœ‰å¯¹è±¡ï¼Œè®¾ç½®é”™è¯¯ä»£ç 并返回 + if (!objectInArea) { + detectResult.setCode(ErrorCode.ERROR_CODE_NO_OBJECT_IN_AREA); + return detectResult; + } + TargetDetectResult targetDetectResult; + // å¦‚æžœä¼ å…¥çš„Bitmap为空,使用备用的Bitmap + if (bitmap == null) { + bitmap = ParcelHelper.copy(bitmapCopy); + } + // 对图åƒè¿›è¡Œå¤„ç†ï¼Œå°è¯•æ£€æµ‹ç›®æ ‡ + targetDetectResult = TargetDetectionRepository.getInstance().processImage(bitmap, true); + // 如果没有检测到任何矩形对象,直接返回 + if (targetDetectResult.getRectArray() == null) { + return detectResult; + } + List<ProductsPO> productsPOList = new ArrayList<>(); + // æ ¹æ®æ£€æµ‹åˆ°çš„ç‰¹å¾æ£€ç´¢äº§å“ï¼Œå¹¶å°†ç»“æžœæ·»åŠ åˆ°åˆ—è¡¨ä¸ + for (int i = 0; i < targetDetectResult.getFeatures().length; i++) { + ProductsPO product = HnswRepository.getInstance().retrieveByFeature(targetDetectResult.getFeatures()[i]); + productsPOList.add(product); + } + targetDetectResult.setProducts(productsPOList); + // é历检测到的产å“,将产å“ä»£ç æ·»åŠ åˆ°ç»“æžœä¸ + for (ProductsPO temp : targetDetectResult.getProducts()){ + detectResult.getProductCodes().add(temp.getProductCode()); + } + // 如果需è¦ç”ŸæˆBitmapï¼Œè¿›è¡Œç›¸å…³å¤„ç† + if (generateBitmap) { + // 在Bitmap上绘制检测结果 + detectResult.setBitmap(OpencvRepository.getInstance().drawDetectResultOnBitmap(targetDetectResult)); + } + return detectResult; + } + } + + /** + * 检查摄åƒå¤´æ£€æµ‹æ˜¯å¦æœ‰æ•ˆçš„功能方法 + * 该方法用于在进行摄åƒå¤´æ£€æµ‹ä»»åС之å‰ï¼Œæ£€æŸ¥å¤šä¸ªå…ˆå†³æ¡ä»¶æ˜¯å¦æ»¡è¶³ + * + * @return 返回错误代ç ,ä¸åŒçš„错误代ç 表示ä¸åŒçš„错误类型,如果所有æ¡ä»¶éƒ½æ»¡è¶³ï¼Œåˆ™è¿”回æˆåŠŸä»£ç + */ + private int checkDetectValid(){ + // 检查摄åƒå¤´æ˜¯å¦å·²æ‰“å¼€ + if (!CameraxController.getInstance().isOpen()){ + // 如果摄åƒå¤´æœªæ‰“开,返回摄åƒå¤´ä¸å¯ç”¨é”™è¯¯ä»£ç + return ErrorCode.ERROR_CODE_CAMERA_NOT_AVAILABLE; + } + // 检查是å¦å·²å®Œæˆæ¿€æ´» + else if (!UserLocalRepository.getInstance().getIsActivation()){ + // 如果未激活,返回未激活错误代ç + return ErrorCode.ERROR_CODE_NOT_ACTIVATED; + } + // 检查å¦ä¹ æ•°æ®æ˜¯å¦å·²åˆå§‹åŒ–å®Œæˆ + else if (!HnswRepository.getInstance().isInitComplete()){ + // 如果å¦ä¹ æ•°æ®æœªåˆå§‹åŒ–完æˆï¼Œè¿”回å¦ä¹ æ•°æ®åˆå§‹åŒ–未完æˆé”™è¯¯ä»£ç + return ErrorCode.ERROR_CODE_LEARNING_DATA_INIT_NOT_COMPLETED; + } + // 检查是å¦å·²è¿›è¡Œè£å‰ªæ“作 + else if (!CropLocalRepository.getInstance().getHasCropped()){ + // 如果未进行è£å‰ªæ“作,返回摄åƒå¤´æœªè£å‰ªé”™è¯¯ä»£ç + return ErrorCode.ERROR_CODE_CAMERA_NOT_CROPPED; + } + // 所有æ¡ä»¶éƒ½æ»¡è¶³æ—¶ï¼Œè¿”回æˆåŠŸä»£ç + else { + return ErrorCode.ERROR_CODE_SUCCESS; + } } } diff --git a/service/src/main/java/com/wmdigit/service/aidl/CateringService.java b/service/src/main/java/com/wmdigit/service/aidl/CateringService.java index dd4e55d..0626e5c 100644 --- a/service/src/main/java/com/wmdigit/service/aidl/CateringService.java +++ b/service/src/main/java/com/wmdigit/service/aidl/CateringService.java @@ -1,6 +1,14 @@ package com.wmdigit.service.aidl; +import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION; + +import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.content.Context; import android.content.Intent; +import android.content.pm.ServiceInfo; +import android.os.Build; import android.os.IBinder; import android.os.RemoteException; @@ -10,6 +18,7 @@ import androidx.lifecycle.LifecycleService; import com.wmdigit.service.ICateringInterface; import com.wmdigit.service.IOnInitListener; +import com.wmdigit.service.R; /** * é¤é¥®AIDLæœåŠ¡ @@ -20,7 +29,7 @@ public class CateringService extends LifecycleService { /** * AIDL接å£å®žçް */ - private final ICateringInterface.Stub stub = new CateringInterfaceImpl(); + private final ICateringInterface.Stub stub = new CateringInterfaceImpl(this); @Nullable @Override @@ -28,4 +37,61 @@ public class CateringService extends LifecycleService { super.onBind(intent); return stub; } + + @Override + public void onCreate() { + super.onCreate(); + createNotificationChannel(); + } + + @Override + public void onDestroy() { + super.onDestroy(); + stopForeground(true); + android.os.Process.killProcess(android.os.Process.myPid()); + } + + private static final String CHANNEL_ID = "my_channel_02"; + private static final String CHANNEL_NAME = "æœåŠ¡é€šçŸ¥"; + private static final String CHANNEL_DESCRIPTION = "识别æœåŠ¡è¿è¡Œä¸"; + private static final int IMPORTANCE = NotificationManager.IMPORTANCE_HIGH; + private static final int NOTIFICATION_ID = 1; + private static final int FOREGROUND_SERVICE_TYPES = FOREGROUND_SERVICE_TYPE_LOCATION | ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA; + + private void createNotificationChannel() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { + Notification notification = new Notification.Builder(this) + .setContentTitle("通知") + .setContentText("识别æœåŠ¡è¿è¡Œä¸") +// .setSmallIcon(R.drawable.ic_launcher_foreground) // å¯ç”¨å°å›¾æ ‡è®¾ç½® + .build(); + startForeground(NOTIFICATION_ID, notification); + return; + } + + NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); + if (mNotificationManager == null) { + throw new IllegalStateException("Failed to get NotificationManager"); + } + + NotificationChannel mChannel = new NotificationChannel(CHANNEL_ID, CHANNEL_NAME, IMPORTANCE); + mChannel.setDescription(CHANNEL_DESCRIPTION); + mChannel.enableVibration(false); + // mChannel.setVibrationPattern(new long[]{100, 200, 300, 400, 500, 400, 300, 200, 400}); // å¦‚æžœéœ€è¦æŒ¯åŠ¨åˆ™å¯ç”¨æ¤è¡Œ + mNotificationManager.createNotificationChannel(mChannel); + + Notification notification = new Notification.Builder(this) + .setContentTitle("通知") + .setContentText("识别æœåŠ¡æ£åœ¨è¿è¡Œ") +// .setSmallIcon(R.drawable.ic_launcher_foreground) // å¯ç”¨å°å›¾æ ‡è®¾ç½® + .setChannelId(CHANNEL_ID) + .build(); + + if (Build.VERSION.SDK_INT >= 30) { + startForeground(NOTIFICATION_ID, notification, FOREGROUND_SERVICE_TYPES); + } else { + startForeground(NOTIFICATION_ID, notification); + } + } + } diff --git a/service/src/main/java/com/wmdigit/service/aidl/model/DetectResult.java b/service/src/main/java/com/wmdigit/service/aidl/model/DetectResult.java index 003a1d0..6b11f93 100644 --- a/service/src/main/java/com/wmdigit/service/aidl/model/DetectResult.java +++ b/service/src/main/java/com/wmdigit/service/aidl/model/DetectResult.java @@ -9,13 +9,16 @@ import androidx.annotation.NonNull; import java.util.ArrayList; import java.util.List; - /** * DetectResult类,表示检测的结果,实现了Parcelable接å£ï¼Œç”¨äºŽç»„ä»¶é—´æ•°æ®ä¼ 递 * @author dizi */ public class DetectResult implements Parcelable { + /** + * 识别结果的状æ€ç ,默认为0 + */ + private int code = 0; /** * 图åƒä½å›¾ */ @@ -33,12 +36,14 @@ public class DetectResult implements Parcelable { /** * æž„é€ å‡½æ•°ï¼Œåˆå§‹åŒ–检测结果对象 * - * @param bitmap 识别的图åƒä½å›¾ + * @param code 识别结果的状æ€ç + * @param bitmap 识别的图åƒä½å›¾ * @param productCodes 识别到的产å“ç¼–ç 列表 */ - public DetectResult(Bitmap bitmap, List<String> productCodes) { + public DetectResult(int code, Bitmap bitmap, List<String> productCodes) { + this.code = code; this.bitmap = bitmap; - this.productCodes = productCodes; + this.productCodes = productCodes != null ? new ArrayList<>(productCodes) : new ArrayList<>(); } /** @@ -88,17 +93,19 @@ public class DetectResult implements Parcelable { */ @Override public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(code); // 先写入productCodesåˆ—è¡¨çš„å¤§å° dest.writeInt(productCodes.size()); - // é历productCodes列表,é€ä¸ªå†™å…¥æ¯ä¸ªå…ƒç´ + // 使用StringBuilder将所有产å“ç¼–ç æ‹¼æŽ¥æˆä¸€ä¸ªå—符串 + StringBuilder sb = new StringBuilder(); for (String productCode : productCodes) { - dest.writeString(productCode); + sb.append(productCode).append(","); } + dest.writeString(sb.toString()); // 检查bitmap是å¦ä¸ºç©ºï¼Œå¹¶æ ¹æ®æƒ…å†µå†™å…¥æ ‡å¿—å’Œbitmapæ•°æ® - if (bitmap == null){ + if (bitmap == null) { dest.writeInt(0); - } - else{ + } else { dest.writeInt(1); dest.writeParcelable(bitmap, flags); } @@ -110,25 +117,59 @@ public class DetectResult implements Parcelable { * * @param in åŒ…å«æ•°æ®çš„Parcel对象 */ - public void readFromParcel(Parcel in){ + public void readFromParcel(Parcel in) { + code = in.readInt(); // 先读å–productCodesåˆ—è¡¨çš„å¤§å° int size = in.readInt(); // æ ¹æ®è¯»å–的大å°åˆå§‹åŒ–productCodes列表 - productCodes = new ArrayList<>(size); - // é历读å–的大å°ï¼Œé€ä¸ªè¯»å–å¹¶æ·»åŠ åˆ°productCodes列表 - for (int i = 0; i < size; i++){ - productCodes.add(in.readString()); + String productCodesStr = in.readString(); + productCodes = new ArrayList<>(); + if (productCodesStr != null && !productCodesStr.isEmpty()) { + String[] codes = productCodesStr.split(","); + for (String code : codes) { + if (!code.isEmpty()) { + productCodes.add(code); + } + } } // 读å–bitmap是å¦å˜åœ¨æ ‡å¿— int hasBitmap = in.readInt(); // æ ¹æ®æ ‡å¿—æ¢å¤bitmap,如果ä¸å˜åœ¨åˆ™ç½®ä¸ºnull - if (hasBitmap == 0){ + if (hasBitmap == 0) { bitmap = null; - } - else{ + } else { // 如果å˜åœ¨ï¼Œåˆ™è¯»å–å¹¶æ¢å¤bitmap对象 - bitmap = in.readParcelable(Bitmap.class.getClassLoader()); + try { + bitmap = in.readParcelable(Bitmap.class.getClassLoader()); + } catch (Exception e) { + // 异常处ç†ï¼Œç¡®ä¿ç¨‹åºä¸ä¼šå´©æºƒ + bitmap = null; + e.printStackTrace(); + } } } + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public Bitmap getBitmap() { + return bitmap; + } + + public void setBitmap(Bitmap bitmap) { + this.bitmap = bitmap; + } + + public List<String> getProductCodes() { + return productCodes; + } + + public void setProductCodes(List<String> productCodes) { + this.productCodes = productCodes != null ? new ArrayList<>(productCodes) : new ArrayList<>(); + } } diff --git a/settings.gradle b/settings.gradle index b0ae41b..47c7862 100644 --- a/settings.gradle +++ b/settings.gradle @@ -34,3 +34,4 @@ include ':module-setting' include ':data-remote' include ':service' include ':opencv' +include ':service-sdk' -- 2.18.1