diff --git a/gpsrelaysentinel/build.gradle b/gpsrelaysentinel/build.gradle index 8d81c9f..8b8ddd9 100644 --- a/gpsrelaysentinel/build.gradle +++ b/gpsrelaysentinel/build.gradle @@ -56,6 +56,7 @@ android { } dependencies { + api project(':libgpsrelaysentinel') api 'com.google.code.gson:gson:2.10.1' diff --git a/gpsrelaysentinel/src/main/java/cc/winboll/studio/gpsrelaysentinel/MainService.java b/gpsrelaysentinel/src/main/java/cc/winboll/studio/gpsrelaysentinel/MainService.java index 8d7c16d..f1aded1 100644 --- a/gpsrelaysentinel/src/main/java/cc/winboll/studio/gpsrelaysentinel/MainService.java +++ b/gpsrelaysentinel/src/main/java/cc/winboll/studio/gpsrelaysentinel/MainService.java @@ -9,12 +9,15 @@ import android.content.Intent; import android.location.Location; import android.location.LocationListener; import android.location.LocationManager; -import android.os.Binder; import android.os.Build; import android.os.Bundle; import android.os.IBinder; import androidx.core.app.NotificationCompat; import cc.winboll.studio.libappbase.LogUtils; +import cc.winboll.studio.libgpsrelaysentinel.manager.GpsSubscribeManager; +import cc.winboll.studio.libgpsrelaysentinel.manager.SubscribeLocationManager; +import cc.winboll.studio.libgpsrelaysentinel.model.GpsSubscribeMsg; +import java.util.Map; public class MainService extends Service { @@ -72,6 +75,31 @@ public class MainService extends Service { String gpsInfo = "Lat: " + location.getLatitude() + ", Lng: " + location.getLongitude(); LogUtils.d(TAG, "Location changed: " + gpsInfo); updateNotification(gpsInfo); + + //管理器初始化 + GpsSubscribeManager subscribeManager = GpsSubscribeManager.getInstance(); + SubscribeLocationManager locationManager = SubscribeLocationManager.getInstance(); + + //遍历所有订阅者做距离判断+定点更新 + Map subscribeMap = subscribeManager.getSubscribeMap(); + for (Map.Entry entry : subscribeMap.entrySet()) { + String sid = entry.getKey(); + GpsSubscribeMsg subscribeMsg = entry.getValue(); + + double nowLat = location.getLatitude(); + double nowLng = location.getLongitude(); + + //判断是否满足推送 + boolean canPush = locationManager.isNeedPush(sid, nowLat, nowLng); + if (canPush) { + //执行发送GPS广播 + //sendGpsBroadcast(...); + + //推送成功立刻刷新订阅者基准坐标 + locationManager.updateSubscriberPoint(sid, nowLat, nowLng); + } + } + } @Override diff --git a/libgpsrelaysentinel/.gitignore b/libgpsrelaysentinel/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/libgpsrelaysentinel/.gitignore @@ -0,0 +1 @@ +/build diff --git a/libgpsrelaysentinel/build.gradle b/libgpsrelaysentinel/build.gradle new file mode 100644 index 0000000..b11c04e --- /dev/null +++ b/libgpsrelaysentinel/build.gradle @@ -0,0 +1,25 @@ +apply plugin: 'com.android.library' +apply plugin: 'maven-publish' +apply from: '../.winboll/winboll_lib_build.gradle' +apply from: '../.winboll/winboll_lint_build.gradle' + +android { + // 适配MIUI12 + compileSdkVersion 30 + buildToolsVersion "30.0.3" + + defaultConfig { + minSdkVersion 21 + targetSdkVersion 30 + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + api fileTree(dir: 'libs', include: ['*.jar']) +} diff --git a/libgpsrelaysentinel/build.properties b/libgpsrelaysentinel/build.properties new file mode 100644 index 0000000..9805e56 --- /dev/null +++ b/libgpsrelaysentinel/build.properties @@ -0,0 +1,8 @@ +#Created by .winboll/winboll_app_build.gradle +#Fri May 01 17:09:11 HKT 2026 +stageCount=57 +libraryProject=libdebugtemp +baseVersion=15.0 +publishVersion=15.0.56 +buildCount=0 +baseBetaVersion=15.0.57 diff --git a/libgpsrelaysentinel/proguard-rules.pro b/libgpsrelaysentinel/proguard-rules.pro new file mode 100644 index 0000000..536058a --- /dev/null +++ b/libgpsrelaysentinel/proguard-rules.pro @@ -0,0 +1,17 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in C:/tools/adt-bundle-windows-x86_64-20131030/sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# 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 *; +#} diff --git a/libgpsrelaysentinel/src/main/AndroidManifest.xml b/libgpsrelaysentinel/src/main/AndroidManifest.xml new file mode 100644 index 0000000..9722560 --- /dev/null +++ b/libgpsrelaysentinel/src/main/AndroidManifest.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/libgpsrelaysentinel/src/main/java/cc/winboll/studio/libgpsrelaysentinel/manager/GpsSubscribeManager.java b/libgpsrelaysentinel/src/main/java/cc/winboll/studio/libgpsrelaysentinel/manager/GpsSubscribeManager.java new file mode 100644 index 0000000..fe028bc --- /dev/null +++ b/libgpsrelaysentinel/src/main/java/cc/winboll/studio/libgpsrelaysentinel/manager/GpsSubscribeManager.java @@ -0,0 +1,75 @@ +package cc.winboll.studio.libgpsrelaysentinel.manager; + +/** + * @Author 豆包&ZhanGSKen + * @Date 2026/05/07 10:25 + */ + +import android.content.Context; +import android.content.Intent; +import java.util.HashMap; +import java.util.Map; + +import cc.winboll.studio.libgpsrelaysentinel.model.GpsSubscribeConst; +import cc.winboll.studio.libgpsrelaysentinel.model.GpsSubscribeMsg; +import cc.winboll.studio.libgpsrelaysentinel.model.GpsSubscribeResult; + +public final class GpsSubscribeManager { + + private static GpsSubscribeManager instance; + private final Map subscribeMap; + private Context appContext; + + private GpsSubscribeManager(){ + subscribeMap = new HashMap(); + } + + public static GpsSubscribeManager getInstance(){ + if(instance == null){ + instance = new GpsSubscribeManager(); + } + return instance; + } + + public void initContext(final Context context){ + this.appContext = context.getApplicationContext(); + } + + public void addSubscribe(final GpsSubscribeMsg subscribeMsg){ + if(subscribeMsg == null){ + return; + } + subscribeMap.put(subscribeMsg.getSubscribeUniqueId(),subscribeMsg); + } + + public void removeSubscribe(final String sid){ + if(sid == null){ + return; + } + subscribeMap.remove(sid); + SubscribeLocationManager.getInstance().removeSubscribe(sid); + } + + public boolean isSubscribeExist(final String sid){ + return subscribeMap.containsKey(sid); + } + + public void sendSubscribeResult(final GpsSubscribeResult result){ + if(appContext == null || result == null){ + return; + } + Intent intent = new Intent(GpsSubscribeConst.ACTION_SUBSCRIBE_CALLBACK); + intent.putExtra("data",result); + appContext.sendBroadcast(intent); + } + + public void clearAllSubscribe(){ + subscribeMap.clear(); + SubscribeLocationManager.getInstance().clearAll(); + } + + public Map getSubscribeMap() { + return subscribeMap; + } +} + diff --git a/libgpsrelaysentinel/src/main/java/cc/winboll/studio/libgpsrelaysentinel/manager/SubscribeLocationManager.java b/libgpsrelaysentinel/src/main/java/cc/winboll/studio/libgpsrelaysentinel/manager/SubscribeLocationManager.java new file mode 100644 index 0000000..9c3548b --- /dev/null +++ b/libgpsrelaysentinel/src/main/java/cc/winboll/studio/libgpsrelaysentinel/manager/SubscribeLocationManager.java @@ -0,0 +1,103 @@ +package cc.winboll.studio.libgpsrelaysentinel.manager; + +/** + * @Author 豆包&ZhanGSKen + * @Date 2026/05/07 10:26 + */ + +import cc.winboll.studio.libgpsrelaysentinel.model.GpsSubscribeConst; +import cc.winboll.studio.libgpsrelaysentinel.model.GpsSubscribeMsg; +import cc.winboll.studio.libgpsrelaysentinel.model.LocationPoint; + +import java.util.HashMap; +import java.util.Map; + +public final class SubscribeLocationManager { + + private static SubscribeLocationManager instance; + private final Map subscribeConfigMap; + private final Map subscriberPointMap; + + private SubscribeLocationManager(){ + subscribeConfigMap = new HashMap(); + subscriberPointMap = new HashMap(); + } + + public static SubscribeLocationManager getInstance(){ + if(instance == null){ + instance = new SubscribeLocationManager(); + } + return instance; + } + + public void putSubscribeConfig(final String sid,final GpsSubscribeMsg msg){ + subscribeConfigMap.put(sid,msg); + } + + public void initSubscriberPoint(final String sid,double lat,double lng){ + subscriberPointMap.put(sid,new LocationPoint(lat,lng,System.currentTimeMillis())); + } + + public void updateSubscriberPoint(final String sid,double lat,double lng){ + subscriberPointMap.put(sid,new LocationPoint(lat,lng,System.currentTimeMillis())); + } + + public LocationPoint getLastPoint(final String sid){ + return subscriberPointMap.get(sid); + } + + public GpsSubscribeMsg getSubscribeConfig(final String sid){ + return subscribeConfigMap.get(sid); + } + + public boolean isNeedPush(final String sid,double nowLat,double nowLng){ + GpsSubscribeMsg config = getSubscribeConfig(sid); + if(config == null){ + return false; + } + + if(config.getSubscribeMode() == GpsSubscribeConst.SUB_TYPE_ALL){ + return true; + } + + LocationPoint lastPoint = getLastPoint(sid); + if(lastPoint == null){ + return true; + } + + double distance = calculateDistance( + lastPoint.getLatitude(),lastPoint.getLongitude(), + nowLat,nowLng + ); + return distance >= config.getStepDistanceM(); + } + + private double calculateDistance(double lat1,double lng1,double lat2,double lng2){ + double radLat1 = Math.toRadians(lat1); + double radLat2 = Math.toRadians(lat2); + double radLng1 = Math.toRadians(lng1); + double radLng2 = Math.toRadians(lng2); + + double latDiff = radLat1 - radLat2; + double lngDiff = radLng1 - radLng2; + + double result = 2 * Math.asin(Math.sqrt( + Math.pow(Math.sin(latDiff / 2),2) + + Math.cos(radLat1) * Math.cos(radLat2) + * Math.pow(Math.sin(lngDiff / 2),2) + )); + result = result * GpsSubscribeConst.EARTH_RADIUS; + return result; + } + + public void removeSubscribe(final String sid){ + subscribeConfigMap.remove(sid); + subscriberPointMap.remove(sid); + } + + public void clearAll(){ + subscribeConfigMap.clear(); + subscriberPointMap.clear(); + } +} + diff --git a/libgpsrelaysentinel/src/main/java/cc/winboll/studio/libgpsrelaysentinel/model/GpsSubscribeConst.java b/libgpsrelaysentinel/src/main/java/cc/winboll/studio/libgpsrelaysentinel/model/GpsSubscribeConst.java new file mode 100644 index 0000000..c5fe6b6 --- /dev/null +++ b/libgpsrelaysentinel/src/main/java/cc/winboll/studio/libgpsrelaysentinel/model/GpsSubscribeConst.java @@ -0,0 +1,46 @@ +package cc.winboll.studio.libgpsrelaysentinel.model; + +/** + * @Author 豆包&ZhanGSKen + * @Date 2026/05/07 10:22 + * WinBoLL Studio + * Java7 | API26-30 + */ +public final class GpsSubscribeConst { + + // 新增:GPS定位推送广播 + public static final String ACTION_GPS_LOCATION = "cc.winboll.studio.ACTION_GPS_LOCATION"; + + //订阅运行模式 + public static final int SUB_TYPE_ALL = 1; + public static final int SUB_TYPE_STEP_DISTANCE = 2; + + //原始数据订阅类型 + public static final int SUBSCRIBE_TYPE_LOCATION = 1; + public static final int SUBSCRIBE_TYPE_SATELLITE = 2; + public static final int SUBSCRIBE_TYPE_NMEA = 3; + + //订阅返回码 + public static final int RESULT_SUCCESS = 0; + public static final int RESULT_PERMISSION_DENY = 1; + public static final int RESULT_PARAM_ERROR = 2; + public static final int RESULT_GPS_NOT_AVAILABLE = 3; + public static final int RESULT_SYSTEM_LIMIT = 4; + + //GPS设备状态 + public static final int GPS_STATE_CLOSE = 0; + public static final int GPS_STATE_SCANNING = 1; + public static final int GPS_STATE_LOCATED = 2; + public static final int GPS_STATE_SIGNAL_WEAK = 3; + + //广播Action + public static final String ACTION_SUBSCRIBE_REQUEST = "cc.winboll.studio.GPS_SUBSCRIBE_REQUEST"; + public static final String ACTION_SUBSCRIBE_CALLBACK = "cc.winboll.studio.GPS_SUBSCRIBE_CALLBACK"; + + //超时毫秒 + public static final long SUBSCRIBE_TIME_OUT = 5000; + + //地球半径 距离计算常量 + public static final double EARTH_RADIUS = 6378137.0; +} + diff --git a/libgpsrelaysentinel/src/main/java/cc/winboll/studio/libgpsrelaysentinel/model/GpsSubscribeMsg.java b/libgpsrelaysentinel/src/main/java/cc/winboll/studio/libgpsrelaysentinel/model/GpsSubscribeMsg.java new file mode 100644 index 0000000..b10bea2 --- /dev/null +++ b/libgpsrelaysentinel/src/main/java/cc/winboll/studio/libgpsrelaysentinel/model/GpsSubscribeMsg.java @@ -0,0 +1,137 @@ +package cc.winboll.studio.libgpsrelaysentinel.model; + +/** + * @Author 豆包&ZhanGSKen + * @Date 2026/05/07 10:24 + */ +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; + +public final class GpsSubscribeMsg implements Parcelable { + + private final String subscribePackage; + private final int subscribeMode; + private final float stepDistanceM; + + private final int subscribeType; + private final long updateInterval; + private final float minDistance; + private final boolean backgroundPush; + private final String subscribeUniqueId; + + public GpsSubscribeMsg(String subscribePackage, + int subscribeMode, + float stepDistanceM, + int subscribeType, + long updateInterval, + float minDistance, + boolean backgroundPush, + String subscribeUniqueId) { + this.subscribePackage = subscribePackage; + this.subscribeMode = subscribeMode; + this.stepDistanceM = stepDistanceM; + this.subscribeType = subscribeType; + this.updateInterval = updateInterval; + this.minDistance = minDistance; + this.backgroundPush = backgroundPush; + this.subscribeUniqueId = subscribeUniqueId; + } + + public String getSubscribePackage() { + return subscribePackage; + } + + public int getSubscribeMode() { + return subscribeMode; + } + + public float getStepDistanceM() { + return stepDistanceM; + } + + public int getSubscribeType() { + return subscribeType; + } + + public long getUpdateInterval() { + return updateInterval; + } + + public float getMinDistance() { + return minDistance; + } + + public boolean isBackgroundPush() { + return backgroundPush; + } + + public String getSubscribeUniqueId() { + return subscribeUniqueId; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(subscribePackage); + dest.writeInt(subscribeMode); + dest.writeFloat(stepDistanceM); + dest.writeInt(subscribeType); + dest.writeLong(updateInterval); + dest.writeFloat(minDistance); + dest.writeByte((byte) (backgroundPush ? 1 : 0)); + dest.writeString(subscribeUniqueId); + } + + public static final Creator CREATOR = new Creator() { + @Override + public GpsSubscribeMsg createFromParcel(Parcel in) { + return new GpsSubscribeMsg( + in.readString(), + in.readInt(), + in.readFloat(), + in.readInt(), + in.readLong(), + in.readFloat(), + in.readByte() == 1, + in.readString() + ); + } + + @Override + public GpsSubscribeMsg[] newArray(int size) { + return new GpsSubscribeMsg[size]; + } + }; + + public Bundle convertToBundle() { + Bundle bundle = new Bundle(); + bundle.putString("pkg", subscribePackage); + bundle.putInt("subMode",subscribeMode); + bundle.putFloat("stepM",stepDistanceM); + bundle.putInt("type", subscribeType); + bundle.putLong("interval", updateInterval); + bundle.putFloat("distance", minDistance); + bundle.putBoolean("bgPush", backgroundPush); + bundle.putString("sid", subscribeUniqueId); + return bundle; + } + + public static GpsSubscribeMsg createByBundle(Bundle bundle) { + return new GpsSubscribeMsg( + bundle.getString("pkg"), + bundle.getInt("subMode"), + bundle.getFloat("stepM"), + bundle.getInt("type"), + bundle.getLong("interval"), + bundle.getFloat("distance"), + bundle.getBoolean("bgPush"), + bundle.getString("sid") + ); + } +} + diff --git a/libgpsrelaysentinel/src/main/java/cc/winboll/studio/libgpsrelaysentinel/model/GpsSubscribeResult.java b/libgpsrelaysentinel/src/main/java/cc/winboll/studio/libgpsrelaysentinel/model/GpsSubscribeResult.java new file mode 100644 index 0000000..74888e5 --- /dev/null +++ b/libgpsrelaysentinel/src/main/java/cc/winboll/studio/libgpsrelaysentinel/model/GpsSubscribeResult.java @@ -0,0 +1,115 @@ +package cc.winboll.studio.libgpsrelaysentinel.model; + +/** + * @Author 豆包&ZhanGSKen + * @Date 2026/05/07 10:25 + */ + +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; + +public final class GpsSubscribeResult implements Parcelable { + + private final String subscribeUniqueId; + private final int resultCode; + private final String resultDesc; + private final int gpsRunningState; + private final long realEffectiveInterval; + private final long currentTimeStamp; + + public GpsSubscribeResult(String subscribeUniqueId, + int resultCode, + String resultDesc, + int gpsRunningState, + long realEffectiveInterval, + long currentTimeStamp) { + this.subscribeUniqueId = subscribeUniqueId; + this.resultCode = resultCode; + this.resultDesc = resultDesc; + this.gpsRunningState = gpsRunningState; + this.realEffectiveInterval = realEffectiveInterval; + this.currentTimeStamp = currentTimeStamp; + } + + public String getSubscribeUniqueId() { + return subscribeUniqueId; + } + + public int getResultCode() { + return resultCode; + } + + public String getResultDesc() { + return resultDesc; + } + + public int getGpsRunningState() { + return gpsRunningState; + } + + public long getRealEffectiveInterval() { + return realEffectiveInterval; + } + + public long getCurrentTimeStamp() { + return currentTimeStamp; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(subscribeUniqueId); + dest.writeInt(resultCode); + dest.writeString(resultDesc); + dest.writeInt(gpsRunningState); + dest.writeLong(realEffectiveInterval); + dest.writeLong(currentTimeStamp); + } + + public static final Creator CREATOR = new Creator() { + @Override + public GpsSubscribeResult createFromParcel(Parcel in) { + return new GpsSubscribeResult( + in.readString(), + in.readInt(), + in.readString(), + in.readInt(), + in.readLong(), + in.readLong() + ); + } + + @Override + public GpsSubscribeResult[] newArray(int size) { + return new GpsSubscribeResult[size]; + } + }; + + public Bundle convertToBundle() { + Bundle bundle = new Bundle(); + bundle.putString("sid", subscribeUniqueId); + bundle.putInt("code", resultCode); + bundle.putString("desc", resultDesc); + bundle.putInt("gpsState", gpsRunningState); + bundle.putLong("realInterval", realEffectiveInterval); + bundle.putLong("time", currentTimeStamp); + return bundle; + } + + public static GpsSubscribeResult createByBundle(Bundle bundle) { + return new GpsSubscribeResult( + bundle.getString("sid"), + bundle.getInt("code"), + bundle.getString("desc"), + bundle.getInt("gpsState"), + bundle.getLong("realInterval"), + bundle.getLong("time") + ); + } +} + diff --git a/libgpsrelaysentinel/src/main/java/cc/winboll/studio/libgpsrelaysentinel/model/LocationPoint.java b/libgpsrelaysentinel/src/main/java/cc/winboll/studio/libgpsrelaysentinel/model/LocationPoint.java new file mode 100644 index 0000000..a74c7f5 --- /dev/null +++ b/libgpsrelaysentinel/src/main/java/cc/winboll/studio/libgpsrelaysentinel/model/LocationPoint.java @@ -0,0 +1,33 @@ +package cc.winboll.studio.libgpsrelaysentinel.model; + +/** + * @Author 豆包&ZhanGSKen + * @Date 2026/05/07 10:23 + * 订阅者基准定点坐标 + * 每次推送成功自动更新 + */ +public final class LocationPoint { + + private final double latitude; + private final double longitude; + private final long recordTime; + + public LocationPoint(double latitude, double longitude, long recordTime) { + this.latitude = latitude; + this.longitude = longitude; + this.recordTime = recordTime; + } + + public double getLatitude() { + return latitude; + } + + public double getLongitude() { + return longitude; + } + + public long getRecordTime() { + return recordTime; + } +} + diff --git a/libgpsrelaysentinel/src/main/java/cc/winboll/studio/libgpsrelaysentinel/receiver/GpsSubscribeObserverReceiver.java b/libgpsrelaysentinel/src/main/java/cc/winboll/studio/libgpsrelaysentinel/receiver/GpsSubscribeObserverReceiver.java new file mode 100644 index 0000000..31603d3 --- /dev/null +++ b/libgpsrelaysentinel/src/main/java/cc/winboll/studio/libgpsrelaysentinel/receiver/GpsSubscribeObserverReceiver.java @@ -0,0 +1,42 @@ +package cc.winboll.studio.libgpsrelaysentinel.receiver; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; + +/** + * @Author 豆包&ZhanGSKen + * @Date 2026/05/07 10:27 + */ + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; + +import cc.winboll.studio.libgpsrelaysentinel.model.GpsSubscribeConst; +import cc.winboll.studio.libgpsrelaysentinel.model.GpsSubscribeResult; + +public final class GpsSubscribeObserverReceiver extends BroadcastReceiver { + + private OnSubscribeResultListener listener; + + public void setOnSubscribeResultListener(OnSubscribeResultListener listener){ + this.listener = listener; + } + + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if(GpsSubscribeConst.ACTION_SUBSCRIBE_CALLBACK.equals(action)){ + GpsSubscribeResult result = intent.getParcelableExtra("data"); + if(listener != null && result != null){ + listener.onResultBack(result); + } + } + } + + public interface OnSubscribeResultListener{ + void onResultBack(GpsSubscribeResult result); + } +} + diff --git a/libgpsrelaysentinel/src/main/java/cc/winboll/studio/libgpsrelaysentinel/service/GpsSubscribeReceiverService.java b/libgpsrelaysentinel/src/main/java/cc/winboll/studio/libgpsrelaysentinel/service/GpsSubscribeReceiverService.java new file mode 100644 index 0000000..ce2c659 --- /dev/null +++ b/libgpsrelaysentinel/src/main/java/cc/winboll/studio/libgpsrelaysentinel/service/GpsSubscribeReceiverService.java @@ -0,0 +1,139 @@ +package cc.winboll.studio.libgpsrelaysentinel.service; + +import android.app.Service; +import android.content.Intent; +import android.os.IBinder; + +/** + * @Author 豆包&ZhanGSKen + * @Date 2026/05/07 10:46 + */ + + import android.app.Service; + import android.content.Context; + import android.content.Intent; + import android.os.Binder; + import android.os.IBinder; + import android.os.RemoteException; + + import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +import cc.winboll.studio.libgpsrelaysentinel.model.GpsSubscribeConst; +import cc.winboll.studio.libgpsrelaysentinel.model.GpsSubscribeMsg; +import cc.winboll.studio.libgpsrelaysentinel.model.GpsSubscribeResult; +import cc.winboll.studio.libgpsrelaysentinel.model.LocationPoint; + +/** + * 对外消息接收服务:外部App可bind或start + * 收到GPS消息后,通过本地广播回调给外部App + */ +public final class GpsSubscribeReceiverService extends Service { + + // 外部回调监听 + public interface GpsMessageListener { + void onGpsLocation(LocationPoint point, GpsSubscribeMsg config); + void onSubscribeResult(GpsSubscribeResult result); + } + + private final List listeners = new CopyOnWriteArrayList(); + private final IBinder localBinder = new LocalBinder(); + + @Override + public void onCreate() { + super.onCreate(); + } + + // 外部App绑定服务 + @Override + public IBinder onBind(Intent intent) { + return localBinder; + } + + // 外部App startService 入口:接收订阅请求 + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + if (intent != null && intent.getParcelableExtra("req") != null) { + GpsSubscribeMsg msg = intent.getParcelableExtra("req"); + handleSubscribeRequest(msg); + } + return START_STICKY; + } + + // 处理订阅请求:发送给管理器,并回执 + private void handleSubscribeRequest(final GpsSubscribeMsg msg) { + // 加入订阅管理 + cc.winboll.studio.libgpsrelaysentinel.manager.GpsSubscribeManager + .getInstance().addSubscribe(msg); + cc.winboll.studio.libgpsrelaysentinel.manager.SubscribeLocationManager + .getInstance().putSubscribeConfig(msg.getSubscribeUniqueId(), msg); + + // 回执成功 + GpsSubscribeResult result = new GpsSubscribeResult( + msg.getSubscribeUniqueId(), + GpsSubscribeConst.RESULT_SUCCESS, + "subscribe ok", + GpsSubscribeConst.GPS_STATE_LOCATED, + 1000, + System.currentTimeMillis() + ); + sendSubscribeResultBroadcast(result); + notifySubscribeResult(result); + } + + // 供内部(GPS服务)调用:推送定位消息 + public void pushLocation(final LocationPoint point, final GpsSubscribeMsg config) { + sendLocationBroadcast(point, config); + notifyGpsLocation(point, config); + } + + // ---------- 广播回调(跨进程/外部App接收) ---------- + private void sendLocationBroadcast(final LocationPoint point, final GpsSubscribeMsg config) { + Intent intent = new Intent(GpsSubscribeConst.ACTION_GPS_LOCATION); + intent.putExtra("point", point); + intent.putExtra("config", config); + sendBroadcast(intent); + } + + private void sendSubscribeResultBroadcast(final GpsSubscribeResult result) { + Intent intent = new Intent(GpsSubscribeConst.ACTION_SUBSCRIBE_CALLBACK); + intent.putExtra("data", result); + sendBroadcast(intent); + } + + // ---------- 本地Binder(同进程直接回调) ---------- + public class LocalBinder extends Binder { + public GpsSubscribeReceiverService getService() { + return GpsSubscribeReceiverService.this; + } + } + + public void addListener(final GpsMessageListener l) { + if (l != null && !listeners.contains(l)) { + listeners.add(l); + } + } + + public void removeListener(final GpsMessageListener l) { + listeners.remove(l); + } + + private void notifyGpsLocation(final LocationPoint point, final GpsSubscribeMsg config) { + for (GpsMessageListener l : listeners) { + l.onGpsLocation(point, config); + } + } + + private void notifySubscribeResult(final GpsSubscribeResult result) { + for (GpsMessageListener l : listeners) { + l.onSubscribeResult(result); + } + } + + @Override + public void onDestroy() { + listeners.clear(); + super.onDestroy(); + } +} + diff --git a/libgpsrelaysentinel/src/main/java/cc/winboll/studio/libgpsrelaysentinel/util/TimeCountUtil.java b/libgpsrelaysentinel/src/main/java/cc/winboll/studio/libgpsrelaysentinel/util/TimeCountUtil.java new file mode 100644 index 0000000..b078b32 --- /dev/null +++ b/libgpsrelaysentinel/src/main/java/cc/winboll/studio/libgpsrelaysentinel/util/TimeCountUtil.java @@ -0,0 +1,51 @@ +package cc.winboll.studio.libgpsrelaysentinel.util; + +/** + * @Author 豆包&ZhanGSKen + * @Date 2026/05/07 10:26 + */ + +import android.os.Handler; +import android.os.Message; + +public final class TimeCountUtil { + + private final Handler mHandler; + private long totalTime; + private boolean isRunning; + public static final int COUNT_FINISH = 1001; + + public TimeCountUtil(final OnCountListener listener) { + mHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + super.handleMessage(msg); + if(msg.what == COUNT_FINISH){ + isRunning = false; + if(listener != null){ + listener.onTimeOut(); + } + } + } + }; + } + + public void start(long time){ + if(isRunning){ + return; + } + totalTime = time; + isRunning = true; + mHandler.sendEmptyMessageDelayed(COUNT_FINISH,totalTime); + } + + public void cancel(){ + mHandler.removeMessages(COUNT_FINISH); + isRunning = false; + } + + public interface OnCountListener{ + void onTimeOut(); + } +} + diff --git a/libgpsrelaysentinel/src/main/java/cc/winboll/studio/libgpsrelaysentinel/view/GpsSubscribeControlView.java b/libgpsrelaysentinel/src/main/java/cc/winboll/studio/libgpsrelaysentinel/view/GpsSubscribeControlView.java new file mode 100644 index 0000000..08b0e95 --- /dev/null +++ b/libgpsrelaysentinel/src/main/java/cc/winboll/studio/libgpsrelaysentinel/view/GpsSubscribeControlView.java @@ -0,0 +1,174 @@ +package cc.winboll.studio.libgpsrelaysentinel.view; + +/** + * @Author 豆包&ZhanGSKen + * @Date 2026/05/07 10:27 + */ + +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.util.AttributeSet; +import android.view.View; +import android.widget.CompoundButton; +import android.widget.EditText; +import android.widget.LinearLayout; +import android.widget.RadioButton; +import android.widget.RadioGroup; +import android.widget.Switch; +import android.widget.TextView; +import cc.winboll.studio.libgpsrelaysentinel.R; + +import java.util.UUID; + +import cc.winboll.studio.libgpsrelaysentinel.model.GpsSubscribeConst; +import cc.winboll.studio.libgpsrelaysentinel.model.GpsSubscribeMsg; +import cc.winboll.studio.libgpsrelaysentinel.model.GpsSubscribeResult; +import cc.winboll.studio.libgpsrelaysentinel.receiver.GpsSubscribeObserverReceiver; +import cc.winboll.studio.libgpsrelaysentinel.util.TimeCountUtil; + +public final class GpsSubscribeControlView extends LinearLayout { + + private RadioGroup rgSubMode; + private RadioButton rbAll; + private RadioButton rbStep; + private LinearLayout layoutStepSetting; + private EditText etStepMeter; + + private Switch mSwitchSubscribe; + private TextView mTvCountTip; + + private TimeCountUtil mTimeCountUtil; + private GpsSubscribeObserverReceiver mResultReceiver; + private String currentSubscribeSid; + private boolean isSubscribeSuccess; + + public GpsSubscribeControlView(Context context) { + super(context); + initView(); + } + + public GpsSubscribeControlView(Context context, AttributeSet attrs) { + super(context, attrs); + initView(); + } + + private void initView(){ + setOrientation(VERTICAL); + inflate(getContext(),R.layout.view_gps_subscribe_control,this); + + rgSubMode = findViewById(R.id.rg_sub_mode); + rbAll = findViewById(R.id.rb_all); + rbStep = findViewById(R.id.rb_step); + layoutStepSetting = findViewById(R.id.layout_step_setting); + etStepMeter = findViewById(R.id.et_step_meter); + + mSwitchSubscribe = findViewById(R.id.switch_subscribe); + mTvCountTip = findViewById(R.id.tv_count_tip); + + initModeSwitch(); + initCountUtil(); + initReceiver(); + initSwitchEvent(); + } + + private void initModeSwitch(){ + rgSubMode.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(RadioGroup group, int checkedId) { + layoutStepSetting.setVisibility(checkedId == R.id.rb_step ? View.VISIBLE : View.GONE); + } + }); + } + + private void initCountUtil(){ + mTimeCountUtil = new TimeCountUtil(new TimeCountUtil.OnCountListener() { + @Override + public void onTimeOut() { + if(!isSubscribeSuccess){ + mSwitchSubscribe.setChecked(false); + mTvCountTip.setText("订阅超时,已自动关闭"); + } + } + }); + } + + private void initReceiver(){ + mResultReceiver = new GpsSubscribeObserverReceiver(); + mResultReceiver.setOnSubscribeResultListener(new GpsSubscribeObserverReceiver.OnSubscribeResultListener() { + @Override + public void onResultBack(GpsSubscribeResult result) { + if(currentSubscribeSid.equals(result.getSubscribeUniqueId())){ + isSubscribeSuccess = true; + mTimeCountUtil.cancel(); + mTvCountTip.setText("订阅已生效,通讯正常"); + } + } + }); + IntentFilter filter = new IntentFilter(); + filter.addAction(GpsSubscribeConst.ACTION_SUBSCRIBE_CALLBACK); + getContext().registerReceiver(mResultReceiver,filter); + } + + private void initSwitchEvent(){ + mSwitchSubscribe.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + if(isChecked){ + startSubscribe(); + }else{ + stopSubscribe(); + } + } + }); + } + + private void startSubscribe(){ + isSubscribeSuccess = false; + currentSubscribeSid = UUID.randomUUID().toString(); + mTvCountTip.setText("等待订阅返回中..."); + + int subMode = GpsSubscribeConst.SUB_TYPE_ALL; + float stepMeter = 10.0f; + + if(rbStep.isChecked()){ + subMode = GpsSubscribeConst.SUB_TYPE_STEP_DISTANCE; + try{ + stepMeter = Float.parseFloat(etStepMeter.getText().toString().trim()); + }catch (Exception e){ + stepMeter = 10.0f; + } + } + + GpsSubscribeMsg msg = new GpsSubscribeMsg( + getContext().getPackageName(), + subMode, + stepMeter, + GpsSubscribeConst.SUBSCRIBE_TYPE_LOCATION, + 1000, + 1.0f, + true, + currentSubscribeSid + ); + + Intent intent = new Intent(GpsSubscribeConst.ACTION_SUBSCRIBE_REQUEST); + intent.putExtra("req",msg); + getContext().sendBroadcast(intent); + + mTimeCountUtil.start(GpsSubscribeConst.SUBSCRIBE_TIME_OUT); + } + + private void stopSubscribe(){ + mTimeCountUtil.cancel(); + isSubscribeSuccess = false; + mTvCountTip.setText("订阅已关闭"); + } + + public void release(){ + mTimeCountUtil.cancel(); + if(mResultReceiver != null){ + getContext().unregisterReceiver(mResultReceiver); + } + } +} + diff --git a/libgpsrelaysentinel/src/main/res/drawable-hdpi/ic_launcher.png b/libgpsrelaysentinel/src/main/res/drawable-hdpi/ic_launcher.png new file mode 100644 index 0000000..96a442e Binary files /dev/null and b/libgpsrelaysentinel/src/main/res/drawable-hdpi/ic_launcher.png differ diff --git a/libgpsrelaysentinel/src/main/res/drawable-mdpi/ic_launcher.png b/libgpsrelaysentinel/src/main/res/drawable-mdpi/ic_launcher.png new file mode 100644 index 0000000..359047d Binary files /dev/null and b/libgpsrelaysentinel/src/main/res/drawable-mdpi/ic_launcher.png differ diff --git a/libgpsrelaysentinel/src/main/res/drawable-xhdpi/ic_launcher.png b/libgpsrelaysentinel/src/main/res/drawable-xhdpi/ic_launcher.png new file mode 100644 index 0000000..71c6d76 Binary files /dev/null and b/libgpsrelaysentinel/src/main/res/drawable-xhdpi/ic_launcher.png differ diff --git a/libgpsrelaysentinel/src/main/res/drawable-xxhdpi/ic_launcher.png b/libgpsrelaysentinel/src/main/res/drawable-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..4df1894 Binary files /dev/null and b/libgpsrelaysentinel/src/main/res/drawable-xxhdpi/ic_launcher.png differ diff --git a/libgpsrelaysentinel/src/main/res/layout/view_gps_subscribe_control.xml b/libgpsrelaysentinel/src/main/res/layout/view_gps_subscribe_control.xml new file mode 100644 index 0000000..e35e2ad --- /dev/null +++ b/libgpsrelaysentinel/src/main/res/layout/view_gps_subscribe_control.xml @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/libgpsrelaysentinel/src/main/res/values-v21/styles.xml b/libgpsrelaysentinel/src/main/res/values-v21/styles.xml new file mode 100644 index 0000000..0948fdc --- /dev/null +++ b/libgpsrelaysentinel/src/main/res/values-v21/styles.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/libgpsrelaysentinel/src/main/res/values/strings.xml b/libgpsrelaysentinel/src/main/res/values/strings.xml new file mode 100644 index 0000000..7d1f811 --- /dev/null +++ b/libgpsrelaysentinel/src/main/res/values/strings.xml @@ -0,0 +1,8 @@ + + + + libdebugtemp + Hello world! + LibraryActivity + + diff --git a/libgpsrelaysentinel/src/main/res/values/styles.xml b/libgpsrelaysentinel/src/main/res/values/styles.xml new file mode 100644 index 0000000..8d78246 --- /dev/null +++ b/libgpsrelaysentinel/src/main/res/values/styles.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file