Compare commits

..

13 Commits

63 changed files with 837 additions and 2544 deletions

View File

@@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" >
<application>
<!-- Put flavor specific code here -->
</application>
</manifest>

View File

@@ -1,25 +0,0 @@
Manifest-Version: 1.0
Built-By: Generated-by-ADT
Created-By: Android Gradle 3.5.0
Name: AndroidManifest.xml
SHA1-Digest: U36A0NWthb49+Rxs33tkIuNYFCI=
Name: jni/Android.mk
SHA1-Digest: ZpGSlRJPL0g9OejiWbQorqj40/Y=
Name: jni/Application.mk
SHA1-Digest: TKh2CbRLeKfvgL4cPfmoxcVz+vc=
Name: jni/hello-jni.cpp
SHA1-Digest: 1btXO19SqB6rDvPo5ynG0brDqTk=
Name: project.properties
SHA1-Digest: 0ekOiGTFMVJOWqAFzNFj/1vxPL8=
Name: res/values/strings.xml
SHA1-Digest: FgRO/zbNaC1wuZKVT7h6NSYBmpY=
Name: src/$package_name$/HelloJni.java
SHA1-Digest: p0e9DNKocjRnsOhetb9bnp9s9J4=

View File

@@ -32,21 +32,28 @@ android {
// versionName 更新后需要手动设置
// .winboll/winbollBuildProps.properties 文件的 stageCount=0
// Gradle编译环境下合起来的 versionName 就是 "${versionName}.0"
versionName "15.11"
versionName "15.12"
if(true) {
versionName = genVersionName("${versionName}")
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
// 米盟 SDK
packagingOptions {
doNotStrip "*/*/libmimo_1011.so"
}
}
dependencies {
// 米盟
api 'com.miui.zeus:mimo-ad-sdk:5.3.+'//请使用最新版sdk
//注意以下5个库必须要引入
//api 'androidx.appcompat:appcompat:1.4.1'
api 'androidx.recyclerview:recyclerview:1.0.0'
api 'com.google.code.gson:gson:2.8.5'
api 'com.github.bumptech.glide:glide:4.9.0'
//annotationProcessor 'com.github.bumptech.glide:compiler:4.9.0'
// https://mvnrepository.com/artifact/com.jzxiang.pickerview/TimePickerDialog
api 'com.jzxiang.pickerview:TimePickerDialog:1.0.1'
@@ -72,7 +79,14 @@ dependencies {
//api 'androidx.vectordrawable:vectordrawable-animated:1.1.0'
//api 'androidx.fragment:fragment:1.1.0'
api 'cc.winboll.studio:libaes:15.11.0'
api 'cc.winboll.studio:libappbase:15.11.0'
// WinBoLL库 nexus.winboll.cc 地址
//api 'cc.winboll.studio:libaes:15.12.0'
//api 'cc.winboll.studio:libappbase:15.12.2'
// WinBoLL备用库 jitpack.io 地址
api 'com.github.ZhanGSKen:AES:aes-v15.12.3'
api 'com.github.ZhanGSKen:APPBase:appbase-v15.12.2'
api fileTree(dir: 'libs', include: ['*.jar'])
}

View File

@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
#Sat Nov 15 08:45:48 GMT 2025
#Sun Dec 07 23:58:27 HKT 2025
stageCount=2
libraryProject=
baseVersion=15.11
publishVersion=15.11.1
buildCount=21
baseBetaVersion=15.11.2
baseVersion=15.12
publishVersion=15.12.1
buildCount=0
baseBetaVersion=15.12.2

View File

@@ -3,9 +3,6 @@
xmlns:android="http://schemas.android.com/apk/res/android"
package="cc.winboll.studio.positions">
<!-- 只能在前台获取精确的位置信息 -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<!-- 只有在前台运行时才能获取大致位置信息 -->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
@@ -30,6 +27,9 @@
<!-- 修改或删除您共享存储空间中的内容 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<!-- 只能在前台获取精确的位置信息 -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-feature
android:name="android.hardware.location.gps"
android:required="false"/>
@@ -65,8 +65,6 @@
</activity>
<activity android:name=".activities.CrashActivity"/>
<activity-alias
android:name=".MainActivityWukong"
android:targetActivity=".MainActivity"
@@ -111,15 +109,9 @@
</activity-alias>
<meta-data
android:name="android.max_aspect"
android:value="4.0"/>
<activity android:name="cc.winboll.studio.positions.activities.LocationActivity"/>
<meta-data
android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version"/>
<activity android:name="cc.winboll.studio.positions.activities.ShortcutActionActivity"/>
<service
android:name=".services.MainService"
@@ -143,6 +135,14 @@
</receiver>
<meta-data
android:name="android.max_aspect"
android:value="4.0"/>
<meta-data
android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version"/>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
@@ -151,11 +151,11 @@
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths"/>
android:resource="@xml/file_provider"/>
</provider>
<activity android:name="cc.winboll.studio.positions.activities.ShortcutActionActivity"/>
<activity android:name="cc.winboll.studio.positions.activities.SettingsActivity"/>
</application>

View File

@@ -14,6 +14,7 @@ import android.os.Handler;
import android.os.Looper;
import android.text.TextUtils;
import android.util.Log;
import android.view.Gravity;
import android.view.Menu;
import android.view.MenuItem;
import android.view.ViewGroup;
@@ -24,7 +25,6 @@ import android.widget.Toast;
import cc.winboll.studio.libaes.utils.WinBoLLActivityManager;
import cc.winboll.studio.libappbase.GlobalApplication;
import cc.winboll.studio.libappbase.ToastUtils;
import cc.winboll.studio.positions.utils.MyActivityLifecycleCallbacks;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
@@ -44,36 +44,24 @@ import java.util.concurrent.atomic.AtomicBoolean;
public class App extends GlobalApplication {
public static volatile AppLevel _mAppLevel = AppLevel.WUKONG;
public static final String COMPONENT_WUKONG = "cc.winboll.studio.positions.MainActivityWukong";
public static final String COMPONENT_LAOJUN = "cc.winboll.studio.positions.MainActivityLaojun";
public static final String ACTION_OPEN_APPPLUS = "cc.winboll.studio.positions.App.ACTION_OPEN_APPPLUS";
public static final String ACTION_CLOSE_APPPLUS = "cc.winboll.studio.positions.App.ACTION_CLOSE_APPPLUS";
private static Handler MAIN_HANDLER = new Handler(Looper.getMainLooper());
MyActivityLifecycleCallbacks mMyActivityLifecycleCallbacks;
@Override
public void onCreate() {
super.onCreate();
setIsDebugging(BuildConfig.DEBUG);
WinBoLLActivityManager.init(this);
// 初始化 Toast 框架
ToastUtils.init(this);
// 设置 Toast 布局样式
//ToastUtils.setView(R.layout.view_toast);
//ToastUtils.setStyle(new WhiteToastStyle());
//ToastUtils.setGravity(Gravity.BOTTOM, 0, 200);
//CrashHandler.getInstance().registerGlobal(this);
//CrashHandler.getInstance().registerPart(this);
mMyActivityLifecycleCallbacks = new MyActivityLifecycleCallbacks();
registerActivityLifecycleCallbacks(mMyActivityLifecycleCallbacks);
}
public static void write(InputStream input, OutputStream output) throws IOException {

View File

@@ -1,43 +0,0 @@
package cc.winboll.studio.positions;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/11/10 07:23
* @Describe 应用级别类型枚举
*/
public enum AppLevel {
WUKONG("wukong", "悟空级别"),
LAOJUN("laojun", "老君级别");
public static final String TAG = "AppLevel";
// 枚举属性
private final String code; // 编码(如 "wukong"
private final String desc; // 描述
// 构造方法Java 7 需显式定义)
AppLevel(String code, String desc) {
this.code = code;
this.desc = desc;
}
// Getter 方法(获取枚举属性)
public String getCode() {
return code;
}
public String getDesc() {
return desc;
}
// 可选:根据 code 获取枚举项(便于业务使用)
public static AppLevel getByCode(String code) {
for (AppLevel level : values()) {
if (level.code.equals(code)) {
return level;
}
}
return null; // 或抛出异常,根据业务需求调整
}
}

View File

@@ -6,7 +6,8 @@ import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.CompoundButton;
@@ -16,14 +17,15 @@ import androidx.annotation.NonNull;
import androidx.appcompat.widget.Toolbar;
import androidx.core.content.ContextCompat;
import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity;
import cc.winboll.studio.libaes.utils.AESThemeUtil;
import cc.winboll.studio.libaes.utils.DevelopUtils;
import cc.winboll.studio.libaes.utils.WinBoLLActivityManager;
import cc.winboll.studio.libaes.views.ADsBannerView;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.ToastUtils;
import cc.winboll.studio.positions.activities.LocationActivity;
import cc.winboll.studio.positions.activities.SettingsActivity;
import cc.winboll.studio.positions.activities.WinBoLLActivity;
import cc.winboll.studio.positions.utils.APPPlusUtils;
import cc.winboll.studio.positions.utils.AppConfigsUtil;
import cc.winboll.studio.positions.utils.JsonShareHandler;
import cc.winboll.studio.positions.utils.ServiceUtil;
/**
@@ -34,8 +36,7 @@ import cc.winboll.studio.positions.utils.ServiceUtil;
*/
public class MainActivity extends WinBoLLActivity implements IWinBoLLActivity {
public static final String TAG = "MainActivity";
// 权限请求码(建议定义为类常量,避免魔法值)
// 权限请求码(建议定义为类常量,避免魔法值)
private static final int REQUEST_LOCATION_PERMISSIONS = 1001;
private static final int REQUEST_BACKGROUND_LOCATION_PERMISSION = 1002;
@@ -45,7 +46,8 @@ public class MainActivity extends WinBoLLActivity implements IWinBoLLActivity {
private Toolbar mToolbar;
// 服务相关:服务实例、绑定状态标记
//private DistanceRefreshService mDistanceService;
//private boolean isServiceBound = false;
private boolean isServiceBound = false;
ADsBannerView mADsBannerView;
@Override
@@ -87,10 +89,6 @@ public class MainActivity extends WinBoLLActivity implements IWinBoLLActivity {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); // 关联主页面布局
// 处理启动时的分享 Intent
handleShareIntent(getIntent());
// 1. 初始化顶部 Toolbar保留原逻辑设置页面标题
initToolbar();
// 2. 初始化其他控件
@@ -101,36 +99,18 @@ public class MainActivity extends WinBoLLActivity implements IWinBoLLActivity {
}
// 4. 绑定服务(仅用于获取服务实时状态,不影响服务独立运行)
//bindDistanceService();
mADsBannerView = findViewById(R.id.adsbanner);
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
// 处理后续接收的分享 Intent如应用已在后台
handleShareIntent(intent);
}
private void handleShareIntent(Intent intent) {
if (intent != null && Intent.ACTION_SEND.equals(intent.getAction())) {
// 调用工具类,弹出确认对话框
JsonShareHandler.handleSharedJsonWithConfirm(this, intent, new JsonShareHandler.ConfirmCallback() {
@Override
public void onConfirm(boolean isConfirm) {
// 回调处理isConfirm 为 true 表示接收并保存false 表示取消
if (!isConfirm) {
Log.d("MainActivity", "用户取消接收文件");
// 可添加取消后的逻辑(如关闭页面)
// finish();
}
}
});
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mADsBannerView != null) {
mADsBannerView.releaseAdResources();
}
// 页面销毁时解绑服务避免Activity与服务相互引用导致内存泄漏
// if (isServiceBound) {
// unbindService(mServiceConn);
@@ -139,6 +119,16 @@ public class MainActivity extends WinBoLLActivity implements IWinBoLLActivity {
// }
}
@Override
protected void onResume() {
super.onResume();
if (mADsBannerView != null) {
mADsBannerView.resumeADs(MainActivity.this);
}
}
// ---------------------- 核心功能1初始化UI组件Toolbar + 服务开关) ----------------------
/**
* 初始化顶部 Toolbar设置页面标题
@@ -147,9 +137,9 @@ public class MainActivity extends WinBoLLActivity implements IWinBoLLActivity {
mToolbar = (Toolbar) findViewById(R.id.toolbar); // Java 7 显式 findViewById + 强转
setSupportActionBar(mToolbar);
// 给ActionBar设置标题先判断非空避免空指针异常
AppLevel appLevel = AppConfigsUtil.getInstance(getApplicationContext()).getAppLevel(true);
getSupportActionBar().setTitle(getString(R.string.app_name));
if (getSupportActionBar() != null) {
getSupportActionBar().setTitle(getString(R.string.app_name));
}
}
/**
@@ -188,6 +178,39 @@ public class MainActivity extends WinBoLLActivity implements IWinBoLLActivity {
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// 主题菜单
AESThemeUtil.inflateMenu(this, menu);
// 调试工具菜单
if (App.isDebugging()) {
DevelopUtils.inflateMenu(this, menu);
}
// 应用其他菜单
getMenuInflater().inflate(R.menu.toolbar_main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int menuItemId = item.getItemId();
if (AESThemeUtil.onAppThemeItemSelected(this, item)) {
recreate();
} if (DevelopUtils.onDevelopItemSelected(this, item)) {
LogUtils.d(TAG, String.format("onOptionsItemSelected item.getItemId() %d ", item.getItemId()));
} else if (item.getItemId() == R.id.item_settings) {
Intent intent = new Intent();
intent.setClass(this, SettingsActivity.class);
startActivity(intent);
} else {
// 在switch语句中处理每个ID并在处理完后返回true未处理的情况返回false。
return super.onOptionsItemSelected(item);
}
return true;
}
/**
* 绑定服务(仅用于获取服务状态,不启动服务)
*/

View File

@@ -1,22 +0,0 @@
package cc.winboll.studio.positions;
import android.os.Bundle;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.ToastUtils;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/11/13 15:21
* @Describe MainActivityLaojun
*/
public class MainActivityLaojun extends MainActivity {
public static final String TAG = "MainActivityLaojun";
@Override
protected void onCreate(Bundle savedInstanceState) {
ToastUtils.show("道法自然");
LogUtils.d(TAG, "玩法归臻");
super.onCreate(savedInstanceState);
}
}

View File

@@ -1,43 +0,0 @@
package cc.winboll.studio.positions;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/11/15 15:14
* @Describe 应用入口级别类型枚举
*/
public enum PointLevel {
DORAEMON("doraemon", "叮铛级别"),
WUKONG("wukong", "悟空级别"),
LAOJUN("laojun", "老君级别");
public static final String TAG = "PointLevel";
// 枚举属性
private final String code; // 编码(如 "wukong"
private final String desc; // 描述
// 构造方法Java 7 需显式定义)
PointLevel(String code, String desc) {
this.code = code;
this.desc = desc;
}
// Getter 方法(获取枚举属性)
public String getCode() {
return code;
}
public String getDesc() {
return desc;
}
// 可选:根据 code 获取枚举项(便于业务使用)
public static PointLevel getByCode(String code) {
for (PointLevel level : values()) {
if (level.code.equals(code)) {
return level;
}
}
return null; // 或抛出异常,根据业务需求调整
}
}

View File

@@ -16,8 +16,10 @@ import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.widget.Toolbar;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.ToastUtils;
import cc.winboll.studio.positions.R;
@@ -33,12 +35,14 @@ import java.util.concurrent.atomic.AtomicBoolean;
* 2. Adapter 初始化传入 MainService 实例,确保数据来源唯一
* 3. 所有位置/任务操作通过 MainService 接口执行
*/
public class LocationActivity extends Activity {
public class LocationActivity extends WinBoLLActivity implements IWinBoLLActivity {
public static final String TAG = "LocationActivity";
private Toolbar mToolbar;
private RecyclerView mRvPosition;
private PositionAdapter mPositionAdapter;
// MainService 引用+绑定状态AtomicBoolean 确保多线程状态可见性)
private MainService mMainService;
private final AtomicBoolean isServiceBound = new AtomicBoolean(false);
@@ -96,11 +100,34 @@ public class LocationActivity extends Activity {
}
};
@Override
public Activity getActivity() {
return this;
}
@Override
public String getTag() {
return TAG;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_location);
mToolbar = findViewById(R.id.toolbar);
setSupportActionBar(mToolbar);
mToolbar.setSubtitle(getTag());
mToolbar.setTitleTextAppearance(this, R.style.Toolbar_TitleText);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
mToolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
LogUtils.d(TAG, "【导航栏】点击返回");
finish();
}
});
// 1. 初始化视图优先执行避免Adapter初始化时视图为空
initView();
// 2. 初始化GPS监听提前创建避免绑定服务后空指针
@@ -167,7 +194,7 @@ public class LocationActivity extends Activity {
}
}
LogUtils.d(TAG, "数据同步完成:服务位置数=" + (servicePosList == null ? 0 : servicePosList.size())
+ ",本地缓存数=" + mLocalPosCache.size());
+ ",本地缓存数=" + mLocalPosCache.size());
} catch (Exception e) {
LogUtils.d(TAG, "同步服务数据失败:" + e.getMessage());
@@ -183,9 +210,9 @@ public class LocationActivity extends Activity {
// 1. 多重安全校验(避免销毁后初始化/重复初始化/依赖未就绪)
if (isAdapterInited.get() || !isServiceBound.get() || mMainService == null || mRvPosition == null) {
LogUtils.w(TAG, "Adapter初始化跳过"
+ "已初始化=" + isAdapterInited.get()
+ ",服务绑定=" + isServiceBound.get()
+ ",视图就绪=" + (mRvPosition != null));
+ "已初始化=" + isAdapterInited.get()
+ ",服务绑定=" + isServiceBound.get()
+ ",视图就绪=" + (mRvPosition != null));
return;
}
@@ -195,54 +222,54 @@ public class LocationActivity extends Activity {
// 3. 设置删除回调(删除时同步服务+本地缓存+Adapter
mPositionAdapter.setOnDeleteClickListener(new PositionAdapter.OnDeleteClickListener() {
@Override
public void onDeleteClick(int position) {
// 安全校验(索引有效+服务绑定+缓存非空)
if (position < 0 || position >= mLocalPosCache.size() || !isServiceBound.get() || mMainService == null) {
LogUtils.w(TAG, "删除位置失败:索引无效/服务未就绪(索引=" + position + ",缓存量=" + mLocalPosCache.size() + "");
return;
}
@Override
public void onDeleteClick(int position) {
// 安全校验(索引有效+服务绑定+缓存非空)
if (position < 0 || position >= mLocalPosCache.size() || !isServiceBound.get() || mMainService == null) {
LogUtils.w(TAG, "删除位置失败:索引无效/服务未就绪(索引=" + position + ",缓存量=" + mLocalPosCache.size() + "");
return;
}
PositionModel deletePos = mLocalPosCache.get(position);
if (deletePos != null && !deletePos.getPositionId().isEmpty()) {
// 步骤1调用服务删除确保服务数据一致性
mMainService.removePosition(deletePos.getPositionId());
// 步骤2删除本地缓存确保缓存与服务同步
synchronized (mLocalPosCache) {
mLocalPosCache.remove(position);
}
// 步骤3通知Adapter刷新基于缓存操作避免空数据
mPositionAdapter.notifyItemRemoved(position);
showToast("删除位置成功:" + deletePos.getMemo());
LogUtils.d(TAG, "删除位置完成ID=" + deletePos.getPositionId() + "(服务+缓存已同步)");
}
}
});
PositionModel deletePos = mLocalPosCache.get(position);
if (deletePos != null && !deletePos.getPositionId().isEmpty()) {
// 步骤1调用服务删除确保服务数据一致性
mMainService.removePosition(deletePos.getPositionId());
// 步骤2删除本地缓存确保缓存与服务同步
synchronized (mLocalPosCache) {
mLocalPosCache.remove(position);
}
// 步骤3通知Adapter刷新基于缓存操作避免空数据
mPositionAdapter.notifyItemRemoved(position);
showToast("删除位置成功:" + deletePos.getMemo());
LogUtils.d(TAG, "删除位置完成ID=" + deletePos.getPositionId() + "(服务+缓存已同步)");
}
}
});
// 4. 设置保存回调(保存时同步服务+本地缓存+Adapter
mPositionAdapter.setOnSavePositionClickListener(new PositionAdapter.OnSavePositionClickListener() {
@Override
public void onSavePositionClick(int position, PositionModel updatedPos) {
// 安全校验(索引有效+服务绑定+数据非空)
if (!isServiceBound.get() || mMainService == null
|| position < 0 || position >= mLocalPosCache.size() || updatedPos == null) {
LogUtils.w(TAG, "保存位置失败:服务未就绪/索引无效/数据空");
showToast("服务未就绪,保存失败");
return;
}
@Override
public void onSavePositionClick(int position, PositionModel updatedPos) {
// 安全校验(索引有效+服务绑定+数据非空)
if (!isServiceBound.get() || mMainService == null
|| position < 0 || position >= mLocalPosCache.size() || updatedPos == null) {
LogUtils.w(TAG, "保存位置失败:服务未就绪/索引无效/数据空");
showToast("服务未就绪,保存失败");
return;
}
// 步骤1调用服务更新确保服务数据一致性
mMainService.updatePosition(updatedPos);
// 步骤2更新本地缓存确保缓存与服务同步
synchronized (mLocalPosCache) {
mLocalPosCache.set(position, updatedPos);
}
// 步骤3通知Adapter刷新基于缓存操作避免空数据
mPositionAdapter.notifyItemChanged(position);
showToast("保存位置成功:" + updatedPos.getMemo());
LogUtils.d(TAG, "保存位置完成ID=" + updatedPos.getPositionId() + "(服务+缓存已同步)");
}
});
// 步骤1调用服务更新确保服务数据一致性
mMainService.updatePosition(updatedPos);
// 步骤2更新本地缓存确保缓存与服务同步
synchronized (mLocalPosCache) {
mLocalPosCache.set(position, updatedPos);
}
// 步骤3通知Adapter刷新基于缓存操作避免空数据
mPositionAdapter.notifyItemChanged(position);
showToast("保存位置成功:" + updatedPos.getMemo());
LogUtils.d(TAG, "保存位置完成ID=" + updatedPos.getPositionId() + "(服务+缓存已同步)");
}
});
// 5. 设置Adapter到RecyclerView最后一步确保Adapter已配置完成
mRvPosition.setAdapter(mPositionAdapter);
@@ -268,7 +295,7 @@ public class LocationActivity extends Activity {
}
Toast.makeText(this, content, Toast.LENGTH_SHORT).show();
}
// ---------------------- 页面交互新增位置逻辑保留适配GPS数据 ----------------------
/**
* 新增位置调用服务addPosition()可选用当前GPS位置初始化新位置
@@ -395,9 +422,7 @@ public class LocationActivity extends Activity {
LogUtils.d(TAG, "onResume服务已绑定但Adapter未初始化重新同步数据");
syncDataFromMainService();
initPositionAdapter();
}
// 2. 服务已绑定且Adapter已初始化刷新数据确保与服务同步
else if (isServiceBound.get() && mMainService != null && isAdapterInited.get() && mPositionAdapter != null) {
} else if (isServiceBound.get() && mMainService != null && isAdapterInited.get() && mPositionAdapter != null) {
syncDataFromMainService();
mPositionAdapter.notifyDataSetChanged();
LogUtils.d(TAG, "onResume刷新位置数据与服务同步");

View File

@@ -0,0 +1,51 @@
package cc.winboll.studio.positions.activities;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import androidx.appcompat.widget.Toolbar;
import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.ToastUtils;
import cc.winboll.studio.positions.R;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/12/07 23:29
* @Describe 应用设置活动窗口
*/
public class SettingsActivity extends WinBoLLActivity implements IWinBoLLActivity {
public static final String TAG = "SettingsActivity";
private Toolbar mToolbar;
@Override
public Activity getActivity() {
return this;
}
@Override
public String getTag() {
return TAG;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_settings);
mToolbar = findViewById(R.id.toolbar);
setSupportActionBar(mToolbar);
mToolbar.setSubtitle(getTag());
mToolbar.setTitleTextAppearance(this, R.style.Toolbar_TitleText);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
mToolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
LogUtils.d(TAG, "【导航栏】点击返回");
finish();
}
});
}
}

View File

@@ -1,61 +0,0 @@
package cc.winboll.studio.positions.activities;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.os.PersistableBundle;
import cc.winboll.studio.libappbase.ToastUtils;
import cc.winboll.studio.positions.R;
import cc.winboll.studio.positions.utils.APPPlusUtils;
import cc.winboll.studio.positions.utils.AppConfigsUtil;
import cc.winboll.studio.positions.AppLevel;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/11/15 13:45
* @Describe 应用快捷方式活动类
*/
public class ShortcutActionActivity extends Activity {
public static final String TAG = "ShortcutActionActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 处理应用级别的切换请求
handleSwitchRequest();
finish();
}
// @Override
// public void onPostCreate(Bundle savedInstanceState, PersistableBundle persistentState) {
// super.onPostCreate(savedInstanceState, persistentState);
// finish();
// }
// @Override
// protected void onStart() {
// super.onStart();
// }
/**
* 处理应用图标快捷菜单的请求
*/
private void handleSwitchRequest() {
Intent intent = getIntent();
if (intent != null && "open_appplus".equals(intent.getDataString())) {
ToastUtils.show("已添加" + getString(R.string.app_name) + "附加组件");
AppConfigsUtil.getInstance(getApplicationContext()).setAppLevel(AppLevel.LAOJUN);
APPPlusUtils.openAPPPlus(this);
//moveTaskToBack(true);
}
if (intent != null && "close_appplus".equals(intent.getDataString())) {
ToastUtils.show("已移除" + getString(R.string.app_name) + "附加组件");
AppConfigsUtil.getInstance(getApplicationContext()).setAppLevel(AppLevel.WUKONG);
APPPlusUtils.closeAPPPlus(this);
//moveTaskToBack(true);
}
}
}

View File

@@ -10,20 +10,16 @@ import android.os.Bundle;
import android.view.MenuItem;
import androidx.appcompat.app.AppCompatActivity;
import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity;
import cc.winboll.studio.libaes.models.AESThemeBean;
import cc.winboll.studio.libaes.utils.AESThemeUtil;
import cc.winboll.studio.libaes.utils.WinBoLLActivityManager;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.ToastUtils;
import cc.winboll.studio.positions.App;
import cc.winboll.studio.positions.PointLevel;
import cc.winboll.studio.positions.R;
import cc.winboll.studio.positions.utils.ActivityAliasUtils;
import cc.winboll.studio.positions.utils.AppConfigsUtil;
public class WinBoLLActivity extends AppCompatActivity implements IWinBoLLActivity {
public static final String TAG = "WinBoLLActivity";
public static volatile PointLevel _mPointLevel = PointLevel.WUKONG;
protected volatile AESThemeBean.ThemeType mThemeType;
@Override
public Activity getActivity() {
@@ -35,58 +31,25 @@ public class WinBoLLActivity extends AppCompatActivity implements IWinBoLLActivi
return TAG;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
mThemeType = getThemeType();
setThemeStyle();
super.onCreate(savedInstanceState);
}
AESThemeBean.ThemeType getThemeType() {
return AESThemeBean.getThemeStyleType(AESThemeUtil.getThemeTypeID(getApplicationContext()));
}
void setThemeStyle() {
setTheme(AESThemeUtil.getThemeTypeID(getApplicationContext()));
}
@Override
protected void onResume() {
super.onResume();
//ToastUtils.show("onResume");
// ActivityAliasUtils 工具使用示例
//
// // 获取真实的目标组件名(即使通过 alias 启动,也能拿到 OriginalActivity
// String realTargetName = ActivityAliasUtils.getRealTargetNameFromIntent(this);
// LogUtils.d("AliasActivity", "真实组件名:" + realTargetName);
// 获取真实的目标组件名(即使通过 alias 启动,也能拿到 OriginalActivity
// String realTargetName = ActivityAliasUtils.getRealTargetNameFromIntent(this);
// LogUtils.d(TAG, "真实组件名:" + realTargetName);
// ToastUtils.show(realTargetName);
// // 判断某个组件是否为 alias
// String componentName = "com.winboll.app.AliasActivity";
// boolean isAlias = ActivityAliasUtils.isActivityAlias(getApplicationContext(), componentName);
// LogUtils.d("判断结果", componentName + " 是否为 alias" + isAlias); // true
// // 获取启动当前 Activity 的组件名(兼容 alias 场景)
// String launchComponent = ActivityAliasUtils.getLaunchComponentName(this);
// LogUtils.d("MainActivity", "启动组件名:" + launchComponent);
/*
* 应用入口逻辑模块
*/
//
// 检查当前活动的启动组件名,设置应用入口级别。
String launchComponent = ActivityAliasUtils.getLaunchComponentName(this);
LogUtils.d("MainActivity", "启动组件名:" + launchComponent);
ToastUtils.show(launchComponent);
// 当前应用处于活动暂停的状态时,就检查应用的入口组件名称,设置应用入口级别。
if (WinBoLLActivity._mPointLevel == PointLevel.DORAEMON) {
if (launchComponent.equals(App.COMPONENT_WUKONG)) {
getSupportActionBar().setTitle(getString(R.string.appplus_name));
ToastUtils.show("WUKONG");
_mPointLevel = PointLevel.WUKONG;
} else if (launchComponent.equals(App.COMPONENT_LAOJUN)) {
getSupportActionBar().setTitle(getString(R.string.app_name));
ToastUtils.show("LAOJUN");
_mPointLevel = PointLevel.LAOJUN;
} else {
// 如果是其他应用组件入口,就关闭活动
finish();
}
}
/*
* 应用级别设置模块
*/
// 读取并配置应用级别
App._mAppLevel = AppConfigsUtil.getInstance(getApplicationContext()).getAppLevel(true);
LogUtils.d(TAG, String.format("onResume %s", getTag()));
}

View File

@@ -9,14 +9,12 @@ package cc.winboll.studio.positions.models;
import android.util.JsonWriter;
import android.util.JsonReader;
import java.io.IOException;
import cc.winboll.studio.positions.AppLevel;
public class AppConfigsModel extends BaseBean {
public static final String TAG = "AppConfigsModel";
boolean isEnableMainService;
AppLevel appLevel;
public AppConfigsModel(boolean isEnableMainService) {
this.isEnableMainService = isEnableMainService;
@@ -26,14 +24,6 @@ public class AppConfigsModel extends BaseBean {
this.isEnableMainService = false;
}
public void setAppLevel(AppLevel appLevel) {
this.appLevel = appLevel;
}
public AppLevel getAppLevel() {
return appLevel;
}
public void setIsEnableMainService(boolean isEnableMainService) {
this.isEnableMainService = isEnableMainService;
}
@@ -52,7 +42,6 @@ public class AppConfigsModel extends BaseBean {
public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException {
super.writeThisToJsonWriter(jsonWriter);
jsonWriter.name("isEnableDistanceRefreshService").value(isEnableMainService());
jsonWriter.name("appLevel").value(getAppLevel().ordinal());
}
// JSON反序列化加载位置数据校验字段
@@ -63,8 +52,6 @@ public class AppConfigsModel extends BaseBean {
} else {
if (name.equals("isEnableDistanceRefreshService")) {
setIsEnableMainService(jsonReader.nextBoolean());
} else if (name.equals("appLevel")) {
setAppLevel((AppLevel.values()[jsonReader.nextInt()]));
} else {
return false;
}

View File

@@ -1,361 +0,0 @@
package cc.winboll.studio.positions.receivers;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/10/28 19:07
* @Describe MotionStatusReceiver
*/
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.text.TextUtils;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.ToastUtils;
import cc.winboll.studio.positions.services.MainService;
import cc.winboll.studio.positions.utils.ServiceUtil;
/**
* 运动状态监听Receiver
* 功能1.持续监听传感器(不关闭) 2.每5秒计算运动状态 3.按状态切换GPS模式实时/30秒定时
*/
public class MotionStatusReceiver extends BroadcastReceiver implements SensorEventListener {
public static final String TAG = "MotionStatusReceiver";
// 广播Action
public static final String ACTION_MOTION_STATUS_RECEIVER = "cc.winboll.studio.positions.receivers.MotionStatusReceiver";
public static final String EXTRA_SENSORS_ENABLE = "EXTRA_SENSORS_ENABLE";
// 传感器启动状态标志位
boolean mIsSensorsEnable = false;
// 运动状态常量
private static final int MOTION_STATUS_STATIC = 0; // 静止/低运动
private static final int MOTION_STATUS_WALKING = 1; // 行走/高速运动
// 配置参数(按需求调整)
private static final float ACCELEROMETER_THRESHOLD = 0.8f; // 加速度阈值
private static final float GYROSCOPE_THRESHOLD = 0.5f; // 陀螺仪阈值
private static final long STATUS_CALC_INTERVAL = 5000; // 运动状态计算间隔5秒
private static final long GPS_STATIC_INTERVAL = 30; // 静止时GPS间隔30秒
// 核心对象
private volatile SensorManager mSensorManager;
private Sensor mAccelerometer;
private Sensor mGyroscope;
private volatile boolean mIsSensorListening = false; // 传感器是否持续监听
private int mCurrentMotionStatus = MOTION_STATUS_STATIC; // 当前运动状态
private Handler mMainHandler; // 主线程Handler用于定时计算
private Context mBroadcastContext; // 广播上下文
// 传感器数据缓存用于5秒内数据汇总避免单次波动误判
private float mAccelMax = 0f; // 5秒内加速度最大值
private float mGyroMax = 0f; // 5秒内陀螺仪最大值
@Override
public void onReceive(Context context, Intent intent) {
LogUtils.d(TAG, "===== 接收器启动onReceive() 开始执行 =====");
this.mBroadcastContext = context;
mMainHandler = new Handler(Looper.getMainLooper());
if (TextUtils.equals(intent.getAction(), ACTION_MOTION_STATUS_RECEIVER)) {
boolean isSettingEnable = intent.getBooleanExtra(EXTRA_SENSORS_ENABLE, false);
if (mIsSensorsEnable == false && isSettingEnable == true) {
mIsSensorsEnable = true;
// 1. 初始化传感器(必执行)
initSensors();
if (mAccelerometer == null || mGyroscope == null) {
LogUtils.e(TAG, "设备缺少加速度/陀螺仪,无法持续监听");
cleanResources(false); // 传感器不可用才清理
return;
}
// 2. 校验参数
if (context == null || intent == null) {
LogUtils.d(TAG, "onReceive():无效参数,终止处理");
cleanResources(false);
return;
}
LogUtils.d(TAG, "onReceive()接收到广播Action=" + intent.getAction());
// 3. 启动持续传感器监听(核心:不关闭,重复调用无影响)
startSensorListening();
// 4. 启动5秒定时计算运动状态核心持续触发状态判断
startStatusCalcTimer();
}
}
// 5. 处理外部广播触发(可选,保留外部控制能力)
// if (TextUtils.equals(intent.getAction(), ACTION_MOTION_STATUS_RECEIVER)) {
// int motionStatus = intent.getIntExtra(EXTRA_MOTION_STATUS, MOTION_STATUS_STATIC);
// String statusDesc = motionStatus == MOTION_STATUS_WALKING ? "高速运动" : "静止/低运动";
// LogUtils.d(TAG, "外部广播触发,强制设置运动状态:" + statusDesc);
// mCurrentMotionStatus = motionStatus;
// handleMotionStatus(mCurrentMotionStatus); // 立即执行GPS切换
// }
}
/**
* 初始化传感器(持续监听,复用实例)
*/
private void initSensors() {
LogUtils.d(TAG, "initSensors():初始化传感器");
if (mSensorManager != null || mBroadcastContext == null) return;
mSensorManager = (SensorManager) mBroadcastContext.getSystemService(Context.SENSOR_SERVICE);
if (mSensorManager == null) {
LogUtils.e(TAG, "设备不支持传感器服务");
return;
}
// 获取传感器实例(持续复用,不销毁)
mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
mGyroscope = mSensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE);
LogUtils.d(TAG, "传感器初始化结果:加速度=" + (mAccelerometer != null) + ",陀螺仪=" + (mGyroscope != null));
}
/**
* 启动传感器持续监听(核心:不关闭,注册一次一直生效)
*/
private void startSensorListening() {
if (mSensorManager == null || mAccelerometer == null || mGyroscope == null) return;
if (!mIsSensorListening) {
// 注册传感器监听(持续生效,直到服务销毁才注销)
mSensorManager.registerListener(
this,
mAccelerometer,
SensorManager.SENSOR_DELAY_NORMAL, // 正常延迟,平衡性能与精度
mMainHandler
);
mSensorManager.registerListener(
this,
mGyroscope,
SensorManager.SENSOR_DELAY_NORMAL,
mMainHandler
);
mIsSensorListening = true;
LogUtils.d(TAG, "startSensorListening():传感器持续监听已启动(不关闭)");
}
}
/**
* 启动5秒定时计算运动状态核心周期性汇总传感器数据
*/
private void startStatusCalcTimer() {
if (mMainHandler == null) return;
// 移除旧任务(避免重复注册)
mMainHandler.removeCallbacks(mStatusCalcRunnable);
// 启动定时任务每5秒执行一次
mMainHandler.postDelayed(mStatusCalcRunnable, STATUS_CALC_INTERVAL);
LogUtils.d(TAG, "startStatusCalcTimer()5秒运动状态计算定时器已启动");
}
/**
* 运动状态计算任务5秒执行一次
*/
private final Runnable mStatusCalcRunnable = new Runnable() {
@Override
public void run() {
// 1. 基于5秒内缓存的最大传感器数据判断状态
boolean isHighMotion = (mAccelMax > ACCELEROMETER_THRESHOLD) && (mGyroMax > GYROSCOPE_THRESHOLD);
int newMotionStatus = isHighMotion ? MOTION_STATUS_WALKING : MOTION_STATUS_STATIC;
// 2. 状态变化时才处理避免频繁切换GPS
if (newMotionStatus != mCurrentMotionStatus) {
mCurrentMotionStatus = newMotionStatus;
String statusDesc = isHighMotion ? "高速运动" : "静止/低运动";
LogUtils.d(TAG, "运动状态更新5秒计算" + statusDesc
+ "(加速度最大值=" + mAccelMax + ",陀螺仪最大值=" + mGyroMax + "");
handleMotionStatus(newMotionStatus); // 切换GPS模式
} else {
LogUtils.d(TAG, "运动状态无变化5秒计算" + (isHighMotion ? "高速运动" : "静止/低运动"));
}
// 3. 重置传感器数据缓存准备下一个5秒周期
mAccelMax = 0f;
mGyroMax = 0f;
// 4. 循环执行定时任务(核心:持续计算)
mMainHandler.postDelayed(this, STATUS_CALC_INTERVAL);
}
};
/**
* 传感器数据变化回调(核心:实时缓存最大数据)
*/
@Override
public void onSensorChanged(SensorEvent event) {
if (event == null) return;
// 实时缓存5秒内的最大传感器数据避免单次波动误判
switch (event.sensor.getType()) {
case Sensor.TYPE_ACCELEROMETER:
float accelTotal = Math.abs(event.values[0]) + Math.abs(event.values[1]) + Math.abs(event.values[2]);
if (accelTotal > mAccelMax) mAccelMax = accelTotal; // 缓存最大值
LogUtils.d(TAG, "加速度传感器实时数据:合值=" + accelTotal + "当前5秒最大值=" + mAccelMax + "");
break;
case Sensor.TYPE_GYROSCOPE:
float gyroTotal = Math.abs(event.values[0]) + Math.abs(event.values[1]) + Math.abs(event.values[2]);
if (gyroTotal > mGyroMax) mGyroMax = gyroTotal; // 缓存最大值
LogUtils.d(TAG, "陀螺仪实时数据:合值=" + gyroTotal + "当前5秒最大值=" + mGyroMax + "");
break;
}
}
/**
* 处理运动状态核心按状态切换GPS模式
*/
private void handleMotionStatus(int motionStatus) {
LogUtils.d(TAG, "handleMotionStatus()开始处理运动状态切换GPS模式");
if (mBroadcastContext == null) {
LogUtils.w(TAG, "上下文为空无法处理GPS");
return;
}
MainService mainService = getMainService();
if (mainService == null) {
LogUtils.e(TAG, "MainService未启动GPS控制失败");
return;
}
if (motionStatus == MOTION_STATUS_WALKING) {
// 高速运动启动GPS实时更新2秒/1米
handleHighMotionGPS(mainService);
} else {
// 静止/低运动启动GPS30秒定时更新
handleStaticGPS(mainService);
}
}
/**
* 高速运动GPS处理实时更新
*/
private void handleHighMotionGPS(MainService mainService) {
// 动态权限判断Android 6.0+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
mBroadcastContext.checkSelfPermission(android.Manifest.permission.ACCESS_FINE_LOCATION)
!= PackageManager.PERMISSION_GRANTED) {
sendPermissionRequestBroadcast();
return;
}
// 启动实时GPS已启动则不重复操作
if (!mainService.isGpsListening()) {
mainService.startGpsLocation(); // 实时更新2秒/1米
mainService.stopGpsStaticTimer(); // 停止定时GPS
LogUtils.d(TAG, "高速运动已启动GPS实时更新");
}
}
/**
* 静止/低运动GPS处理30秒定时更新
*/
private void handleStaticGPS(MainService mainService) {
// 停止实时GPS已停止则不重复操作
if (mainService.isGpsListening()) {
mainService.stopGpsLocation(); // 停止实时更新
LogUtils.d(TAG, "静止/低运动已停止GPS实时更新");
}
// 启动30秒定时GPS已启动则不重复操作
mainService.startGpsStaticTimer(GPS_STATIC_INTERVAL); // 30秒一次
LogUtils.d(TAG, "静止/低运动已启动GPS30秒定时更新");
}
/**
* 获取MainService实例复用逻辑
*/
private MainService getMainService() {
if (mBroadcastContext == null) return null;
// 优先获取单例
MainService singleton = MainService.getInstance(mBroadcastContext);
if (singleton != null && singleton.isServiceRunning()) {
return singleton;
}
// 启动服务并重试
if (!ServiceUtil.isServiceAlive(mBroadcastContext, MainService.class.getName())) {
mBroadcastContext.startService(new Intent(mBroadcastContext, MainService.class));
try {
Thread.sleep(500); // 等待服务启动
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
return MainService.getInstance(mBroadcastContext);
}
/**
* 发送GPS权限申请广播Receiver无法直接申请
*/
private void sendPermissionRequestBroadcast() {
Intent permissionIntent = new Intent("cc.winboll.studio.positions.ACTION_REQUEST_GPS_PERMISSION");
permissionIntent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
mBroadcastContext.sendBroadcast(permissionIntent);
LogUtils.d(TAG, "GPS权限缺失已发送申请广播");
}
/**
* 资源清理核心传感器不关闭仅清理Handler和上下文
* @param isForceStopSensor 是否强制停止传感器仅服务销毁时传true
*/
private void cleanResources(boolean isForceStopSensor) {
// 1. 停止定时计算任务
if (mMainHandler != null) {
mMainHandler.removeCallbacksAndMessages(null);
mMainHandler = null;
LogUtils.d(TAG, "cleanResources():已停止运动状态计算定时器");
}
// 2. 强制停止传感器(仅当外部触发销毁时执行,正常情况不关闭)
if (isForceStopSensor && mSensorManager != null && mIsSensorListening) {
mSensorManager.unregisterListener(this);
mIsSensorListening = false;
LogUtils.d(TAG, "cleanResources():已强制停止传感器监听");
}
// 3. 置空上下文(避免内存泄漏)
mBroadcastContext = null;
}
/**
* 传感器精度变化回调(日志监控)
*/
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
String sensorType = sensor.getType() == Sensor.TYPE_ACCELEROMETER ? "加速度" : "陀螺仪";
String accuracyDesc = getAccuracyDesc(accuracy);
LogUtils.d(TAG, sensorType + "传感器精度变化:" + accuracyDesc);
}
/**
* 传感器精度描述转换
*/
private String getAccuracyDesc(int accuracy) {
switch (accuracy) {
case SensorManager.SENSOR_STATUS_ACCURACY_HIGH: return "";
case SensorManager.SENSOR_STATUS_ACCURACY_MEDIUM: return "";
case SensorManager.SENSOR_STATUS_ACCURACY_LOW: return "";
case SensorManager.SENSOR_STATUS_UNRELIABLE: return "不可靠";
default: return "未知";
}
}
/**
* 补充Receiver销毁时强制清理需在MainService注销时调用
*/
public void forceCleanResources() {
cleanResources(true); // 强制停止传感器
}
}

View File

@@ -1,194 +0,0 @@
package cc.winboll.studio.positions.utils;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/11/10 09:51
* @Describe 应用图标切换工具类(启用组件时创建对应快捷方式)
*/
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Build;
import android.widget.Toast;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.positions.App;
import cc.winboll.studio.positions.MainActivity;
public class APPPlusUtils {
public static final String TAG = "APPPlusUtils";
// 快捷方式配置(名称+图标,需与实际资源匹配)
// private static final String PLUS_SHORTCUT_NAME = "位置服务-Laojun";
// private static final int PLUS_SHORTCUT_ICON = R.mipmap.ic_launcher; // Laojun 图标资源
/**
* 添加Plus组件与图标
*/
public static boolean openAPPPlus(Context context) {
if (context == null) {
LogUtils.d(TAG, "切换失败:上下文为空");
Toast.makeText(context, "图标切换失败", Toast.LENGTH_SHORT).show();
return false;
}
PackageManager pm = context.getPackageManager();
ComponentName plusComponentLaojun = new ComponentName(context, App.COMPONENT_LAOJUN);
//ComponentName plusComponentWuKong = new ComponentName(context, MainActivity.COMPONENT_WUKONG);
try {
//disableComponent(pm, plusComponentWuKong);
enableComponent(pm, plusComponentLaojun);
// 2. 创建 Laojun 组件对应的快捷方式(自动去重)
// boolean shortcutCreated = createComponentShortcut(context, plusComponent, PLUS_SHORTCUT_NAME, PLUS_SHORTCUT_ICON);
//
// // 3. 通知桌面刷新图标
// context.sendBroadcast(new Intent(Intent.ACTION_PACKAGE_CHANGED)
// .setData(android.net.Uri.parse("package:" + context.getPackageName())));
//
// // 4. 反馈结果
// String logMsg = shortcutCreated ? "启用 Laojun + 快捷方式创建成功" : "启用 Laojun 成功,快捷方式创建失败";
// String toastMsg = shortcutCreated ? "图标切换为 Laojun已创建快捷方式" : "图标切换为 Laojun快捷方式创建失败";
// LogUtils.d(TAG, logMsg);
// Toast.makeText(context, toastMsg, Toast.LENGTH_SHORT).show();
//
return true;
} catch (Exception e) {
LogUtils.e(TAG, "Laojun 图标切换失败:" + e.getMessage());
// 失败兜底:启用 Wukong 组件
//enableComponent(pm, wukongComponent);
Toast.makeText(context, "图标切换失败" + e.getMessage(), Toast.LENGTH_SHORT).show();
return false;
}
}
/**
* 移除Plus组件
*/
public static boolean closeAPPPlus(Context context) {
if (context == null) {
LogUtils.d(TAG, "切换失败:上下文为空");
Toast.makeText(context, "图标切换失败", Toast.LENGTH_SHORT).show();
return false;
}
PackageManager pm = context.getPackageManager();
ComponentName plusComponentLaojun = new ComponentName(context, App.COMPONENT_LAOJUN);
//ComponentName plusComponentWuKong = new ComponentName(context, MainActivity.COMPONENT_WUKONG);
disableComponent(pm, plusComponentLaojun);
//enableComponent(pm, plusComponentWuKong);
return true;
}
/**
* 创建指定组件的桌面快捷方式(自动去重,兼容 Android 8.0+
* @param component 目标组件(如 LAOJUN_ACTIVITY
* @param name 快捷方式名称
* @param iconRes 快捷方式图标资源ID
* @return 是否创建成功
*/
private static boolean createComponentShortcut(Context context, ComponentName component, String name, int iconRes) {
if (context == null || component == null || name == null || iconRes == 0) {
LogUtils.d(TAG, "快捷方式创建失败:参数为空");
return false;
}
// Android 8.0+API 26+):使用 ShortcutManager系统推荐
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
try {
PackageManager pm = context.getPackageManager();
android.content.pm.ShortcutManager shortcutManager = context.getSystemService(android.content.pm.ShortcutManager.class);
if (shortcutManager == null || !shortcutManager.isRequestPinShortcutSupported()) {
LogUtils.d(TAG, "系统不支持创建快捷方式");
return false;
}
// 检查是否已存在该组件的快捷方式(去重)
for (android.content.pm.ShortcutInfo info : shortcutManager.getPinnedShortcuts()) {
if (component.getClassName().equals(info.getIntent().getComponent().getClassName())) {
LogUtils.d(TAG, "快捷方式已存在:" + component.getClassName());
return true;
}
}
// 构建启动目标组件的意图
Intent launchIntent = new Intent(Intent.ACTION_MAIN)
.setComponent(component)
.addCategory(Intent.CATEGORY_LAUNCHER)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
// 构建快捷方式信息
android.content.pm.ShortcutInfo shortcutInfo = new android.content.pm.ShortcutInfo.Builder(context, component.getClassName())
.setShortLabel(name)
.setLongLabel(name)
.setIcon(android.graphics.drawable.Icon.createWithResource(context, iconRes))
.setIntent(launchIntent)
.build();
// 请求创建快捷方式(需用户确认)
shortcutManager.requestPinShortcut(shortcutInfo, null);
return true;
} catch (Exception e) {
LogUtils.d(TAG, "Android O+ 快捷方式创建失败:" + e.getMessage());
return false;
}
} else {
// Android 8.0 以下:使用广播(兼容旧机型)
try {
// 构建启动目标组件的意图
Intent launchIntent = new Intent(Intent.ACTION_MAIN)
.setComponent(component)
.addCategory(Intent.CATEGORY_LAUNCHER)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
// 构建创建快捷方式的广播意图
Intent installIntent = new Intent("com.android.launcher.action.INSTALL_SHORTCUT");
installIntent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, launchIntent);
installIntent.putExtra(Intent.EXTRA_SHORTCUT_NAME, name);
installIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE,
Intent.ShortcutIconResource.fromContext(context, iconRes));
installIntent.putExtra("duplicate", false); // 禁止重复创建
context.sendBroadcast(installIntent);
return true;
} catch (Exception e) {
LogUtils.d(TAG, "Android O- 快捷方式创建失败:" + e.getMessage());
return false;
}
}
}
/**
* 启用组件(带状态检查,避免重复操作)
*/
private static void enableComponent(PackageManager pm, ComponentName component) {
if (pm.getComponentEnabledSetting(component) != PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
pm.setComponentEnabledSetting(
component,
PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
PackageManager.DONT_KILL_APP | PackageManager.SYNCHRONOUS
);
}
}
/**
* 禁用组件(带状态检查,避免重复操作)
*/
private static void disableComponent(PackageManager pm, ComponentName component) {
if (pm.getComponentEnabledSetting(component) != PackageManager.COMPONENT_ENABLED_STATE_DISABLED) {
pm.setComponentEnabledSetting(
component,
PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
PackageManager.DONT_KILL_APP | PackageManager.SYNCHRONOUS
);
}
}
}

View File

@@ -1,148 +0,0 @@
package cc.winboll.studio.positions.utils;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.text.TextUtils;
import cc.winboll.studio.libappbase.LogUtils;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/11/15 15:23
* @Describe Activity Alias 工具类(兼容 Android 所有版本Java 7 语法)
* 用于获取 activity-alias 对应的原始 Activity 组件名、判断 alias 类型、获取启动组件名等
*/
public class ActivityAliasUtils {
private static final String TAG = "ActivityAliasUtils";
/**
* 获取 activity-alias 指向的原始 Activity 组件名
*
* @param context 上下文(建议用 ApplicationContext
* @param aliasName activity-alias 的组件名(完整路径,如 ".AliasActivity" 或 "com.winboll.app.AliasActivity"
* @return 原始 Activity 的完整组件名(如 "com.winboll.app.OriginalActivity"),失败返回 null
*/
public static String getTargetActivityName(Context context, String aliasName) {
// 校验参数
if (context == null || TextUtils.isEmpty(aliasName)) {
LogUtils.e(TAG, "getTargetActivityName: context is null or aliasName is empty");
return null;
}
// 补全组件名(若传入的是短名,自动拼接包名)
String fullAliasName = aliasName.startsWith(".")
? context.getPackageName() + aliasName
: aliasName;
try {
// 1. 获取 PackageManager
PackageManager packageManager = context.getPackageManager();
// 2. 解析 activity-alias 的 ActivityInfoflag 必须设为 PackageManager.GET_META_DATA否则可能获取不到 targetActivity
ActivityInfo aliasActivityInfo = packageManager.getActivityInfo(
new android.content.ComponentName(context.getPackageName(), fullAliasName),
PackageManager.GET_META_DATA
);
// 3. 获取 targetActivity原始 Activity 组件名)
String targetActivity = aliasActivityInfo.targetActivity;
if (TextUtils.isEmpty(targetActivity)) {
LogUtils.e(TAG, "getTargetActivityName: targetActivity is empty for alias " + fullAliasName);
return null;
}
// 4. 补全原始 Activity 的完整包名(若 targetActivity 是短名)
String fullTargetName = targetActivity.startsWith(".")
? context.getPackageName() + targetActivity
: targetActivity;
LogUtils.d(TAG, "getTargetActivityName: alias=" + fullAliasName + ", target=" + fullTargetName);
return fullTargetName;
} catch (PackageManager.NameNotFoundException e) {
LogUtils.e(TAG, "getTargetActivityName: alias not found - " + fullAliasName, e);
} catch (Exception e) {
LogUtils.e(TAG, "getTargetActivityName: unknown error", e);
}
return null;
}
/**
* 判断某个组件名是否为 activity-alias而非原始 Activity
*
* @param context 上下文
* @param componentName 待判断的组件名(完整路径)
* @return true是 activity-aliasfalse不是或判断失败
*/
public static boolean isActivityAlias(Context context, String componentName) {
// 调用 getTargetActivityName若返回非空则说明是 alias
return !TextUtils.isEmpty(getTargetActivityName(context, componentName));
}
/**
* 从启动的 Intent 中获取实际的目标组件名(处理 alias 场景)
* 适用于 Activity 中获取自身真实组件名(原始 Activity 名)
*
* @param context 当前 Activity 上下文
* @return 真实的目标组件名(原始 Activity 名,若为 alias 启动则返回原始 Activity否则返回自身
*/
public static String getRealTargetNameFromIntent(Context context) {
if (context == null) {
LogUtils.e(TAG, "getRealTargetNameFromIntent: context is null");
return null;
}
// 获取当前 Activity 的组件名(可能是 alias
String currentComponentName = context.getClass().getName();
// 检查是否为 alias若是则返回 target否则返回自身
String targetName = getTargetActivityName(context, currentComponentName);
return TextUtils.isEmpty(targetName) ? currentComponentName : targetName;
}
/**
* 获取当前活动上下文Activity的启动组件名即启动时使用的组件名可能是 alias 或原始 Activity
* 场景:若通过 alias 启动 Activity返回 alias 名;若直接启动原始 Activity返回原始 Activity 名
*
* @param context 当前 Activity 上下文(必须是 Activity 实例,不能是 ApplicationContext
* @return 启动组件的完整名,失败返回 null
*/
public static String getLaunchComponentName(Context context) {
// 1. 校验上下文类型(必须是 Activity否则无法获取启动 Intent
if (context == null) {
LogUtils.e(TAG, "getLaunchComponentName: context is null");
return null;
}
if (!(context instanceof android.app.Activity)) {
LogUtils.e(TAG, "getLaunchComponentName: context must be Activity instance, current is " + context.getClass().getName());
return null;
}
try {
// 2. 获取启动当前 Activity 的 Intent
android.app.Activity activity = (android.app.Activity) context;
Intent launchIntent = activity.getIntent();
if (launchIntent == null) {
LogUtils.e(TAG, "getLaunchComponentName: launch Intent is null");
return null;
}
// 3. 从 Intent 中获取启动组件名ComponentName
android.content.ComponentName componentName = launchIntent.getComponent();
if (componentName == null) {
LogUtils.e(TAG, "getLaunchComponentName: ComponentName is null in launch Intent");
return null;
}
// 4. 获取组件的完整类名(即启动时使用的组件名)
String launchComponentName = componentName.getClassName();
LogUtils.d(TAG, "getLaunchComponentName: current launch component is " + launchComponentName);
return launchComponentName;
} catch (Exception e) {
LogUtils.e(TAG, "getLaunchComponentName: failed to get launch component name", e);
return null;
}
}
}

View File

@@ -1,8 +1,6 @@
package cc.winboll.studio.positions.utils;
import android.content.Context;
import cc.winboll.studio.positions.AppLevel;
import cc.winboll.studio.positions.models.AppConfigsModel;
import cc.winboll.studio.positions.App;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
@@ -60,27 +58,11 @@ public class AppConfigsUtil {
}
public void setIsEnableMainService(boolean isEnableMainService) {
if (mAppConfigsModel == null) {
if(mAppConfigsModel == null) {
mAppConfigsModel = new AppConfigsModel();
}
mAppConfigsModel.setIsEnableMainService(isEnableMainService);
saveConfigs();
}
public AppLevel getAppLevel(boolean isReloadConfigs) {
if (isReloadConfigs) {
loadConfigs();
}
return (mAppConfigsModel == null) ?AppLevel.WUKONG: mAppConfigsModel.getAppLevel();
}
public void setAppLevel(AppLevel appLevel) {
if (mAppConfigsModel == null) {
mAppConfigsModel = new AppConfigsModel();
}
App._mAppLevel = appLevel;
mAppConfigsModel.setAppLevel(appLevel);
saveConfigs();
}
}

View File

@@ -1,241 +0,0 @@
package cc.winboll.studio.positions.utils;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.provider.MediaStore;
import android.util.Log;
import android.widget.Toast;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/11/13 15:42
* @Describe JsonShareHandler
* 外部 JSON 文件分享处理工具类
* 功能:接收外部分享的 .json 文件,弹出确认对话框,保存到外部存储 files/BaseBean 目录
*/
public class JsonShareHandler {
private static final String TAG = "JsonShareHandler";
private static final String TARGET_DIR = "BaseBean";
private static final String MIME_TYPE_JSON = "application/json";
private static final String FILE_SUFFIX_JSON = ".json";
// 对话框回调接口Java7 无 Lambda用接口实现
public interface ConfirmCallback {
void onConfirm(boolean isConfirm);
}
/**
* 处理外部分享的 Intent先弹出确认对话框再决定是否接收文件
* @param context 上下文(需为 Activity否则无法弹出对话框
* @param intent 分享 Intent
* @param callback 确认结果回调(用于 Activity 处理后续逻辑)
*/
public static void handleSharedJsonWithConfirm(final Context context, final Intent intent, final ConfirmCallback callback) {
if (context == null || intent == null || callback == null) {
Log.e(TAG, "参数为空,处理失败");
if (callback != null) callback.onConfirm(false);
return;
}
// 1. 先验证 Intent 合法性(提前过滤无效分享)
String action = intent.getAction();
String type = intent.getType();
if (!Intent.ACTION_SEND.equals(action) || type == null) {
Log.e(TAG, "非文件分享 Intent");
Toast.makeText(context, "不支持的分享类型", Toast.LENGTH_SHORT).show();
callback.onConfirm(false);
return;
}
// 2. 弹出确认对话框
new AlertDialog.Builder(context)
.setTitle("接收 JSON 文件")
.setMessage("是否接收并保存该 JSON 文件?")
.setPositiveButton("Yes", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
// 3. 点击 Yes处理文件保存
String savedPath = handleSharedJsonFile(context, intent);
if (savedPath != null) {
Toast.makeText(context, "文件保存成功:" + savedPath, Toast.LENGTH_LONG).show();
callback.onConfirm(true);
} else {
Toast.makeText(context, "文件保存失败", Toast.LENGTH_SHORT).show();
callback.onConfirm(false);
}
}
})
.setNegativeButton("No", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
// 4. 点击 No直接退出处理
callback.onConfirm(false);
}
})
.setCancelable(false) // 不可点击外部取消
.show();
}
/**
* 核心文件处理逻辑(原有功能,无修改)
*/
private static String handleSharedJsonFile(Context context, Intent intent) {
String action = intent.getAction();
String type = intent.getType();
// 验证 JSON 格式
if (!MIME_TYPE_JSON.equals(type) && !type.contains("json")) {
Uri uri = intent.getParcelableExtra(Intent.EXTRA_STREAM);
if (uri == null || !getFileNameFromUri(context, uri).endsWith(FILE_SUFFIX_JSON)) {
Log.e(TAG, "接收的文件不是 JSON 格式");
return null;
}
}
Uri sharedUri = intent.getParcelableExtra(Intent.EXTRA_STREAM);
if (sharedUri == null) {
Log.e(TAG, "未获取到分享的文件 Uri");
return null;
}
try {
// 创建保存目录
File saveDir = getTargetSaveDir(context);
if (!saveDir.exists() && !saveDir.mkdirs()) {
Log.e(TAG, "创建保存目录失败:" + saveDir.getAbsolutePath());
return null;
}
// 解析文件名(兼容低版本)
String fileName = getFileNameFromUri(context, sharedUri);
if (fileName == null || !fileName.endsWith(FILE_SUFFIX_JSON)) {
fileName = "default_" + System.currentTimeMillis() + FILE_SUFFIX_JSON;
Log.w(TAG, "文件名解析失败,使用默认名称:" + fileName);
}
// 复制文件
File targetFile = new File(saveDir, fileName);
boolean copySuccess = copyFileFromUri(context, sharedUri, targetFile);
return copySuccess ? targetFile.getAbsolutePath() : null;
} catch (Exception e) {
Log.e(TAG, "处理分享文件异常:" + e.getMessage());
return null;
}
}
/**
* 获取目标保存目录(兼容 Android 10+ 分区存储)
*/
private static File getTargetSaveDir(Context context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
return new File(context.getExternalFilesDir(null), TARGET_DIR);
} else {
return new File(
Environment.getExternalStorageDirectory() + File.separator +
"Android" + File.separator +
"data" + File.separator +
context.getPackageName() + File.separator +
"files" + File.separator +
TARGET_DIR
);
}
}
/**
* 从 Uri 解析文件名(兼容所有 Android 版本)
*/
private static String getFileNameFromUri(Context context, Uri uri) {
if (uri == null) return null;
// 1. 文件 Urifile:// 开头)
if ("file".equals(uri.getScheme())) {
return new File(uri.getPath()).getName();
}
// 2. 内容 Uricontent:// 开头)
if ("content".equals(uri.getScheme())) {
Cursor cursor = null;
try {
cursor = context.getContentResolver().query(
uri,
new String[]{MediaStore.MediaColumns.DISPLAY_NAME},
null,
null,
null
);
if (cursor != null && cursor.moveToFirst()) {
int nameIndex = cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME);
if (nameIndex != -1) {
return cursor.getString(nameIndex);
}
}
} catch (Exception e) {
Log.e(TAG, "解析内容 Uri 文件名失败:" + e.getMessage());
} finally {
if (cursor != null) cursor.close();
}
}
// 3. 解析失败,返回默认名称
String lastPathSegment = uri.getLastPathSegment();
return lastPathSegment != null ? lastPathSegment : "unknown.json";
}
/**
* 复制 Uri 指向的文件到目标路径
*/
private static boolean copyFileFromUri(Context context, Uri sourceUri, File targetFile) {
InputStream inputStream = null;
OutputStream outputStream = null;
try {
inputStream = context.getContentResolver().openInputStream(sourceUri);
if (inputStream == null) {
Log.e(TAG, "无法打开源文件输入流");
return false;
}
outputStream = new FileOutputStream(targetFile);
byte[] buffer = new byte[1024 * 4];
int length;
while ((length = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, length);
}
outputStream.flush();
Log.d(TAG, "文件保存成功:" + targetFile.getAbsolutePath());
return true;
} catch (IOException e) {
Log.e(TAG, "文件复制异常:" + e.getMessage());
return false;
} finally {
try {
if (inputStream != null) inputStream.close();
if (outputStream != null) outputStream.close();
} catch (IOException e) {
Log.e(TAG, "关闭流异常:" + e.getMessage());
}
}
}
/**
* 检查外部存储是否可用
*/
public static boolean isExternalStorageAvailable() {
String state = Environment.getExternalStorageState();
return Environment.MEDIA_MOUNTED.equals(state);
}
}

View File

@@ -1,168 +0,0 @@
package cc.winboll.studio.positions.utils;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/11/05 15:49
* @Describe LocalMotionDetector
*/
import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import cc.winboll.studio.libappbase.LogUtils;
/**
* 本机运动状态监测工具(无联网,纯传感器)
*/
public class LocalMotionDetector implements SensorEventListener {
public static final String TAG = "LocalMotionDetector";
// 配置参数(重点修改:调高运动阈值,适配坐立持机场景)
private static final float MOTION_THRESHOLD = 1.8f; // 从0.5f调高到1.8f(过滤坐立轻微晃动)
private static final long STATUS_CHECK_INTERVAL = 3000; // 3秒判断一次状态
private static final int STEP_CHANGE_THRESHOLD = 2; // 3秒≥2步判定行走
private SensorManager mSensorManager;
private Sensor mAccelerometer;
private Sensor mStepCounter;
private Handler mMainHandler;
private MotionStatusCallback mCallback;
private boolean mIsDetecting = false;
private float mLastAccelMagnitude = 0f;
private int mLastStepCount = 0;
private int mCurrentStepCount = 0;
private boolean mIsWalking = false;
// 单例模式
private static LocalMotionDetector sInstance;
public static LocalMotionDetector getInstance() {
if (sInstance == null) {
synchronized (LocalMotionDetector.class) {
if (sInstance == null) {
sInstance = new LocalMotionDetector();
}
}
}
return sInstance;
}
private LocalMotionDetector() {
mMainHandler = new Handler(Looper.getMainLooper());
}
/**
* 开始监测运动状态
*/
public void startDetection(Context context, MotionStatusCallback callback) {
if (mIsDetecting) return;
mCallback = callback;
mSensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
// 初始化传感器
mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
mStepCounter = mSensorManager.getDefaultSensor(Sensor.TYPE_STEP_COUNTER);
// 注册传感器监听
if (mAccelerometer != null) {
mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_NORMAL, mMainHandler);
}
if (mStepCounter != null) {
mSensorManager.registerListener(this, mStepCounter, SensorManager.SENSOR_DELAY_NORMAL, mMainHandler);
LogUtils.d(TAG, "计步传感器已启动");
} else {
LogUtils.d(TAG, "设备不支持计步传感器,仅用加速度判断");
}
// 启动定时状态检测
mMainHandler.postDelayed(mStatusCheckRunnable, STATUS_CHECK_INTERVAL);
mIsDetecting = true;
LogUtils.d(TAG, "运动状态监测已启动");
}
/**
* 停止监测
*/
public void stopDetection() {
if (!mIsDetecting) return;
if (mSensorManager != null) {
mSensorManager.unregisterListener(this);
}
mMainHandler.removeCallbacksAndMessages(null);
mIsDetecting = false;
mIsWalking = false;
mCallback = null;
LogUtils.d(TAG, "运动状态监测已停止");
}
@Override
public void onSensorChanged(SensorEvent event) {
if (!mIsDetecting) return;
switch (event.sensor.getType()) {
case Sensor.TYPE_ACCELEROMETER:
// 计算加速度幅度(保留原逻辑,阈值已调高)
float accelX = Math.abs(event.values[0]);
float accelY = Math.abs(event.values[1]);
float accelZ = Math.abs(event.values[2]);
mLastAccelMagnitude = accelX + accelY + accelZ;
break;
case Sensor.TYPE_STEP_COUNTER:
// 累计步数
mCurrentStepCount = (int) event.values[0];
break;
}
}
/**
* 定时判断运动状态优化逻辑计步为0时即使有轻微加速度也判定为静止
*/
private final Runnable mStatusCheckRunnable = new Runnable() {
@Override
public void run() {
if (!mIsDetecting || mCallback == null) return;
//LogUtils.d(TAG, "mStatusCheckRunnable run");
boolean newIsWalking = false;
// 结合计步器+加速度判断(优化:优先计步,无步数时严格按高阈值判断)
if (mStepCounter != null) {
int stepChange = mCurrentStepCount - mLastStepCount;
// 只有“步数达标” 或 “无步数但加速度远超坐立幅度”,才判定为行走
newIsWalking = (stepChange >= STEP_CHANGE_THRESHOLD)
&& (mLastAccelMagnitude >= MOTION_THRESHOLD); // 增加步数+加速度双重校验
mLastStepCount = mCurrentStepCount;
} else {
// 无计步器时,仅用高阈值判断
newIsWalking = mLastAccelMagnitude >= MOTION_THRESHOLD;
}
// 状态变化时回调
if (newIsWalking != mIsWalking) {
mIsWalking = newIsWalking;
String statusDesc = mIsWalking ? "行走状态" : "静止/低运动状态";
LogUtils.d(TAG, "运动状态变化:" + statusDesc + " | 加速度幅度:" + mLastAccelMagnitude); // 增加日志便于调试
mCallback.onMotionStatusChanged(mIsWalking, statusDesc);
}
LogUtils.d(TAG, String.format("运动状态 newIsWalking %s", newIsWalking));
// 循环检测
mMainHandler.postDelayed(this, STATUS_CHECK_INTERVAL);
}
};
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {}
/**
* 运动状态回调接口
*/
public interface MotionStatusCallback {
void onMotionStatusChanged(boolean isWalking, String statusDesc);
}
}

View File

@@ -1,105 +0,0 @@
package cc.winboll.studio.positions.utils;
import android.app.Activity;
import android.app.Application;
import android.content.Intent;
import android.os.Bundle;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.ToastUtils;
import cc.winboll.studio.positions.PointLevel;
import cc.winboll.studio.positions.activities.WinBoLLActivity;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/11/15 15:59
* @Describe 应用活动窗口状态响应类
* 主要用于设置应用级别与组件状态
*/
public class MyActivityLifecycleCallbacks implements Application.ActivityLifecycleCallbacks {
public static final String TAG = "MyActivityLifecycleCallbacks";
public String mInfo = "";
public MyActivityLifecycleCallbacks() {
}
void createActivityeInfo(Activity activity) {
StringBuilder sb = new StringBuilder();
Intent receivedIntent = activity.getIntent();
sb.append("\nCallingActivity : \n");
if (activity.getCallingActivity() != null) {
sb.append(activity.getCallingActivity().getPackageName());
}
sb.append("\nReceived Intent Package : \n");
sb.append(receivedIntent.getPackage());
Bundle extras = receivedIntent.getExtras();
if (extras != null) {
for (String key : extras.keySet()) {
sb.append("\nIntentInfo");
sb.append("\n键: ");
sb.append(key);
sb.append(", 值: ");
sb.append(extras.get(key));
//Log.d("IntentInfo", "键: " + key + ", 值: " + extras.get(key));
}
}
mInfo = sb.toString();
//Log.d("IntentInfo", "发送Intent的应用包名: " + senderPackage);
}
public void showActivityeInfo() {
//ToastUtils.show("ActivityeInfo : " + mInfo);
LogUtils.d(TAG, "ActivityeInfo : " + mInfo);
}
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
// 在这里可以做一些初始化相关的操作例如记录Activity的创建时间等
//System.out.println(activity.getLocalClassName() + " was created");
LogUtils.d(TAG, activity.getLocalClassName() + " was created");
createActivityeInfo(activity);
}
@Override
public void onActivityStarted(Activity activity) {
//System.out.println(activity.getLocalClassName() + " was started");
LogUtils.d(TAG, activity.getLocalClassName() + " was started");
//createActivityeInfo(activity);
}
@Override
public void onActivityResumed(Activity activity) {
//System.out.println(activity.getLocalClassName() + " was resumed");
LogUtils.d(TAG, activity.getLocalClassName() + " was resumed");
//createActivityeInfo(activity);
}
@Override
public void onActivityPaused(Activity activity) {
ToastUtils.show("Activity Paused");
// 应用从正在活动状态抽离出来时设置应用入口级别状态设置为时空虚幻而不确定的哆啦A梦级别。
WinBoLLActivity._mPointLevel = PointLevel.DORAEMON;
//System.out.println(activity.getLocalClassName() + " was paused");
LogUtils.d(TAG, activity.getLocalClassName() + " was paused");
}
@Override
public void onActivityStopped(Activity activity) {
//System.out.println(activity.getLocalClassName() + " was stopped");
LogUtils.d(TAG, activity.getLocalClassName() + " was stopped");
}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
// 可以在这里添加保存状态的自定义逻辑
}
@Override
public void onActivityDestroyed(Activity activity) {
//System.out.println(activity.getLocalClassName() + " was destroyed");
LogUtils.d(TAG, activity.getLocalClassName() + " was destroyed");
}
}

View File

@@ -1,282 +0,0 @@
package cc.winboll.studio.positions.views;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/11/10 08:29
* @Describe 沙漏计时器控件
*/
import android.content.Context;
import android.graphics.Color;
import android.graphics.drawable.ClipDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.text.InputFilter;
import android.text.InputType;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.Switch;
import android.widget.TextView;
/**
* 沙漏视图类Java 7语法修复ProgressDrawable和setHeight问题
*/
public class HourglassView extends LinearLayout {
public static final String TAG = "HourglassView";
// 数据模型
private String hourglassId;
private int hour; // 小时
private int minute; // 分钟
private boolean isEnabled; // 开关状态
// 控件引用
private EditText etHour;
private EditText etMinute;
private ProgressBar progressBar;
private Switch switchControl;
// 样式参数
private int textSize = 16;
private int padding = 8;
private int progressColor = 0xFF2196F3; // 进度条颜色
private int progressBgColor = 0xFFE0E0E0; // 进度条背景色
private int textColor = 0xFF333333;
private int editTextWidth = 40; // 输入框宽度dp
private int progressHeight = 8; // 进度条高度dp新增参数
public HourglassView(Context context) {
super(context);
initView();
}
public HourglassView(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
}
/**
* 初始化视图布局
*/
private void initView() {
setOrientation(HORIZONTAL);
setGravity(Gravity.CENTER_VERTICAL);
setPadding(dp2px(padding), dp2px(padding), dp2px(padding), dp2px(padding));
// 1. 左侧时间输入区域(水平布局)
LinearLayout inputLayout = new LinearLayout(getContext());
inputLayout.setOrientation(HORIZONTAL);
inputLayout.setGravity(Gravity.CENTER_VERTICAL);
LayoutParams inputParams = new LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT
);
inputParams.setMargins(0, 0, dp2px(padding * 2), 0);
addView(inputLayout, inputParams);
// 小时输入框
etHour = createNumberEditText();
etHour.setHint("");
etHour.setFilters(new InputFilter[]{new InputFilter.LengthFilter(2)});
inputLayout.addView(etHour, getEditTextParams());
// 分隔符
TextView divider = new TextView(getContext());
divider.setText(":");
divider.setTextSize(textSize);
divider.setTextColor(textColor);
LayoutParams dividerParams = new LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT
);
dividerParams.setMargins(dp2px(padding / 2), 0, dp2px(padding / 2), 0);
inputLayout.addView(divider, dividerParams);
// 分钟输入框
etMinute = createNumberEditText();
etMinute.setHint("");
etMinute.setFilters(new InputFilter[]{new InputFilter.LengthFilter(2)});
inputLayout.addView(etMinute, getEditTextParams());
// 2. 中间进度条修复通过LayoutParams设置高度替代setHeight
progressBar = new ProgressBar(getContext(), null, android.R.attr.progressBarStyleHorizontal);
progressBar.setProgressDrawable(createProgressDrawable()); // 传入Drawable类型
// 修复核心用LayoutParams设置进度条高度兼容低版本
LayoutParams progressParams = new LayoutParams(
0,
dp2px(progressHeight), // 直接在布局参数中设置高度dp转px
1.0f
);
progressParams.setMargins(0, 0, dp2px(padding * 2), 0);
addView(progressBar, progressParams);
// 3. 右侧开关
switchControl = new Switch(getContext());
switchControl.setOnCheckedChangeListener(new Switch.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(android.widget.CompoundButton buttonView, boolean isChecked) {
isEnabled = isChecked;
// 开关状态控制输入框是否可编辑
etHour.setEnabled(!isChecked);
etMinute.setEnabled(!isChecked);
// 更新进度条(仅在开关开启时生效)
if (isChecked) {
updateProgressBar();
}
}
});
addView(switchControl);
// 初始状态
isEnabled = false;
etHour.setEnabled(true);
etMinute.setEnabled(true);
}
/**
* 创建数字输入框
*/
private EditText createNumberEditText() {
EditText editText = new EditText(getContext());
editText.setInputType(InputType.TYPE_CLASS_NUMBER);
editText.setTextSize(textSize);
editText.setTextColor(textColor);
editText.setGravity(Gravity.CENTER);
editText.setSingleLine(true);
editText.setBackgroundResource(android.R.drawable.edit_text); // 默认输入框背景
return editText;
}
/**
* 获取输入框布局参数
*/
private LayoutParams getEditTextParams() {
LayoutParams params = new LayoutParams(
dp2px(editTextWidth),
ViewGroup.LayoutParams.WRAP_CONTENT
);
params.setMargins(0, 0, dp2px(padding), 0);
return params;
}
/**
* 修复核心创建ProgressDrawable返回Drawable类型而非Paint
* 用LayerDrawable实现「背景+进度」的双层进度条
*/
private Drawable createProgressDrawable() {
// 1. 进度条背景(灰色)
ColorDrawable bgDrawable = new ColorDrawable(progressBgColor);
// 2. 进度条前景(主题色)
ColorDrawable progressDrawable = new ColorDrawable(progressColor);
// 3. 用ClipDrawable包裹前景实现进度裁剪
ClipDrawable clipDrawable = new ClipDrawable(progressDrawable, Gravity.LEFT, ClipDrawable.HORIZONTAL);
// 4. 组合成LayerDrawable顺序背景在下进度在上
Drawable[] layers = new Drawable[]{bgDrawable, clipDrawable};
LayerDrawable layerDrawable = new LayerDrawable(layers);
// 5. 设置进度条的层级ID必须与系统ProgressBar的ID匹配
layerDrawable.setId(0, android.R.id.background);
layerDrawable.setId(1, android.R.id.progress);
return layerDrawable;
}
/**
* 更新进度条(总时间 = 小时*60 + 分钟,单位:分钟)
*/
private void updateProgressBar() {
try {
// 获取输入的时间为空时默认0
int inputHour = TextUtils.isEmpty(etHour.getText().toString().trim())
? 0 : Integer.parseInt(etHour.getText().toString().trim());
int inputMinute = TextUtils.isEmpty(etMinute.getText().toString().trim())
? 0 : Integer.parseInt(etMinute.getText().toString().trim());
// 校验时间合法性小时0-99分钟0-59
inputHour = Math.max(0, Math.min(99, inputHour));
inputMinute = Math.max(0, Math.min(59, inputMinute));
// 计算总分钟数(进度条最大值)
int totalMinutes = inputHour * 60 + inputMinute;
totalMinutes = Math.max(1, totalMinutes); // 最小1分钟避免进度条无长度
// 更新进度条
progressBar.setMax(totalMinutes);
progressBar.setProgress(totalMinutes); // 初始显示满进度,可根据实际需求修改
// 更新数据模型
this.hour = inputHour;
this.minute = inputMinute;
} catch (NumberFormatException e) {
// 输入非法时重置进度条
progressBar.setMax(0);
progressBar.setProgress(0);
}
}
/**
* dp转px适配不同设备
*/
private int dp2px(int dp) {
return (int) (dp * getContext().getResources().getDisplayMetrics().density + 0.5f);
}
// ------------------- 数据模型 getter/setter -------------------
public String getHourglassId() {
return hourglassId;
}
public void setHourglassId(String hourglassId) {
this.hourglassId = hourglassId;
}
public int getHour() {
return hour;
}
public void setHour(int hour) {
this.hour = Math.max(0, Math.min(99, hour)); // 限制范围
etHour.setText(String.valueOf(this.hour));
}
public int getMinute() {
return minute;
}
public void setMinute(int minute) {
this.minute = Math.max(0, Math.min(59, minute)); // 限制范围
etMinute.setText(String.valueOf(this.minute));
}
public boolean isEnabled() {
return isEnabled;
}
public void setEnabled(boolean enabled) {
isEnabled = enabled;
switchControl.setChecked(enabled);
}
/**
* 手动更新进度条(外部调用)
*/
public void refreshProgress() {
if (isEnabled) {
updateProgressBar();
}
}
// 工具类判断字符串是否为空Java7无TextUtils.isEmpty手动实现
private static class TextUtils {
public static boolean isEmpty(CharSequence str) {
return str == null || str.length() == 0;
}
}
}

View File

@@ -33,10 +33,6 @@ import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import cc.winboll.studio.positions.App;
import cc.winboll.studio.positions.AppLevel;
import cc.winboll.studio.positions.activities.WinBoLLActivity;
import cc.winboll.studio.positions.PointLevel;
public class PositionTaskListView extends LinearLayout {
// 视图模式常量
@@ -384,7 +380,7 @@ public class PositionTaskListView extends LinearLayout {
// 步骤3刷新Adapter局部刷新+范围通知,避免列表错乱)
notifyItemRemoved(position);
notifyItemRangeChanged(position, mAdapterData.size());
LogUtils.d(TAG, "Adapter已移除任务刷新列表位置索引=" + position + "");
// 步骤4通知外部如Activity任务已更新
@@ -461,7 +457,7 @@ public class PositionTaskListView extends LinearLayout {
}
});
}
private String genSelectedTimeText(long timeMillis) {
// 2. 格式化时间字符串Java 7 用 SimpleDateFormat需处理 ParseException
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.getDefault());
@@ -487,16 +483,6 @@ public class PositionTaskListView extends LinearLayout {
final EditText etEditDistance = dialogView.findViewById(R.id.et_edit_distance);
Button btnCancel = dialogView.findViewById(R.id.btn_dialog_cancel);
Button btnSave = dialogView.findViewById(R.id.btn_dialog_save);
HourglassView hourglassView = dialogView.findViewById(R.id.hourglassView);
if (WinBoLLActivity._mPointLevel == PointLevel.WUKONG) {
hourglassView.setVisibility(View.GONE);
} else if (WinBoLLActivity._mPointLevel == PointLevel.LAOJUN) {
hourglassView.setHourglassId("hourglass_001");
hourglassView.setHour(1);
hourglassView.setMinute(30);
hourglassView.setEnabled(false); // 开启开关
}
// 绑定外层对话框内的控件

View File

@@ -1,67 +1,81 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="20dp">
android:layout_height="match_parent">
<LinearLayout
android:id="@+id/layout_location_info"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_marginTop="30dp"
android:orientation="vertical"
android:gravity="center_horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="实时位置信息"
android:textSize="22sp"
android:textStyle="bold"/>
<TextView
android:id="@+id/tv_longitude"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="当前经度:等待更新..."
android:textSize="18sp"
android:layout_marginTop="15dp"/>
<TextView
android:id="@+id/tv_latitude"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="当前纬度:等待更新..."
android:textSize="18sp"
android:layout_marginTop="10dp"/>
</LinearLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_position_list"
<cc.winboll.studio.libaes.views.ASupportToolbar
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/layout_location_info"
android:layout_above="@id/fab_p_button"
android:layout_marginTop="20dp"
android:paddingBottom="10dp"/>
android:layout_height="@dimen/toolbar_height"
android:id="@+id/toolbar"
android:gravity="center_vertical"/>
<Button
android:id="@+id/fab_p_button"
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:layout_margin="20dp"
android:background="@drawable/circle_button_bg"
android:text="P"
android:textColor="@android:color/white"
android:textSize="24sp"
android:elevation="6dp"
android:padding="0dp"
android:onClick="addNewPosition"/>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:padding="20dp"
android:layout_weight="1.0">
</RelativeLayout>
<LinearLayout
android:id="@+id/layout_location_info"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_marginTop="30dp"
android:orientation="vertical"
android:gravity="center_horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="实时位置信息"
android:textSize="22sp"
android:textStyle="bold"/>
<TextView
android:id="@+id/tv_longitude"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="当前经度:等待更新..."
android:textSize="18sp"
android:layout_marginTop="15dp"/>
<TextView
android:id="@+id/tv_latitude"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="当前纬度:等待更新..."
android:textSize="18sp"
android:layout_marginTop="10dp"/>
</LinearLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_position_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/layout_location_info"
android:layout_above="@id/fab_p_button"
android:layout_marginTop="20dp"
android:paddingBottom="10dp"/>
<Button
android:id="@+id/fab_p_button"
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:layout_margin="20dp"
android:background="@drawable/circle_button_bg"
android:text="P"
android:textColor="@android:color/white"
android:textSize="24sp"
android:elevation="6dp"
android:padding="0dp"
android:onClick="addNewPosition"/>
</RelativeLayout>
</LinearLayout>

View File

@@ -2,38 +2,51 @@
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
android:layout_height="match_parent">
<androidx.appcompat.widget.Toolbar
<cc.winboll.studio.libaes.views.ASupportToolbar
android:layout_width="match_parent"
android:layout_height="@dimen/toolbar_height"
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"/>
android:gravity="center_vertical"/>
<Switch
android:id="@+id/switch_service_control"
android:layout_margin="16dp"
android:text="GPS服务开关"
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
android:layout_height="0dp"
android:orientation="vertical"
android:layout_weight="1.0">
<Button
<Switch
android:id="@+id/switch_service_control"
android:layout_margin="16dp"
android:text="GPS服务开关"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:onClick="onPositions"
android:text="位置与任务管理"
android:id="@+id/btn_manage_positions"/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:onClick="onLog"
android:text="查看应用日志"/>
</LinearLayout>
<cc.winboll.studio.libaes.views.ADsBannerView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:onClick="onPositions"
android:text="位置与任务管理"
android:id="@+id/btn_manage_positions"/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:onClick="onLog"
android:text="查看应用日志"/>
android:id="@+id/adsbanner"
android:layout_alignParentBottom="true"/>
</LinearLayout>

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<cc.winboll.studio.libaes.views.ASupportToolbar
android:layout_width="match_parent"
android:layout_height="@dimen/toolbar_height"
android:id="@+id/toolbar"
android:gravity="center_vertical"/>
<cc.winboll.studio.libaes.views.ADsControlView
android:id="@+id/ads_control_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>

View File

@@ -65,16 +65,6 @@
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<cc.winboll.studio.positions.views.HourglassView
android:id="@+id/hourglassView"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
@@ -87,7 +77,7 @@
android:layout_height="wrap_content"
android:text="开始时间"
android:id="@+id/btn_select_time"/>
<TextView
android:id="@+id/tv_selected_time"
android:layout_width="0dp"
@@ -95,6 +85,7 @@
android:text="Text"
android:layout_weight="1.0"/>
</LinearLayout>
<LinearLayout

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/item_settings"
android:title="Settings"/>
</menu>

View File

@@ -1,9 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">悟空笔记</string>
<string name="appplus_name">时空任务</string>
<string name="open_appplus">开疆扩土</string>
<string name="close_appplus">返璞归真</string>
<string name="appplus_open_disabled">余力不足</string>
<string name="appplus_close_disabled">辎重难返</string>
</resources>

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="toolbar_height">60dp</dimen>
<dimen name="text_content_size">18dp</dimen>
<dimen name="text_title_size">24dp</dimen>
<dimen name="text_subtitle_size">16dp</dimen>
</resources>

View File

@@ -1,8 +1,9 @@
<resources>
<string name="app_name">Positions</string>
<string name="appplus_name">PositionsPlus</string>
<string name="appplus_name">PositionsPlus</string>
<string name="open_appplus">Open APP Plus</string>
<string name="close_appplus">Close APP Plus</string>
<string name="appplus_open_disabled">APP Plus Open Disable</string>
<string name="appplus_close_disabled">APP Plus Close Disable</string>
</resources>

View File

@@ -14,4 +14,12 @@
</style>
<!-- 设置Toolbar标题字体的大小 -->
<style name="Toolbar.TitleText" parent="@android:style/TextAppearance.DeviceDefault.Widget.ActionBar.Title">
<item name="android:textSize">@dimen/text_title_size</item>
</style>
<style name="Toolbar.SubTitleText" parent="@android:style/TextAppearance.DeviceDefault.Widget.ActionBar.Title">
<item name="android:textSize">@dimen/text_subtitle_size</item>
</style>
</resources>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-files-path
name="BaseBean"
path="BaseBean/" />
</paths>

View File

@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
#Sun Dec 07 03:22:51 HKT 2025
stageCount=7
#Sun Dec 07 03:42:32 HKT 2025
stageCount=8
libraryProject=
baseVersion=15.11
publishVersion=15.11.6
publishVersion=15.11.7
buildCount=0
baseBetaVersion=15.11.7
baseBetaVersion=15.11.8

View File

@@ -14,8 +14,8 @@
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher_stage"
android:roundIcon="@drawable/ic_launcher_stage"
android:icon="@drawable/ic_launcher"
android:roundIcon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/MyAppTheme"
android:resizeableActivity="true"
@@ -37,7 +37,7 @@
android:targetActivity=".MainActivity"
android:exported="true"
android:label="@string/app_name"
android:icon="@drawable/ic_launcher_stage"
android:icon="@drawable/ic_launcher"
android:enabled="true">
<intent-filter>
@@ -280,4 +280,4 @@
</application>
</manifest>
</manifest>

View File

@@ -1,34 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportHeight="108"
android:viewportWidth="108">
<path
android:fillType="evenOdd"
android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
android:strokeColor="#00000000"
android:strokeWidth="1">
<aapt:attr name="android:fillColor">
<gradient
android:endX="78.5885"
android:endY="90.9159"
android:startX="48.7653"
android:startY="61.0927"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
android:strokeColor="#00000000"
android:strokeWidth="1" />
</vector>

View File

@@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24"
android:viewportWidth="24">
<path
android:fillColor="#ff000000"
android:pathData="M4,1C2.89,1 2,1.89 2,3V7C2,8.11 2.89,9 4,9H1V11H13V9H10C11.11,9 12,8.11 12,7V3C12,1.89 11.11,1 10,1H4M4,3H10V7H4V3M3,12V14H5V12H3M14,13C12.89,13 12,13.89 12,15V19C12,20.11 12.89,21 14,21H11V23H23V21H20C21.11,21 22,20.11 22,19V15C22,13.89 21.11,13 20,13H14M3,15V17H5V15H3M14,15H20V19H14V15M3,18V20H5V18H3M6,18V20H8V18H6M9,18V20H11V18H9Z"/>
</vector>

View File

@@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24"
android:viewportWidth="24">
<path
android:fillColor="#ff000000"
android:pathData="M4,1C2.89,1 2,1.89 2,3V7C2,8.11 2.89,9 4,9H1V11H13V9H10C11.11,9 12,8.11 12,7V3C12,1.89 11.11,1 10,1H4M4,3H10V7H4V3M14,13C12.89,13 12,13.89 12,15V19C12,20.11 12.89,21 14,21H11V23H23V21H20C21.11,21 22,20.11 22,19V15C22,13.89 21.11,13 20,13H14M3.88,13.46L2.46,14.88L4.59,17L2.46,19.12L3.88,20.54L6,18.41L8.12,20.54L9.54,19.12L7.41,17L9.54,14.88L8.12,13.46L6,15.59L3.88,13.46M14,15H20V19H14V15Z"/>
</vector>

View File

@@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24"
android:viewportWidth="24">
<path
android:fillColor="#ff000000"
android:pathData="M22,6C22,4.9 21.1,4 20,4H4C2.9,4 2,4.9 2,6V18C2,19.1 2.9,20 4,20H20C21.1,20 22,19.1 22,18V6M20,6L12,11L4,6H20M20,18H4V8L12,13L20,8V18Z"/>
</vector>

View File

@@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24"
android:viewportWidth="24">
<path
android:fillColor="#ff000000"
android:pathData="M24,7H22V13H24V7M24,15H22V17H24V15M20,6C20,4.9 19.1,4 18,4H2C0.9,4 0,4.9 0,6V18C0,19.1 0.9,20 2,20H18C19.1,20 20,19.1 20,18V6M18,6L10,11L2,6H18M18,18H2V8L10,13L18,8V18Z"/>
</vector>

View File

@@ -1,35 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="1565dp"
android:height="1565dp"
android:viewportWidth="1565"
android:viewportHeight="1565">
<path
android:fillColor="#FFFFFFFF"
android:strokeColor="#FFFFFFFF"
android:strokeWidth="4.0"
android:strokeLineCap="round"
android:strokeMiterLimit="10"
android:pathData="M793.8 222.7C782.5 239.2 736.3 298.6 685.6 361.9 647 410.2 633.3 428.9 633.6 432.7 633.7 433.4 633.8 434.6 633.9 435.3 634.1 437.8 647.9 440.7 663.8 441.5 706.7 443.8 717 445.3 724.9 450.6 733.7 456.4 735.9 467.4 736 504.2 736 504.2 736 521.9 736 521.9 736 521.9 730.8 522.4 730.8 522.4 727.9 522.8 691.8 525.3 650.5 528 609.3 530.8 571.9 533.3 567.5 533.6 531.8 536 503.4 540.5 487.4 546 475 550.3 442.2 566.5 428.2 575.2 392.1 597.6 360.1 629.2 338 664.3 317 697.7 304.6 729.6 292.5 781 287.8 801 283.9 805.6 267.2 811 255.1 814.9 248.7 818.3 243.4 823.5 239.7 827.2 239.2 828.4 237.7 836.5 236.7 841.5 235.5 850 235 855.5 234.3 863.9 232.6 911.1 232.6 924.5 232.6 934.6 234.2 959.8 235 963 237.4 972 242.3 975.8 258.7 981.7 271.7 986.3 277.4 989.5 280.4 993.9 283.1 997.9 286.5 1008.7 287.4 1016.5 289.3 1031.7 293.3 1050.2 297.7 1064 306.4 1091.9 317.6 1107.7 330 1109.5 332.4 1109.9 334.7 1110.3 335 1110.5 335.3 1110.6 339.1 1111.1 343.5 1111.4 347.9 1111.8 353.8 1112.3 356.5 1112.5 370 1113.7 407.8 1115.8 430 1116.5 439.6 1116.8 453.4 1117.3 460.5 1117.5 467.7 1117.8 482.3 1118.2 493 1118.5 503.7 1118.8 520.8 1119.2 531 1119.5 587.8 1121.1 683 1122 794.5 1122 918.8 1122 996.1 1121 1075 1118.5 1083.5 1118.2 1098.6 1117.8 1108.5 1117.4 1118.4 1117.1 1129.4 1116.7 1133 1116.5 1136.6 1116.3 1144.7 1115.8 1151 1115.5 1183.3 1114 1207.5 1110.9 1221.5 1106.3 1235.3 1101.9 1244.8 1094.1 1249.9 1083.2 1253.5 1075.4 1253.7 1074.4 1259 1044 1266.4 1001.7 1269.1 993.5 1276.9 989.2 1278.9 988.1 1284.7 986.1 1289.9 984.6 1305.8 980.1 1313.3 975.5 1314.4 969.7 1316.2 959.5 1317.1 881.4 1315.7 859.5 1314 834.8 1313.1 831.4 1307 824.7 1301.2 818.3 1295 814.8 1283 811 1278 809.5 1272.4 807 1270.5 805.6 1264 800.6 1259.5 790.1 1252.5 763 1243.5 728.4 1235.9 706.8 1224.6 683.5 1202.3 637.7 1167.5 602.5 1111.8 569.1 1087.6 554.7 1054.4 542.7 1028.1 539 1024.1 538.4 1020.5 537.8 1020.1 537.6 1019.8 537.4 1016.4 536.9 1012.7 536.5 1009 536.2 1005.1 535.7 1004.2 535.5 999.7 534.7 988.4 533.6 973.5 532.5 969.7 532.2 964.5 531.8 962 531.5 959.5 531.2 940.4 530.1 919.5 529 851.7 525.4 836.1 523.7 829.5 519.4 821.2 514 825.2 495.6 837.6 481.8 843.8 474.9 844.2 474.3 856.8 448 862.6 436 868.9 425.2 879.9 408.7 888.2 396.2 895 385.5 895 385.1 895 384.6 889.7 383.3 883.3 382 851.1 375.9 812.7 367.8 806.9 365.9 792.4 361.2 790.7 358.7 790.2 342.5 789.7 326.8 792.4 303.4 800 258.5 802.1 246.1 803.3 220.9 801.9 219.6 801.7 219.4 800.5 218.9 799.3 218.6 797.4 218 796.5 218.7 793.8 222.7Z"/>
<path
android:fillColor="#FF62686C"
android:strokeColor="#FF62686C"
android:strokeWidth="4.0"
android:strokeLineCap="round"
android:strokeMiterLimit="10"
android:pathData="M570.4 704.1C553.5 706.1 539.7 713 532.9 722.7 523.5 736.3 519.9 764.2 519.6 826 519.5 859.4 519.7 860.6 530.1 872.9 543.4 888.6 560.4 900 574.1 902.1 586.7 904 601.5 898.9 615.7 887.5 632.5 874.1 633.5 868.9 632.7 793.6 632.2 742.5 631.6 736 627 725.2 620.5 710 596.2 700.9 570.4 704.1Z"/>
<path
android:fillColor="#FF62686C"
android:strokeColor="#FF62686C"
android:strokeWidth="4.0"
android:strokeLineCap="round"
android:strokeMiterLimit="10"
android:pathData="M949.2 705.5C935.1 708.7 927 716.8 922.6 732.4 918.4 746.7 917.8 757.3 917.3 816.5 917.1 847.8 917.3 874 917.7 874.6 919.2 876.7 945.3 892.3 955.5 897.1 973.4 905.5 983.5 905.4 978.9 896.8 977.1 893.4 979.1 892.6 985.9 893.9 993.6 895.4 997.8 894.3 1005.2 889.1 1010.9 885 1020.2 873.6 1023.8 866.5 1029.6 854.9 1031.1 841.6 1031.1 800.5 1031.1 750.1 1028.6 737.6 1014.9 720.4 1009.4 713.5 1004.6 709.8 996.9 706.8 992.2 704.9 989.1 704.6 973.5 704.4 960.6 704.1 953.7 704.5 949.2 705.5Z"/>
<path
android:fillColor="#FF62686C"
android:strokeColor="#FF62686C"
android:strokeWidth="4.0"
android:strokeLineCap="round"
android:strokeMiterLimit="10"
android:pathData="M717 988.7C671.1 990.4 640.9 994.2 635.6 998.8 631.5 1002.5 636.3 1017.5 643.6 1024 650.9 1030.3 661.7 1032.5 694 1034 721.9 1035.3 829.3 1035.3 857 1034 891.4 1032.4 901.4 1029.9 908.9 1021.3 914.5 1015 917.9 1003.4 915.2 999.4 910.4 992.1 856 987.9 772 988.2 745.9 988.3 721.1 988.5 717 988.7Z"/>
</vector>

View File

@@ -9,5 +9,5 @@
android:top="0dp"
android:right="0dp"
android:bottom="0dp"
android:drawable="@drawable/ic_iw"/>
android:drawable="@drawable/ic_launcher"/>
</layer-list>

View File

@@ -1,13 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
android:clickable="true"
android:layout_width="24dp"
android:layout_height="24dp">
<item android:drawable="@drawable/ic_launcher_background"/>
<item
android:left="0dp"
android:top="0dp"
android:right="0dp"
android:bottom="0dp"
android:drawable="@drawable/ic_launcher_foreground_disable"/>
</layer-list>

View File

@@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24"
android:viewportWidth="24">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M16.61,15.15C16.15,15.15 15.77,14.78 15.77,14.32S16.15,13.5 16.61,13.5H16.61C17.07,13.5 17.45,13.86 17.45,14.32C17.45,14.78 17.07,15.15 16.61,15.15M7.41,15.15C6.95,15.15 6.57,14.78 6.57,14.32C6.57,13.86 6.95,13.5 7.41,13.5H7.41C7.87,13.5 8.24,13.86 8.24,14.32C8.24,14.78 7.87,15.15 7.41,15.15M16.91,10.14L18.58,7.26C18.67,7.09 18.61,6.88 18.45,6.79C18.28,6.69 18.07,6.75 18,6.92L16.29,9.83C14.95,9.22 13.5,8.9 12,8.91C10.47,8.91 9,9.24 7.73,9.82L6.04,6.91C5.95,6.74 5.74,6.68 5.57,6.78C5.4,6.87 5.35,7.08 5.44,7.25L7.1,10.13C4.25,11.69 2.29,14.58 2,18H22C21.72,14.59 19.77,11.7 16.91,10.14H16.91Z"/>
</vector>

View File

@@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24"
android:viewportWidth="24">
<path
android:fillColor="#FF808080"
android:pathData="M16.61,15.15C16.15,15.15 15.77,14.78 15.77,14.32S16.15,13.5 16.61,13.5H16.61C17.07,13.5 17.45,13.86 17.45,14.32C17.45,14.78 17.07,15.15 16.61,15.15M7.41,15.15C6.95,15.15 6.57,14.78 6.57,14.32C6.57,13.86 6.95,13.5 7.41,13.5H7.41C7.87,13.5 8.24,13.86 8.24,14.32C8.24,14.78 7.87,15.15 7.41,15.15M16.91,10.14L18.58,7.26C18.67,7.09 18.61,6.88 18.45,6.79C18.28,6.69 18.07,6.75 18,6.92L16.29,9.83C14.95,9.22 13.5,8.9 12,8.91C10.47,8.91 9,9.24 7.73,9.82L6.04,6.91C5.95,6.74 5.74,6.68 5.57,6.78C5.4,6.87 5.35,7.08 5.44,7.25L7.1,10.13C4.25,11.69 2.29,14.58 2,18H22C21.72,14.59 19.77,11.7 16.91,10.14H16.91Z"/>
</vector>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

View File

@@ -9,7 +9,7 @@
android:id="@+id/toolbar_icon"
android:layout_width="48dp"
android:layout_height="48dp"
android:src="@drawable/ic_iw"
android:src="@drawable/ic_launcher"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
android:layout_marginStart="6dp"

View File

@@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

View File

@@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB