Compare commits
11 Commits
d0cccc6597
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8c8b0b056f | ||
|
|
ff9aadb2e6 | ||
|
|
af788d00cd | ||
|
|
f0fe8de4e2 | ||
|
|
e11841f19d | ||
|
|
9c2c5d6f5f | ||
|
|
1951b76a7c | ||
|
|
23e372ae93 | ||
|
|
a61f468fcf | ||
|
|
db8d37ec36 | ||
|
|
1ab9f0ab88 |
1
.idea/.name
generated
Normal file
1
.idea/.name
generated
Normal file
@@ -0,0 +1 @@
|
|||||||
|
SmsMessage
|
||||||
6
.idea/AndroidProjectSystem.xml
generated
Normal file
6
.idea/AndroidProjectSystem.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="AndroidProjectSystem">
|
||||||
|
<option name="providerId" value="com.android.tools.idea.GradleProjectSystem" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
2
.idea/compiler.xml
generated
2
.idea/compiler.xml
generated
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="CompilerConfiguration">
|
<component name="CompilerConfiguration">
|
||||||
<bytecodeTargetLevel target="17" />
|
<bytecodeTargetLevel target="21" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
8
.idea/deploymentTargetSelector.xml
generated
8
.idea/deploymentTargetSelector.xml
generated
@@ -4,6 +4,14 @@
|
|||||||
<selectionStates>
|
<selectionStates>
|
||||||
<SelectionState runConfigName="app">
|
<SelectionState runConfigName="app">
|
||||||
<option name="selectionMode" value="DROPDOWN" />
|
<option name="selectionMode" value="DROPDOWN" />
|
||||||
|
<DropdownSelection timestamp="2026-04-04T17:31:49.855391Z">
|
||||||
|
<Target type="DEFAULT_BOOT">
|
||||||
|
<handle>
|
||||||
|
<DeviceId pluginId="LocalEmulator" identifier="path=/Users/wchino/.android/avd/Pixel_9_Pro.avd" />
|
||||||
|
</handle>
|
||||||
|
</Target>
|
||||||
|
</DropdownSelection>
|
||||||
|
<DialogSelection />
|
||||||
</SelectionState>
|
</SelectionState>
|
||||||
</selectionStates>
|
</selectionStates>
|
||||||
</component>
|
</component>
|
||||||
|
|||||||
6
.idea/gradle.xml
generated
6
.idea/gradle.xml
generated
@@ -4,10 +4,9 @@
|
|||||||
<component name="GradleSettings">
|
<component name="GradleSettings">
|
||||||
<option name="linkedExternalProjectsSettings">
|
<option name="linkedExternalProjectsSettings">
|
||||||
<GradleProjectSettings>
|
<GradleProjectSettings>
|
||||||
<option name="testRunner" value="GRADLE" />
|
<option name="testRunner" value="CHOOSE_PER_TEST" />
|
||||||
<option name="distributionType" value="DEFAULT_WRAPPED" />
|
|
||||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||||
<option name="gradleJvm" value="jbr-17" />
|
<option name="gradleJvm" value="jbr-21" />
|
||||||
<option name="modules">
|
<option name="modules">
|
||||||
<set>
|
<set>
|
||||||
<option value="$PROJECT_DIR$" />
|
<option value="$PROJECT_DIR$" />
|
||||||
@@ -15,7 +14,6 @@
|
|||||||
<option value="$PROJECT_DIR$/library" />
|
<option value="$PROJECT_DIR$/library" />
|
||||||
</set>
|
</set>
|
||||||
</option>
|
</option>
|
||||||
<option name="resolveExternalAnnotations" value="false" />
|
|
||||||
</GradleProjectSettings>
|
</GradleProjectSettings>
|
||||||
</option>
|
</option>
|
||||||
</component>
|
</component>
|
||||||
|
|||||||
8
.idea/markdown.xml
generated
Normal file
8
.idea/markdown.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="MarkdownSettings">
|
||||||
|
<option name="previewPanelProviderInfo">
|
||||||
|
<ProviderInfo name="Compose (experimental)" className="com.intellij.markdown.compose.preview.ComposePanelProvider" />
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
3
.idea/misc.xml
generated
3
.idea/misc.xml
generated
@@ -1,6 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="jbr-17" project-jdk-type="JavaSDK">
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
|
||||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||||
</component>
|
</component>
|
||||||
<component name="ProjectType">
|
<component name="ProjectType">
|
||||||
|
|||||||
17
.idea/runConfigurations.xml
generated
Normal file
17
.idea/runConfigurations.xml
generated
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="RunConfigurationProducerService">
|
||||||
|
<option name="ignoredProducers">
|
||||||
|
<set>
|
||||||
|
<option value="com.intellij.execution.junit.AbstractAllInDirectoryConfigurationProducer" />
|
||||||
|
<option value="com.intellij.execution.junit.AllInPackageConfigurationProducer" />
|
||||||
|
<option value="com.intellij.execution.junit.PatternConfigurationProducer" />
|
||||||
|
<option value="com.intellij.execution.junit.TestInClassConfigurationProducer" />
|
||||||
|
<option value="com.intellij.execution.junit.UniqueIdConfigurationProducer" />
|
||||||
|
<option value="com.intellij.execution.junit.testDiscovery.JUnitTestDiscoveryConfigurationProducer" />
|
||||||
|
<option value="org.jetbrains.kotlin.idea.junit.KotlinJUnitRunConfigurationProducer" />
|
||||||
|
<option value="org.jetbrains.kotlin.idea.junit.KotlinPatternConfigurationProducer" />
|
||||||
|
</set>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
171
AGENTS.md
Normal file
171
AGENTS.md
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
# AGENTS.md — SmsMessage (notiMessage)
|
||||||
|
|
||||||
|
Android 应用,监听通知栏短信/通知内容并上传至服务器。适配华为、MIUI 等手机。
|
||||||
|
|
||||||
|
## 项目结构
|
||||||
|
|
||||||
|
```
|
||||||
|
notiMessage/
|
||||||
|
├── app/ # 主应用模块 (com.miraclegarden.smsmessage)
|
||||||
|
│ ├── src/main/java/.../
|
||||||
|
│ │ ├── Activity/ # Activity 类 (MainActivity, NotificationActivity, SettingActivity, AppListActivity)
|
||||||
|
│ │ ├── service/ # Service 类 (NotificationService — 通知监听核心)
|
||||||
|
│ │ ├── comm/ # 通用组件 (CommonAdapter, ViewHolder)
|
||||||
|
│ │ ├── App.java # Application 类,管理通知列表持久化
|
||||||
|
│ │ ├── MessageInfo.java, AppInfo.java # 数据模型
|
||||||
|
│ │ └── GsonUtils.java # JSON 工具类
|
||||||
|
│ └── src/main/res/ # 资源文件
|
||||||
|
├── library/ # 基础库模块 (com.miraclegarden.library)
|
||||||
|
│ └── MiracleGardenActivity<T> # ViewBinding 基类
|
||||||
|
├── build.gradle # 根构建文件 (AGP 7.3.0)
|
||||||
|
├── settings.gradle # 模块声明 + 仓库配置
|
||||||
|
└── gradle.properties # Gradle 配置
|
||||||
|
```
|
||||||
|
|
||||||
|
## 构建命令
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 构建
|
||||||
|
./gradlew assembleDebug # 构建 debug APK
|
||||||
|
./gradlew assembleRelease # 构建 release APK
|
||||||
|
./gradlew build # 完整构建(编译 + lint + 测试)
|
||||||
|
|
||||||
|
# Lint
|
||||||
|
./gradlew lint # 运行所有模块 lint
|
||||||
|
./gradlew :app:lint # 仅 app 模块 lint
|
||||||
|
./gradlew :library:lint # 仅 library 模块 lint
|
||||||
|
|
||||||
|
# 单元测试
|
||||||
|
./gradlew test # 运行所有模块单元测试
|
||||||
|
./gradlew :app:testDebugUnitTest # 仅 app 模块 debug 单元测试
|
||||||
|
./gradlew :app:testDebugUnitTest --tests "com.miraclegarden.smsmessage.ExampleUnitTest" # 运行单个测试类
|
||||||
|
./gradlew :app:testDebugUnitTest --tests "*.ExampleUnitTest.addition_isCorrect" # 运行单个测试方法
|
||||||
|
|
||||||
|
# Android 测试(需要设备/模拟器)
|
||||||
|
./gradlew connectedAndroidTest # 运行所有模块 instrumented 测试
|
||||||
|
./gradlew :app:connectedDebugAndroidTest # 仅 app 模块
|
||||||
|
|
||||||
|
# 清理
|
||||||
|
./gradlew clean
|
||||||
|
```
|
||||||
|
|
||||||
|
## SDK 与依赖版本
|
||||||
|
|
||||||
|
| 配置项 | app 模块 | library 模块 |
|
||||||
|
|--------|---------|-------------|
|
||||||
|
| compileSdk | 32 | 32 |
|
||||||
|
| minSdk | 24 | 21 |
|
||||||
|
| targetSdk | 32 | 32 |
|
||||||
|
| Java | 17 | 17 |
|
||||||
|
|
||||||
|
**关键依赖**: OkHttp 5.0.0-alpha.10 (网络), Gson 2.9.0 (JSON), ViewBinding (UI绑定), Material 1.6.1
|
||||||
|
**测试框架**: JUnit 4.13.2, Espresso 3.4.0, AndroidX Test JUnit 1.1.3
|
||||||
|
**Gradle**: 7.5.1, AGP 7.3.0
|
||||||
|
|
||||||
|
## 语言与代码规范
|
||||||
|
|
||||||
|
### 语言
|
||||||
|
|
||||||
|
纯 Java 项目,无 Kotlin。所有源文件均为 `.java`。
|
||||||
|
|
||||||
|
### 导入顺序
|
||||||
|
|
||||||
|
遵循 Android 标准导入顺序:
|
||||||
|
1. `android.*` — Android 框架
|
||||||
|
2. `androidx.*` — AndroidX 兼容库
|
||||||
|
3. 项目内部包 (`com.miraclegarden.*`)
|
||||||
|
4. 第三方库 (`org.json.*`, `okhttp3.*`, `com.google.gson.*`)
|
||||||
|
5. `java.*` — Java 标准库
|
||||||
|
|
||||||
|
导入之间按组用空行分隔。不使用通配符导入(`*`)。
|
||||||
|
|
||||||
|
### 命名规范
|
||||||
|
|
||||||
|
| 类型 | 规范 | 示例 |
|
||||||
|
|------|------|------|
|
||||||
|
| 类名 | PascalCase | `NotificationService`, `MessageInfo`, `CommonAdapter` |
|
||||||
|
| 方法名 | camelCase | `initData()`, `initView()`, `getSbnByNotificatinList()` |
|
||||||
|
| 变量 | camelCase | `messageInfo`, `sharedPreferences`, `appName` |
|
||||||
|
| 常量 | UPPER_SNAKE_CASE | `private static final String TAG = "..."` |
|
||||||
|
| 成员变量前缀 | 通用组件用 `m` 前缀 | `mContext`, `mDatas`, `mLayoutId` |
|
||||||
|
| 布局文件 | activity_xxx / item_xxx | `activity_main.xml`, `activity_notification.xml` |
|
||||||
|
| Activity 包名 | 大写开头子包 | `com.miraclegarden.smsmessage.Activity` |
|
||||||
|
|
||||||
|
### 类结构模式
|
||||||
|
|
||||||
|
Activity 类遵循统一初始化模式:
|
||||||
|
```java
|
||||||
|
public class XxxActivity extends MiracleGardenActivity<ActivityXxxBinding> {
|
||||||
|
private static final String TAG = "XxxActivity";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
initData();
|
||||||
|
initView();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initData() { /* 数据初始化 */ }
|
||||||
|
private void initView() { /* UI 绑定与事件监听 */ }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### ViewBinding
|
||||||
|
|
||||||
|
- 所有 Activity 继承 `MiracleGardenActivity<T extends ViewBinding>`
|
||||||
|
- 通过泛型参数自动反射创建 binding 实例
|
||||||
|
- 直接使用 `binding.xxx` 访问视图,**不使用** `findViewById`
|
||||||
|
|
||||||
|
### 数据模型
|
||||||
|
|
||||||
|
- POJO 风格:private 字段 + getter/setter
|
||||||
|
- 提供无参和全参构造函数
|
||||||
|
- 静态工厂方法用于类型转换(如 `MessageInfo.AppInfoToMessageInfo()`)
|
||||||
|
- 行内注释说明字段用途(`// 应用名`, `// 包名`)
|
||||||
|
|
||||||
|
### 网络请求
|
||||||
|
|
||||||
|
- 使用 OkHttp,异步调用 (`enqueue`)
|
||||||
|
- JSON 请求体使用 `org.json.JSONObject` 手动构建
|
||||||
|
- MediaType 常量: `public static final MediaType JSON = MediaType.parse("application/json; charset=utf-8")`
|
||||||
|
- 回调中通过 `NotificationActivity.sendMessage()` 反馈 UI
|
||||||
|
|
||||||
|
### 错误处理
|
||||||
|
|
||||||
|
- try/catch 包裹关键操作,catch 中使用 `e.printStackTrace()` 或 `Log.e(TAG, msg, e)`
|
||||||
|
- 部分场景直接抛出 `RuntimeException`(如 JSONException)
|
||||||
|
- null 检查使用 `TextUtils.isEmpty()` 或直接 `== null` 判断
|
||||||
|
- lint 配置: `abortOnError false`(lint 错误不阻塞构建)
|
||||||
|
|
||||||
|
### 注解使用
|
||||||
|
|
||||||
|
| 注解 | 用途 |
|
||||||
|
|------|------|
|
||||||
|
| `@Override` | 方法重写(必须标注) |
|
||||||
|
| `@Nullable` / `@NonNull` | 参数和返回值空安全 |
|
||||||
|
| `@SuppressLint` | 抑制特定 lint 警告(如 `"StaticFieldLeak"`, `"SimpleDateFormat"`) |
|
||||||
|
| `@RequiresApi` | API 版本要求标注 |
|
||||||
|
|
||||||
|
### 注释风格
|
||||||
|
|
||||||
|
- 类级别使用 Javadoc `/** */` 风格,含 `@Author`、创建时间、用途
|
||||||
|
- 方法级别使用 `/** */` 或单行 `//` 注释
|
||||||
|
- 行内注释使用中文(项目主要面向中文开发者)
|
||||||
|
- 代码注释中保留被注释掉的调试代码(`//LogUtils.i(...)`, `//notifyItemRangeChanged`)
|
||||||
|
|
||||||
|
### 数据持久化
|
||||||
|
|
||||||
|
- 使用 `SharedPreferences`,统一名称为 `"server"`
|
||||||
|
- 通过 `App` 类的静态方法管理通知列表(`saveNotiList`, `getNotiList`, `saveNotiBean`, `deleteNotiBean`)
|
||||||
|
- 列表数据序列化为 JSON 字符串存储
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
- **无 CI/CD 配置** — 无自动化构建流程
|
||||||
|
- **无代码格式化工具** — 无 .editorconfig、checkstyle、spotless 等配置
|
||||||
|
- **测试覆盖极低** — 仅有默认生成的示例测试,无业务逻辑测试
|
||||||
|
- **签名信息硬编码** — keystore 密码直接写在 build.gradle 中
|
||||||
|
- **ProGuard 未启用** — release 构建 `minifyEnabled false`
|
||||||
|
- **仓库包含阿里云/华为镜像** — 构建时需要网络访问这些 Maven 仓库
|
||||||
|
- 修改代码时保持现有风格一致,即使风格不够规范
|
||||||
|
- 中文注释和 UI 字符串是项目惯例,新增代码保持一致
|
||||||
@@ -8,14 +8,14 @@ ext {
|
|||||||
|
|
||||||
android {
|
android {
|
||||||
namespace 'com.miraclegarden.smsmessage'
|
namespace 'com.miraclegarden.smsmessage'
|
||||||
compileSdk 32
|
compileSdk 34
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "com.miraclegarden.smsmessage"
|
applicationId "com.miraclegarden.smsmessage"
|
||||||
minSdk 24
|
minSdk 24
|
||||||
targetSdk 32
|
targetSdk 34
|
||||||
versionCode 11
|
versionCode 12
|
||||||
versionName "2.1"
|
versionName "2.2"
|
||||||
|
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
@@ -59,13 +59,13 @@ android {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation "com.github.yingliangwei:MiracleGardenLib:1.0"
|
implementation "com.github.yingliangwei:MiracleGardenLib:1.0"
|
||||||
implementation 'androidx.databinding:viewbinding:7.3.0'
|
implementation 'com.squareup.okhttp3:okhttp:4.12.0'
|
||||||
implementation 'com.squareup.okhttp3:okhttp:5.0.0-alpha.10'
|
implementation 'androidx.appcompat:appcompat:1.7.0'
|
||||||
implementation 'androidx.appcompat:appcompat:1.5.0'
|
implementation 'com.google.android.material:material:1.12.0'
|
||||||
implementation 'com.google.android.material:material:1.6.1'
|
implementation 'androidx.core:core:1.13.1'
|
||||||
implementation 'com.github.yingliangwei:MiracleGardenLib:1.0'
|
implementation 'androidx.work:work-runtime:2.9.1'
|
||||||
testImplementation 'junit:junit:4.13.2'
|
testImplementation 'junit:junit:4.13.2'
|
||||||
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
|
androidTestImplementation 'androidx.test.ext:junit:1.2.1'
|
||||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
|
androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1'
|
||||||
api 'com.google.code.gson:gson:2.9.0'
|
api 'com.google.code.gson:gson:2.11.0'
|
||||||
}
|
}
|
||||||
Binary file not shown.
BIN
app/release/baselineProfiles/0/app-release.dm
Normal file
BIN
app/release/baselineProfiles/0/app-release.dm
Normal file
Binary file not shown.
BIN
app/release/baselineProfiles/1/app-release.dm
Normal file
BIN
app/release/baselineProfiles/1/app-release.dm
Normal file
Binary file not shown.
BIN
app/release/noti-msg.apk
Normal file
BIN
app/release/noti-msg.apk
Normal file
Binary file not shown.
@@ -11,10 +11,27 @@
|
|||||||
"type": "SINGLE",
|
"type": "SINGLE",
|
||||||
"filters": [],
|
"filters": [],
|
||||||
"attributes": [],
|
"attributes": [],
|
||||||
"versionCode": 11,
|
"versionCode": 12,
|
||||||
"versionName": "2.1",
|
"versionName": "2.2",
|
||||||
"outputFile": "app-release.apk"
|
"outputFile": "app-release.apk"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"elementType": "File"
|
"elementType": "File",
|
||||||
|
"baselineProfiles": [
|
||||||
|
{
|
||||||
|
"minApi": 28,
|
||||||
|
"maxApi": 30,
|
||||||
|
"baselineProfiles": [
|
||||||
|
"baselineProfiles/1/app-release.dm"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"minApi": 31,
|
||||||
|
"maxApi": 2147483647,
|
||||||
|
"baselineProfiles": [
|
||||||
|
"baselineProfiles/0/app-release.dm"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"minSdkVersionForDexing": 24
|
||||||
}
|
}
|
||||||
@@ -8,6 +8,16 @@
|
|||||||
<!-- 添加接收系统启动消息(用于开机启动)权限 -->
|
<!-- 添加接收系统启动消息(用于开机启动)权限 -->
|
||||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||||
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
|
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
|
||||||
|
<!-- 前台服务权限 -->
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
|
<!-- Android 14+ specialUse 前台服务权限 -->
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
|
||||||
|
<!-- Android 13+ 通知权限 -->
|
||||||
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||||
|
<!-- WorkManager 需要 -->
|
||||||
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
|
<!-- 电池优化白名单请求 -->
|
||||||
|
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
|
||||||
|
|
||||||
<!-- 下面这个也必须加,否则 Android 11+ 拿不到列表 -->
|
<!-- 下面这个也必须加,否则 Android 11+ 拿不到列表 -->
|
||||||
<queries>
|
<queries>
|
||||||
@@ -20,6 +30,7 @@
|
|||||||
</intent>
|
</intent>
|
||||||
</queries>
|
</queries>
|
||||||
<application
|
<application
|
||||||
|
android:name=".App"
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||||
android:fullBackupContent="@xml/backup_rules"
|
android:fullBackupContent="@xml/backup_rules"
|
||||||
@@ -31,7 +42,7 @@
|
|||||||
android:roundIcon="@mipmap/app_logo"
|
android:roundIcon="@mipmap/app_logo"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/Theme.SmsMessage"
|
android:theme="@style/Theme.SmsMessage"
|
||||||
tools:targetApi="31">
|
tools:targetApi="34">
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".Activity.MainActivity"
|
android:name=".Activity.MainActivity"
|
||||||
@@ -54,21 +65,42 @@
|
|||||||
android:name=".Activity.AppListActivity"
|
android:name=".Activity.AppListActivity"
|
||||||
android:theme="@style/Theme.SmsMessage1"
|
android:theme="@style/Theme.SmsMessage1"
|
||||||
android:exported="true" />
|
android:exported="true" />
|
||||||
|
<activity
|
||||||
|
android:name=".Activity.LoginActivity"
|
||||||
|
android:exported="false" />
|
||||||
|
<activity
|
||||||
|
android:name=".Activity.ChangePasswordActivity"
|
||||||
|
android:exported="false" />
|
||||||
|
<activity
|
||||||
|
android:name=".Activity.BankListActivity"
|
||||||
|
android:exported="false" />
|
||||||
|
<activity
|
||||||
|
android:name=".Activity.PermissionActivity"
|
||||||
|
android:exported="false" />
|
||||||
|
|
||||||
<!--通知栏获取短信-->
|
<!--通知栏获取短信-->
|
||||||
<service
|
<service
|
||||||
android:name=".service.NotificationService"
|
android:name=".service.NotificationService"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
|
android:foregroundServiceType="specialUse"
|
||||||
android:label="通知监控"
|
android:label="通知监控"
|
||||||
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"
|
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"
|
||||||
android:priority="1000">
|
android:priority="1000">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
|
||||||
<action android:name="android.service.notification.NotificationListenerService" />
|
<action android:name="android.service.notification.NotificationListenerService" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
|
<!-- 开机自启广播接收器 -->
|
||||||
|
<receiver
|
||||||
|
android:name=".service.BootReceiver"
|
||||||
|
android:enabled="true"
|
||||||
|
android:exported="true">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
|
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|||||||
@@ -6,17 +6,22 @@ import android.os.Bundle;
|
|||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.view.Gravity;
|
import android.view.Gravity;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewTreeObserver;
|
|
||||||
import android.view.Window;
|
import android.view.Window;
|
||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
import android.widget.ImageView;
|
import android.widget.AdapterView;
|
||||||
import android.widget.TextView;
|
import android.widget.ArrayAdapter;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import com.miraclegarden.smsmessage.databinding.DialogActionConfirmBinding;
|
import com.miraclegarden.smsmessage.databinding.DialogActionConfirmBinding;
|
||||||
|
import com.miraclegarden.smsmessage.model.ApiError;
|
||||||
|
import com.miraclegarden.smsmessage.model.BankInfo;
|
||||||
|
import com.miraclegarden.smsmessage.network.ApiService;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 通用弹窗
|
* 选择银行账户弹窗(添加监听时使用)
|
||||||
*/
|
*/
|
||||||
public class ActionConfirmDialog extends Dialog {
|
public class ActionConfirmDialog extends Dialog {
|
||||||
private final Context context;
|
private final Context context;
|
||||||
@@ -24,65 +29,61 @@ public class ActionConfirmDialog extends Dialog {
|
|||||||
AppInfo appInfo;
|
AppInfo appInfo;
|
||||||
int index;
|
int index;
|
||||||
DialogActionConfirmBinding actionConfirmBinding;
|
DialogActionConfirmBinding actionConfirmBinding;
|
||||||
|
ApiService apiService;
|
||||||
|
List<BankInfo> bankList = new ArrayList<>();
|
||||||
|
BankInfo selectedBank = null;
|
||||||
|
|
||||||
public interface OnToActionListener {
|
public interface OnToActionListener {
|
||||||
void toSumbit(AppInfo appInfo);
|
void toSumbit(AppInfo appInfo);
|
||||||
void toCancel();
|
void toCancel();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setOnToActionListener(OnToActionListener onNextCallListener) {
|
public void setOnToActionListener(OnToActionListener onNextCallListener) {
|
||||||
this.onToActionListener = onNextCallListener;
|
this.onToActionListener = onNextCallListener;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public ActionConfirmDialog(Context context, AppInfo appInfo) {
|
public ActionConfirmDialog(Context context, AppInfo appInfo) {
|
||||||
super(context, R.style.MaterialDesignDialog);
|
super(context, R.style.MaterialDesignDialog);
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.appInfo = appInfo;
|
this.appInfo = appInfo;
|
||||||
|
this.apiService = new ApiService(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
actionConfirmBinding = DialogActionConfirmBinding.inflate(getLayoutInflater());
|
actionConfirmBinding = DialogActionConfirmBinding.inflate(getLayoutInflater());
|
||||||
setContentView(actionConfirmBinding.getRoot());
|
setContentView(actionConfirmBinding.getRoot());
|
||||||
|
|
||||||
actionConfirmBinding.ivIcon.setImageDrawable(appInfo.getIcon());
|
actionConfirmBinding.ivIcon.setImageDrawable(appInfo.getIcon());
|
||||||
actionConfirmBinding.tvAppname.setText(appInfo.getAppName());
|
actionConfirmBinding.tvAppname.setText(appInfo.getAppName());
|
||||||
actionConfirmBinding.tvPackage.setText(appInfo.getPackageName());
|
actionConfirmBinding.tvPackage.setText(appInfo.getPackageName());
|
||||||
if(!TextUtils.isEmpty(appInfo.getName())){
|
|
||||||
actionConfirmBinding.tvName.setText(appInfo.getName());
|
|
||||||
}
|
|
||||||
if(!TextUtils.isEmpty(appInfo.getCode())){
|
|
||||||
actionConfirmBinding.tvCode.setText(appInfo.getCode());
|
|
||||||
}
|
|
||||||
if(!TextUtils.isEmpty(appInfo.getRemark())){
|
|
||||||
actionConfirmBinding.tvRemark.setText(appInfo.getRemark());
|
|
||||||
}
|
|
||||||
|
|
||||||
actionConfirmBinding.sumbitTv.setOnClickListener(new View.OnClickListener() {
|
// 加载银行账户列表
|
||||||
@Override
|
loadBankList();
|
||||||
public void onClick(View view) {
|
|
||||||
if(TextUtils.isEmpty(actionConfirmBinding.tvName.getText().toString().trim())){
|
actionConfirmBinding.sumbitTv.setOnClickListener(view -> {
|
||||||
Toast.makeText(context,"名称不能为空",Toast.LENGTH_SHORT).show();
|
if (selectedBank == null) {
|
||||||
return;
|
Toast.makeText(context, "请选择关联的银行账户", Toast.LENGTH_SHORT).show();
|
||||||
}
|
return;
|
||||||
if(TextUtils.isEmpty(actionConfirmBinding.tvCode.getText().toString().trim())){
|
}
|
||||||
Toast.makeText(context,"Code不能为空",Toast.LENGTH_SHORT).show();
|
|
||||||
return;
|
appInfo.setBankInfoId(selectedBank.getId());
|
||||||
}
|
appInfo.setName(selectedBank.getBankName());
|
||||||
appInfo.setName(actionConfirmBinding.tvName.getText().toString().trim());
|
appInfo.setCode(selectedBank.getAccount());
|
||||||
appInfo.setCode(actionConfirmBinding.tvCode.getText().toString().trim());
|
if (selectedBank.getRemark() != null) {
|
||||||
appInfo.setRemark(actionConfirmBinding.tvRemark.getText().toString().trim());
|
appInfo.setRemark(selectedBank.getRemark());
|
||||||
if(onToActionListener!=null){
|
}
|
||||||
dismiss();
|
|
||||||
onToActionListener.toSumbit(appInfo);
|
if (onToActionListener != null) {
|
||||||
}
|
dismiss();
|
||||||
|
onToActionListener.toSumbit(appInfo);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
actionConfirmBinding.cancelTv.setOnClickListener(view -> {
|
actionConfirmBinding.cancelTv.setOnClickListener(view -> {
|
||||||
dismiss();
|
dismiss();
|
||||||
if(onToActionListener!=null){
|
if (onToActionListener != null) {
|
||||||
onToActionListener.toCancel();
|
onToActionListener.toCancel();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -92,8 +93,83 @@ public class ActionConfirmDialog extends Dialog {
|
|||||||
wlp.gravity = Gravity.CENTER;
|
wlp.gravity = Gravity.CENTER;
|
||||||
wlp.width = WindowManager.LayoutParams.WRAP_CONTENT;
|
wlp.width = WindowManager.LayoutParams.WRAP_CONTENT;
|
||||||
wlp.height = WindowManager.LayoutParams.WRAP_CONTENT;
|
wlp.height = WindowManager.LayoutParams.WRAP_CONTENT;
|
||||||
|
|
||||||
window.setAttributes(wlp);
|
window.setAttributes(wlp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void loadBankList() {
|
||||||
|
actionConfirmBinding.spinnerBank.setEnabled(false);
|
||||||
|
|
||||||
|
apiService.getBankList(new ApiService.ApiCallback<List<BankInfo>>() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(List<BankInfo> result) {
|
||||||
|
if (context instanceof android.app.Activity) {
|
||||||
|
((android.app.Activity) context).runOnUiThread(() -> {
|
||||||
|
bankList.clear();
|
||||||
|
if (result != null) {
|
||||||
|
bankList.addAll(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bankList.isEmpty()) {
|
||||||
|
Toast.makeText(context, "暂无银行账户,请先到银行账户管理添加", Toast.LENGTH_LONG).show();
|
||||||
|
dismiss();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setupSpinner();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(ApiError error) {
|
||||||
|
if (context instanceof android.app.Activity) {
|
||||||
|
((android.app.Activity) context).runOnUiThread(() -> {
|
||||||
|
Toast.makeText(context, "加载银行账户失败: " + error.getMessage(), Toast.LENGTH_LONG).show();
|
||||||
|
dismiss();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupSpinner() {
|
||||||
|
List<String> bankNames = new ArrayList<>();
|
||||||
|
bankNames.add("请选择银行账户");
|
||||||
|
for (BankInfo bank : bankList) {
|
||||||
|
bankNames.add(bank.getDisplayName());
|
||||||
|
}
|
||||||
|
|
||||||
|
ArrayAdapter<String> adapter = new ArrayAdapter<>(context,
|
||||||
|
android.R.layout.simple_spinner_item, bankNames);
|
||||||
|
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||||
|
actionConfirmBinding.spinnerBank.setAdapter(adapter);
|
||||||
|
actionConfirmBinding.spinnerBank.setEnabled(true);
|
||||||
|
|
||||||
|
actionConfirmBinding.spinnerBank.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||||
|
@Override
|
||||||
|
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||||
|
if (position > 0) {
|
||||||
|
selectedBank = bankList.get(position - 1);
|
||||||
|
} else {
|
||||||
|
selectedBank = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNothingSelected(AdapterView<?> parent) {
|
||||||
|
selectedBank = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 如果已有绑定的银行,选中它
|
||||||
|
if (!TextUtils.isEmpty(appInfo.getBankInfoId())) {
|
||||||
|
for (int i = 0; i < bankList.size(); i++) {
|
||||||
|
if (appInfo.getBankInfoId().equals(bankList.get(i).getId())) {
|
||||||
|
actionConfirmBinding.spinnerBank.setSelection(i + 1);
|
||||||
|
selectedBank = bankList.get(i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,14 @@
|
|||||||
package com.miraclegarden.smsmessage.Activity;
|
package com.miraclegarden.smsmessage.Activity;
|
||||||
|
|
||||||
import android.content.SharedPreferences;
|
import android.content.Intent;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
|
|
||||||
import com.miraclegarden.library.app.MiracleGardenActivity;
|
import com.miraclegarden.library.app.MiracleGardenActivity;
|
||||||
import com.miraclegarden.smsmessage.ActionConfirmDialog;
|
import com.miraclegarden.smsmessage.ActionConfirmDialog;
|
||||||
@@ -21,21 +19,38 @@ import com.miraclegarden.smsmessage.R;
|
|||||||
import com.miraclegarden.smsmessage.comm.CommonAdapter;
|
import com.miraclegarden.smsmessage.comm.CommonAdapter;
|
||||||
import com.miraclegarden.smsmessage.comm.ViewHolder;
|
import com.miraclegarden.smsmessage.comm.ViewHolder;
|
||||||
import com.miraclegarden.smsmessage.databinding.AppListSettingBinding;
|
import com.miraclegarden.smsmessage.databinding.AppListSettingBinding;
|
||||||
|
import com.miraclegarden.smsmessage.network.TokenManager;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
public class AppListActivity extends MiracleGardenActivity<AppListSettingBinding> {
|
public class AppListActivity extends MiracleGardenActivity<AppListSettingBinding> {
|
||||||
public static SharedPreferences sp;
|
private TokenManager tokenManager;
|
||||||
private ArrayList<AppInfo> appList = new ArrayList<>();
|
private ArrayList<AppInfo> appList = new ArrayList<>();
|
||||||
CommonAdapter userAdapter;
|
CommonAdapter userAdapter;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
sp = getSharedPreferences("server", MODE_PRIVATE);
|
|
||||||
|
|
||||||
initData();
|
tokenManager = TokenManager.getInstance(this);
|
||||||
|
if (!tokenManager.isLoggedIn()) {
|
||||||
|
startActivity(new Intent(this, LoginActivity.class));
|
||||||
|
finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
initView();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initView() {
|
||||||
|
String username = tokenManager.getUsername();
|
||||||
|
if (username != null) {
|
||||||
|
binding.tvUsername.setText(username);
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.backIv.setOnClickListener(view -> finish());
|
||||||
|
binding.recyclerview.setLayoutManager(new LinearLayoutManager(this));
|
||||||
|
initAdapter();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -71,9 +86,7 @@ public class AppListActivity extends MiracleGardenActivity<AppListSettingBinding
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
private void initData() {
|
private void initAdapter() {
|
||||||
binding.backIv.setOnClickListener(view -> finish());
|
|
||||||
binding.recyclerview.setLayoutManager(new LinearLayoutManager(this));
|
|
||||||
userAdapter = new CommonAdapter<>(AppListActivity.this, R.layout.item_user, appList) {
|
userAdapter = new CommonAdapter<>(AppListActivity.this, R.layout.item_user, appList) {
|
||||||
@Override
|
@Override
|
||||||
public void convert(ViewHolder holder, AppInfo info, int index) {
|
public void convert(ViewHolder holder, AppInfo info, int index) {
|
||||||
|
|||||||
@@ -0,0 +1,134 @@
|
|||||||
|
package com.miraclegarden.smsmessage.Activity;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.miraclegarden.library.app.MiracleGardenActivity;
|
||||||
|
import com.miraclegarden.smsmessage.databinding.ActivityBankEditBinding;
|
||||||
|
import com.miraclegarden.smsmessage.model.ApiError;
|
||||||
|
import com.miraclegarden.smsmessage.model.BankInfo;
|
||||||
|
import com.miraclegarden.smsmessage.network.ApiService;
|
||||||
|
import com.miraclegarden.smsmessage.network.TokenManager;
|
||||||
|
|
||||||
|
public class BankEditActivity extends MiracleGardenActivity<ActivityBankEditBinding> {
|
||||||
|
|
||||||
|
private ApiService apiService;
|
||||||
|
private TokenManager tokenManager;
|
||||||
|
private String bankId;
|
||||||
|
private boolean isEditMode = false;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
tokenManager = TokenManager.getInstance(this);
|
||||||
|
if (!tokenManager.isLoggedIn()) {
|
||||||
|
startActivity(new Intent(this, LoginActivity.class));
|
||||||
|
finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
apiService = new ApiService(this);
|
||||||
|
|
||||||
|
initData();
|
||||||
|
initView();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initData() {
|
||||||
|
bankId = getIntent().getStringExtra("bank_id");
|
||||||
|
isEditMode = bankId != null;
|
||||||
|
|
||||||
|
if (isEditMode) {
|
||||||
|
binding.tvTitle.setText("编辑银行");
|
||||||
|
binding.etBankName.setText(getIntent().getStringExtra("bank_name"));
|
||||||
|
binding.etAccount.setText(getIntent().getStringExtra("account"));
|
||||||
|
binding.etRemark.setText(getIntent().getStringExtra("remark"));
|
||||||
|
} else {
|
||||||
|
binding.tvTitle.setText("添加银行");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initView() {
|
||||||
|
String username = tokenManager.getUsername();
|
||||||
|
if (username != null) {
|
||||||
|
binding.tvUsername.setText(username);
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.backIv.setOnClickListener(v -> finish());
|
||||||
|
binding.btnSave.setOnClickListener(v -> attemptSave());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void attemptSave() {
|
||||||
|
String bankName = binding.etBankName.getText().toString().trim();
|
||||||
|
String account = binding.etAccount.getText().toString().trim();
|
||||||
|
String remark = binding.etRemark.getText().toString().trim();
|
||||||
|
|
||||||
|
if (TextUtils.isEmpty(bankName)) {
|
||||||
|
binding.etBankName.setError("请输入银行名称");
|
||||||
|
binding.etBankName.requestFocus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TextUtils.isEmpty(account)) {
|
||||||
|
binding.etAccount.setError("请输入账户");
|
||||||
|
binding.etAccount.requestFocus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
BankInfo bankInfo = new BankInfo(bankName, account, remark);
|
||||||
|
|
||||||
|
showLoading(true);
|
||||||
|
|
||||||
|
if (isEditMode) {
|
||||||
|
apiService.updateBank(bankId, bankInfo, new ApiService.ApiCallback<BankInfo>() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(BankInfo result) {
|
||||||
|
runOnUiThread(() -> {
|
||||||
|
showLoading(false);
|
||||||
|
Toast.makeText(BankEditActivity.this, "修改成功", Toast.LENGTH_SHORT).show();
|
||||||
|
finish();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(ApiError error) {
|
||||||
|
runOnUiThread(() -> {
|
||||||
|
showLoading(false);
|
||||||
|
Toast.makeText(BankEditActivity.this,
|
||||||
|
"修改失败: " + error.getMessage(), Toast.LENGTH_LONG).show();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
apiService.createBank(bankInfo, new ApiService.ApiCallback<BankInfo>() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(BankInfo result) {
|
||||||
|
runOnUiThread(() -> {
|
||||||
|
showLoading(false);
|
||||||
|
Toast.makeText(BankEditActivity.this, "添加成功", Toast.LENGTH_SHORT).show();
|
||||||
|
finish();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(ApiError error) {
|
||||||
|
runOnUiThread(() -> {
|
||||||
|
showLoading(false);
|
||||||
|
Toast.makeText(BankEditActivity.this,
|
||||||
|
"添加失败: " + error.getMessage(), Toast.LENGTH_LONG).show();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showLoading(boolean show) {
|
||||||
|
binding.progressBar.setVisibility(show ? View.VISIBLE : View.GONE);
|
||||||
|
binding.btnSave.setEnabled(!show);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,139 @@
|
|||||||
|
package com.miraclegarden.smsmessage.Activity;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
|
||||||
|
import com.miraclegarden.library.app.MiracleGardenActivity;
|
||||||
|
import com.miraclegarden.smsmessage.R;
|
||||||
|
import com.miraclegarden.smsmessage.comm.CommonAdapter;
|
||||||
|
import com.miraclegarden.smsmessage.comm.ViewHolder;
|
||||||
|
import com.miraclegarden.smsmessage.databinding.ActivityBankListBinding;
|
||||||
|
import com.miraclegarden.smsmessage.model.ApiError;
|
||||||
|
import com.miraclegarden.smsmessage.model.BankInfo;
|
||||||
|
import com.miraclegarden.smsmessage.network.ApiService;
|
||||||
|
import com.miraclegarden.smsmessage.network.TokenManager;
|
||||||
|
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.TimeZone;
|
||||||
|
|
||||||
|
public class BankListActivity extends MiracleGardenActivity<ActivityBankListBinding> {
|
||||||
|
|
||||||
|
private ApiService apiService;
|
||||||
|
private TokenManager tokenManager;
|
||||||
|
private List<BankInfo> bankList = new ArrayList<>();
|
||||||
|
private CommonAdapter<BankInfo> adapter;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
tokenManager = TokenManager.getInstance(this);
|
||||||
|
if (!tokenManager.isLoggedIn()) {
|
||||||
|
startActivity(new Intent(this, LoginActivity.class));
|
||||||
|
finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
apiService = new ApiService(this);
|
||||||
|
|
||||||
|
initView();
|
||||||
|
loadBankList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initView() {
|
||||||
|
String username = tokenManager.getUsername();
|
||||||
|
if (username != null) {
|
||||||
|
binding.tvUsername.setText(username);
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.backIv.setOnClickListener(v -> finish());
|
||||||
|
|
||||||
|
binding.recyclerview.setLayoutManager(new LinearLayoutManager(this));
|
||||||
|
adapter = new CommonAdapter<BankInfo>(this, R.layout.item_bank, bankList) {
|
||||||
|
@Override
|
||||||
|
public void convert(ViewHolder holder, BankInfo bankInfo, int index) {
|
||||||
|
holder.setText(R.id.tv_bank_name, bankInfo.getBankName());
|
||||||
|
holder.setText(R.id.tv_account, bankInfo.getAccount());
|
||||||
|
|
||||||
|
if (bankInfo.getRemark() != null && !bankInfo.getRemark().isEmpty()) {
|
||||||
|
holder.setText(R.id.tv_remark, bankInfo.getRemark());
|
||||||
|
holder.getView(R.id.tv_remark).setVisibility(View.VISIBLE);
|
||||||
|
} else {
|
||||||
|
holder.getView(R.id.tv_remark).setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
String createdAt = formatTime(bankInfo.getCreatedAt());
|
||||||
|
String updatedAt = formatTime(bankInfo.getUpdatedAt());
|
||||||
|
holder.setText(R.id.tv_created_at, "创建: " + createdAt);
|
||||||
|
holder.setText(R.id.tv_updated_at, "更新: " + updatedAt);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
binding.recyclerview.setAdapter(adapter);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String formatTime(String isoTime) {
|
||||||
|
if (isoTime == null || isoTime.isEmpty()) {
|
||||||
|
return "--";
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
SimpleDateFormat inputFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault());
|
||||||
|
inputFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||||
|
Date date = inputFormat.parse(isoTime);
|
||||||
|
|
||||||
|
SimpleDateFormat outputFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.getDefault());
|
||||||
|
outputFormat.setTimeZone(TimeZone.getDefault());
|
||||||
|
return outputFormat.format(date);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return isoTime.substring(0, Math.min(16, isoTime.length()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadBankList() {
|
||||||
|
binding.loadingLy.setVisibility(View.VISIBLE);
|
||||||
|
binding.emptyLy.setVisibility(View.GONE);
|
||||||
|
|
||||||
|
apiService.getBankList(new ApiService.ApiCallback<List<BankInfo>>() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(List<BankInfo> result) {
|
||||||
|
runOnUiThread(() -> {
|
||||||
|
binding.loadingLy.setVisibility(View.GONE);
|
||||||
|
bankList.clear();
|
||||||
|
if (result != null) {
|
||||||
|
bankList.addAll(result);
|
||||||
|
}
|
||||||
|
adapter.notifyDataSetChanged();
|
||||||
|
|
||||||
|
if (bankList.isEmpty()) {
|
||||||
|
binding.emptyLy.setVisibility(View.VISIBLE);
|
||||||
|
} else {
|
||||||
|
binding.emptyLy.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(ApiError error) {
|
||||||
|
runOnUiThread(() -> {
|
||||||
|
binding.loadingLy.setVisibility(View.GONE);
|
||||||
|
Toast.makeText(BankListActivity.this,
|
||||||
|
"加载失败: " + error.getMessage(), Toast.LENGTH_LONG).show();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
loadBankList();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
package com.miraclegarden.smsmessage.Activity;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.miraclegarden.library.app.MiracleGardenActivity;
|
||||||
|
import com.miraclegarden.smsmessage.databinding.ActivityChangePasswordBinding;
|
||||||
|
import com.miraclegarden.smsmessage.model.ApiError;
|
||||||
|
import com.miraclegarden.smsmessage.network.ApiService;
|
||||||
|
import com.miraclegarden.smsmessage.network.TokenManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改密码页面(首登强制修改)
|
||||||
|
*/
|
||||||
|
public class ChangePasswordActivity extends MiracleGardenActivity<ActivityChangePasswordBinding> {
|
||||||
|
|
||||||
|
private ApiService apiService;
|
||||||
|
private TokenManager tokenManager;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
apiService = new ApiService(this);
|
||||||
|
tokenManager = TokenManager.getInstance(this);
|
||||||
|
|
||||||
|
initView();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initView() {
|
||||||
|
binding.btnSubmit.setOnClickListener(v -> attemptChangePassword());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void attemptChangePassword() {
|
||||||
|
String oldPassword = binding.etOldPassword.getText().toString().trim();
|
||||||
|
String newPassword = binding.etNewPassword.getText().toString().trim();
|
||||||
|
String confirmPassword = binding.etConfirmPassword.getText().toString().trim();
|
||||||
|
|
||||||
|
if (TextUtils.isEmpty(oldPassword)) {
|
||||||
|
binding.etOldPassword.setError("请输入旧密码");
|
||||||
|
binding.etOldPassword.requestFocus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TextUtils.isEmpty(newPassword)) {
|
||||||
|
binding.etNewPassword.setError("请输入新密码");
|
||||||
|
binding.etNewPassword.requestFocus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newPassword.length() < 6) {
|
||||||
|
binding.etNewPassword.setError("新密码至少6位");
|
||||||
|
binding.etNewPassword.requestFocus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!newPassword.equals(confirmPassword)) {
|
||||||
|
binding.etConfirmPassword.setError("两次输入的密码不一致");
|
||||||
|
binding.etConfirmPassword.requestFocus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
showLoading(true);
|
||||||
|
|
||||||
|
apiService.changePassword(oldPassword, newPassword, new ApiService.ApiCallback<Void>() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(Void result) {
|
||||||
|
runOnUiThread(() -> {
|
||||||
|
showLoading(false);
|
||||||
|
tokenManager.setFirstLoginComplete();
|
||||||
|
Toast.makeText(ChangePasswordActivity.this, "密码修改成功", Toast.LENGTH_SHORT).show();
|
||||||
|
navigateToMain();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(ApiError error) {
|
||||||
|
runOnUiThread(() -> {
|
||||||
|
showLoading(false);
|
||||||
|
Toast.makeText(ChangePasswordActivity.this,
|
||||||
|
"修改失败: " + error.getMessage(), Toast.LENGTH_LONG).show();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showLoading(boolean show) {
|
||||||
|
binding.progressBar.setVisibility(show ? View.VISIBLE : View.GONE);
|
||||||
|
binding.btnSubmit.setEnabled(!show);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void navigateToMain() {
|
||||||
|
Intent intent = new Intent(this, MainActivity.class);
|
||||||
|
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
|
startActivity(intent);
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBackPressed() {
|
||||||
|
Toast.makeText(this, "请先修改密码", Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
package com.miraclegarden.smsmessage.Activity;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.miraclegarden.library.app.MiracleGardenActivity;
|
||||||
|
import com.miraclegarden.smsmessage.databinding.ActivityLoginBinding;
|
||||||
|
import com.miraclegarden.smsmessage.model.ApiError;
|
||||||
|
import com.miraclegarden.smsmessage.model.LoginResponse;
|
||||||
|
import com.miraclegarden.smsmessage.network.ApiService;
|
||||||
|
import com.miraclegarden.smsmessage.network.TokenManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 登录页面
|
||||||
|
*/
|
||||||
|
public class LoginActivity extends MiracleGardenActivity<ActivityLoginBinding> {
|
||||||
|
|
||||||
|
private ApiService apiService;
|
||||||
|
private TokenManager tokenManager;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
apiService = new ApiService(this);
|
||||||
|
tokenManager = TokenManager.getInstance(this);
|
||||||
|
|
||||||
|
if (tokenManager.isLoggedIn()) {
|
||||||
|
navigateToMain();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
initView();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initView() {
|
||||||
|
binding.btnLogin.setOnClickListener(v -> attemptLogin());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void attemptLogin() {
|
||||||
|
String username = binding.etUsername.getText().toString().trim();
|
||||||
|
String password = binding.etPassword.getText().toString().trim();
|
||||||
|
|
||||||
|
if (TextUtils.isEmpty(username)) {
|
||||||
|
binding.etUsername.setError("请输入用户名");
|
||||||
|
binding.etUsername.requestFocus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TextUtils.isEmpty(password)) {
|
||||||
|
binding.etPassword.setError("请输入密码");
|
||||||
|
binding.etPassword.requestFocus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
showLoading(true);
|
||||||
|
|
||||||
|
apiService.login(username, password, new ApiService.ApiCallback<LoginResponse>() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(LoginResponse result) {
|
||||||
|
runOnUiThread(() -> {
|
||||||
|
showLoading(false);
|
||||||
|
tokenManager.saveLoginData(result);
|
||||||
|
|
||||||
|
if (result.isFirstLogin()) {
|
||||||
|
navigateToChangePassword();
|
||||||
|
} else {
|
||||||
|
Toast.makeText(LoginActivity.this, "登录成功", Toast.LENGTH_SHORT).show();
|
||||||
|
navigateToMain();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(ApiError error) {
|
||||||
|
runOnUiThread(() -> {
|
||||||
|
showLoading(false);
|
||||||
|
String msg;
|
||||||
|
switch (error.getStatusCode()) {
|
||||||
|
case 400:
|
||||||
|
msg = "用户名和密码不能为空";
|
||||||
|
break;
|
||||||
|
case 401:
|
||||||
|
msg = "用户名或密码错误";
|
||||||
|
break;
|
||||||
|
case 403:
|
||||||
|
msg = "账户已被停用,请联系管理员";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
msg = "登录失败: " + error.getMessage();
|
||||||
|
}
|
||||||
|
Toast.makeText(LoginActivity.this, msg, Toast.LENGTH_LONG).show();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showLoading(boolean show) {
|
||||||
|
binding.progressBar.setVisibility(show ? View.VISIBLE : View.GONE);
|
||||||
|
binding.btnLogin.setEnabled(!show);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void navigateToMain() {
|
||||||
|
Intent intent = new Intent(this, MainActivity.class);
|
||||||
|
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
|
startActivity(intent);
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void navigateToChangePassword() {
|
||||||
|
Intent intent = new Intent(this, ChangePasswordActivity.class);
|
||||||
|
startActivity(intent);
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,70 +4,160 @@ import android.Manifest;
|
|||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.content.pm.PermissionInfo;
|
import android.net.Uri;
|
||||||
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.PowerManager;
|
||||||
import android.util.Log;
|
import android.provider.Settings;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.core.app.ActivityCompat;
|
|
||||||
import androidx.core.content.ContextCompat;
|
import androidx.core.content.ContextCompat;
|
||||||
|
|
||||||
import com.miraclegarden.library.app.MiracleGardenActivity;
|
import com.miraclegarden.library.app.MiracleGardenActivity;
|
||||||
import com.miraclegarden.smsmessage.databinding.ActivityMainBinding;
|
import com.miraclegarden.smsmessage.databinding.ActivityMainBinding;
|
||||||
|
import com.miraclegarden.smsmessage.network.TokenManager;
|
||||||
import java.io.IOException;
|
import com.miraclegarden.smsmessage.util.OEMBackgroundHelper;
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class MainActivity extends MiracleGardenActivity<ActivityMainBinding> {
|
public class MainActivity extends MiracleGardenActivity<ActivityMainBinding> {
|
||||||
public static SharedPreferences sp;
|
|
||||||
private static final String TAG = "MainActivity";
|
private TokenManager tokenManager;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
sp = getSharedPreferences("server", MODE_PRIVATE);
|
|
||||||
|
|
||||||
initData();
|
tokenManager = TokenManager.getInstance(this);
|
||||||
|
|
||||||
|
if (!tokenManager.isLoggedIn()) {
|
||||||
|
startActivity(new Intent(this, LoginActivity.class));
|
||||||
|
finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
initView();
|
initView();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initData() {
|
@Override
|
||||||
if (sp == null) {
|
protected void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
if (!tokenManager.isLoggedIn()) {
|
||||||
|
startActivity(new Intent(this, LoginActivity.class));
|
||||||
|
finish();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
binding.host.setText(sp.getString("host", ""));
|
updatePermissionStatus();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initView() {
|
private void initView() {
|
||||||
binding.yes.setOnClickListener(v -> {
|
String username = tokenManager.getUsername();
|
||||||
if (sp == null) {
|
if (username != null) {
|
||||||
return;
|
binding.tvUsername.setText(username);
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.host.setText(sp.getString("host", "https://www.judy88.xin/api/bills/app-upload"));
|
binding.tvDeviceBrand.setText("检测到设备: " + OEMBackgroundHelper.getManufacturer());
|
||||||
|
|
||||||
if (binding.host.getText().toString().length() == 0) {
|
binding.btnLogout.setOnClickListener(v -> logout());
|
||||||
Toast.makeText(this, "服务器和添加参数不能为空", Toast.LENGTH_SHORT).show();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!binding.host.getText().toString().startsWith("http")) {
|
binding.btnStartMonitoring.setOnClickListener(v -> {
|
||||||
Toast.makeText(this, "服务器地址错误", Toast.LENGTH_SHORT).show();
|
startActivity(new Intent(this, NotificationActivity.class));
|
||||||
return;
|
});
|
||||||
}
|
|
||||||
SharedPreferences.Editor edit = sp.edit();
|
binding.btnBankManage.setOnClickListener(v -> {
|
||||||
edit.putString("host", binding.host.getText().toString());
|
startActivity(new Intent(this, BankListActivity.class));
|
||||||
edit.apply();
|
});
|
||||||
startActivity(new Intent(MainActivity.this, NotificationActivity.class));
|
|
||||||
finish();
|
binding.permissionNotificationAccess.setOnClickListener(v -> {
|
||||||
|
startActivity(new Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS));
|
||||||
|
});
|
||||||
|
|
||||||
|
binding.permissionPostNotifications.setOnClickListener(v -> {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS)
|
||||||
|
!= PackageManager.PERMISSION_GRANTED) {
|
||||||
|
requestPermissions(new String[]{Manifest.permission.POST_NOTIFICATIONS}, 100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
binding.permissionBattery.setOnClickListener(v -> {
|
||||||
|
requestBatteryOptimization();
|
||||||
|
});
|
||||||
|
|
||||||
|
binding.btnAutostart.setOnClickListener(v -> {
|
||||||
|
OEMBackgroundHelper.openAutoStartSettings(this);
|
||||||
|
});
|
||||||
|
|
||||||
|
binding.btnBatteryOptimization.setOnClickListener(v -> {
|
||||||
|
OEMBackgroundHelper.requestBatteryOptimizationExemption(this);
|
||||||
});
|
});
|
||||||
new Handler().postDelayed(() -> binding.yes.performClick(),2000);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void logout() {
|
||||||
|
tokenManager.clear();
|
||||||
|
Toast.makeText(this, "已退出登录", Toast.LENGTH_SHORT).show();
|
||||||
|
Intent intent = new Intent(this, LoginActivity.class);
|
||||||
|
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
|
startActivity(intent);
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updatePermissionStatus() {
|
||||||
|
boolean notificationAccessEnabled = isNotificationListenerEnabled();
|
||||||
|
updatePermissionItem(binding.iconNotificationAccess, binding.statusNotificationAccess,
|
||||||
|
notificationAccessEnabled, notificationAccessEnabled ? "已授权" : "未授权");
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
boolean postNotificationsGranted = ContextCompat.checkSelfPermission(this,
|
||||||
|
Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED;
|
||||||
|
updatePermissionItem(binding.iconPostNotifications, binding.statusPostNotifications,
|
||||||
|
postNotificationsGranted, postNotificationsGranted ? "已授权" : "未授权");
|
||||||
|
binding.permissionPostNotifications.setVisibility(View.VISIBLE);
|
||||||
|
} else {
|
||||||
|
binding.permissionPostNotifications.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean batteryOptimized = isBatteryOptimizationEnabled();
|
||||||
|
updatePermissionItem(binding.iconBattery, binding.statusBattery,
|
||||||
|
!batteryOptimized, batteryOptimized ? "未授权" : "已授权");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updatePermissionItem(ImageView icon, TextView status, boolean granted, String statusText) {
|
||||||
|
if (granted) {
|
||||||
|
icon.setImageResource(android.R.drawable.checkbox_on_background);
|
||||||
|
icon.setColorFilter(ContextCompat.getColor(this, android.R.color.holo_green_dark));
|
||||||
|
} else {
|
||||||
|
icon.setImageResource(android.R.drawable.ic_dialog_info);
|
||||||
|
icon.setColorFilter(ContextCompat.getColor(this, android.R.color.holo_red_dark));
|
||||||
|
}
|
||||||
|
status.setText(statusText);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isNotificationListenerEnabled() {
|
||||||
|
String flat = Settings.Secure.getString(getContentResolver(), "enabled_notification_listeners");
|
||||||
|
return flat != null && flat.contains(getPackageName());
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isBatteryOptimizationEnabled() {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
|
PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE);
|
||||||
|
return pm != null && !pm.isIgnoringBatteryOptimizations(getPackageName());
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void requestBatteryOptimization() {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
|
try {
|
||||||
|
Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
|
||||||
|
intent.setData(Uri.parse("package:" + getPackageName()));
|
||||||
|
startActivity(intent);
|
||||||
|
} catch (Exception e) {
|
||||||
|
startActivity(new Intent(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,166 +1,214 @@
|
|||||||
package com.miraclegarden.smsmessage.Activity;
|
package com.miraclegarden.smsmessage.Activity;
|
||||||
|
|
||||||
import android.Manifest;
|
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
import android.content.ComponentName;
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import android.os.Message;
|
import android.os.Message;
|
||||||
|
import android.os.PowerManager;
|
||||||
import android.provider.Settings;
|
import android.provider.Settings;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.view.View;
|
|
||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
import android.widget.ScrollView;
|
import android.widget.ScrollView;
|
||||||
import android.widget.TextView;
|
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.RequiresApi;
|
|
||||||
import androidx.core.content.ContextCompat;
|
|
||||||
import androidx.core.widget.NestedScrollView;
|
|
||||||
|
|
||||||
import com.miraclegarden.library.app.MiracleGardenActivity;
|
import com.miraclegarden.library.app.MiracleGardenActivity;
|
||||||
import com.miraclegarden.smsmessage.AppInfo;
|
import com.miraclegarden.smsmessage.App;
|
||||||
import com.miraclegarden.smsmessage.AppListUtil;
|
|
||||||
import com.miraclegarden.smsmessage.MessageInfo;
|
|
||||||
import com.miraclegarden.smsmessage.databinding.ActivityNotificationBinding;
|
import com.miraclegarden.smsmessage.databinding.ActivityNotificationBinding;
|
||||||
|
import com.miraclegarden.smsmessage.network.TokenManager;
|
||||||
import com.miraclegarden.smsmessage.service.NotificationService;
|
import com.miraclegarden.smsmessage.service.NotificationService;
|
||||||
|
import com.miraclegarden.smsmessage.service.RetryManager;
|
||||||
|
|
||||||
import org.json.JSONException;
|
import java.lang.ref.WeakReference;
|
||||||
import org.json.JSONObject;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
import okhttp3.Call;
|
|
||||||
import okhttp3.Callback;
|
|
||||||
import okhttp3.FormBody;
|
|
||||||
import okhttp3.MediaType;
|
|
||||||
import okhttp3.OkHttpClient;
|
|
||||||
import okhttp3.Request;
|
|
||||||
import okhttp3.RequestBody;
|
|
||||||
import okhttp3.Response;
|
|
||||||
|
|
||||||
public class NotificationActivity extends MiracleGardenActivity<ActivityNotificationBinding> {
|
public class NotificationActivity extends MiracleGardenActivity<ActivityNotificationBinding> {
|
||||||
|
|
||||||
private static final Handler handler = new Handler(Looper.myLooper()) {
|
private static final int MAX_LOG_LINES = 200;
|
||||||
|
private static WeakReference<NotificationActivity> instanceRef;
|
||||||
|
|
||||||
|
private TokenManager tokenManager;
|
||||||
|
private final Handler handler = new Handler(Looper.getMainLooper()) {
|
||||||
@Override
|
@Override
|
||||||
public void handleMessage(@NonNull Message msg) {
|
public void handleMessage(@NonNull Message msg) {
|
||||||
super.handleMessage(msg);
|
super.handleMessage(msg);
|
||||||
String str = (String) msg.obj;
|
String str = (String) msg.obj;
|
||||||
if (str != null) {
|
if (str != null && binding != null && binding.tvLog != null) {
|
||||||
@SuppressLint("SimpleDateFormat") SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd-HH:mm:ss");
|
SimpleDateFormat format = new SimpleDateFormat("HH:mm:ss");
|
||||||
String t = format.format(new Date());
|
String t = format.format(new Date());
|
||||||
if (textView != null) {
|
binding.tvLog.append(t + " " + str + "\n");
|
||||||
textView.append(t + ":" + str + "\n\r");
|
trimLogIfNeeded();
|
||||||
scrollView.post(() -> scrollView.fullScroll(ScrollView.FOCUS_DOWN));
|
binding.scrollView.post(() ->
|
||||||
}
|
binding.scrollView.fullScroll(ScrollView.FOCUS_DOWN));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
public static SharedPreferences sharedPreferences;
|
|
||||||
|
|
||||||
public static void sendMessage(String str) {
|
public static void sendMessage(String str) {
|
||||||
Message message = new Message();
|
NotificationActivity activity = instanceRef != null ? instanceRef.get() : null;
|
||||||
message.obj = str;
|
if (activity != null && activity.handler != null) {
|
||||||
NotificationActivity.handler.sendMessage(message);
|
Message message = Message.obtain();
|
||||||
|
message.obj = str;
|
||||||
|
activity.handler.sendMessage(message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("StaticFieldLeak")
|
|
||||||
private static TextView textView;
|
|
||||||
@SuppressLint("StaticFieldLeak")
|
|
||||||
private static NestedScrollView scrollView;
|
|
||||||
private final String[] permissions = new String[]{
|
|
||||||
Manifest.permission.RECEIVE_BOOT_COMPLETED
|
|
||||||
};
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
sharedPreferences = getSharedPreferences("server", MODE_PRIVATE);
|
|
||||||
|
tokenManager = TokenManager.getInstance(this);
|
||||||
|
if (!tokenManager.isLoggedIn()) {
|
||||||
|
Toast.makeText(this, "请先登录", Toast.LENGTH_SHORT).show();
|
||||||
|
startActivity(new Intent(this, LoginActivity.class));
|
||||||
|
finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
instanceRef = new WeakReference<>(this);
|
||||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||||
NotificationActivity.textView = binding.text;
|
|
||||||
NotificationActivity.scrollView = binding.scrollable;
|
|
||||||
initPermission();
|
|
||||||
initView();
|
initView();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
updateUI();
|
||||||
|
handler.post(statsUpdateRunnable);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPause() {
|
||||||
|
super.onPause();
|
||||||
|
handler.removeCallbacks(statsUpdateRunnable);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDestroy() {
|
||||||
|
super.onDestroy();
|
||||||
|
handler.removeCallbacksAndMessages(null);
|
||||||
|
if (instanceRef != null && instanceRef.get() == this) {
|
||||||
|
instanceRef = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void initView() {
|
private void initView() {
|
||||||
binding.button2.setOnClickListener(v -> {
|
String username = tokenManager.getUsername();
|
||||||
//打开监听引用消息Notification access
|
if (username != null) {
|
||||||
Intent intent_s = new Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS);
|
binding.tvUsername.setText(username);
|
||||||
startActivity(intent_s);
|
}
|
||||||
});
|
|
||||||
binding.button.setOnClickListener(v -> {
|
|
||||||
|
|
||||||
|
binding.ivBack.setOnClickListener(v -> finish());
|
||||||
|
|
||||||
|
binding.btnSettings.setOnClickListener(v -> {
|
||||||
|
startActivity(new Intent(this, SettingActivity.class));
|
||||||
});
|
});
|
||||||
|
|
||||||
binding.button1.setOnClickListener(view -> {
|
binding.btnClearLog.setOnClickListener(v -> {
|
||||||
if (textView != null) {
|
binding.tvLog.setText("");
|
||||||
textView.setText("");
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
binding.toolbar.setOnLongClickListener(view -> {
|
binding.btnToggleMonitor.setOnClickListener(v -> toggleMonitoring());
|
||||||
// Toast.makeText(NotificationActivity.this,"点我干嘛",Toast.LENGTH_SHORT).show();
|
|
||||||
startActivity(new Intent(NotificationActivity.this,SettingActivity.class));
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
binding.jiantingList.setOnClickListener(new View.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(View view) {
|
|
||||||
Intent intent = new Intent(NotificationActivity.this,SettingActivity.class);
|
|
||||||
intent.putExtra("is_list",true);
|
|
||||||
startActivity(intent);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initPermission() {
|
private void toggleMonitoring() {
|
||||||
checkPermission();
|
if (!isNotificationListenerEnabled()) {
|
||||||
for (String permission : permissions) {
|
Toast.makeText(this, "请先开启通知访问权限", Toast.LENGTH_LONG).show();
|
||||||
if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
|
startActivity(new Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS));
|
||||||
sendMessage("没有" + permission + "权限");
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (NotificationService.isMonitoring()) {
|
||||||
|
NotificationService.stopMonitoring(this);
|
||||||
|
sendMessage("正在停止监听...");
|
||||||
|
} else {
|
||||||
|
if (App.getNotiList(this).isEmpty()) {
|
||||||
|
Toast.makeText(this, "请先在\"监听设置\"中添加要监听的APP", Toast.LENGTH_LONG).show();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
NotificationService.requestStartMonitoring(this);
|
||||||
|
sendMessage("正在开启监听服务...");
|
||||||
|
updateUI();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void updateUI() {
|
||||||
|
NotificationActivity activity = instanceRef != null ? instanceRef.get() : null;
|
||||||
|
if (activity == null || activity.binding == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
//打开设置界面
|
if (NotificationService.isMonitoring()) {
|
||||||
private void checkPermission() {
|
activity.binding.btnToggleMonitor.setText("停止监听");
|
||||||
if (!isEnabled()) {
|
activity.binding.btnToggleMonitor.setEnabled(true);
|
||||||
startActivity(new Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS"));
|
activity.binding.btnToggleMonitor.setBackgroundTintList(
|
||||||
|
android.content.res.ColorStateList.valueOf(0xFFFF5252));
|
||||||
|
activity.binding.tvStatus.setText("状态: 监听中");
|
||||||
|
activity.binding.tvStatus.setTextColor(0xFF4CAF50);
|
||||||
|
} else {
|
||||||
|
activity.binding.btnToggleMonitor.setText("开始监听");
|
||||||
|
activity.binding.btnToggleMonitor.setEnabled(true);
|
||||||
|
activity.binding.btnToggleMonitor.setBackgroundTintList(
|
||||||
|
android.content.res.ColorStateList.valueOf(activity.getResources().getColor(com.miraclegarden.smsmessage.R.color.purple_500)));
|
||||||
|
activity.binding.tvStatus.setText("状态: 未启动");
|
||||||
|
activity.binding.tvStatus.setTextColor(0xFF888888);
|
||||||
|
}
|
||||||
|
activity.updateStatistics();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateStatistics() {
|
||||||
|
if (binding == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
android.content.SharedPreferences sp = getSharedPreferences("server", MODE_PRIVATE);
|
||||||
|
int totalCount = sp.getInt("total_count", 0);
|
||||||
|
int uploadedCount = sp.getInt("uploaded_count", 0);
|
||||||
|
int configuredCount = App.getNotiList(this).size();
|
||||||
|
|
||||||
|
binding.tvConfiguredApps.setText("已配置: " + configuredCount + "个");
|
||||||
|
binding.tvMonitoredCount.setText("监听: " + totalCount);
|
||||||
|
binding.tvUploadedCount.setText("上传: " + uploadedCount);
|
||||||
|
|
||||||
|
if (totalCount > 0) {
|
||||||
|
int successRate = (uploadedCount * 100) / totalCount;
|
||||||
|
binding.tvSuccessRate.setText("成功率: " + successRate + "%");
|
||||||
|
} else {
|
||||||
|
binding.tvSuccessRate.setText("成功率: --");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private final Runnable statsUpdateRunnable = new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
updateStatistics();
|
||||||
|
handler.postDelayed(this, 2000);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// 判断是否打开了通知监听权限
|
private void trimLogIfNeeded() {
|
||||||
private boolean isEnabled() {
|
if (binding == null || binding.tvLog == null) return;
|
||||||
String pkgName = getPackageName();
|
String fullText = binding.tvLog.getText().toString();
|
||||||
final String flat = Settings.Secure.getString(getContentResolver(), "enabled_notification_listeners");
|
String[] lines = fullText.split("\n");
|
||||||
if (!TextUtils.isEmpty(flat)) {
|
if (lines.length > MAX_LOG_LINES) {
|
||||||
final String[] names = flat.split(":");
|
StringBuilder sb = new StringBuilder();
|
||||||
for (String name : names) {
|
for (int i = lines.length - MAX_LOG_LINES; i < lines.length; i++) {
|
||||||
final ComponentName cn = ComponentName.unflattenFromString(name);
|
sb.append(lines[i]);
|
||||||
if (cn != null) {
|
if (i < lines.length - 1) {
|
||||||
if (TextUtils.equals(pkgName, cn.getPackageName())) {
|
sb.append("\n");
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
binding.tvLog.setText(sb.toString());
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isNotificationListenerEnabled() {
|
||||||
|
String flat = Settings.Secure.getString(getContentResolver(), "enabled_notification_listeners");
|
||||||
|
return flat != null && flat.contains(getPackageName());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,138 @@
|
|||||||
|
package com.miraclegarden.smsmessage.Activity;
|
||||||
|
|
||||||
|
import android.Manifest;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.PowerManager;
|
||||||
|
import android.provider.Settings;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
|
|
||||||
|
import com.miraclegarden.library.app.MiracleGardenActivity;
|
||||||
|
import com.miraclegarden.smsmessage.databinding.ActivityPermissionBinding;
|
||||||
|
import com.miraclegarden.smsmessage.network.TokenManager;
|
||||||
|
import com.miraclegarden.smsmessage.util.OEMBackgroundHelper;
|
||||||
|
|
||||||
|
public class PermissionActivity extends MiracleGardenActivity<ActivityPermissionBinding> {
|
||||||
|
|
||||||
|
private TokenManager tokenManager;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
tokenManager = TokenManager.getInstance(this);
|
||||||
|
if (!tokenManager.isLoggedIn()) {
|
||||||
|
startActivity(new Intent(this, LoginActivity.class));
|
||||||
|
finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
initView();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
updatePermissionStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initView() {
|
||||||
|
String username = tokenManager.getUsername();
|
||||||
|
if (username != null) {
|
||||||
|
binding.tvUsername.setText(username);
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.ivBack.setOnClickListener(v -> finish());
|
||||||
|
|
||||||
|
binding.layoutNotificationAccess.setOnClickListener(v -> {
|
||||||
|
startActivity(new Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS));
|
||||||
|
});
|
||||||
|
|
||||||
|
binding.layoutPostNotifications.setOnClickListener(v -> {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS)
|
||||||
|
!= android.content.pm.PackageManager.PERMISSION_GRANTED) {
|
||||||
|
requestPermissions(new String[]{Manifest.permission.POST_NOTIFICATIONS}, 100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
binding.layoutBattery.setOnClickListener(v -> {
|
||||||
|
requestBatteryOptimization();
|
||||||
|
});
|
||||||
|
|
||||||
|
binding.btnAutostart.setOnClickListener(v -> {
|
||||||
|
OEMBackgroundHelper.openAutoStartSettings(this);
|
||||||
|
});
|
||||||
|
|
||||||
|
binding.btnBatteryOptimization.setOnClickListener(v -> {
|
||||||
|
OEMBackgroundHelper.requestBatteryOptimizationExemption(this);
|
||||||
|
});
|
||||||
|
|
||||||
|
binding.tvDeviceBrand.setText("设备: " + OEMBackgroundHelper.getManufacturer());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updatePermissionStatus() {
|
||||||
|
boolean notificationAccessEnabled = isNotificationListenerEnabled();
|
||||||
|
updatePermissionItem(binding.iconNotificationAccess, binding.statusNotificationAccess,
|
||||||
|
notificationAccessEnabled, notificationAccessEnabled ? "已授权" : "未授权");
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
boolean postNotificationsGranted = ContextCompat.checkSelfPermission(this,
|
||||||
|
Manifest.permission.POST_NOTIFICATIONS) == android.content.pm.PackageManager.PERMISSION_GRANTED;
|
||||||
|
updatePermissionItem(binding.iconPostNotifications, binding.statusPostNotifications,
|
||||||
|
postNotificationsGranted, postNotificationsGranted ? "已授权" : "未授权");
|
||||||
|
binding.layoutPostNotifications.setVisibility(View.VISIBLE);
|
||||||
|
} else {
|
||||||
|
binding.layoutPostNotifications.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean batteryOptimized = isBatteryOptimizationEnabled();
|
||||||
|
updatePermissionItem(binding.iconBattery, binding.statusBattery,
|
||||||
|
!batteryOptimized, batteryOptimized ? "未授权" : "已授权");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updatePermissionItem(ImageView icon, TextView status, boolean granted, String statusText) {
|
||||||
|
if (granted) {
|
||||||
|
icon.setImageResource(android.R.drawable.checkbox_on_background);
|
||||||
|
icon.setColorFilter(ContextCompat.getColor(this, android.R.color.holo_green_dark));
|
||||||
|
} else {
|
||||||
|
icon.setImageResource(android.R.drawable.ic_dialog_info);
|
||||||
|
icon.setColorFilter(ContextCompat.getColor(this, android.R.color.holo_red_dark));
|
||||||
|
}
|
||||||
|
status.setText(statusText);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isNotificationListenerEnabled() {
|
||||||
|
String flat = Settings.Secure.getString(getContentResolver(), "enabled_notification_listeners");
|
||||||
|
return flat != null && flat.contains(getPackageName());
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isBatteryOptimizationEnabled() {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
|
PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE);
|
||||||
|
return pm != null && !pm.isIgnoringBatteryOptimizations(getPackageName());
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void requestBatteryOptimization() {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
|
try {
|
||||||
|
Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
|
||||||
|
intent.setData(Uri.parse("package:" + getPackageName()));
|
||||||
|
startActivity(intent);
|
||||||
|
} catch (Exception e) {
|
||||||
|
startActivity(new Intent(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,31 +1,15 @@
|
|||||||
package com.miraclegarden.smsmessage.Activity;
|
package com.miraclegarden.smsmessage.Activity;
|
||||||
|
|
||||||
import android.Manifest;
|
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.content.pm.PermissionInfo;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
|
||||||
import android.os.Looper;
|
|
||||||
import android.os.Message;
|
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.inputmethod.InputMethodManager;
|
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.ScrollView;
|
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.core.app.ActivityCompat;
|
|
||||||
import androidx.core.content.ContextCompat;
|
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
|
||||||
import com.miraclegarden.library.app.MiracleGardenActivity;
|
import com.miraclegarden.library.app.MiracleGardenActivity;
|
||||||
import com.miraclegarden.smsmessage.ActionConfirmDialog;
|
|
||||||
import com.miraclegarden.smsmessage.App;
|
import com.miraclegarden.smsmessage.App;
|
||||||
import com.miraclegarden.smsmessage.AppInfo;
|
import com.miraclegarden.smsmessage.AppInfo;
|
||||||
import com.miraclegarden.smsmessage.AppListUtil;
|
import com.miraclegarden.smsmessage.AppListUtil;
|
||||||
@@ -34,236 +18,111 @@ import com.miraclegarden.smsmessage.MessageInfo;
|
|||||||
import com.miraclegarden.smsmessage.R;
|
import com.miraclegarden.smsmessage.R;
|
||||||
import com.miraclegarden.smsmessage.comm.CommonAdapter;
|
import com.miraclegarden.smsmessage.comm.CommonAdapter;
|
||||||
import com.miraclegarden.smsmessage.comm.ViewHolder;
|
import com.miraclegarden.smsmessage.comm.ViewHolder;
|
||||||
import com.miraclegarden.smsmessage.databinding.ActivityMainBinding;
|
|
||||||
import com.miraclegarden.smsmessage.databinding.ActivitySettingBinding;
|
import com.miraclegarden.smsmessage.databinding.ActivitySettingBinding;
|
||||||
|
import com.miraclegarden.smsmessage.network.TokenManager;
|
||||||
|
|
||||||
import org.json.JSONException;
|
|
||||||
import org.json.JSONObject;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.text.SimpleDateFormat;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Date;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import okhttp3.Call;
|
|
||||||
import okhttp3.Callback;
|
|
||||||
import okhttp3.MediaType;
|
|
||||||
import okhttp3.OkHttpClient;
|
|
||||||
import okhttp3.Request;
|
|
||||||
import okhttp3.RequestBody;
|
|
||||||
import okhttp3.Response;
|
|
||||||
|
|
||||||
public class SettingActivity extends MiracleGardenActivity<ActivitySettingBinding> {
|
public class SettingActivity extends MiracleGardenActivity<ActivitySettingBinding> {
|
||||||
public static SharedPreferences sp;
|
|
||||||
private static final String TAG = "SettingActivity";
|
|
||||||
private ArrayList<MessageInfo> appList = new ArrayList<>();
|
private ArrayList<MessageInfo> appList = new ArrayList<>();
|
||||||
CommonAdapter userAdapter;
|
private CommonAdapter userAdapter;
|
||||||
boolean is_list = false;
|
private TokenManager tokenManager;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
sp = getSharedPreferences("server", MODE_PRIVATE);
|
|
||||||
initData();
|
|
||||||
initList();
|
|
||||||
|
|
||||||
is_list = getIntent().getBooleanExtra("is_list",false);
|
tokenManager = TokenManager.getInstance(this);
|
||||||
if(!is_list){
|
if (!tokenManager.isLoggedIn()) {
|
||||||
binding.setPostLy.setVisibility(View.VISIBLE);
|
startActivity(new Intent(this, LoginActivity.class));
|
||||||
binding.titleTv.setText("设置");
|
finish();
|
||||||
}else{
|
return;
|
||||||
binding.titleTv.setText("监听App列表");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
initView();
|
||||||
|
initList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initView() {
|
||||||
|
String username = tokenManager.getUsername();
|
||||||
|
if (username != null) {
|
||||||
|
binding.tvUsername.setText(username);
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.ivBack.setOnClickListener(v -> finish());
|
||||||
|
|
||||||
|
binding.btnAddApp.setOnClickListener(v -> {
|
||||||
|
startActivity(new Intent(this, AppListActivity.class));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onResume() {
|
protected void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
initChangeList();
|
refreshList();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initChangeList() {
|
private void refreshList() {
|
||||||
List<MessageInfo> appList1 = App.getNotiList(SettingActivity.this);
|
List<MessageInfo> list = App.getNotiList(this);
|
||||||
if(appList1 == null){
|
appList = new ArrayList<>(list);
|
||||||
appList = new ArrayList<>();
|
|
||||||
}else{
|
|
||||||
appList = (ArrayList<MessageInfo>) appList1;
|
|
||||||
}
|
|
||||||
userAdapter.setDates(appList);
|
userAdapter.setDates(appList);
|
||||||
if(appList.size()>0){
|
|
||||||
|
if (appList.size() > 0) {
|
||||||
binding.recyclerview.setVisibility(View.VISIBLE);
|
binding.recyclerview.setVisibility(View.VISIBLE);
|
||||||
binding.nodataIv.setVisibility(View.GONE);
|
binding.tvEmpty.setVisibility(View.GONE);
|
||||||
}else{
|
} else {
|
||||||
binding.recyclerview.setVisibility(View.GONE);
|
binding.recyclerview.setVisibility(View.GONE);
|
||||||
binding.nodataIv.setVisibility(View.VISIBLE);
|
binding.tvEmpty.setVisibility(View.VISIBLE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initData() {
|
|
||||||
binding.host.setText(sp.getString("host", "https://www.judy88.xin/api/bills/app-upload"));
|
|
||||||
binding.addBt.setOnClickListener(view -> startActivity(new Intent(SettingActivity.this,AppListActivity.class)));
|
|
||||||
binding.backIv.setOnClickListener(view -> finish());
|
|
||||||
|
|
||||||
binding.yes.setOnClickListener(v -> {
|
|
||||||
if (sp == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (binding.host.getText().toString().length() == 0) {
|
|
||||||
Toast.makeText(this, "服务器和添加参数不能为空", Toast.LENGTH_SHORT).show();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!binding.host.getText().toString().startsWith("http")) {
|
|
||||||
Toast.makeText(this, "服务器地址错误", Toast.LENGTH_SHORT).show();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
hideKeyboard();
|
|
||||||
AppInfo appInfo = AppListUtil.getAppByPackageName(SettingActivity.this,"com.miraclegarden.smsmessage");
|
|
||||||
appInfo.setCode("111");
|
|
||||||
appInfo.setName("测试");
|
|
||||||
appInfo.setRemark("测试更换接口");
|
|
||||||
MessageInfo messageInfo = MessageInfo.AppInfoToMessageInfo(appInfo);
|
|
||||||
Submit(messageInfo,"测试更换接口","测试更换接口");
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public MediaType JSON = MediaType.parse("application/json; charset=utf-8");
|
|
||||||
|
|
||||||
public void Submit(MessageInfo messageInfo, String title, String context) {
|
|
||||||
OkHttpClient client = new OkHttpClient();
|
|
||||||
try {
|
|
||||||
JSONObject jsonObject = new JSONObject();
|
|
||||||
jsonObject.put("name", messageInfo.getName());
|
|
||||||
jsonObject.put("code", messageInfo.getCode());
|
|
||||||
jsonObject.put("remark", messageInfo.getRemark());
|
|
||||||
jsonObject.put("appName", messageInfo.getAppName());
|
|
||||||
jsonObject.put("packageName", messageInfo.getPackageName());
|
|
||||||
JSONObject jsonObject1 = new JSONObject();
|
|
||||||
jsonObject1.put("title", title);
|
|
||||||
jsonObject1.put("context", context);
|
|
||||||
jsonObject.put("data", jsonObject1);
|
|
||||||
String json = jsonObject.toString();
|
|
||||||
RequestBody body = RequestBody.create(json, JSON);
|
|
||||||
// 构建请求
|
|
||||||
Request request = new Request.Builder()
|
|
||||||
.url(binding.host.getText().toString())
|
|
||||||
.post(body)
|
|
||||||
.build();
|
|
||||||
Log.i("地址是啥","地址是啥1111:"+binding.host.getText().toString());
|
|
||||||
Log.i("地址是啥","地址是啥2222:"+json);
|
|
||||||
|
|
||||||
client.newCall(request).enqueue(new Callback() {
|
|
||||||
@Override
|
|
||||||
public void onFailure(@NonNull Call call, @NonNull IOException e) {
|
|
||||||
// NotificationActivity.sendMessage("提交失败:" + e);
|
|
||||||
// Toast.makeText(SettingActivity.this,"接口地址访问异常",Toast.LENGTH_SHORT).show();
|
|
||||||
sendMessage("接口地址访问异常");
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
|
|
||||||
String str = response.body().string();
|
|
||||||
if (response.isSuccessful()) {
|
|
||||||
Log.i("地址是啥","地址是啥:"+str);
|
|
||||||
// {"ok":true,"received":true}
|
|
||||||
toSaveAddress();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Log.i("地址是啥","地址是啥11:"+str);
|
|
||||||
sendMessage("接口地址访问异常");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (JSONException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void hideKeyboard() {
|
|
||||||
InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
|
|
||||||
if (imm != null && binding.host.getWindowToken() != null) {
|
|
||||||
imm.hideSoftInputFromWindow(binding.host.getWindowToken(), 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Handler handler = new Handler(Looper.myLooper()) {
|
|
||||||
@Override
|
|
||||||
public void handleMessage(@NonNull Message msg) {
|
|
||||||
super.handleMessage(msg);
|
|
||||||
String str = (String) msg.obj;
|
|
||||||
if (str != null) {
|
|
||||||
Toast.makeText(SettingActivity.this,str,Toast.LENGTH_SHORT).show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
public void sendMessage(String str) {
|
|
||||||
Message message = new Message();
|
|
||||||
message.obj = str;
|
|
||||||
handler.sendMessage(message);
|
|
||||||
}
|
|
||||||
private void toSaveAddress() {
|
|
||||||
|
|
||||||
SharedPreferences.Editor edit = sp.edit();
|
|
||||||
edit.putString("host", binding.host.getText().toString());
|
|
||||||
edit.apply();
|
|
||||||
sendMessage( "服务器地址修改成功:" );
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void initList() {
|
private void initList() {
|
||||||
binding.recyclerview.setLayoutManager(new LinearLayoutManager(this));
|
binding.recyclerview.setLayoutManager(new LinearLayoutManager(this));
|
||||||
userAdapter = new CommonAdapter<>(SettingActivity.this, R.layout.item_user, appList) {
|
userAdapter = new CommonAdapter<>(this, R.layout.item_user, appList) {
|
||||||
@Override
|
@Override
|
||||||
public void convert(ViewHolder holder, MessageInfo info, int index) {
|
public void convert(ViewHolder holder, MessageInfo info, int index) {
|
||||||
AppInfo appInfo = AppListUtil.getAppByPackageName(SettingActivity.this,info.getPackageName());
|
AppInfo appInfo = AppListUtil.getAppByPackageName(SettingActivity.this, info.getPackageName());
|
||||||
ImageView delete_img = holder.getView(R.id.delete_img);
|
|
||||||
delete_img.setVisibility(View.VISIBLE);
|
|
||||||
delete_img.setOnClickListener(view -> {
|
|
||||||
DeleteConfirmDialog deleteConfirmDialog = new DeleteConfirmDialog(SettingActivity.this,info);
|
|
||||||
deleteConfirmDialog.setOnToActionListener(() -> {
|
|
||||||
App.deleteNotiBean(SettingActivity.this,info);
|
|
||||||
initChangeList();
|
|
||||||
});
|
|
||||||
deleteConfirmDialog.show();
|
|
||||||
|
|
||||||
|
ImageView deleteBtn = holder.getView(R.id.delete_img);
|
||||||
|
deleteBtn.setVisibility(View.VISIBLE);
|
||||||
|
deleteBtn.setOnClickListener(v -> {
|
||||||
|
DeleteConfirmDialog dialog = new DeleteConfirmDialog(SettingActivity.this, info);
|
||||||
|
dialog.setOnConfirmListener(() -> {
|
||||||
|
App.deleteNotiBean(SettingActivity.this, info);
|
||||||
|
refreshList();
|
||||||
|
});
|
||||||
|
dialog.show();
|
||||||
});
|
});
|
||||||
if(appInfo!=null){
|
|
||||||
|
if (appInfo != null) {
|
||||||
((ImageView) holder.getView(R.id.iv_icon)).setImageDrawable(appInfo.getIcon());
|
((ImageView) holder.getView(R.id.iv_icon)).setImageDrawable(appInfo.getIcon());
|
||||||
}else{
|
} else {
|
||||||
((ImageView) holder.getView(R.id.iv_icon)).setImageResource(R.mipmap.app_logo);
|
((ImageView) holder.getView(R.id.iv_icon)).setImageResource(R.mipmap.app_logo);
|
||||||
}
|
}
|
||||||
|
|
||||||
holder.setText(R.id.tv_appname, info.getAppName());
|
holder.setText(R.id.tv_appname, info.getAppName());
|
||||||
holder.setText(R.id.tv_package, info.getPackageName());
|
holder.setText(R.id.tv_package, info.getPackageName());
|
||||||
|
|
||||||
if (!TextUtils.isEmpty(info.getName())) {
|
if (!TextUtils.isEmpty(info.getName())) {
|
||||||
holder.setText(R.id.tv_name, info.getName()+"/");
|
holder.setText(R.id.tv_name, info.getName());
|
||||||
}else{
|
} else {
|
||||||
holder.setText(R.id.tv_name, "--/");
|
holder.setText(R.id.tv_name, "未设置");
|
||||||
}
|
}
|
||||||
if (!TextUtils.isEmpty(info.getCode())) {
|
if (!TextUtils.isEmpty(info.getCode())) {
|
||||||
holder.setText(R.id.tv_code, info.getCode()+"/");
|
holder.setText(R.id.tv_code, info.getCode());
|
||||||
}else{
|
} else {
|
||||||
holder.setText(R.id.tv_code, "--/");
|
holder.setText(R.id.tv_code, "--");
|
||||||
}
|
}
|
||||||
if (!TextUtils.isEmpty(info.getRemark())) {
|
if (!TextUtils.isEmpty(info.getRemark())) {
|
||||||
holder.setText(R.id.tv_remark, info.getRemark());
|
holder.setText(R.id.tv_remark, "(" + info.getRemark() + ")");
|
||||||
}else{
|
holder.getView(R.id.tv_remark).setVisibility(View.VISIBLE);
|
||||||
holder.setText(R.id.tv_remark, "--");
|
} else {
|
||||||
|
holder.getView(R.id.tv_remark).setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
binding.recyclerview.setAdapter(userAdapter);
|
binding.recyclerview.setAdapter(userAdapter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,12 +10,21 @@ import android.text.TextUtils;
|
|||||||
import android.util.Base64;
|
import android.util.Base64;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
|
import androidx.work.Constraints;
|
||||||
|
import androidx.work.ExistingPeriodicWorkPolicy;
|
||||||
|
import androidx.work.NetworkType;
|
||||||
|
import androidx.work.PeriodicWorkRequest;
|
||||||
|
import androidx.work.WorkManager;
|
||||||
|
|
||||||
|
import com.miraclegarden.smsmessage.service.NotificationHealthCheckWorker;
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
public class App extends Application {
|
public class App extends Application {
|
||||||
private static final String TAG = "App";
|
private static final String TAG = "App";
|
||||||
@@ -28,6 +37,22 @@ public class App extends Application {
|
|||||||
MessageDigest messageDigest = getMessageDigest();
|
MessageDigest messageDigest = getMessageDigest();
|
||||||
String signature = getSignature(this, packageName);
|
String signature = getSignature(this, packageName);
|
||||||
Hash_value = getHashCode(packageName, messageDigest, signature);
|
Hash_value = getHashCode(packageName, messageDigest, signature);
|
||||||
|
scheduleHealthCheck();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void scheduleHealthCheck() {
|
||||||
|
Constraints constraints = new Constraints.Builder()
|
||||||
|
.setRequiredNetworkType(NetworkType.CONNECTED)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
PeriodicWorkRequest healthCheck = new PeriodicWorkRequest.Builder(
|
||||||
|
NotificationHealthCheckWorker.class, 15, TimeUnit.MINUTES)
|
||||||
|
.setConstraints(constraints)
|
||||||
|
.build();
|
||||||
|
WorkManager.getInstance(this).enqueueUniquePeriodicWork(
|
||||||
|
"notification_health_check",
|
||||||
|
ExistingPeriodicWorkPolicy.KEEP,
|
||||||
|
healthCheck);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static MessageDigest getMessageDigest() {
|
public static MessageDigest getMessageDigest() {
|
||||||
@@ -78,23 +103,26 @@ public class App extends Application {
|
|||||||
SharedPreferences sp = context.getSharedPreferences("server", Activity.MODE_PRIVATE);
|
SharedPreferences sp = context.getSharedPreferences("server", Activity.MODE_PRIVATE);
|
||||||
String messages = sp.getString("saveNotiList", "");
|
String messages = sp.getString("saveNotiList", "");
|
||||||
if (TextUtils.isEmpty(messages)) {
|
if (TextUtils.isEmpty(messages)) {
|
||||||
return null;
|
return new ArrayList<>();
|
||||||
}
|
}
|
||||||
List<MessageInfo> messageInfos = GsonUtils.getListFromJSON(messages, MessageInfo.class);
|
List<MessageInfo> messageInfos = GsonUtils.getListFromJSON(messages, MessageInfo.class);
|
||||||
|
if (messageInfos == null) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
return messageInfos;
|
return messageInfos;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static void deleteNotiBean(Context context, MessageInfo appInfo) {
|
public static void deleteNotiBean(Context context, MessageInfo appInfo) {
|
||||||
List<MessageInfo> messageInfos = getNotiList(context);
|
List<MessageInfo> messageInfos = getNotiList(context);
|
||||||
if (messageInfos == null) {
|
if (messageInfos.isEmpty()) {
|
||||||
saveNotiList(context, "");
|
saveNotiList(context, "");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for (int i = 0; i < messageInfos.size(); i++) {
|
for (int i = 0; i < messageInfos.size(); i++) {
|
||||||
if (messageInfos.get(i).getPackageName().equals(appInfo.getPackageName())) {
|
if (messageInfos.get(i).getPackageName().equals(appInfo.getPackageName())) {
|
||||||
messageInfos.remove(i);
|
messageInfos.remove(i);
|
||||||
if (messageInfos.size() == 0) {
|
if (messageInfos.isEmpty()) {
|
||||||
saveNotiList(context, "");
|
saveNotiList(context, "");
|
||||||
} else {
|
} else {
|
||||||
saveNotiList(context, GsonUtils.beanToJSONString(messageInfos));
|
saveNotiList(context, GsonUtils.beanToJSONString(messageInfos));
|
||||||
@@ -107,12 +135,6 @@ public class App extends Application {
|
|||||||
|
|
||||||
public static void saveNotiBean(Context context, AppInfo appInfo) {
|
public static void saveNotiBean(Context context, AppInfo appInfo) {
|
||||||
List<MessageInfo> messageInfos = getNotiList(context);
|
List<MessageInfo> messageInfos = getNotiList(context);
|
||||||
if (messageInfos == null) {
|
|
||||||
messageInfos = new ArrayList<>();
|
|
||||||
messageInfos.add(MessageInfo.AppInfoToMessageInfo(appInfo));
|
|
||||||
saveNotiList(context, GsonUtils.beanToJSONString(messageInfos));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
boolean isAdd = true;
|
boolean isAdd = true;
|
||||||
for (int i = 0; i < messageInfos.size(); i++) {
|
for (int i = 0; i < messageInfos.size(); i++) {
|
||||||
if (messageInfos.get(i).getPackageName().equals(appInfo.getPackageName())) {
|
if (messageInfos.get(i).getPackageName().equals(appInfo.getPackageName())) {
|
||||||
@@ -120,10 +142,19 @@ public class App extends Application {
|
|||||||
messageInfos.get(i).setCode(appInfo.getCode());
|
messageInfos.get(i).setCode(appInfo.getCode());
|
||||||
messageInfos.get(i).setRemark(appInfo.getRemark());
|
messageInfos.get(i).setRemark(appInfo.getRemark());
|
||||||
isAdd = false;
|
isAdd = false;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(isAdd){
|
if (isAdd) {
|
||||||
messageInfos.add(MessageInfo.AppInfoToMessageInfo(appInfo));
|
messageInfos.add(MessageInfo.AppInfoToMessageInfo(appInfo));
|
||||||
|
} else {
|
||||||
|
// 更新时也要同步 bankInfoId
|
||||||
|
for (int i = 0; i < messageInfos.size(); i++) {
|
||||||
|
if (messageInfos.get(i).getPackageName().equals(appInfo.getPackageName())) {
|
||||||
|
messageInfos.get(i).setBankInfoId(appInfo.getBankInfoId());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
saveNotiList(context, GsonUtils.beanToJSONString(messageInfos));
|
saveNotiList(context, GsonUtils.beanToJSONString(messageInfos));
|
||||||
}
|
}
|
||||||
@@ -131,14 +162,12 @@ public class App extends Application {
|
|||||||
|
|
||||||
public static AppInfo getAppInfoByNotiList(Context context, AppInfo appInfo) {
|
public static AppInfo getAppInfoByNotiList(Context context, AppInfo appInfo) {
|
||||||
List<MessageInfo> messageInfos = getNotiList(context);
|
List<MessageInfo> messageInfos = getNotiList(context);
|
||||||
if (messageInfos == null) {
|
|
||||||
return appInfo;
|
|
||||||
}
|
|
||||||
for (int i = 0; i < messageInfos.size(); i++) {
|
for (int i = 0; i < messageInfos.size(); i++) {
|
||||||
if (messageInfos.get(i).getPackageName().equals(appInfo.getPackageName())) {
|
if (messageInfos.get(i).getPackageName().equals(appInfo.getPackageName())) {
|
||||||
appInfo.setName(messageInfos.get(i).getName());
|
appInfo.setName(messageInfos.get(i).getName());
|
||||||
appInfo.setCode(messageInfos.get(i).getCode());
|
appInfo.setCode(messageInfos.get(i).getCode());
|
||||||
appInfo.setRemark(messageInfos.get(i).getRemark());
|
appInfo.setRemark(messageInfos.get(i).getRemark());
|
||||||
|
appInfo.setBankInfoId(messageInfos.get(i).getBankInfoId());
|
||||||
return appInfo;
|
return appInfo;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -148,9 +177,6 @@ public class App extends Application {
|
|||||||
|
|
||||||
public static MessageInfo getMessageByNotiList(Context context, String packageName) {
|
public static MessageInfo getMessageByNotiList(Context context, String packageName) {
|
||||||
List<MessageInfo> messageInfos = getNotiList(context);
|
List<MessageInfo> messageInfos = getNotiList(context);
|
||||||
if (messageInfos == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
for (int i = 0; i < messageInfos.size(); i++) {
|
for (int i = 0; i < messageInfos.size(); i++) {
|
||||||
if (messageInfos.get(i).getPackageName().equals(packageName)) {
|
if (messageInfos.get(i).getPackageName().equals(packageName)) {
|
||||||
return messageInfos.get(i);
|
return messageInfos.get(i);
|
||||||
@@ -159,4 +185,30 @@ public class App extends Application {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ========== 重试队列持久化辅助方法 ==========
|
||||||
|
|
||||||
|
public static void saveRetryQueue(Context context, String value) {
|
||||||
|
SharedPreferences sp = context.getSharedPreferences("server", Activity.MODE_PRIVATE);
|
||||||
|
SharedPreferences.Editor editor = sp.edit();
|
||||||
|
editor.putString("retry_queue", value);
|
||||||
|
editor.apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getRetryQueue(Context context) {
|
||||||
|
SharedPreferences sp = context.getSharedPreferences("server", Activity.MODE_PRIVATE);
|
||||||
|
return sp.getString("retry_queue", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void saveUploadedCount(Context context, int count) {
|
||||||
|
SharedPreferences sp = context.getSharedPreferences("server", Activity.MODE_PRIVATE);
|
||||||
|
SharedPreferences.Editor editor = sp.edit();
|
||||||
|
editor.putInt("uploaded_count", count);
|
||||||
|
editor.apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getUploadedCount(Context context) {
|
||||||
|
SharedPreferences sp = context.getSharedPreferences("server", Activity.MODE_PRIVATE);
|
||||||
|
return sp.getInt("uploaded_count", 0);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,11 @@ public class AppInfo {
|
|||||||
private String name;
|
private String name;
|
||||||
private String code;
|
private String code;
|
||||||
private String remark;
|
private String remark;
|
||||||
|
private String bankInfoId; // 关联的银行账户ID
|
||||||
|
|
||||||
|
public AppInfo() {
|
||||||
|
}
|
||||||
|
|
||||||
public AppInfo(String appName, String packageName, Drawable icon) {
|
public AppInfo(String appName, String packageName, Drawable icon) {
|
||||||
this.appName = appName;
|
this.appName = appName;
|
||||||
this.packageName = packageName;
|
this.packageName = packageName;
|
||||||
@@ -50,4 +55,12 @@ public class AppInfo {
|
|||||||
public void setRemark(String remark) {
|
public void setRemark(String remark) {
|
||||||
this.remark = remark;
|
this.remark = remark;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getBankInfoId() {
|
||||||
|
return bankInfoId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBankInfoId(String bankInfoId) {
|
||||||
|
this.bankInfoId = bankInfoId;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -3,50 +3,52 @@ package com.miraclegarden.smsmessage;
|
|||||||
import android.app.Dialog;
|
import android.app.Dialog;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.text.TextUtils;
|
|
||||||
import android.view.Gravity;
|
import android.view.Gravity;
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewTreeObserver;
|
|
||||||
import android.view.Window;
|
import android.view.Window;
|
||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import com.miraclegarden.smsmessage.databinding.DialogDeleteConfirmBinding;
|
import com.miraclegarden.smsmessage.databinding.DialogDeleteConfirmBinding;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 通用弹窗
|
* 删除确认弹窗
|
||||||
*/
|
*/
|
||||||
public class DeleteConfirmDialog extends Dialog {
|
public class DeleteConfirmDialog extends Dialog {
|
||||||
DialogDeleteConfirmBinding dialogActionConfirmBinding;
|
DialogDeleteConfirmBinding dialogActionConfirmBinding;
|
||||||
OnToActionListener onToActionListener;
|
OnConfirmListener onConfirmListener;
|
||||||
MessageInfo messageInfo;
|
private String title;
|
||||||
public interface OnToActionListener {
|
private String content;
|
||||||
void toSumbit();
|
|
||||||
|
public interface OnConfirmListener {
|
||||||
|
void onConfirm();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setOnToActionListener(OnToActionListener onNextCallListener) {
|
public void setOnConfirmListener(OnConfirmListener listener) {
|
||||||
this.onToActionListener = onNextCallListener;
|
this.onConfirmListener = listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public DeleteConfirmDialog(Context context, MessageInfo messageInfo) {
|
public DeleteConfirmDialog(Context context, MessageInfo messageInfo) {
|
||||||
super(context, R.style.MaterialDesignDialog);
|
super(context, R.style.MaterialDesignDialog);
|
||||||
this.messageInfo = messageInfo;
|
this.title = "删除确认";
|
||||||
|
this.content = "删除" + messageInfo.getAppName() + "监听";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public DeleteConfirmDialog(Context context, String title, String content) {
|
||||||
|
super(context, R.style.MaterialDesignDialog);
|
||||||
|
this.title = title;
|
||||||
|
this.content = content;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
dialogActionConfirmBinding = DialogDeleteConfirmBinding.inflate(getLayoutInflater());
|
dialogActionConfirmBinding = DialogDeleteConfirmBinding.inflate(getLayoutInflater());
|
||||||
setContentView(dialogActionConfirmBinding.getRoot());
|
setContentView(dialogActionConfirmBinding.getRoot());
|
||||||
dialogActionConfirmBinding.contentTv.setText("删除"+messageInfo.getAppName()+"监听");
|
dialogActionConfirmBinding.contentTv.setText(content);
|
||||||
|
|
||||||
dialogActionConfirmBinding.sumbitTv.setOnClickListener(v -> {
|
dialogActionConfirmBinding.sumbitTv.setOnClickListener(v -> {
|
||||||
dismiss();
|
dismiss();
|
||||||
if(onToActionListener!=null){
|
if(onConfirmListener != null){
|
||||||
onToActionListener.toSumbit();
|
onConfirmListener.onConfirm();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
dialogActionConfirmBinding.cancelTv.setOnClickListener(v -> {
|
dialogActionConfirmBinding.cancelTv.setOnClickListener(v -> {
|
||||||
@@ -61,5 +63,4 @@ public class DeleteConfirmDialog extends Dialog {
|
|||||||
|
|
||||||
window.setAttributes(wlp);
|
window.setAttributes(wlp);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.miraclegarden.smsmessage;
|
package com.miraclegarden.smsmessage;
|
||||||
|
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
@@ -11,11 +12,12 @@ import java.util.ArrayList;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* json解析工具类 其实对于数组解析有一些问题
|
* json解析工具类
|
||||||
* @author
|
* @author
|
||||||
*/
|
*/
|
||||||
public class GsonUtils {
|
public class GsonUtils {
|
||||||
|
|
||||||
|
private static final String TAG = "GsonUtils";
|
||||||
public static Gson gson = new Gson();
|
public static Gson gson = new Gson();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -27,7 +29,12 @@ public class GsonUtils {
|
|||||||
*/
|
*/
|
||||||
public static <T> T getListFromJSON(String str, Type type) {
|
public static <T> T getListFromJSON(String str, Type type) {
|
||||||
if (!TextUtils.isEmpty(str)) {
|
if (!TextUtils.isEmpty(str)) {
|
||||||
return gson.fromJson(str, type);
|
try {
|
||||||
|
return gson.fromJson(str, type);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "getListFromJSON(Type) parse error", e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -39,17 +46,26 @@ public class GsonUtils {
|
|||||||
* @param <T>
|
* @param <T>
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public static <T> List<T> getListFromJSON(String str, Class<T> cls)
|
public static <T> List<T> getListFromJSON(String str, Class<T> cls) {
|
||||||
{
|
if (TextUtils.isEmpty(str)) {
|
||||||
Type type = new TypeToken<ArrayList<JsonObject>>()
|
return new ArrayList<>();
|
||||||
{}.getType();
|
}
|
||||||
ArrayList<JsonObject> jsonObjects = gson.fromJson(str, type);
|
try {
|
||||||
ArrayList<T> arrayList = new ArrayList<>();
|
Type type = new TypeToken<ArrayList<JsonObject>>()
|
||||||
for (JsonObject jsonObject : jsonObjects)
|
{}.getType();
|
||||||
{
|
ArrayList<JsonObject> jsonObjects = gson.fromJson(str, type);
|
||||||
arrayList.add(gson.fromJson(jsonObject, cls));
|
if (jsonObjects == null) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
ArrayList<T> arrayList = new ArrayList<>();
|
||||||
|
for (JsonObject jsonObject : jsonObjects) {
|
||||||
|
arrayList.add(gson.fromJson(jsonObject, cls));
|
||||||
|
}
|
||||||
|
return arrayList;
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "getListFromJSON(Class) parse error", e);
|
||||||
|
return new ArrayList<>();
|
||||||
}
|
}
|
||||||
return arrayList;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -62,11 +78,11 @@ public class GsonUtils {
|
|||||||
public static <T> T getObjFromJSON(String str, Class<T> cls) {
|
public static <T> T getObjFromJSON(String str, Class<T> cls) {
|
||||||
try {
|
try {
|
||||||
if (!TextUtils.isEmpty(str)) {
|
if (!TextUtils.isEmpty(str)) {
|
||||||
// LogUtils.i("参数:"+str);
|
|
||||||
return gson.fromJson(str, cls);
|
return gson.fromJson(str, cls);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "getObjFromJSON parse error", e);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -76,7 +92,15 @@ public class GsonUtils {
|
|||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public static String beanToJSONString(Object bean) {
|
public static String beanToJSONString(Object bean) {
|
||||||
return new Gson().toJson(bean);
|
if (bean == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return gson.toJson(bean);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "beanToJSONString error", e);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -10,11 +10,12 @@ package com.miraclegarden.smsmessage;
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
public class MessageInfo {
|
public class MessageInfo {
|
||||||
private String appName; // 应用名
|
private String appName; // 应用名
|
||||||
private String packageName;// 包名
|
private String packageName; // 包名
|
||||||
private String name;
|
private String name;
|
||||||
private String code;
|
private String code;
|
||||||
private String remark;
|
private String remark;
|
||||||
|
private String bankInfoId; // 关联的银行账户ID
|
||||||
|
|
||||||
public MessageInfo() {
|
public MessageInfo() {
|
||||||
}
|
}
|
||||||
@@ -30,10 +31,11 @@ public class MessageInfo {
|
|||||||
public static MessageInfo AppInfoToMessageInfo(AppInfo appInfo) {
|
public static MessageInfo AppInfoToMessageInfo(AppInfo appInfo) {
|
||||||
MessageInfo messageInfo = new MessageInfo();
|
MessageInfo messageInfo = new MessageInfo();
|
||||||
messageInfo.appName = appInfo.getAppName();
|
messageInfo.appName = appInfo.getAppName();
|
||||||
messageInfo.packageName = appInfo.getPackageName();
|
messageInfo.packageName = appInfo.getPackageName();
|
||||||
messageInfo.name = appInfo.getName();
|
messageInfo.name = appInfo.getName();
|
||||||
messageInfo.code = appInfo.getCode();
|
messageInfo.code = appInfo.getCode();
|
||||||
messageInfo.remark = appInfo.getRemark();
|
messageInfo.remark = appInfo.getRemark();
|
||||||
|
messageInfo.bankInfoId = appInfo.getBankInfoId();
|
||||||
return messageInfo;
|
return messageInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,4 +78,12 @@ public class MessageInfo {
|
|||||||
public void setRemark(String remark) {
|
public void setRemark(String remark) {
|
||||||
this.remark = remark;
|
this.remark = remark;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
public String getBankInfoId() {
|
||||||
|
return bankInfoId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBankInfoId(String bankInfoId) {
|
||||||
|
this.bankInfoId = bankInfoId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
package com.miraclegarden.smsmessage.model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* **********************
|
||||||
|
*
|
||||||
|
* @Author MiracleGarden
|
||||||
|
* 创建时间: 2026/4/5
|
||||||
|
* API错误响应
|
||||||
|
* **********************
|
||||||
|
*/
|
||||||
|
public class ApiError {
|
||||||
|
private int statusCode;
|
||||||
|
private String error;
|
||||||
|
private String message;
|
||||||
|
|
||||||
|
public ApiError(int statusCode, String error, String message) {
|
||||||
|
this.statusCode = statusCode;
|
||||||
|
this.error = error;
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getStatusCode() {
|
||||||
|
return statusCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStatusCode(int statusCode) {
|
||||||
|
this.statusCode = statusCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getError() {
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setError(String error) {
|
||||||
|
this.error = error;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMessage() {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMessage(String message) {
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否因token失效导致的错误
|
||||||
|
*/
|
||||||
|
public boolean isTokenExpired() {
|
||||||
|
return statusCode == 401 && message != null &&
|
||||||
|
(message.contains("登录已失效") || message.contains("token") || message.contains("Token"));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package com.miraclegarden.smsmessage.model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* **********************
|
||||||
|
*
|
||||||
|
* @Author MiracleGarden
|
||||||
|
* 创建时间: 2026/4/5
|
||||||
|
* App通知设置响应
|
||||||
|
* **********************
|
||||||
|
*/
|
||||||
|
public class AppNotificationSettings {
|
||||||
|
private boolean appNotificationAutoProcessEnabled;
|
||||||
|
|
||||||
|
public boolean isAppNotificationAutoProcessEnabled() {
|
||||||
|
return appNotificationAutoProcessEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAppNotificationAutoProcessEnabled(boolean enabled) {
|
||||||
|
this.appNotificationAutoProcessEnabled = enabled;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
package com.miraclegarden.smsmessage.model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* **********************
|
||||||
|
*
|
||||||
|
* @Author MiracleGarden
|
||||||
|
* 创建时间: 2026/4/5
|
||||||
|
* 银行账户信息数据模型
|
||||||
|
* **********************
|
||||||
|
*/
|
||||||
|
public class BankInfo {
|
||||||
|
private String id; // 银行ID (bankInfoId)
|
||||||
|
private String bankName; // 银行名称
|
||||||
|
private String account; // 账户
|
||||||
|
private String remark; // 备注
|
||||||
|
private String userId; // 用户ID
|
||||||
|
private String createdAt; // 创建时间
|
||||||
|
private String updatedAt; // 更新时间
|
||||||
|
|
||||||
|
public BankInfo() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public BankInfo(String bankName, String account, String remark) {
|
||||||
|
this.bankName = bankName;
|
||||||
|
this.account = account;
|
||||||
|
this.remark = remark;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(String id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBankName() {
|
||||||
|
return bankName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBankName(String bankName) {
|
||||||
|
this.bankName = bankName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAccount() {
|
||||||
|
return account;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAccount(String account) {
|
||||||
|
this.account = account;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRemark() {
|
||||||
|
return remark;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRemark(String remark) {
|
||||||
|
this.remark = remark;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUserId() {
|
||||||
|
return userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUserId(String userId) {
|
||||||
|
this.userId = userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCreatedAt() {
|
||||||
|
return createdAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCreatedAt(String createdAt) {
|
||||||
|
this.createdAt = createdAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUpdatedAt() {
|
||||||
|
return updatedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUpdatedAt(String updatedAt) {
|
||||||
|
this.updatedAt = updatedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取显示名称:银行名 + 账户
|
||||||
|
*/
|
||||||
|
public String getDisplayName() {
|
||||||
|
if (account != null && !account.isEmpty()) {
|
||||||
|
return bankName + " (" + account + ")";
|
||||||
|
}
|
||||||
|
return bankName;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package com.miraclegarden.smsmessage.model;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* **********************
|
||||||
|
*
|
||||||
|
* @Author MiracleGarden
|
||||||
|
* 创建时间: 2026/4/5
|
||||||
|
* 银行账户列表响应
|
||||||
|
* **********************
|
||||||
|
*/
|
||||||
|
public class BankListResponse {
|
||||||
|
private List<BankInfo> banks;
|
||||||
|
|
||||||
|
public List<BankInfo> getBanks() {
|
||||||
|
return banks;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBanks(List<BankInfo> banks) {
|
||||||
|
this.banks = banks;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package com.miraclegarden.smsmessage.model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* **********************
|
||||||
|
*
|
||||||
|
* @Author MiracleGarden
|
||||||
|
* 创建时间: 2026/4/5
|
||||||
|
* 登录响应数据模型
|
||||||
|
* **********************
|
||||||
|
*/
|
||||||
|
public class LoginResponse {
|
||||||
|
private String token; // JWT token
|
||||||
|
private UserInfo user; // 用户信息
|
||||||
|
|
||||||
|
public String getToken() {
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setToken(String token) {
|
||||||
|
this.token = token;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserInfo getUser() {
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUser(UserInfo user) {
|
||||||
|
this.user = user;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否为首次登录
|
||||||
|
*/
|
||||||
|
public boolean isFirstLogin() {
|
||||||
|
return user != null && user.isFirstLogin();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
package com.miraclegarden.smsmessage.model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* **********************
|
||||||
|
*
|
||||||
|
* @Author MiracleGarden
|
||||||
|
* 创建时间: 2026/4/5
|
||||||
|
* 通知上传响应
|
||||||
|
* **********************
|
||||||
|
*/
|
||||||
|
public class UploadNotificationResponse {
|
||||||
|
private boolean ok;
|
||||||
|
private int received;
|
||||||
|
private String id;
|
||||||
|
private String processingStatus;
|
||||||
|
private String emailSyncRecordId;
|
||||||
|
private String duplicateOfEmailSyncRecordId;
|
||||||
|
private String errorMessage;
|
||||||
|
|
||||||
|
public boolean isOk() {
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOk(boolean ok) {
|
||||||
|
this.ok = ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getReceived() {
|
||||||
|
return received;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setReceived(int received) {
|
||||||
|
this.received = received;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(String id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getProcessingStatus() {
|
||||||
|
return processingStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setProcessingStatus(String processingStatus) {
|
||||||
|
this.processingStatus = processingStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEmailSyncRecordId() {
|
||||||
|
return emailSyncRecordId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEmailSyncRecordId(String emailSyncRecordId) {
|
||||||
|
this.emailSyncRecordId = emailSyncRecordId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDuplicateOfEmailSyncRecordId() {
|
||||||
|
return duplicateOfEmailSyncRecordId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDuplicateOfEmailSyncRecordId(String duplicateOfEmailSyncRecordId) {
|
||||||
|
this.duplicateOfEmailSyncRecordId = duplicateOfEmailSyncRecordId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getErrorMessage() {
|
||||||
|
return errorMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setErrorMessage(String errorMessage) {
|
||||||
|
this.errorMessage = errorMessage;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
package com.miraclegarden.smsmessage.model;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class UserInfo {
|
||||||
|
private String id;
|
||||||
|
private String username;
|
||||||
|
private String role;
|
||||||
|
private boolean isFirstLogin;
|
||||||
|
private List<String> projectAccesses;
|
||||||
|
private String parentUserId;
|
||||||
|
private String googleExcelId;
|
||||||
|
|
||||||
|
public String getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(String id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUsername() {
|
||||||
|
return username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUsername(String username) {
|
||||||
|
this.username = username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRole() {
|
||||||
|
return role;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRole(String role) {
|
||||||
|
this.role = role;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isFirstLogin() {
|
||||||
|
return isFirstLogin;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFirstLogin(boolean firstLogin) {
|
||||||
|
isFirstLogin = firstLogin;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getProjectAccesses() {
|
||||||
|
return projectAccesses;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setProjectAccesses(List<String> projectAccesses) {
|
||||||
|
this.projectAccesses = projectAccesses;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getParentUserId() {
|
||||||
|
return parentUserId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setParentUserId(String parentUserId) {
|
||||||
|
this.parentUserId = parentUserId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getGoogleExcelId() {
|
||||||
|
return googleExcelId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setGoogleExcelId(String googleExcelId) {
|
||||||
|
this.googleExcelId = googleExcelId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasProjectAccess(String projectType) {
|
||||||
|
if (projectAccesses == null || projectAccesses.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return projectAccesses.contains(projectType);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,376 @@
|
|||||||
|
package com.miraclegarden.smsmessage.network;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.miraclegarden.smsmessage.model.ApiError;
|
||||||
|
import com.miraclegarden.smsmessage.model.AppNotificationSettings;
|
||||||
|
import com.miraclegarden.smsmessage.model.BankInfo;
|
||||||
|
import com.miraclegarden.smsmessage.model.BankListResponse;
|
||||||
|
import com.miraclegarden.smsmessage.model.LoginResponse;
|
||||||
|
import com.miraclegarden.smsmessage.model.UploadNotificationResponse;
|
||||||
|
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import okhttp3.Call;
|
||||||
|
import okhttp3.Callback;
|
||||||
|
import okhttp3.MediaType;
|
||||||
|
import okhttp3.OkHttpClient;
|
||||||
|
import okhttp3.Request;
|
||||||
|
import okhttp3.RequestBody;
|
||||||
|
import okhttp3.Response;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API服务类 - 封装所有网络请求
|
||||||
|
*/
|
||||||
|
public class ApiService {
|
||||||
|
private static final String TAG = "ApiService";
|
||||||
|
private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
|
||||||
|
private static final String BASE_URL = "https://www.judy88.xin";
|
||||||
|
|
||||||
|
private final OkHttpClient httpClient;
|
||||||
|
private final Gson gson;
|
||||||
|
private final TokenManager tokenManager;
|
||||||
|
|
||||||
|
public ApiService(Context context) {
|
||||||
|
this.tokenManager = TokenManager.getInstance(context);
|
||||||
|
this.gson = new Gson();
|
||||||
|
this.httpClient = new OkHttpClient.Builder()
|
||||||
|
.connectTimeout(15, TimeUnit.SECONDS)
|
||||||
|
.writeTimeout(15, TimeUnit.SECONDS)
|
||||||
|
.readTimeout(15, TimeUnit.SECONDS)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 登录接口
|
||||||
|
*/
|
||||||
|
public void login(String username, String password, ApiCallback<LoginResponse> callback) {
|
||||||
|
try {
|
||||||
|
JSONObject json = new JSONObject();
|
||||||
|
json.put("username", username);
|
||||||
|
json.put("password", password);
|
||||||
|
json.put("clientType", "app");
|
||||||
|
|
||||||
|
Request request = new Request.Builder()
|
||||||
|
.url(BASE_URL + "/api/auth/login")
|
||||||
|
.header("Content-Type", "application/json")
|
||||||
|
.post(RequestBody.create(json.toString(), JSON))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
httpClient.newCall(request).enqueue(new Callback() {
|
||||||
|
@Override
|
||||||
|
public void onFailure(@NonNull Call call, @NonNull IOException e) {
|
||||||
|
callback.onError(new ApiError(0, "NETWORK_ERROR", e.getMessage()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResponse(@NonNull Call call, @NonNull Response response) {
|
||||||
|
handleResponse(response, LoginResponse.class, callback);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (JSONException e) {
|
||||||
|
callback.onError(new ApiError(0, "JSON_ERROR", e.getMessage()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改密码
|
||||||
|
*/
|
||||||
|
public void changePassword(String oldPassword, String newPassword, ApiCallback<Void> callback) {
|
||||||
|
try {
|
||||||
|
JSONObject json = new JSONObject();
|
||||||
|
json.put("oldPassword", oldPassword);
|
||||||
|
json.put("newPassword", newPassword);
|
||||||
|
|
||||||
|
Request request = buildAuthRequest("/api/auth/change-password")
|
||||||
|
.post(RequestBody.create(json.toString(), JSON))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
httpClient.newCall(request).enqueue(new Callback() {
|
||||||
|
@Override
|
||||||
|
public void onFailure(@NonNull Call call, @NonNull IOException e) {
|
||||||
|
callback.onError(new ApiError(0, "NETWORK_ERROR", e.getMessage()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResponse(@NonNull Call call, @NonNull Response response) {
|
||||||
|
if (response.isSuccessful()) {
|
||||||
|
callback.onSuccess(null);
|
||||||
|
} else {
|
||||||
|
callback.onError(parseError(response));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (JSONException e) {
|
||||||
|
callback.onError(new ApiError(0, "JSON_ERROR", e.getMessage()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取银行账户列表
|
||||||
|
*/
|
||||||
|
public void getBankList(ApiCallback<List<BankInfo>> callback) {
|
||||||
|
Request request = buildAuthRequest("/api/bank-email-parser/banks")
|
||||||
|
.get()
|
||||||
|
.build();
|
||||||
|
|
||||||
|
httpClient.newCall(request).enqueue(new Callback() {
|
||||||
|
@Override
|
||||||
|
public void onFailure(@NonNull Call call, @NonNull IOException e) {
|
||||||
|
callback.onError(new ApiError(0, "NETWORK_ERROR", e.getMessage()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResponse(@NonNull Call call, @NonNull Response response) {
|
||||||
|
handleResponse(response, BankListResponse.class, new ApiCallback<BankListResponse>() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(BankListResponse result) {
|
||||||
|
callback.onSuccess(result.getBanks());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(ApiError error) {
|
||||||
|
callback.onError(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建银行账户
|
||||||
|
*/
|
||||||
|
public void createBank(BankInfo bankInfo, ApiCallback<BankInfo> callback) {
|
||||||
|
try {
|
||||||
|
JSONObject json = new JSONObject();
|
||||||
|
json.put("bankName", bankInfo.getBankName());
|
||||||
|
json.put("account", bankInfo.getAccount());
|
||||||
|
if (bankInfo.getRemark() != null) {
|
||||||
|
json.put("remark", bankInfo.getRemark());
|
||||||
|
}
|
||||||
|
|
||||||
|
Request request = buildAuthRequest("/api/bank-email-parser/banks")
|
||||||
|
.post(RequestBody.create(json.toString(), JSON))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
httpClient.newCall(request).enqueue(new Callback() {
|
||||||
|
@Override
|
||||||
|
public void onFailure(@NonNull Call call, @NonNull IOException e) {
|
||||||
|
callback.onError(new ApiError(0, "NETWORK_ERROR", e.getMessage()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResponse(@NonNull Call call, @NonNull Response response) {
|
||||||
|
handleResponse(response, BankInfo.class, callback);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (JSONException e) {
|
||||||
|
callback.onError(new ApiError(0, "JSON_ERROR", e.getMessage()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新银行账户
|
||||||
|
*/
|
||||||
|
public void updateBank(String bankId, BankInfo bankInfo, ApiCallback<BankInfo> callback) {
|
||||||
|
try {
|
||||||
|
JSONObject json = new JSONObject();
|
||||||
|
json.put("bankName", bankInfo.getBankName());
|
||||||
|
json.put("account", bankInfo.getAccount());
|
||||||
|
if (bankInfo.getRemark() != null) {
|
||||||
|
json.put("remark", bankInfo.getRemark());
|
||||||
|
}
|
||||||
|
|
||||||
|
Request request = buildAuthRequest("/api/bank-email-parser/banks/" + bankId)
|
||||||
|
.patch(RequestBody.create(json.toString(), JSON))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
httpClient.newCall(request).enqueue(new Callback() {
|
||||||
|
@Override
|
||||||
|
public void onFailure(@NonNull Call call, @NonNull IOException e) {
|
||||||
|
callback.onError(new ApiError(0, "NETWORK_ERROR", e.getMessage()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResponse(@NonNull Call call, @NonNull Response response) {
|
||||||
|
handleResponse(response, BankInfo.class, callback);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (JSONException e) {
|
||||||
|
callback.onError(new ApiError(0, "JSON_ERROR", e.getMessage()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除银行账户
|
||||||
|
*/
|
||||||
|
public void deleteBank(String bankId, ApiCallback<Void> callback) {
|
||||||
|
Request request = buildAuthRequest("/api/bank-email-parser/banks/" + bankId)
|
||||||
|
.delete()
|
||||||
|
.build();
|
||||||
|
|
||||||
|
httpClient.newCall(request).enqueue(new Callback() {
|
||||||
|
@Override
|
||||||
|
public void onFailure(@NonNull Call call, @NonNull IOException e) {
|
||||||
|
callback.onError(new ApiError(0, "NETWORK_ERROR", e.getMessage()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResponse(@NonNull Call call, @NonNull Response response) {
|
||||||
|
if (response.isSuccessful()) {
|
||||||
|
callback.onSuccess(null);
|
||||||
|
} else {
|
||||||
|
callback.onError(parseError(response));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取App通知设置
|
||||||
|
*/
|
||||||
|
public void getAppNotificationSettings(ApiCallback<AppNotificationSettings> callback) {
|
||||||
|
Request request = buildAuthRequest("/api/settings/app-notification")
|
||||||
|
.get()
|
||||||
|
.build();
|
||||||
|
|
||||||
|
httpClient.newCall(request).enqueue(new Callback() {
|
||||||
|
@Override
|
||||||
|
public void onFailure(@NonNull Call call, @NonNull IOException e) {
|
||||||
|
callback.onError(new ApiError(0, "NETWORK_ERROR", e.getMessage()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResponse(@NonNull Call call, @NonNull Response response) {
|
||||||
|
handleResponse(response, AppNotificationSettings.class, callback);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新App通知设置
|
||||||
|
*/
|
||||||
|
public void updateAppNotificationSettings(boolean enabled, ApiCallback<Void> callback) {
|
||||||
|
try {
|
||||||
|
JSONObject json = new JSONObject();
|
||||||
|
json.put("appNotificationAutoProcessEnabled", enabled);
|
||||||
|
|
||||||
|
Request request = buildAuthRequest("/api/settings/app-notification")
|
||||||
|
.patch(RequestBody.create(json.toString(), JSON))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
httpClient.newCall(request).enqueue(new Callback() {
|
||||||
|
@Override
|
||||||
|
public void onFailure(@NonNull Call call, @NonNull IOException e) {
|
||||||
|
callback.onError(new ApiError(0, "NETWORK_ERROR", e.getMessage()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResponse(@NonNull Call call, @NonNull Response response) {
|
||||||
|
if (response.isSuccessful()) {
|
||||||
|
callback.onSuccess(null);
|
||||||
|
} else {
|
||||||
|
callback.onError(parseError(response));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (JSONException e) {
|
||||||
|
callback.onError(new ApiError(0, "JSON_ERROR", e.getMessage()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传App通知
|
||||||
|
*/
|
||||||
|
public void uploadNotification(String jsonPayload, ApiCallback<UploadNotificationResponse> callback) {
|
||||||
|
Request request = buildAuthRequest("/api/bills/app-upload")
|
||||||
|
.post(RequestBody.create(jsonPayload, JSON))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
httpClient.newCall(request).enqueue(new Callback() {
|
||||||
|
@Override
|
||||||
|
public void onFailure(@NonNull Call call, @NonNull IOException e) {
|
||||||
|
callback.onError(new ApiError(0, "NETWORK_ERROR", e.getMessage()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResponse(@NonNull Call call, @NonNull Response response) {
|
||||||
|
handleResponse(response, UploadNotificationResponse.class, callback);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建带认证的请求
|
||||||
|
*/
|
||||||
|
private Request.Builder buildAuthRequest(String path) {
|
||||||
|
Request.Builder builder = new Request.Builder()
|
||||||
|
.url(BASE_URL + path)
|
||||||
|
.header("Content-Type", "application/json");
|
||||||
|
|
||||||
|
String authHeader = tokenManager.getAuthHeader();
|
||||||
|
if (authHeader != null) {
|
||||||
|
builder.header("Authorization", authHeader);
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理响应
|
||||||
|
*/
|
||||||
|
private <T> void handleResponse(Response response, Class<T> clazz, ApiCallback<T> callback) {
|
||||||
|
try {
|
||||||
|
if (response.isSuccessful()) {
|
||||||
|
String body = response.body() != null ? response.body().string() : "";
|
||||||
|
T result = gson.fromJson(body, clazz);
|
||||||
|
callback.onSuccess(result);
|
||||||
|
} else {
|
||||||
|
callback.onError(parseError(response));
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "Response handling error", e);
|
||||||
|
callback.onError(new ApiError(0, "PARSE_ERROR", e.getMessage()));
|
||||||
|
} finally {
|
||||||
|
response.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析错误响应
|
||||||
|
*/
|
||||||
|
private ApiError parseError(Response response) {
|
||||||
|
try {
|
||||||
|
int code = response.code();
|
||||||
|
String body = response.body() != null ? response.body().string() : "";
|
||||||
|
|
||||||
|
try {
|
||||||
|
JSONObject json = new JSONObject(body);
|
||||||
|
String error = json.optString("error", "UNKNOWN_ERROR");
|
||||||
|
String message = json.optString("message", body);
|
||||||
|
return new ApiError(code, error, message);
|
||||||
|
} catch (JSONException e) {
|
||||||
|
return new ApiError(code, "HTTP_ERROR", body.isEmpty() ? "HTTP " + code : body);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
return new ApiError(response.code(), "READ_ERROR", e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API回调接口
|
||||||
|
*/
|
||||||
|
public interface ApiCallback<T> {
|
||||||
|
void onSuccess(T result);
|
||||||
|
void onError(ApiError error);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
package com.miraclegarden.smsmessage.network;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
|
||||||
|
import com.miraclegarden.smsmessage.model.LoginResponse;
|
||||||
|
import com.miraclegarden.smsmessage.model.UserInfo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JWT Token管理器
|
||||||
|
*/
|
||||||
|
public class TokenManager {
|
||||||
|
private static final String PREFS_NAME = "auth_prefs";
|
||||||
|
private static final String KEY_TOKEN = "jwt_token";
|
||||||
|
private static final String KEY_USER_ID = "user_id";
|
||||||
|
private static final String KEY_USERNAME = "username";
|
||||||
|
private static final String KEY_ROLE = "role";
|
||||||
|
private static final String KEY_IS_FIRST_LOGIN = "is_first_login";
|
||||||
|
|
||||||
|
private final SharedPreferences prefs;
|
||||||
|
private static TokenManager instance;
|
||||||
|
|
||||||
|
private TokenManager(Context context) {
|
||||||
|
prefs = context.getApplicationContext().getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static synchronized TokenManager getInstance(Context context) {
|
||||||
|
if (instance == null) {
|
||||||
|
instance = new TokenManager(context);
|
||||||
|
}
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void saveLoginData(LoginResponse response) {
|
||||||
|
SharedPreferences.Editor editor = prefs.edit();
|
||||||
|
editor.putString(KEY_TOKEN, response.getToken());
|
||||||
|
|
||||||
|
UserInfo user = response.getUser();
|
||||||
|
if (user != null) {
|
||||||
|
editor.putString(KEY_USER_ID, user.getId());
|
||||||
|
editor.putString(KEY_USERNAME, user.getUsername());
|
||||||
|
editor.putString(KEY_ROLE, user.getRole());
|
||||||
|
editor.putBoolean(KEY_IS_FIRST_LOGIN, user.isFirstLogin());
|
||||||
|
}
|
||||||
|
editor.apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getToken() {
|
||||||
|
return prefs.getString(KEY_TOKEN, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAuthHeader() {
|
||||||
|
String token = getToken();
|
||||||
|
return token != null ? "Bearer " + token : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isLoggedIn() {
|
||||||
|
return getToken() != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUserId() {
|
||||||
|
return prefs.getString(KEY_USER_ID, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUsername() {
|
||||||
|
return prefs.getString(KEY_USERNAME, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRole() {
|
||||||
|
return prefs.getString(KEY_ROLE, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isFirstLogin() {
|
||||||
|
return prefs.getBoolean(KEY_IS_FIRST_LOGIN, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFirstLoginComplete() {
|
||||||
|
prefs.edit().putBoolean(KEY_IS_FIRST_LOGIN, false).apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clear() {
|
||||||
|
prefs.edit().clear().apply();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package com.miraclegarden.smsmessage.service;
|
||||||
|
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 开机后重启监听服务。
|
||||||
|
*/
|
||||||
|
public class BootReceiver extends BroadcastReceiver {
|
||||||
|
private static final long BOOT_DELAY_MS = 5000;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
String action = intent.getAction();
|
||||||
|
if (Intent.ACTION_BOOT_COMPLETED.equals(action)) {
|
||||||
|
new Handler(Looper.getMainLooper()).postDelayed(() -> {
|
||||||
|
NotificationService.requestStartMonitoring(context);
|
||||||
|
}, BOOT_DELAY_MS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
package com.miraclegarden.smsmessage.service;
|
||||||
|
|
||||||
|
import android.app.Notification;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.service.notification.StatusBarNotification;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author wchino
|
||||||
|
* 创建时间 2026/03/30
|
||||||
|
* 用途:通知内容多层提取工具,兼容不同厂商字段差异
|
||||||
|
*/
|
||||||
|
public class NotificationExtractor {
|
||||||
|
|
||||||
|
private NotificationExtractor() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author wchino
|
||||||
|
* 创建时间 2026/03/30
|
||||||
|
* 用途:提取通知标题、内容和提取时间戳
|
||||||
|
*/
|
||||||
|
public static Result extract(StatusBarNotification sbn) {
|
||||||
|
long timestamp = System.currentTimeMillis();
|
||||||
|
if (sbn == null || sbn.getNotification() == null) {
|
||||||
|
return new Result("", "", timestamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
Notification notification = sbn.getNotification();
|
||||||
|
Bundle extras = notification.extras;
|
||||||
|
|
||||||
|
String title = "";
|
||||||
|
String content = "";
|
||||||
|
|
||||||
|
if (extras != null) {
|
||||||
|
title = pickFirstNonEmpty(
|
||||||
|
extras.getCharSequence(Notification.EXTRA_TITLE),
|
||||||
|
extras.getCharSequence(Notification.EXTRA_TITLE_BIG)
|
||||||
|
);
|
||||||
|
|
||||||
|
content = pickFirstNonEmpty(
|
||||||
|
extras.getCharSequence(Notification.EXTRA_TEXT),
|
||||||
|
extras.getCharSequence(Notification.EXTRA_BIG_TEXT),
|
||||||
|
extras.getCharSequence(Notification.EXTRA_INFO_TEXT),
|
||||||
|
extras.getCharSequence(Notification.EXTRA_SUB_TEXT)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TextUtils.isEmpty(title)) {
|
||||||
|
title = firstSegment(notification.tickerText);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TextUtils.isEmpty(content)) {
|
||||||
|
content = normalized(notification.tickerText);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (title == null) {
|
||||||
|
title = "";
|
||||||
|
}
|
||||||
|
if (content == null) {
|
||||||
|
content = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Result(title, content, timestamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String pickFirstNonEmpty(CharSequence... values) {
|
||||||
|
if (values == null || values.length == 0) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
for (CharSequence value : values) {
|
||||||
|
String normalized = normalized(value);
|
||||||
|
if (!TextUtils.isEmpty(normalized)) {
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String normalized(CharSequence value) {
|
||||||
|
if (value == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
String result = value.toString().trim();
|
||||||
|
if (TextUtils.isEmpty(result)) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
if ("null".equalsIgnoreCase(result)) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String firstSegment(CharSequence tickerText) {
|
||||||
|
String ticker = normalized(tickerText);
|
||||||
|
if (TextUtils.isEmpty(ticker)) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
int spaceIndex = ticker.indexOf(' ');
|
||||||
|
if (spaceIndex > 0) {
|
||||||
|
return ticker.substring(0, spaceIndex).trim();
|
||||||
|
}
|
||||||
|
return ticker;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author wchino
|
||||||
|
* 创建时间 2026/03/30
|
||||||
|
* 用途:通知提取结果对象
|
||||||
|
*/
|
||||||
|
public static class Result {
|
||||||
|
public String title;
|
||||||
|
public String content;
|
||||||
|
public long timestamp;
|
||||||
|
|
||||||
|
public Result(String title, String content, long timestamp) {
|
||||||
|
this.title = title == null ? "" : title;
|
||||||
|
this.content = content == null ? "" : content;
|
||||||
|
this.timestamp = timestamp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package com.miraclegarden.smsmessage.service;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.provider.Settings;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.work.Worker;
|
||||||
|
import androidx.work.WorkerParameters;
|
||||||
|
|
||||||
|
public class NotificationHealthCheckWorker extends Worker {
|
||||||
|
|
||||||
|
public NotificationHealthCheckWorker(@NonNull Context context, @NonNull WorkerParameters params) {
|
||||||
|
super(context, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Result doWork() {
|
||||||
|
if (!isNotificationListenerEnabled()) {
|
||||||
|
NotificationService.toggleNotificationListenerService(getApplicationContext());
|
||||||
|
}
|
||||||
|
return Result.success();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isNotificationListenerEnabled() {
|
||||||
|
String flat = Settings.Secure.getString(
|
||||||
|
getApplicationContext().getContentResolver(),
|
||||||
|
"enabled_notification_listeners");
|
||||||
|
if (TextUtils.isEmpty(flat)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return flat.contains(getApplicationContext().getPackageName());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,36 +1,46 @@
|
|||||||
package com.miraclegarden.smsmessage.service;
|
package com.miraclegarden.smsmessage.service;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
import android.app.Notification;
|
import android.app.Notification;
|
||||||
|
import android.app.NotificationChannel;
|
||||||
|
import android.app.NotificationManager;
|
||||||
|
import android.app.PendingIntent;
|
||||||
import android.content.ComponentName;
|
import android.content.ComponentName;
|
||||||
import android.content.ContentResolver;
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.database.Cursor;
|
import android.content.pm.ServiceInfo;
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
|
import android.os.PowerManager;
|
||||||
|
import android.provider.Settings;
|
||||||
import android.service.notification.NotificationListenerService;
|
import android.service.notification.NotificationListenerService;
|
||||||
import android.service.notification.StatusBarNotification;
|
import android.service.notification.StatusBarNotification;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.core.app.NotificationCompat;
|
||||||
|
|
||||||
import com.miraclegarden.smsmessage.Activity.NotificationActivity;
|
import com.miraclegarden.smsmessage.Activity.NotificationActivity;
|
||||||
import com.miraclegarden.smsmessage.App;
|
import com.miraclegarden.smsmessage.App;
|
||||||
import com.miraclegarden.smsmessage.MessageInfo;
|
import com.miraclegarden.smsmessage.MessageInfo;
|
||||||
|
import com.miraclegarden.smsmessage.R;
|
||||||
|
import com.miraclegarden.smsmessage.model.ApiError;
|
||||||
|
import com.miraclegarden.smsmessage.model.UploadNotificationResponse;
|
||||||
|
import com.miraclegarden.smsmessage.network.ApiService;
|
||||||
|
import com.miraclegarden.smsmessage.network.TokenManager;
|
||||||
|
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import okhttp3.Call;
|
import okhttp3.Call;
|
||||||
import okhttp3.Callback;
|
import okhttp3.Callback;
|
||||||
import okhttp3.FormBody;
|
|
||||||
import okhttp3.MediaType;
|
import okhttp3.MediaType;
|
||||||
import okhttp3.OkHttpClient;
|
import okhttp3.OkHttpClient;
|
||||||
import okhttp3.Request;
|
import okhttp3.Request;
|
||||||
@@ -39,119 +49,291 @@ import okhttp3.Response;
|
|||||||
|
|
||||||
public class NotificationService extends NotificationListenerService {
|
public class NotificationService extends NotificationListenerService {
|
||||||
private static final String TAG = "NotificationService";
|
private static final String TAG = "NotificationService";
|
||||||
|
private static final int FOREGROUND_NOTIFICATION_ID = 9001;
|
||||||
|
private static final String CHANNEL_ID = "notification_listener_channel";
|
||||||
|
private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
|
||||||
|
private static final String WAKELOCK_TAG = "NotificationService:WakeLock";
|
||||||
|
|
||||||
|
private static final OkHttpClient HTTP_CLIENT = new OkHttpClient.Builder()
|
||||||
|
.connectTimeout(15, TimeUnit.SECONDS)
|
||||||
|
.writeTimeout(15, TimeUnit.SECONDS)
|
||||||
|
.readTimeout(15, TimeUnit.SECONDS)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
private RetryManager retryManager;
|
||||||
|
private PowerManager.WakeLock wakeLock;
|
||||||
|
private static boolean isMonitoring = false;
|
||||||
|
private ApiService apiService;
|
||||||
|
private TokenManager tokenManager;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate() {
|
||||||
|
super.onCreate();
|
||||||
|
createNotificationChannel();
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||||
|
startForeground(FOREGROUND_NOTIFICATION_ID, buildForegroundNotification(0, false), ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE);
|
||||||
|
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
|
startForeground(FOREGROUND_NOTIFICATION_ID, buildForegroundNotification(0, false), ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE);
|
||||||
|
} else {
|
||||||
|
startForeground(FOREGROUND_NOTIFICATION_ID, buildForegroundNotification(0, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
apiService = new ApiService(this);
|
||||||
|
tokenManager = TokenManager.getInstance(this);
|
||||||
|
|
||||||
|
retryManager = new RetryManager(this);
|
||||||
|
retryManager.setUploadCallback(this::performUpload);
|
||||||
|
retryManager.restoreFromPersistence();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||||
|
if (intent != null && "ACTION_START_MONITORING".equals(intent.getAction())) {
|
||||||
|
startMonitoring();
|
||||||
|
}
|
||||||
NotificationActivity.sendMessage("监听服务成功!");
|
NotificationActivity.sendMessage("监听服务成功!");
|
||||||
return super.onStartCommand(intent, flags, startId);
|
return START_STICKY;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean isMonitoring() {
|
||||||
|
return isMonitoring;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startMonitoring() {
|
||||||
|
if (isMonitoring) return;
|
||||||
|
isMonitoring = true;
|
||||||
|
|
||||||
|
toggleNotificationListenerService(this);
|
||||||
|
updateForegroundNotification(retryManager != null ? retryManager.getUploadedCount() : 0);
|
||||||
|
NotificationActivity.sendMessage("已开启持续监听模式");
|
||||||
|
NotificationActivity.updateUI();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void stopMonitoring(Context context) {
|
||||||
|
isMonitoring = false;
|
||||||
|
NotificationActivity.sendMessage("已停止监听服务");
|
||||||
|
NotificationActivity.updateUI();
|
||||||
|
Toast.makeText(context, "监听服务已停止", Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void acquireWakeLockForUpload() {
|
||||||
|
if (wakeLock != null && wakeLock.isHeld()) return;
|
||||||
|
PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE);
|
||||||
|
if (pm != null) {
|
||||||
|
wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_TAG);
|
||||||
|
wakeLock.acquire(30_000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void releaseWakeLockAfterUpload() {
|
||||||
|
if (wakeLock != null && wakeLock.isHeld()) {
|
||||||
|
wakeLock.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void requestStartMonitoring(Context context) {
|
||||||
|
toggleNotificationListenerService(context);
|
||||||
|
try {
|
||||||
|
Intent intent = new Intent(context, NotificationService.class);
|
||||||
|
intent.setAction("ACTION_START_MONITORING");
|
||||||
|
context.startService(intent);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "启动监听失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onNotificationPosted(StatusBarNotification sbn) {
|
public void onNotificationPosted(StatusBarNotification sbn) {
|
||||||
getSbnByNotificatinList(sbn);
|
if (sbn == null) return;
|
||||||
|
|
||||||
}
|
if (sbn.getPackageName().equals(getPackageName())) return;
|
||||||
|
|
||||||
public void getSbnByNotificatinList(StatusBarNotification sbn) {
|
|
||||||
MessageInfo messageInfo = App.getMessageByNotiList(this, sbn.getPackageName());
|
MessageInfo messageInfo = App.getMessageByNotiList(this, sbn.getPackageName());
|
||||||
if (messageInfo != null) {
|
if (messageInfo == null) return;
|
||||||
initData(messageInfo, sbn);
|
|
||||||
|
NotificationExtractor.Result result = NotificationExtractor.extract(sbn);
|
||||||
|
|
||||||
|
if (TextUtils.isEmpty(result.title) && TextUtils.isEmpty(result.content)) {
|
||||||
|
NotificationActivity.sendMessage("[" + messageInfo.getAppName() + "] 通知内容为空,跳过");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (result.title.contains("正在运行") || result.title.contains("正在监听")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
NotificationActivity.sendMessage(result.title + " " + result.content);
|
||||||
|
submitNotification(messageInfo, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String text = "";
|
private void submitNotification(MessageInfo messageInfo, NotificationExtractor.Result result) {
|
||||||
|
// 检查是否已登录
|
||||||
private void initData(MessageInfo messageInfo, StatusBarNotification sbn) {
|
if (!tokenManager.isLoggedIn()) {
|
||||||
Bundle bundle = sbn.getNotification().extras;
|
NotificationActivity.sendMessage("未登录,请先登录");
|
||||||
String title = bundle.getString(Notification.EXTRA_TITLE, "获取标题失败!");
|
return;
|
||||||
String context = bundle.getString(Notification.EXTRA_TEXT, "获取内容失败!");
|
|
||||||
if (context.equals("获取内容失败!")) {
|
|
||||||
if (sbn.getNotification().tickerText != null) {
|
|
||||||
context = sbn.getNotification().tickerText.toString();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
NotificationActivity.sendMessage(title + " " + context);
|
|
||||||
if (!text.equals(context)) {
|
// 检查是否关联了银行账户
|
||||||
if (!title.equals("获取标题失败!") && !context.equals("获取内容失败!") && !title.contains("正在运行")) {
|
if (TextUtils.isEmpty(messageInfo.getBankInfoId())) {
|
||||||
NotificationActivity.sendMessage("准备发送服务器:成功");
|
NotificationActivity.sendMessage("该应用未关联银行账户,请先配置");
|
||||||
Submit(messageInfo,title, context);
|
return;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
|
|
||||||
|
|
||||||
public void Submit(MessageInfo messageInfo,String title, String context) {
|
|
||||||
OkHttpClient client = new OkHttpClient();
|
|
||||||
SharedPreferences sharedPreferences = getSharedPreferences("server", MODE_PRIVATE);
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
JSONObject jsonObject = new JSONObject();
|
JSONObject jsonObject = new JSONObject();
|
||||||
|
// 根据文档,bankInfoId 是建议字段
|
||||||
|
jsonObject.put("bankInfoId", messageInfo.getBankInfoId());
|
||||||
|
|
||||||
|
// data 字段包含通知内容
|
||||||
|
JSONObject dataObject = new JSONObject();
|
||||||
|
dataObject.put("title", result.title);
|
||||||
|
dataObject.put("context", result.content);
|
||||||
|
dataObject.put("timestamp", result.timestamp);
|
||||||
|
jsonObject.put("data", dataObject);
|
||||||
|
|
||||||
|
// 保留原有字段用于兼容性
|
||||||
jsonObject.put("name", messageInfo.getName());
|
jsonObject.put("name", messageInfo.getName());
|
||||||
jsonObject.put("code", messageInfo.getCode());
|
jsonObject.put("code", messageInfo.getCode());
|
||||||
jsonObject.put("remark", messageInfo.getRemark());
|
jsonObject.put("remark", messageInfo.getRemark());
|
||||||
jsonObject.put("appName", messageInfo.getAppName());
|
jsonObject.put("appName", messageInfo.getAppName());
|
||||||
jsonObject.put("packageName", messageInfo.getPackageName());
|
jsonObject.put("packageName", messageInfo.getPackageName());
|
||||||
|
|
||||||
JSONObject jsonObject1 = new JSONObject();
|
String payload = jsonObject.toString();
|
||||||
jsonObject1.put("title", title+System.currentTimeMillis());
|
NotificationActivity.sendMessage("准备发送服务器:成功");
|
||||||
jsonObject1.put("context", context+System.currentTimeMillis());
|
retryManager.enqueue(payload);
|
||||||
jsonObject.put("data", jsonObject1);
|
|
||||||
|
|
||||||
String json = jsonObject.toString();
|
|
||||||
|
|
||||||
RequestBody body = RequestBody.create(json, JSON);
|
|
||||||
|
|
||||||
// 构建请求
|
|
||||||
Request request = new Request.Builder()
|
|
||||||
.url(sharedPreferences.getString("host", ""))
|
|
||||||
.post(body)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
client.newCall(request).enqueue(new Callback() {
|
|
||||||
@Override
|
|
||||||
public void onFailure(@NonNull Call call, @NonNull IOException e) {
|
|
||||||
NotificationActivity.sendMessage("提交失败:" + e);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
|
|
||||||
if (response.isSuccessful()) {
|
|
||||||
String str = response.body().string();
|
|
||||||
NotificationActivity.sendMessage(title + "提交成功:" + str);
|
|
||||||
text = context;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
NotificationActivity.sendMessage("提交失败:");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (JSONException e) {
|
} catch (JSONException e) {
|
||||||
throw new RuntimeException(e);
|
Log.e(TAG, "JSON构建失败", e);
|
||||||
|
NotificationActivity.sendMessage("JSON构建失败: " + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private void performUpload(String jsonPayload, RetryManager.UploadResultListener listener) {
|
||||||
* 监听断开
|
acquireWakeLockForUpload();
|
||||||
*/
|
|
||||||
|
// 使用新的API服务上传
|
||||||
|
apiService.uploadNotification(jsonPayload, new ApiService.ApiCallback<UploadNotificationResponse>() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(UploadNotificationResponse result) {
|
||||||
|
releaseWakeLockAfterUpload();
|
||||||
|
String message = "提交成功";
|
||||||
|
if (result.getId() != null) {
|
||||||
|
message += " (ID: " + result.getId().substring(0, Math.min(8, result.getId().length())) + "...)";
|
||||||
|
}
|
||||||
|
if (result.getDuplicateOfEmailSyncRecordId() != null) {
|
||||||
|
message += " [已去重]";
|
||||||
|
}
|
||||||
|
NotificationActivity.sendMessage(message);
|
||||||
|
listener.onSuccess();
|
||||||
|
updateForegroundNotification(retryManager.getUploadedCount());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(ApiError error) {
|
||||||
|
releaseWakeLockAfterUpload();
|
||||||
|
String errorMsg = error.getMessage();
|
||||||
|
if (error.isTokenExpired()) {
|
||||||
|
errorMsg = "登录已失效,请重新登录";
|
||||||
|
tokenManager.clear();
|
||||||
|
}
|
||||||
|
NotificationActivity.sendMessage("提交失败: " + errorMsg);
|
||||||
|
listener.onFailure(errorMsg);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onListenerDisconnected() {
|
public void onListenerDisconnected() {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
Log.w(TAG, "通知监听断开,尝试恢复");
|
||||||
// 通知侦听器断开连接 - 请求重新绑定
|
NotificationActivity.sendMessage("通知监听断开,尝试恢复...");
|
||||||
requestRebind(new ComponentName(this, NotificationListenerService.class));
|
toggleNotificationListenerService(this);
|
||||||
|
|
||||||
|
new Handler(Looper.getMainLooper()).postDelayed(() -> {
|
||||||
|
if (!isNotificationListenerEnabled()) {
|
||||||
|
notifyNlsDisabled();
|
||||||
|
}
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isNotificationListenerEnabled() {
|
||||||
|
String flat = Settings.Secure.getString(
|
||||||
|
getContentResolver(),
|
||||||
|
"enabled_notification_listeners");
|
||||||
|
if (TextUtils.isEmpty(flat)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return flat.contains(getPackageName());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void notifyNlsDisabled() {
|
||||||
|
Log.w(TAG, "通知监听已被禁用,向用户发出警告");
|
||||||
|
NotificationActivity.sendMessage("警告:通知监听已被禁用,请重新开启!");
|
||||||
|
|
||||||
|
Intent intent = new Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS);
|
||||||
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
|
startActivity(intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
super.onDestroy();
|
||||||
|
isMonitoring = false;
|
||||||
|
NotificationActivity.updateUI();
|
||||||
|
if (wakeLock != null && wakeLock.isHeld()) {
|
||||||
|
wakeLock.release();
|
||||||
|
wakeLock = null;
|
||||||
|
}
|
||||||
|
if (retryManager != null) {
|
||||||
|
retryManager.destroy();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param context 反正第二次启动失败
|
|
||||||
*/
|
|
||||||
public static void toggleNotificationListenerService(Context context) {
|
public static void toggleNotificationListenerService(Context context) {
|
||||||
|
ComponentName thisComponent = new ComponentName(context, NotificationService.class);
|
||||||
PackageManager pm = context.getPackageManager();
|
PackageManager pm = context.getPackageManager();
|
||||||
pm.setComponentEnabledSetting(new ComponentName(context, NotificationService.class),
|
pm.setComponentEnabledSetting(thisComponent,
|
||||||
PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
|
PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
|
||||||
|
PackageManager.DONT_KILL_APP);
|
||||||
pm.setComponentEnabledSetting(new ComponentName(context, NotificationService.class),
|
pm.setComponentEnabledSetting(thisComponent,
|
||||||
PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP);
|
PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
|
||||||
|
PackageManager.DONT_KILL_APP);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void createNotificationChannel() {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
NotificationChannel channel = new NotificationChannel(
|
||||||
|
CHANNEL_ID,
|
||||||
|
"通知监听服务",
|
||||||
|
NotificationManager.IMPORTANCE_LOW
|
||||||
|
);
|
||||||
|
channel.setDescription("保持通知监听服务运行");
|
||||||
|
channel.setShowBadge(false);
|
||||||
|
NotificationManager nm = getSystemService(NotificationManager.class);
|
||||||
|
if (nm != null) {
|
||||||
|
nm.createNotificationChannel(channel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Notification buildForegroundNotification(int uploadedCount, boolean monitoring) {
|
||||||
|
String title = monitoring ? "持续监听中" : "通知监听服务";
|
||||||
|
String text = monitoring
|
||||||
|
? "已上传 " + uploadedCount + " 条 · 持续运行中"
|
||||||
|
: "已上传 " + uploadedCount + " 条";
|
||||||
|
|
||||||
|
return new NotificationCompat.Builder(this, CHANNEL_ID)
|
||||||
|
.setContentTitle(title)
|
||||||
|
.setContentText(text)
|
||||||
|
.setSmallIcon(R.mipmap.app_logo)
|
||||||
|
.setOngoing(true)
|
||||||
|
.setPriority(NotificationCompat.PRIORITY_LOW)
|
||||||
|
.setCategory(NotificationCompat.CATEGORY_SERVICE)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateForegroundNotification(int uploadedCount) {
|
||||||
|
NotificationManager nm = getSystemService(NotificationManager.class);
|
||||||
|
if (nm != null) {
|
||||||
|
nm.notify(FOREGROUND_NOTIFICATION_ID, buildForegroundNotification(uploadedCount, isMonitoring));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,272 @@
|
|||||||
|
package com.miraclegarden.smsmessage.service;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
|
import android.os.SystemClock;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import org.json.JSONArray;
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传失败重试管理器(内存队列 + SharedPreferences 持久化)。
|
||||||
|
* <p>
|
||||||
|
* 主要职责:
|
||||||
|
* 1. 入队失败上传任务并立即尝试上传;
|
||||||
|
* 2. 上传失败后按延迟策略重试;
|
||||||
|
* 3. 进程重启后从本地恢复重试队列;
|
||||||
|
* 4. 统计成功上传总数,供前台通知展示。
|
||||||
|
*/
|
||||||
|
public class RetryManager {
|
||||||
|
private static final String TAG = "RetryManager";
|
||||||
|
|
||||||
|
private static final String SP_NAME = "server";
|
||||||
|
private static final String KEY_RETRY_QUEUE = "retry_queue";
|
||||||
|
private static final String KEY_UPLOADED_COUNT = "uploaded_count";
|
||||||
|
private static final String KEY_TOTAL_COUNT = "total_count";
|
||||||
|
private static final String KEY_FAILED_COUNT = "failed_count";
|
||||||
|
|
||||||
|
private static final int MAX_QUEUE_SIZE = 100;
|
||||||
|
private static final long[] RETRY_DELAYS = new long[]{2000L, 5000L, 15000L};
|
||||||
|
|
||||||
|
private final SharedPreferences sharedPreferences;
|
||||||
|
private final Handler handler;
|
||||||
|
private final List<QueueItem> retryQueue = new ArrayList<>();
|
||||||
|
|
||||||
|
private UploadCallback uploadCallback;
|
||||||
|
private int uploadedCount;
|
||||||
|
private int totalCount;
|
||||||
|
private int failedCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传抽象回调。
|
||||||
|
*/
|
||||||
|
public interface UploadCallback {
|
||||||
|
void onUpload(String jsonPayload, UploadResultListener listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传结果监听。
|
||||||
|
*/
|
||||||
|
public interface UploadResultListener {
|
||||||
|
void onSuccess();
|
||||||
|
|
||||||
|
void onFailure(String error);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重试队列元素。
|
||||||
|
*/
|
||||||
|
private static class QueueItem {
|
||||||
|
String jsonPayload;
|
||||||
|
int retryCount; // starts at 0, max 2 (3 attempts total: initial + 2 retries)
|
||||||
|
long nextRetryTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RetryManager(Context context) {
|
||||||
|
Context appContext = context.getApplicationContext();
|
||||||
|
this.sharedPreferences = appContext.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE);
|
||||||
|
this.handler = new Handler(Looper.getMainLooper());
|
||||||
|
this.uploadedCount = sharedPreferences.getInt(KEY_UPLOADED_COUNT, 0);
|
||||||
|
this.totalCount = sharedPreferences.getInt(KEY_TOTAL_COUNT, 0);
|
||||||
|
this.failedCount = sharedPreferences.getInt(KEY_FAILED_COUNT, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置上传实现。
|
||||||
|
*/
|
||||||
|
public void setUploadCallback(UploadCallback callback) {
|
||||||
|
this.uploadCallback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加任务到队列并立即尝试上传。
|
||||||
|
*/
|
||||||
|
public void enqueue(String jsonPayload) {
|
||||||
|
if (jsonPayload == null || jsonPayload.trim().isEmpty()) {
|
||||||
|
Log.w(TAG, "enqueue payload 为空,忽略");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
incrementTotalCount();
|
||||||
|
|
||||||
|
QueueItem item = new QueueItem();
|
||||||
|
item.jsonPayload = jsonPayload;
|
||||||
|
item.retryCount = 0;
|
||||||
|
item.nextRetryTime = SystemClock.elapsedRealtime();
|
||||||
|
|
||||||
|
synchronized (retryQueue) {
|
||||||
|
if (retryQueue.size() >= MAX_QUEUE_SIZE) {
|
||||||
|
QueueItem removed = retryQueue.remove(0);
|
||||||
|
Log.w(TAG, "重试队列已满,丢弃最旧任务: " + (removed == null ? "null" : removed.jsonPayload));
|
||||||
|
}
|
||||||
|
retryQueue.add(item);
|
||||||
|
persistQueue();
|
||||||
|
}
|
||||||
|
|
||||||
|
attemptUpload(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从持久化恢复重试队列并重新触发上传。
|
||||||
|
*/
|
||||||
|
public void restoreFromPersistence() {
|
||||||
|
String queueJson = sharedPreferences.getString(KEY_RETRY_QUEUE, "");
|
||||||
|
if (queueJson == null || queueJson.trim().isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<QueueItem> restoredItems = new ArrayList<>();
|
||||||
|
try {
|
||||||
|
JSONArray jsonArray = new JSONArray(queueJson);
|
||||||
|
for (int i = 0; i < jsonArray.length(); i++) {
|
||||||
|
JSONObject object = jsonArray.optJSONObject(i);
|
||||||
|
if (object == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
QueueItem item = new QueueItem();
|
||||||
|
item.jsonPayload = object.optString("jsonPayload", "");
|
||||||
|
item.retryCount = object.optInt("retryCount", 0);
|
||||||
|
item.nextRetryTime = object.optLong("nextRetryTime", SystemClock.elapsedRealtime());
|
||||||
|
|
||||||
|
if (item.jsonPayload == null || item.jsonPayload.trim().isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
restoredItems.add(item);
|
||||||
|
}
|
||||||
|
} catch (JSONException e) {
|
||||||
|
Log.e(TAG, "恢复重试队列失败", e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized (retryQueue) {
|
||||||
|
retryQueue.clear();
|
||||||
|
retryQueue.addAll(restoredItems);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (QueueItem item : new ArrayList<>(restoredItems)) {
|
||||||
|
long delay = item.nextRetryTime - SystemClock.elapsedRealtime();
|
||||||
|
if (delay > 0) {
|
||||||
|
handler.postDelayed(() -> attemptUpload(item), delay);
|
||||||
|
} else {
|
||||||
|
attemptUpload(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取累计成功上传数量。
|
||||||
|
*/
|
||||||
|
public int getUploadedCount() {
|
||||||
|
return uploadedCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTotalCount() {
|
||||||
|
return totalCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getFailedCount() {
|
||||||
|
return failedCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void incrementTotalCount() {
|
||||||
|
totalCount++;
|
||||||
|
sharedPreferences.edit()
|
||||||
|
.putInt(KEY_TOTAL_COUNT, totalCount)
|
||||||
|
.apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void incrementFailedCount() {
|
||||||
|
failedCount++;
|
||||||
|
sharedPreferences.edit()
|
||||||
|
.putInt(KEY_FAILED_COUNT, failedCount)
|
||||||
|
.apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清理回调,通常在 Service 销毁时调用。
|
||||||
|
*/
|
||||||
|
public void destroy() {
|
||||||
|
handler.removeCallbacksAndMessages(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void attemptUpload(final QueueItem item) {
|
||||||
|
if (item == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uploadCallback == null) {
|
||||||
|
Log.w(TAG, "uploadCallback 未设置,无法上传");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uploadCallback.onUpload(item.jsonPayload, new UploadResultListener() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess() {
|
||||||
|
synchronized (retryQueue) {
|
||||||
|
retryQueue.remove(item);
|
||||||
|
persistQueue();
|
||||||
|
}
|
||||||
|
uploadedCount++;
|
||||||
|
sharedPreferences.edit()
|
||||||
|
.putInt(KEY_UPLOADED_COUNT, uploadedCount)
|
||||||
|
.apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(String error) {
|
||||||
|
synchronized (retryQueue) {
|
||||||
|
if (!retryQueue.contains(item)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.retryCount < 3) {
|
||||||
|
long delay = RETRY_DELAYS[Math.min(item.retryCount, RETRY_DELAYS.length - 1)];
|
||||||
|
item.retryCount++;
|
||||||
|
item.nextRetryTime = SystemClock.elapsedRealtime() + delay;
|
||||||
|
handler.postDelayed(() -> attemptUpload(item), delay);
|
||||||
|
persistQueue();
|
||||||
|
Log.w(TAG, "上传失败,准备重试。retryCount=" + item.retryCount + ", error=" + error);
|
||||||
|
} else {
|
||||||
|
retryQueue.remove(item);
|
||||||
|
persistQueue();
|
||||||
|
incrementFailedCount();
|
||||||
|
Log.e(TAG, "上传失败达到上限,丢弃任务: " + error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void persistQueue() {
|
||||||
|
JSONArray jsonArray = new JSONArray();
|
||||||
|
synchronized (retryQueue) {
|
||||||
|
Iterator<QueueItem> iterator = retryQueue.iterator();
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
QueueItem item = iterator.next();
|
||||||
|
JSONObject object = new JSONObject();
|
||||||
|
try {
|
||||||
|
object.put("jsonPayload", item.jsonPayload);
|
||||||
|
object.put("retryCount", item.retryCount);
|
||||||
|
object.put("nextRetryTime", item.nextRetryTime);
|
||||||
|
jsonArray.put(object);
|
||||||
|
} catch (JSONException e) {
|
||||||
|
Log.e(TAG, "序列化重试项失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sharedPreferences.edit()
|
||||||
|
.putString(KEY_RETRY_QUEUE, jsonArray.toString())
|
||||||
|
.apply();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,147 @@
|
|||||||
|
package com.miraclegarden.smsmessage.util;
|
||||||
|
|
||||||
|
import android.content.ActivityNotFoundException;
|
||||||
|
import android.content.ComponentName;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.PowerManager;
|
||||||
|
import android.provider.Settings;
|
||||||
|
|
||||||
|
public class OEMBackgroundHelper {
|
||||||
|
|
||||||
|
private static final String MIUI_SECURITY = "com.miui.securitycenter";
|
||||||
|
private static final String MIUI_AUTOSTART = "com.miui.permcenter.autostart.AutoStartManagementActivity";
|
||||||
|
|
||||||
|
private static final String COLOROS_SAFECENTER = "com.coloros.safecenter";
|
||||||
|
private static final String COLOROS_STARTUP = "com.coloros.safecenter.permission.startup.StartupAppListActivity";
|
||||||
|
|
||||||
|
private static final String OPPO_SAFE = "com.oppo.safe";
|
||||||
|
private static final String OPPO_STARTUP = "com.oppo.safe.permission.startup.StartupAppListActivity";
|
||||||
|
|
||||||
|
private static final String VIVO_PERMISSION = "com.vivo.permissionmanager";
|
||||||
|
private static final String VIVO_BGSTARTUP = "com.vivo.permissionmanager.activity.BgStartUpManagerActivity";
|
||||||
|
|
||||||
|
private static final String IQOO_SECURE = "com.iqoo.secure";
|
||||||
|
private static final String IQOO_BGSTARTUP = "com.iqoo.secure.ui.phoneoptimize.BgStartUpManager";
|
||||||
|
|
||||||
|
private static final String HUAWEI_SYSTEM_MANAGER = "com.huawei.systemmanager";
|
||||||
|
private static final String HUAWEI_STARTUP_CTRL = "com.huawei.systemmanager.startupmgr.ui.StartupNormalAppListActivity";
|
||||||
|
private static final String HUAWEI_POWER_MANAGER = "com.huawei.systemmanager.power.ui.HwPowerManagerActivity";
|
||||||
|
|
||||||
|
public static void openAutoStartSettings(Context context) {
|
||||||
|
String manufacturer = Build.MANUFACTURER.toLowerCase();
|
||||||
|
|
||||||
|
Intent intent = new Intent();
|
||||||
|
boolean success = false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (manufacturer.contains("xiaomi") || manufacturer.contains("redmi")) {
|
||||||
|
intent.setComponent(new ComponentName(MIUI_SECURITY, MIUI_AUTOSTART));
|
||||||
|
success = true;
|
||||||
|
} else if (manufacturer.contains("oppo")) {
|
||||||
|
if (tryStartActivity(context, intent, COLOROS_SAFECENTER, COLOROS_STARTUP)) return;
|
||||||
|
if (tryStartActivity(context, intent, OPPO_SAFE, OPPO_STARTUP)) return;
|
||||||
|
} else if (manufacturer.contains("realme")) {
|
||||||
|
if (tryStartActivity(context, intent, COLOROS_SAFECENTER, COLOROS_STARTUP)) return;
|
||||||
|
} else if (manufacturer.contains("vivo")) {
|
||||||
|
if (tryStartActivity(context, intent, VIVO_PERMISSION, VIVO_BGSTARTUP)) return;
|
||||||
|
if (tryStartActivity(context, intent, IQOO_SECURE, IQOO_BGSTARTUP)) return;
|
||||||
|
} else if (manufacturer.contains("huawei") || manufacturer.contains("honor")) {
|
||||||
|
if (tryStartActivity(context, intent, HUAWEI_SYSTEM_MANAGER, HUAWEI_STARTUP_CTRL)) return;
|
||||||
|
if (tryStartActivity(context, intent, HUAWEI_SYSTEM_MANAGER, HUAWEI_POWER_MANAGER)) return;
|
||||||
|
} else if (manufacturer.contains("oneplus")) {
|
||||||
|
openBatteryOptimizationSettings(context);
|
||||||
|
return;
|
||||||
|
} else if (manufacturer.contains("samsung")) {
|
||||||
|
openAppBatterySettings(context);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
context.startActivity(intent);
|
||||||
|
} else {
|
||||||
|
openBatteryOptimizationSettings(context);
|
||||||
|
}
|
||||||
|
} catch (ActivityNotFoundException e) {
|
||||||
|
openBatteryOptimizationSettings(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void openBatteryOptimizationSettings(Context context) {
|
||||||
|
try {
|
||||||
|
Intent intent = new Intent(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS);
|
||||||
|
context.startActivity(intent);
|
||||||
|
} catch (ActivityNotFoundException e) {
|
||||||
|
Intent intent = new Intent(Settings.ACTION_SETTINGS);
|
||||||
|
context.startActivity(intent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void requestBatteryOptimizationExemption(Context context) {
|
||||||
|
if (!isIgnoringBatteryOptimizations(context)) {
|
||||||
|
try {
|
||||||
|
Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
|
||||||
|
intent.setData(Uri.parse("package:" + context.getPackageName()));
|
||||||
|
context.startActivity(intent);
|
||||||
|
} catch (ActivityNotFoundException e) {
|
||||||
|
openBatteryOptimizationSettings(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isIgnoringBatteryOptimizations(Context context) {
|
||||||
|
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
|
||||||
|
return pm.isIgnoringBatteryOptimizations(context.getPackageName());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getManufacturer() {
|
||||||
|
return Build.MANUFACTURER;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean needsAutoStartPermission() {
|
||||||
|
String manufacturer = Build.MANUFACTURER.toLowerCase();
|
||||||
|
return manufacturer.contains("xiaomi") || manufacturer.contains("redmi")
|
||||||
|
|| manufacturer.contains("oppo") || manufacturer.contains("realme")
|
||||||
|
|| manufacturer.contains("vivo") || manufacturer.contains("huawei")
|
||||||
|
|| manufacturer.contains("honor") || manufacturer.contains("oneplus");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getAutoStartPermissionName() {
|
||||||
|
String manufacturer = Build.MANUFACTURER.toLowerCase();
|
||||||
|
if (manufacturer.contains("xiaomi") || manufacturer.contains("redmi")) {
|
||||||
|
return "自启动";
|
||||||
|
} else if (manufacturer.contains("oppo") || manufacturer.contains("realme")) {
|
||||||
|
return "自动启动";
|
||||||
|
} else if (manufacturer.contains("vivo")) {
|
||||||
|
return "后台启动";
|
||||||
|
} else if (manufacturer.contains("huawei") || manufacturer.contains("honor")) {
|
||||||
|
return "自动启动管理";
|
||||||
|
} else if (manufacturer.contains("oneplus")) {
|
||||||
|
return "电池优化";
|
||||||
|
}
|
||||||
|
return "电池/自启动";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean tryStartActivity(Context context, Intent intent, String pkg, String cls) {
|
||||||
|
try {
|
||||||
|
intent.setComponent(new ComponentName(pkg, cls));
|
||||||
|
context.startActivity(intent);
|
||||||
|
return true;
|
||||||
|
} catch (ActivityNotFoundException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void openAppBatterySettings(Context context) {
|
||||||
|
try {
|
||||||
|
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
|
||||||
|
intent.setData(Uri.parse("package:" + context.getPackageName()));
|
||||||
|
context.startActivity(intent);
|
||||||
|
} catch (ActivityNotFoundException e) {
|
||||||
|
Intent intent = new Intent(Settings.ACTION_SETTINGS);
|
||||||
|
context.startActivity(intent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
139
app/src/main/res/layout/activity_bank_edit.xml
Normal file
139
app/src/main/res/layout/activity_bank_edit.xml
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:background="#F5F5F5">
|
||||||
|
|
||||||
|
<!-- 顶部栏 -->
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="56dp"
|
||||||
|
android:background="@color/purple_500"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingStart="16dp"
|
||||||
|
android:paddingEnd="16dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/back_iv"
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:src="@drawable/ic_action_back" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_title"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:text="添加银行"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_username"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="用户"
|
||||||
|
android:textColor="#E0E0E0"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="16dp"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:background="@color/white"
|
||||||
|
android:padding="16dp"
|
||||||
|
android:elevation="2dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="银行名称"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:textColor="#666666" />
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/et_bank_name"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:background="@drawable/pass_word_bg"
|
||||||
|
android:hint="如:工商银行、招商银行"
|
||||||
|
android:paddingStart="12dp"
|
||||||
|
android:paddingEnd="12dp"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textColor="@color/black"
|
||||||
|
android:inputType="text" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:text="账户"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:textColor="#666666" />
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/et_account"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:background="@drawable/pass_word_bg"
|
||||||
|
android:hint="请输入账户"
|
||||||
|
android:paddingStart="12dp"
|
||||||
|
android:paddingEnd="12dp"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textColor="@color/black"
|
||||||
|
android:inputType="text" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:text="备注(可选)"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:textColor="#666666" />
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/et_remark"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:background="@drawable/pass_word_bg"
|
||||||
|
android:hint="请输入备注"
|
||||||
|
android:paddingStart="12dp"
|
||||||
|
android:paddingEnd="12dp"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textColor="@color/black"
|
||||||
|
android:inputType="text" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btn_save"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:layout_marginTop="24dp"
|
||||||
|
android:background="@color/purple_500"
|
||||||
|
android:text="保存"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="16sp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/progress_bar"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layout_marginTop="32dp"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
134
app/src/main/res/layout/activity_bank_list.xml
Normal file
134
app/src/main/res/layout/activity_bank_list.xml
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="#F5F5F5">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<!-- 顶部栏 -->
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="56dp"
|
||||||
|
android:background="@color/purple_500"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingStart="16dp"
|
||||||
|
android:paddingEnd="16dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/back_iv"
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:src="@drawable/ic_action_back" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:text="银行账户列表"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_username"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="用户"
|
||||||
|
android:textColor="#E0E0E0"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- 提示信息 -->
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="12dp"
|
||||||
|
android:background="#FFF3E0"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:padding="12dp"
|
||||||
|
android:gravity="center_vertical">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="20dp"
|
||||||
|
android:layout_height="20dp"
|
||||||
|
android:src="@android:drawable/ic_dialog_info"
|
||||||
|
android:tint="#FF9800" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:text="如需添加或修改银行账户,请前往浏览器访问 Web 管理界面"
|
||||||
|
android:textColor="#E65100"
|
||||||
|
android:textSize="13sp" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- 列表区域 -->
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/recyclerview"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:padding="8dp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- Loading 遮罩层 -->
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/loading_ly"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="#80FFFFFF"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:visibility="gone">
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="48dp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:text="加载中..."
|
||||||
|
android:textColor="#666666"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- 空状态 -->
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/empty_ly"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:visibility="gone">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="暂无银行账户"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textColor="#999999" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:text="请前往浏览器访问 Web 管理界面添加"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:textColor="#FF9800" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
131
app/src/main/res/layout/activity_change_password.xml
Normal file
131
app/src/main/res/layout/activity_change_password.xml
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:background="#F5F5F5">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="56dp"
|
||||||
|
android:background="@color/white"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingStart="16dp"
|
||||||
|
android:paddingEnd="16dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/back_iv"
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:src="@drawable/ic_action_back"
|
||||||
|
android:contentDescription="返回" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:text="修改密码"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:textColor="@color/black"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0.5dp"
|
||||||
|
android:background="#DDDDDD" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="16dp"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:background="@color/white"
|
||||||
|
android:padding="16dp"
|
||||||
|
android:elevation="2dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="旧密码"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:textColor="#666666" />
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/et_old_password"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:background="@drawable/pass_word_bg"
|
||||||
|
android:hint="请输入旧密码"
|
||||||
|
android:paddingStart="12dp"
|
||||||
|
android:paddingEnd="12dp"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textColor="@color/black"
|
||||||
|
android:inputType="textPassword" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:text="新密码"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:textColor="#666666" />
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/et_new_password"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:background="@drawable/pass_word_bg"
|
||||||
|
android:hint="请输入新密码(至少6位)"
|
||||||
|
android:paddingStart="12dp"
|
||||||
|
android:paddingEnd="12dp"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textColor="@color/black"
|
||||||
|
android:inputType="textPassword" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:text="确认新密码"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:textColor="#666666" />
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/et_confirm_password"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:background="@drawable/pass_word_bg"
|
||||||
|
android:hint="请再次输入新密码"
|
||||||
|
android:paddingStart="12dp"
|
||||||
|
android:paddingEnd="12dp"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textColor="@color/black"
|
||||||
|
android:inputType="textPassword" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btn_submit"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:layout_marginTop="24dp"
|
||||||
|
android:background="@color/purple_500"
|
||||||
|
android:text="提交"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="16sp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/progress_bar"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layout_marginTop="32dp"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
110
app/src/main/res/layout/activity_login.xml
Normal file
110
app/src/main/res/layout/activity_login.xml
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:background="#F5F5F5">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="56dp"
|
||||||
|
android:background="@color/white"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingStart="16dp"
|
||||||
|
android:paddingEnd="16dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/back_iv"
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:src="@drawable/ic_action_back"
|
||||||
|
android:contentDescription="返回" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:text="用户登录"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:textColor="@color/black"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0.5dp"
|
||||||
|
android:background="#DDDDDD" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="16dp"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:background="@color/white"
|
||||||
|
android:padding="16dp"
|
||||||
|
android:elevation="2dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="用户名"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:textColor="#666666" />
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/et_username"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:background="@drawable/pass_word_bg"
|
||||||
|
android:hint="请输入用户名"
|
||||||
|
android:paddingStart="12dp"
|
||||||
|
android:paddingEnd="12dp"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textColor="@color/black"
|
||||||
|
android:inputType="text" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:text="密码"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:textColor="#666666" />
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/et_password"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:background="@drawable/pass_word_bg"
|
||||||
|
android:hint="请输入密码"
|
||||||
|
android:paddingStart="12dp"
|
||||||
|
android:paddingEnd="12dp"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textColor="@color/black"
|
||||||
|
android:inputType="textPassword" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btn_login"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:layout_marginTop="24dp"
|
||||||
|
android:background="@color/purple_500"
|
||||||
|
android:text="登录"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="16sp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/progress_bar"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layout_marginTop="32dp"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
@@ -1,63 +1,290 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:orientation="vertical">
|
android:fillViewport="true"
|
||||||
|
android:background="#F5F5F5">
|
||||||
<com.google.android.material.appbar.AppBarLayout
|
|
||||||
android:id="@+id/top_ab"
|
|
||||||
android:visibility="gone"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
tools:ignore="MissingConstraints">
|
|
||||||
|
|
||||||
<androidx.appcompat.widget.Toolbar
|
|
||||||
android:id="@+id/toolbar"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
app:title="@string/app_name"
|
|
||||||
app:titleTextColor="@color/white" />
|
|
||||||
</com.google.android.material.appbar.AppBarLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_below="@id/top_ab"
|
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:padding="10dp">
|
android:padding="16dp">
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
<!-- 用户信息卡片 -->
|
||||||
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:hint="服务器地址:http://xxx.xxx.xxx/xxx"
|
android:background="@color/white"
|
||||||
tools:ignore="HardcodedText">
|
android:orientation="horizontal"
|
||||||
|
android:padding="16dp"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:elevation="2dp">
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
<ImageView
|
||||||
android:id="@+id/host"
|
android:layout_width="48dp"
|
||||||
android:text="https://www.judy88.xin/api/bills/app-upload"
|
android:layout_height="48dp"
|
||||||
android:layout_width="match_parent"
|
android:src="@mipmap/app_logo"
|
||||||
android:layout_height="wrap_content" />
|
android:contentDescription="用户头像" />
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_marginStart="12dp"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_username"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="用户"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textColor="@color/black"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="已登录"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:textColor="#4CAF50" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btn_logout"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="36dp"
|
||||||
|
android:text="退出"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:backgroundTint="#FF6666" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- 功能按钮区 -->
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btn_start_monitoring"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="56dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:text="开始监听"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:backgroundTint="@color/purple_500" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btn_bank_manage"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="56dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:text="银行账户"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:backgroundTint="#FF3700B3" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- 权限设置 -->
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="24dp"
|
||||||
|
android:layout_marginBottom="12dp"
|
||||||
|
android:text="权限设置"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:textColor="#333333" />
|
||||||
|
|
||||||
|
<!-- 通知访问权限 -->
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/permission_notification_access"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@color/white"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:padding="16dp"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:elevation="1dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/icon_notification_access"
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:src="@android:drawable/ic_dialog_info"
|
||||||
|
app:tint="@color/purple_500" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_marginStart="12dp"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="通知访问权限"
|
||||||
|
android:textSize="15sp"
|
||||||
|
android:textColor="#333333" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/status_notification_access"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="点击授权"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:textColor="#888888" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="20dp"
|
||||||
|
android:layout_height="20dp"
|
||||||
|
android:src="@drawable/ic_action_next"
|
||||||
|
app:tint="#888888" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- 通知消息权限 -->
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/permission_post_notifications"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:background="@color/white"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:padding="16dp"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:elevation="1dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/icon_post_notifications"
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:src="@android:drawable/ic_dialog_info"
|
||||||
|
app:tint="@color/purple_500" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_marginStart="12dp"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="通知消息权限"
|
||||||
|
android:textSize="15sp"
|
||||||
|
android:textColor="#333333" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/status_post_notifications"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="点击授权"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:textColor="#888888" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="20dp"
|
||||||
|
android:layout_height="20dp"
|
||||||
|
android:src="@drawable/ic_action_next"
|
||||||
|
app:tint="#888888" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- 电池优化 -->
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/permission_battery"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:background="@color/white"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:padding="16dp"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:elevation="1dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/icon_battery"
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:src="@android:drawable/ic_dialog_info"
|
||||||
|
app:tint="@color/purple_500" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_marginStart="12dp"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="电池优化白名单"
|
||||||
|
android:textSize="15sp"
|
||||||
|
android:textColor="#333333" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/status_battery"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="点击设置"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:textColor="#888888" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="20dp"
|
||||||
|
android:layout_height="20dp"
|
||||||
|
android:src="@drawable/ic_action_next"
|
||||||
|
app:tint="#888888" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- 厂商设置 -->
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="24dp"
|
||||||
|
android:layout_marginBottom="12dp"
|
||||||
|
android:text="厂商设置"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:textColor="#333333" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_device_brand"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="12dp"
|
||||||
|
android:text="检测到设备"
|
||||||
|
android:textColor="#666666"
|
||||||
|
android:textSize="12sp" />
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/yes"
|
android:id="@+id/btn_autostart"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="保存参数"
|
android:text="开启自启动权限"
|
||||||
tools:ignore="HardcodedText" />
|
android:textColor="@color/white"
|
||||||
</LinearLayout>
|
android:backgroundTint="#FF3700B3" />
|
||||||
|
|
||||||
<LinearLayout
|
<Button
|
||||||
android:layout_width="match_parent"
|
android:id="@+id/btn_battery_optimization"
|
||||||
android:layout_height="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:gravity="center"
|
android:layout_height="wrap_content"
|
||||||
android:background="@color/purple_500">
|
android:layout_marginTop="8dp"
|
||||||
<ImageView
|
android:text="关闭电池优化"
|
||||||
android:layout_width="120dp"
|
android:textColor="@color/white"
|
||||||
android:layout_height="120dp"
|
android:backgroundTint="#FF3700B3" />
|
||||||
android:src="@mipmap/app_logo" />
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</RelativeLayout>
|
</LinearLayout>
|
||||||
|
</ScrollView>
|
||||||
|
|||||||
@@ -1,91 +1,197 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical"
|
||||||
|
android:background="#F5F5F5">
|
||||||
|
|
||||||
<com.google.android.material.appbar.AppBarLayout
|
<!-- 顶部栏 -->
|
||||||
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="56dp"
|
||||||
|
android:background="@color/purple_500"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingStart="16dp"
|
||||||
|
android:paddingEnd="16dp">
|
||||||
|
|
||||||
<androidx.appcompat.widget.Toolbar
|
<ImageView
|
||||||
android:id="@+id/toolbar"
|
android:id="@+id/iv_back"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:src="@drawable/ic_action_back" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
app:title="@string/app_name"
|
android:layout_marginStart="16dp"
|
||||||
app:titleTextColor="@color/white" />
|
android:text="监听控制台"
|
||||||
</com.google.android.material.appbar.AppBarLayout>
|
android:textColor="@color/white"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_username"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="用户"
|
||||||
|
android:textColor="#E0E0E0"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- 状态栏 -->
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:background="@color/white"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingStart="16dp"
|
||||||
|
android:paddingEnd="16dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_status"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="状态: 未启动"
|
||||||
|
android:textColor="#888888"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btn_settings"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="36dp"
|
||||||
|
android:text="设置"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:backgroundTint="#FF3700B3" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- 统计栏 -->
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical"
|
android:layout_marginStart="16dp"
|
||||||
android:padding="5dp">
|
android:layout_marginEnd="16dp"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
android:background="@color/white"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:padding="12dp">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="match_parent"
|
android:id="@+id/tv_configured_apps"
|
||||||
android:layout_height="50dp"
|
android:layout_width="0dp"
|
||||||
android:gravity="center_vertical"
|
android:layout_height="wrap_content"
|
||||||
android:text="监听App列表"
|
android:layout_weight="1"
|
||||||
android:paddingStart="5dp"
|
android:gravity="center"
|
||||||
android:drawableTint="@color/black"
|
android:text="已配置: 0个"
|
||||||
android:drawableEnd="@drawable/ic_action_next"
|
android:textColor="#333333"
|
||||||
android:id="@+id/jianting_list"
|
android:textSize="14sp" />
|
||||||
android:textColor="@color/black" />
|
|
||||||
<View
|
<View
|
||||||
android:layout_width="match_parent"
|
android:layout_width="1dp"
|
||||||
android:layout_height="1px"
|
android:layout_height="20dp"
|
||||||
android:background="#DDDDDD" />
|
android:background="#DDDDDD" />
|
||||||
<LinearLayout
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_monitored_count"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="监听: 0"
|
||||||
|
android:textColor="#333333"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="1dp"
|
||||||
|
android:layout_height="20dp"
|
||||||
|
android:background="#DDDDDD" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_uploaded_count"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="上传: 0"
|
||||||
|
android:textColor="#333333"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="1dp"
|
||||||
|
android:layout_height="20dp"
|
||||||
|
android:background="#DDDDDD" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_success_rate"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="成功率: --"
|
||||||
|
android:textColor="#333333"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- 日志区域 -->
|
||||||
|
<androidx.core.widget.NestedScrollView
|
||||||
|
android:id="@+id/scroll_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:layout_marginBottom="16dp"
|
||||||
|
android:background="@color/white"
|
||||||
|
android:fillViewport="true"
|
||||||
|
android:padding="12dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_log"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="horizontal">
|
android:textColor="#333333"
|
||||||
|
android:textSize="13sp"
|
||||||
|
android:fontFamily="monospace"
|
||||||
|
android:textIsSelectable="true" />
|
||||||
|
</androidx.core.widget.NestedScrollView>
|
||||||
|
|
||||||
<Button
|
<!-- 底部按钮 -->
|
||||||
android:id="@+id/button2"
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_margin="5dp"
|
android:background="@color/white"
|
||||||
android:layout_weight="1"
|
android:orientation="horizontal"
|
||||||
android:text="开启监听"
|
android:padding="16dp">
|
||||||
android:textColor="@color/white"
|
|
||||||
tools:ignore="HardcodedText" />
|
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/button"
|
android:id="@+id/btn_clear_log"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="48dp"
|
||||||
android:layout_margin="5dp"
|
android:layout_weight="1"
|
||||||
android:layout_weight="1"
|
android:layout_marginEnd="8dp"
|
||||||
android:visibility="gone"
|
android:text="清空日志"
|
||||||
android:text="测试接口"
|
android:textColor="#666666"
|
||||||
android:textColor="@color/white"
|
android:backgroundTint="#EEEEEE" />
|
||||||
tools:ignore="HardcodedText" />
|
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/button1"
|
android:id="@+id/btn_toggle_monitor"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="48dp"
|
||||||
android:layout_margin="5dp"
|
android:layout_weight="1"
|
||||||
android:layout_weight="1"
|
android:layout_marginStart="8dp"
|
||||||
android:text="清理数据"
|
android:text="开始监听"
|
||||||
android:textColor="@color/white"
|
android:textColor="@color/white"
|
||||||
tools:ignore="HardcodedText" />
|
android:backgroundTint="@color/purple_500" />
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<androidx.core.widget.NestedScrollView
|
|
||||||
android:id="@+id/scrollable"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:fillViewport="true">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/text"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:textIsSelectable="true" />
|
|
||||||
</androidx.core.widget.NestedScrollView>
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</LinearLayout>
|
|
||||||
|
</LinearLayout>
|
||||||
|
|||||||
260
app/src/main/res/layout/activity_permission.xml
Normal file
260
app/src/main/res/layout/activity_permission.xml
Normal file
@@ -0,0 +1,260 @@
|
|||||||
|
<?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:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:background="#F5F5F5">
|
||||||
|
|
||||||
|
<!-- 顶部栏 -->
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="56dp"
|
||||||
|
android:background="@color/purple_500"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingStart="16dp"
|
||||||
|
android:paddingEnd="16dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/iv_back"
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:src="@drawable/ic_action_back" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:text="权限设置"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_username"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="用户"
|
||||||
|
android:textColor="#E0E0E0"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<ScrollView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:fillViewport="true"
|
||||||
|
android:padding="16dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<!-- 应用权限 -->
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="12dp"
|
||||||
|
android:text="应用权限"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:textColor="#333333" />
|
||||||
|
|
||||||
|
<!-- 通知访问权限 -->
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/layout_notification_access"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@color/white"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:padding="16dp"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:foreground="?attr/selectableItemBackground">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/icon_notification_access"
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:src="@android:drawable/ic_dialog_info"
|
||||||
|
app:tint="@color/purple_500" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_marginStart="12dp"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="通知访问权限"
|
||||||
|
android:textSize="15sp"
|
||||||
|
android:textColor="#333333" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/status_notification_access"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="点击授权"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:textColor="#888888" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="20dp"
|
||||||
|
android:layout_height="20dp"
|
||||||
|
android:src="@drawable/ic_action_next"
|
||||||
|
app:tint="#888888" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- 通知消息权限 -->
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/layout_post_notifications"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@color/white"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:padding="16dp"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:foreground="?attr/selectableItemBackground">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/icon_post_notifications"
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:src="@android:drawable/ic_dialog_info"
|
||||||
|
app:tint="@color/purple_500" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_marginStart="12dp"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="通知消息权限"
|
||||||
|
android:textSize="15sp"
|
||||||
|
android:textColor="#333333" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/status_post_notifications"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="点击授权"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:textColor="#888888" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="20dp"
|
||||||
|
android:layout_height="20dp"
|
||||||
|
android:src="@drawable/ic_action_next"
|
||||||
|
app:tint="#888888" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- 电池优化 -->
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/layout_battery"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@color/white"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:padding="16dp"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:layout_marginBottom="16dp"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:foreground="?attr/selectableItemBackground">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/icon_battery"
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:src="@android:drawable/ic_dialog_info"
|
||||||
|
app:tint="@color/purple_500" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_marginStart="12dp"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="电池优化白名单"
|
||||||
|
android:textSize="15sp"
|
||||||
|
android:textColor="#333333" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/status_battery"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="点击设置"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:textColor="#888888" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="20dp"
|
||||||
|
android:layout_height="20dp"
|
||||||
|
android:src="@drawable/ic_action_next"
|
||||||
|
app:tint="#888888" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- 厂商设置 -->
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="12dp"
|
||||||
|
android:text="厂商设置"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:textColor="#333333" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_device_brand"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="12dp"
|
||||||
|
android:text="检测到设备"
|
||||||
|
android:textColor="#666666"
|
||||||
|
android:textSize="12sp" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btn_autostart"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="开启自启动权限"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:backgroundTint="#FF3700B3" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btn_battery_optimization"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:text="关闭电池优化"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:backgroundTint="#FF3700B3" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
</ScrollView>
|
||||||
|
</LinearLayout>
|
||||||
@@ -1,123 +1,101 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical"
|
||||||
|
android:background="#F5F5F5">
|
||||||
<com.google.android.material.appbar.AppBarLayout
|
|
||||||
android:id="@+id/top_ab"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
tools:ignore="MissingConstraints">
|
|
||||||
|
|
||||||
<androidx.appcompat.widget.Toolbar
|
|
||||||
android:id="@+id/toolbar"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
app:title=""
|
|
||||||
app:titleTextColor="@color/white" >
|
|
||||||
<ImageView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:id="@+id/back_iv"
|
|
||||||
android:src="@drawable/ic_action_back"/>
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="设置"
|
|
||||||
android:id="@+id/title_tv"
|
|
||||||
android:textSize="18sp"
|
|
||||||
android:textColor="@color/white"/>
|
|
||||||
</androidx.appcompat.widget.Toolbar>
|
|
||||||
</com.google.android.material.appbar.AppBarLayout>
|
|
||||||
|
|
||||||
|
<!-- 顶部栏 -->
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="56dp"
|
||||||
android:layout_below="@id/top_ab"
|
android:background="@color/purple_500"
|
||||||
android:orientation="vertical"
|
android:gravity="center_vertical"
|
||||||
android:padding="10dp">
|
android:orientation="horizontal"
|
||||||
<LinearLayout
|
android:paddingStart="16dp"
|
||||||
android:id="@+id/set_post_ly"
|
android:paddingEnd="16dp">
|
||||||
android:layout_width="match_parent"
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/iv_back"
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:src="@drawable/ic_action_back" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:visibility="gone"
|
android:layout_marginStart="16dp"
|
||||||
android:orientation="vertical">
|
android:text="监听APP管理"
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
android:textColor="@color/white"
|
||||||
android:layout_width="match_parent"
|
android:textSize="18sp"
|
||||||
android:layout_height="wrap_content"
|
android:textStyle="bold" />
|
||||||
android:hint="服务器地址:"
|
|
||||||
tools:ignore="HardcodedText">
|
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
<View
|
||||||
android:id="@+id/host"
|
android:layout_width="0dp"
|
||||||
android:layout_width="match_parent"
|
android:layout_height="wrap_content"
|
||||||
android:layout_height="wrap_content" />
|
android:layout_weight="1" />
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/yes"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="保存"
|
|
||||||
android:textColor="@color/white"
|
|
||||||
tools:ignore="HardcodedText" />
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="?actionBarSize"
|
|
||||||
android:orientation="horizontal">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:text="监听App列表"
|
|
||||||
android:textColor="@color/black" />
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/add_bt"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="添加"
|
|
||||||
android:textColor="@color/white"
|
|
||||||
tools:ignore="HardcodedText" />
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<androidx.appcompat.widget.LinearLayoutCompat
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<View
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="1px"
|
|
||||||
android:background="#DDDDDD" />
|
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
|
||||||
android:id="@+id/recyclerview"
|
|
||||||
android:visibility="gone"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent" />
|
|
||||||
<TextView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:drawableTop="@mipmap/no_data"
|
|
||||||
android:id="@+id/nodata_iv"
|
|
||||||
android:visibility="gone"
|
|
||||||
android:textColor="@color/black"
|
|
||||||
android:textSize="16sp"
|
|
||||||
android:gravity="center_horizontal"
|
|
||||||
android:drawablePadding="10dp"
|
|
||||||
android:text="暂无监听"/>
|
|
||||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_username"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="用户"
|
||||||
|
android:textColor="#E0E0E0"
|
||||||
|
android:textSize="14sp" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- 添加按钮 -->
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="56dp"
|
||||||
|
android:background="@color/white"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingStart="16dp"
|
||||||
|
android:paddingEnd="16dp">
|
||||||
|
|
||||||
</RelativeLayout>
|
<TextView
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="已监听的APP"
|
||||||
|
android:textColor="#333333"
|
||||||
|
android:textSize="16sp" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btn_add_app"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="36dp"
|
||||||
|
android:text="添加"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:backgroundTint="#FF3700B3" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- 列表区域 -->
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginTop="8dp">
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/recyclerview"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:visibility="visible" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_empty"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:drawableTop="@mipmap/no_data"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="暂无监听的APP\n点击右上角添加"
|
||||||
|
android:textColor="#999999"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:visibility="gone" />
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|||||||
@@ -1,89 +1,101 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:orientation="vertical">
|
android:background="#F5F5F5">
|
||||||
|
|
||||||
<com.google.android.material.appbar.AppBarLayout
|
<!-- 顶部栏 -->
|
||||||
android:id="@+id/top_ab"
|
<LinearLayout
|
||||||
|
android:id="@+id/header"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="56dp"
|
||||||
|
android:background="@color/purple_500"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingStart="16dp"
|
||||||
|
android:paddingEnd="16dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/back_iv"
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:src="@drawable/ic_action_back" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:text="选择要监听的APP"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_username"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="用户"
|
||||||
|
android:textColor="#E0E0E0"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/recyclerview"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
tools:ignore="MissingConstraints">
|
android:layout_below="@id/header"
|
||||||
|
android:paddingStart="8dp"
|
||||||
<androidx.appcompat.widget.Toolbar
|
android:paddingEnd="8dp" />
|
||||||
android:id="@+id/toolbar"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
app:titleTextColor="@color/white" >
|
|
||||||
<ImageView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:id="@+id/back_iv"
|
|
||||||
android:src="@drawable/ic_action_back"/>
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="本机App列表"
|
|
||||||
android:textSize="18sp"
|
|
||||||
android:textColor="@color/white"/>
|
|
||||||
</androidx.appcompat.widget.Toolbar>
|
|
||||||
</com.google.android.material.appbar.AppBarLayout>
|
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_below="@id/top_ab"
|
|
||||||
android:paddingStart="5dp"
|
|
||||||
android:paddingEnd="5dp"
|
|
||||||
android:id="@+id/recyclerview"/>
|
|
||||||
|
|
||||||
<androidx.appcompat.widget.LinearLayoutCompat
|
<androidx.appcompat.widget.LinearLayoutCompat
|
||||||
android:id="@+id/one_ly"
|
android:id="@+id/one_ly"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_below="@id/recyclerview"
|
android:layout_below="@id/recyclerview"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical"
|
||||||
|
android:visibility="gone">
|
||||||
|
|
||||||
<View
|
<View
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="1px"
|
android:layout_height="1px"
|
||||||
android:layout_marginStart="5dp"
|
android:layout_marginStart="8dp"
|
||||||
android:layout_marginEnd="5dp"
|
android:layout_marginEnd="8dp"
|
||||||
android:background="#DDDDDD"/>
|
android:background="#DDDDDD" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="获取所有app列表失败 你可以:"
|
android:text="获取所有app列表失败"
|
||||||
android:layout_marginTop="10dp"
|
android:layout_marginTop="10dp"
|
||||||
android:textStyle="bold"
|
android:textStyle="bold"
|
||||||
android:layout_marginStart="10dp"
|
android:layout_marginStart="16dp"
|
||||||
android:textColor="@color/black"
|
android:textColor="#333333" />
|
||||||
android:gravity="center_vertical"/>
|
|
||||||
<ImageView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_margin="5dp"
|
|
||||||
android:src="@mipmap/get_list"
|
|
||||||
android:adjustViewBounds="true"/>
|
|
||||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/loading_ly"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="@color/white"
|
||||||
|
android:layout_below="@id/header"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
<LinearLayout
|
<ProgressBar
|
||||||
android:layout_width="match_parent"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="wrap_content" />
|
||||||
android:background="@color/white"
|
|
||||||
android:layout_below="@id/top_ab"
|
|
||||||
android:gravity="center"
|
|
||||||
android:id="@+id/loading_ly"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<ProgressBar
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_centerHorizontal="true"
|
android:layout_marginTop="16dp"
|
||||||
android:indeterminateDrawable="@drawable/pass_word_bg1" />
|
android:text="加载中..."
|
||||||
|
android:textColor="#666666" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
</RelativeLayout>
|
||||||
|
|
||||||
</RelativeLayout>
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<androidx.appcompat.widget.LinearLayoutCompat
|
<androidx.appcompat.widget.LinearLayoutCompat
|
||||||
android:layout_width="285dp"
|
android:layout_width="320dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
@@ -21,6 +21,7 @@
|
|||||||
android:lineSpacingExtra="4dp"
|
android:lineSpacingExtra="4dp"
|
||||||
android:textColor="@color/black"
|
android:textColor="@color/black"
|
||||||
android:textSize="16sp" />
|
android:textSize="16sp" />
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
@@ -63,52 +64,33 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:gravity="center_vertical"
|
android:gravity="center_vertical"
|
||||||
android:paddingStart="10dp"
|
android:paddingStart="12dp"
|
||||||
android:paddingEnd="10dp"
|
android:paddingEnd="12dp"
|
||||||
|
android:paddingBottom="12dp"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<EditText
|
<TextView
|
||||||
android:id="@+id/tv_name"
|
android:layout_width="wrap_content"
|
||||||
android:layout_width="match_parent"
|
android:layout_height="wrap_content"
|
||||||
android:layout_height="35dp"
|
android:text="关联银行账户"
|
||||||
android:textSize="12sp"
|
android:textSize="12sp"
|
||||||
android:text=""
|
android:textColor="#666666" />
|
||||||
android:hint="请输入名称"
|
|
||||||
android:paddingStart="10dp"
|
|
||||||
android:paddingEnd="10dp"
|
|
||||||
android:textColorHint="#333333"
|
|
||||||
android:background="@drawable/pass_word_bg"
|
|
||||||
android:textColor="@color/black" />
|
|
||||||
|
|
||||||
<EditText
|
<Spinner
|
||||||
android:id="@+id/tv_code"
|
android:id="@+id/spinner_bank"
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="35dp"
|
|
||||||
android:textSize="12sp"
|
|
||||||
android:layout_marginTop="10dp"
|
|
||||||
android:background="@drawable/pass_word_bg"
|
|
||||||
android:text=""
|
|
||||||
android:hint="请输入Code"
|
|
||||||
android:paddingStart="10dp"
|
|
||||||
android:textColorHint="#333333"
|
|
||||||
|
|
||||||
android:paddingEnd="10dp"
|
|
||||||
android:inputType="number"
|
|
||||||
android:textColor="@color/black" />
|
|
||||||
|
|
||||||
<EditText
|
|
||||||
android:id="@+id/tv_remark"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="40dp"
|
android:layout_height="40dp"
|
||||||
android:textSize="12sp"
|
android:layout_marginTop="6dp"
|
||||||
android:layout_marginTop="10dp"
|
android:background="@drawable/pass_word_bg" />
|
||||||
android:background="@drawable/pass_word_bg"
|
|
||||||
android:hint="请输入备注"
|
<TextView
|
||||||
android:textColorHint="#333333"
|
android:layout_width="wrap_content"
|
||||||
android:paddingStart="10dp"
|
android:layout_height="wrap_content"
|
||||||
android:paddingEnd="10dp"
|
android:layout_marginTop="8dp"
|
||||||
android:text=""
|
android:text="从银行账户列表选择关联的银行"
|
||||||
android:textColor="@color/black" />
|
android:textSize="11sp"
|
||||||
|
android:textColor="#999999" />
|
||||||
|
|
||||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||||
|
|
||||||
<View
|
<View
|
||||||
@@ -155,4 +137,4 @@
|
|||||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||||
|
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|||||||
109
app/src/main/res/layout/item_bank.xml
Normal file
109
app/src/main/res/layout/item_bank.xml
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:background="@color/white"
|
||||||
|
android:layout_margin="4dp"
|
||||||
|
android:elevation="1dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:padding="12dp"
|
||||||
|
android:gravity="center_vertical">
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="40dp"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
android:background="#F3E5F5">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:text="银"
|
||||||
|
android:textColor="@color/purple_500"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_marginStart="12dp"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:gravity="center_vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_bank_name"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="银行名称"
|
||||||
|
android:textSize="15sp"
|
||||||
|
android:textColor="#333333"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:text="|"
|
||||||
|
android:textColor="#CCCCCC"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_account"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:text="账户"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:textColor="#666666" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_remark"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="2dp"
|
||||||
|
android:text="备注"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:textColor="#999999"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_created_at"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="创建: --"
|
||||||
|
android:textSize="11sp"
|
||||||
|
android:textColor="#AAAAAA" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_updated_at"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="更新: --"
|
||||||
|
android:textSize="11sp"
|
||||||
|
android:textColor="#AAAAAA" />
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
@@ -5,44 +5,40 @@
|
|||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:gravity="center_vertical">
|
android:gravity="center_vertical">
|
||||||
|
|
||||||
<LinearLayout
|
<RelativeLayout
|
||||||
|
android:id="@+id/layout_big"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:id="@+id/layout_big"
|
|
||||||
android:paddingTop="12dp"
|
android:paddingTop="12dp"
|
||||||
android:paddingBottom="12dp">
|
android:paddingBottom="12dp"
|
||||||
|
android:paddingStart="16dp"
|
||||||
|
android:paddingEnd="16dp">
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/iv_icon"
|
android:id="@+id/iv_icon"
|
||||||
android:layout_width="70dp"
|
android:layout_width="56dp"
|
||||||
android:layout_height="70dp"
|
android:layout_height="56dp"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
android:src="@mipmap/app_logo" />
|
android:src="@mipmap/app_logo" />
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
|
android:id="@+id/content_layout"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical"
|
android:layout_toEndOf="@id/iv_icon"
|
||||||
android:layout_marginStart="12dp">
|
android:layout_toStartOf="@id/delete_img"
|
||||||
<LinearLayout
|
android:layout_centerVertical="true"
|
||||||
android:layout_width="match_parent"
|
android:layout_marginStart="12dp"
|
||||||
|
android:layout_marginEnd="12dp"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_appname"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="horizontal">
|
android:textSize="16sp"
|
||||||
<TextView
|
android:text="111111"
|
||||||
android:id="@+id/tv_appname"
|
android:textStyle="bold" />
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:textSize="16sp"
|
|
||||||
android:text="111111"
|
|
||||||
android:textStyle="bold" />
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/delete_img"
|
|
||||||
android:visibility="invisible"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:src="@mipmap/delete_img"/>
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/tv_package"
|
android:id="@+id/tv_package"
|
||||||
@@ -52,46 +48,74 @@
|
|||||||
android:text="2222222"
|
android:text="2222222"
|
||||||
android:textColor="#888888" />
|
android:textColor="#888888" />
|
||||||
|
|
||||||
<androidx.appcompat.widget.LinearLayoutCompat
|
<!-- 银行信息区域 - 突出显示 -->
|
||||||
android:layout_width="match_parent"
|
<LinearLayout
|
||||||
android:layout_height="25dp"
|
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:paddingStart="5dp"
|
|
||||||
android:id="@+id/show_more"
|
android:id="@+id/show_more"
|
||||||
android:paddingEnd="5dp"
|
android:layout_width="match_parent"
|
||||||
android:background="@drawable/pass_word_bg"
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="6dp"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:background="@drawable/shape_dialog_bg3"
|
||||||
android:orientation="horizontal">
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="银行: "
|
||||||
|
android:textSize="13sp"
|
||||||
|
android:textColor="@color/purple_500"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/tv_name"
|
android:id="@+id/tv_name"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:textSize="12sp"
|
android:textSize="13sp"
|
||||||
android:text="11"
|
android:text="--"
|
||||||
android:textColor="#888888" />
|
android:textColor="@color/purple_500"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:text=" | 账户: "
|
||||||
|
android:textSize="13sp"
|
||||||
|
android:textColor="#666666" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/tv_code"
|
android:id="@+id/tv_code"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:textSize="12sp"
|
android:textSize="13sp"
|
||||||
android:text="22"
|
android:text="--"
|
||||||
android:textColor="#888888" />
|
android:textColor="#333333" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/tv_remark"
|
android:id="@+id/tv_remark"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
android:textSize="12sp"
|
android:textSize="12sp"
|
||||||
android:text="33"
|
android:text=""
|
||||||
android:textColor="#888888" />
|
android:textColor="#999999" />
|
||||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
</LinearLayout>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</LinearLayout>
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/delete_img"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentEnd="true"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:visibility="invisible"
|
||||||
|
android:src="@mipmap/delete_img"/>
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
<View
|
<View
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="1px"
|
android:layout_height="1px"
|
||||||
android:background="#DDDDDD"/>
|
android:background="#DDDDDD"/>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|||||||
51
app/src/main/res/layout/layout_header.xml
Normal file
51
app/src/main/res/layout/layout_header.xml
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="56dp"
|
||||||
|
android:background="@color/purple_500"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingStart="16dp"
|
||||||
|
android:paddingEnd="16dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/iv_back"
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:src="@drawable/ic_action_back"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_title"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="标题"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_username"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="用户"
|
||||||
|
android:textColor="#E0E0E0"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:visibility="gone" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/iv_setting"
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:src="@android:drawable/ic_menu_preferences"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
<!-- Base application theme. -->
|
<!-- Base application theme. -->
|
||||||
<style name="Theme.SmsMessage" parent="Theme.MaterialComponents.DayNight.NoActionBar">
|
<style name="Theme.SmsMessage" parent="Theme.MaterialComponents.Light.NoActionBar">
|
||||||
<!-- Primary brand color. -->
|
<!-- Primary brand color. -->
|
||||||
<item name="colorPrimary">@color/purple_200</item>
|
<item name="colorPrimary">@color/purple_200</item>
|
||||||
<item name="colorPrimaryVariant">@color/purple_700</item>
|
<item name="colorPrimaryVariant">@color/purple_700</item>
|
||||||
@@ -10,11 +10,16 @@
|
|||||||
<item name="colorSecondaryVariant">@color/teal_200</item>
|
<item name="colorSecondaryVariant">@color/teal_200</item>
|
||||||
<item name="colorOnSecondary">@color/black</item>
|
<item name="colorOnSecondary">@color/black</item>
|
||||||
<!-- Status bar color. -->
|
<!-- Status bar color. -->
|
||||||
<item name="android:statusBarColor">?attr/colorOnPrimary</item>
|
<item name="android:statusBarColor">@color/white</item>
|
||||||
<!-- Customize your theme here. -->
|
<!-- Customize your theme here. -->
|
||||||
<item name="android:navigationBarColor">@color/purple_500</item>
|
<item name="android:navigationBarColor">@color/white</item>
|
||||||
<item name="android:windowBackground">@color/purple_500</item>
|
<item name="android:windowBackground">@color/white</item>
|
||||||
<item name="android:windowIsTranslucent">true</item>
|
<item name="android:windowIsTranslucent">false</item>
|
||||||
|
<!-- Status bar text/icon color for light background -->
|
||||||
|
<item name="android:windowLightStatusBar">true</item>
|
||||||
|
<!-- Default text color for light theme -->
|
||||||
|
<item name="android:textColorPrimary">#333333</item>
|
||||||
|
<item name="android:textColorSecondary">#666666</item>
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
<!-- Base application theme. -->
|
<!-- Base application theme. -->
|
||||||
<style name="Theme.SmsMessage" parent="Theme.MaterialComponents.DayNight.NoActionBar">
|
<style name="Theme.SmsMessage" parent="Theme.MaterialComponents.Light.NoActionBar">
|
||||||
<!-- Primary brand color. -->
|
<!-- Primary brand color. -->
|
||||||
<item name="colorPrimary">@color/purple_500</item>
|
<item name="colorPrimary">@color/purple_500</item>
|
||||||
<item name="colorPrimaryVariant">@color/purple_500</item>
|
<item name="colorPrimaryVariant">@color/purple_500</item>
|
||||||
@@ -10,10 +10,16 @@
|
|||||||
<item name="colorSecondaryVariant">@color/teal_700</item>
|
<item name="colorSecondaryVariant">@color/teal_700</item>
|
||||||
<item name="colorOnSecondary">@color/black</item>
|
<item name="colorOnSecondary">@color/black</item>
|
||||||
<!-- Status bar color. -->
|
<!-- Status bar color. -->
|
||||||
<item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
|
<item name="android:statusBarColor">@color/white</item>
|
||||||
<!-- Customize your theme here. -->
|
<!-- Customize your theme here. -->
|
||||||
<item name="android:navigationBarColor">@color/purple_500</item>
|
<item name="android:navigationBarColor">@color/white</item>
|
||||||
<item name="android:windowIsTranslucent">true</item>
|
<item name="android:windowBackground">@color/white</item>
|
||||||
|
<item name="android:windowIsTranslucent">false</item>
|
||||||
|
<!-- Status bar text/icon color for light background -->
|
||||||
|
<item name="android:windowLightStatusBar">true</item>
|
||||||
|
<!-- Default text color for light theme -->
|
||||||
|
<item name="android:textColorPrimary">#333333</item>
|
||||||
|
<item name="android:textColorSecondary">#666666</item>
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
<style name="Theme.SmsMessage1" parent="Theme.MaterialComponents.DayNight.NoActionBar">
|
<style name="Theme.SmsMessage1" parent="Theme.MaterialComponents.DayNight.NoActionBar">
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||||
plugins {
|
plugins {
|
||||||
id 'com.android.application' version '7.3.0' apply false
|
id 'com.android.application' version '8.4.0' apply false
|
||||||
id 'com.android.library' version '7.3.0' apply false
|
id 'com.android.library' version '8.4.0' apply false
|
||||||
}
|
}
|
||||||
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,6 @@
|
|||||||
#Thu Jan 12 14:47:56 CST 2023
|
#Thu Jan 12 14:47:56 CST 2023
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionUrl=https://mirrors.cloud.tencent.com/gradle/gradle-7.5.1-bin.zip
|
distributionUrl=https://mirrors.cloud.tencent.com/gradle/gradle-8.7-bin.zip
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
|||||||
@@ -4,11 +4,11 @@ plugins {
|
|||||||
|
|
||||||
android {
|
android {
|
||||||
namespace 'com.miraclegarden.library'
|
namespace 'com.miraclegarden.library'
|
||||||
compileSdk 32
|
compileSdk 34
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdk 21
|
minSdk 21
|
||||||
targetSdk 32
|
targetSdk 34
|
||||||
|
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
consumerProguardFiles "consumer-rules.pro"
|
consumerProguardFiles "consumer-rules.pro"
|
||||||
@@ -25,13 +25,16 @@ android {
|
|||||||
targetCompatibility JavaVersion.VERSION_17
|
targetCompatibility JavaVersion.VERSION_17
|
||||||
}
|
}
|
||||||
|
|
||||||
|
buildFeatures {
|
||||||
|
viewBinding true
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation 'androidx.databinding:viewbinding:7.3.0'
|
implementation 'androidx.appcompat:appcompat:1.7.0'
|
||||||
implementation 'androidx.appcompat:appcompat:1.5.0'
|
implementation 'com.google.android.material:material:1.12.0'
|
||||||
implementation 'com.google.android.material:material:1.6.1'
|
|
||||||
testImplementation 'junit:junit:4.13.2'
|
testImplementation 'junit:junit:4.13.2'
|
||||||
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
|
androidTestImplementation 'androidx.test.ext:junit:1.2.1'
|
||||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
|
androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1'
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user