```
├── .github/
├── FUNDING.yml
├── ISSUE_TEMPLATE/
├── bug_report.yml (300 tokens)
├── config.yml
├── feature_request.yml (200 tokens)
├── workflows/
├── Build-Apk.yml (300 tokens)
├── Build-Release.yml (700 tokens)
├── .gitignore (100 tokens)
├── CHANGELOG.md (100 tokens)
├── LICENSE (omitted)
├── README.md (700 tokens)
├── app/
├── .gitignore
├── build.gradle.kts (1700 tokens)
├── proguard-rules.pro
├── schemas/
├── li.songe.gkd.db.AppDb/
├── 1.json (1400 tokens)
├── 10.json (2.3k tokens)
├── 11.json (2.1k tokens)
├── 12.json (2.1k tokens)
├── 13.json (2.2k tokens)
├── 14.json (2.5k tokens)
├── 2.json (1500 tokens)
├── 3.json (1700 tokens)
├── 4.json (1800 tokens)
├── 5.json (1800 tokens)
├── 6.json (1800 tokens)
├── 7.json (2k tokens)
├── 8.json (2.1k tokens)
├── 9.json (2.1k tokens)
├── src/
├── androidTest/
├── kotlin/
├── li/
├── songe/
├── gkd/
├── ExampleInstrumentedTest.kt (100 tokens)
├── gkd/
├── AndroidManifest.xml (100 tokens)
├── main/
├── AndroidManifest.xml (2.6k tokens)
├── aidl/
├── li/
├── songe/
├── gkd/
├── shizuku/
├── CommandResult.aidl
├── IUserService.aidl (100 tokens)
├── kotlin/
├── com/
├── google/
├── android/
├── accessibility/
├── selecttospeak/
├── SelectToSpeakService.kt
├── li/
├── songe/
├── gkd/
├── App.kt (1800 tokens)
├── MainActivity.kt (4.5k tokens)
├── MainViewModel.kt (2.8k tokens)
├── OpenFileActivity.kt
├── OpenSchemeActivity.kt (100 tokens)
├── OpenTileActivity.kt (200 tokens)
├── a11y/
├── A11yCommonImpl.kt (100 tokens)
├── A11yContext.kt (3.9k tokens)
├── A11yExt.kt (900 tokens)
├── A11yFeat.kt (1500 tokens)
├── A11yRuleEngine.kt (3.8k tokens)
├── A11yState.kt (2.1k tokens)
├── data/
├── A11yEventLog.kt (600 tokens)
├── ActionLog.kt (900 tokens)
├── ActivityLog.kt (400 tokens)
├── AppConfig.kt (400 tokens)
├── AppInfo.kt (800 tokens)
├── AppRule.kt (300 tokens)
├── AppVisitLog.kt (400 tokens)
├── AttrInfo.kt (500 tokens)
├── BaseSnapshot.kt
├── CategoryConfig.kt (500 tokens)
├── ComplexSnapshot.kt (200 tokens)
├── CrashData.kt (200 tokens)
├── DeviceInfo.kt (100 tokens)
├── GithubPoliciesAsset.kt (100 tokens)
├── GkdAction.kt (2.1k tokens)
├── GlobalRule.kt (600 tokens)
├── NodeInfo.kt (1500 tokens)
├── RawSubscription.kt (8.8k tokens)
├── ResolvedGroup.kt (200 tokens)
├── ResolvedRule.kt (1800 tokens)
├── RpcError.kt (100 tokens)
├── Snapshot.kt (400 tokens)
├── SubsConfig.kt (1900 tokens)
├── SubsItem.kt (500 tokens)
├── SubsVersion.kt
├── UserInfo.kt (100 tokens)
├── Value.kt
├── db/
├── AppDb.kt (800 tokens)
├── notif/
├── Notif.kt (900 tokens)
├── NotifChannel.kt (200 tokens)
├── StopServiceReceiver.kt (300 tokens)
├── permission/
├── PermissionDialog.kt (400 tokens)
├── PermissionState.kt (2.1k tokens)
├── service/
├── A11yService.kt (1400 tokens)
├── ActivityService.kt (1200 tokens)
├── ActivityTileService.kt (100 tokens)
├── BaseTileService.kt (300 tokens)
├── ButtonService.kt (400 tokens)
├── ButtonTileService.kt (100 tokens)
├── EventService.kt (1800 tokens)
├── EventTileService.kt (100 tokens)
├── ExposeService.kt (500 tokens)
├── GkdTileService.kt (1500 tokens)
├── HttpService.kt (2k tokens)
├── HttpTileService.kt (100 tokens)
├── MatchTileService.kt (100 tokens)
├── OverlayWindowService.kt (2.5k tokens)
├── ScreenshotService.kt (400 tokens)
├── SnapshotTileService.kt (400 tokens)
├── StatusService.kt (1400 tokens)
├── TrackService.kt (2.8k tokens)
├── shizuku/
├── AccessibilityManager.kt (100 tokens)
├── ActivityManager.kt (400 tokens)
├── ActivityTaskManager.kt (200 tokens)
├── AppOpsService.kt (500 tokens)
├── AutomationService.kt (1100 tokens)
├── CommandResult.kt (100 tokens)
├── HiddenApiType.kt (500 tokens)
├── HiddenCast.kt (300 tokens)
├── InputManager.kt (300 tokens)
├── InputShellCommand.kt (1900 tokens)
├── PackageManager.kt (600 tokens)
├── ProxyUiAutomationConnection.kt (2.2k tokens)
├── ShizukuApi.kt (1800 tokens)
├── TaskStackListener.kt (400 tokens)
├── UserManager.kt (200 tokens)
├── UserService.kt (1600 tokens)
├── WindowManager.kt (100 tokens)
├── store/
├── SettingsStore.kt (600 tokens)
├── StorageExt.kt (600 tokens)
├── StoreExt.kt (500 tokens)
├── ui/
├── A11yEventLogPage.kt (2.9k tokens)
├── A11yEventLogVm.kt (100 tokens)
├── A11yScopeAppListPage.kt (2.5k tokens)
├── A11yScopeAppListVm.kt (400 tokens)
├── AboutPage.kt (3.2k tokens)
├── AboutVm.kt
├── ActionLogPage.kt (4.2k tokens)
├── ActionLogVm.kt (400 tokens)
├── ActivityLogPage.kt (1900 tokens)
├── ActivityLogVm.kt (100 tokens)
├── AdvancedPage.kt (5.2k tokens)
├── AdvancedVm.kt (100 tokens)
├── AppConfigPage.kt (3.5k tokens)
├── AppConfigVm.kt (1700 tokens)
├── AppOpsAllowPage.kt (1300 tokens)
├── AppOpsAllowVm.kt (100 tokens)
├── AuthA11yPage.kt (3.3k tokens)
├── AuthA11yVm.kt (100 tokens)
├── BlockA11yAppListPage.kt (2.9k tokens)
├── BlockA11yAppListVm.kt (400 tokens)
├── CrashReportPage.kt (800 tokens)
├── CrashReportVm.kt (100 tokens)
├── EditBlockAppListPage.kt (600 tokens)
├── EditBlockAppListVm.kt (200 tokens)
├── ImagePreviewPage.kt (4.2k tokens)
├── SlowGroupPage.kt (1200 tokens)
├── SnapshotPage.kt (3.2k tokens)
├── SnapshotVm.kt
├── SubsAppGroupListPage.kt (2.7k tokens)
├── SubsAppGroupListVm.kt (200 tokens)
├── SubsAppListPage.kt (1900 tokens)
├── SubsAppListVm.kt (1000 tokens)
├── SubsCategoryGroupPage.kt (2.9k tokens)
├── SubsCategoryGroupVm.kt (400 tokens)
├── SubsCategoryPage.kt (2.3k tokens)
├── SubsCategoryVm.kt (100 tokens)
├── SubsGlobalGroupExcludePage.kt (3.6k tokens)
├── SubsGlobalGroupExcludeVm.kt (600 tokens)
├── SubsGlobalGroupListPage.kt (2.4k tokens)
├── SubsGlobalGroupListVm.kt (200 tokens)
├── UpsertRuleGroupPage.kt (1300 tokens)
├── UpsertRuleGroupVm.kt (2.1k tokens)
├── WebViewPage.kt (1900 tokens)
├── component/
├── AnimatedBooleanContent.kt (200 tokens)
├── AnimatedIcon.kt (400 tokens)
├── Animation.kt (300 tokens)
├── AnimationFloatingActionButton.kt (700 tokens)
├── AppBarTextField.kt (800 tokens)
├── AppCheckBoxCard.kt (400 tokens)
├── AppIcon.kt (200 tokens)
├── AppNameText.kt (900 tokens)
├── AuthButtonGroup.kt (200 tokens)
├── AuthCard.kt (300 tokens)
├── CopyTextCard.kt (400 tokens)
├── CustomIconButton.kt (500 tokens)
├── CustomOutlinedTextField.kt (1300 tokens)
├── DialogOptions.kt (1000 tokens)
├── EmptyText.kt (100 tokens)
├── FixedTimeText.kt (400 tokens)
├── FullscreenDialog.kt (400 tokens)
├── GroupNameText.kt (600 tokens)
├── Hooks.kt (1100 tokens)
├── InnerDisableSwitch.kt (400 tokens)
├── InputSubsLinkOption.kt (1000 tokens)
├── ManualAuthDialog.kt (800 tokens)
├── MenuExt.kt (500 tokens)
├── ModifierExt.kt
├── MultiTextField.kt (600 tokens)
├── PerfCheckbox.kt (200 tokens)
├── PerfIcon.kt (1700 tokens)
├── PerfSwitch.kt (200 tokens)
├── PerfTopAppBar.kt (400 tokens)
├── QueryPkgTipCard.kt (400 tokens)
├── RotatingLoadingIcon.kt (400 tokens)
├── RuleGroupCard.kt (2.7k tokens)
├── RuleGroupDialog.kt (1800 tokens)
├── RuleGroupState.kt (3.7k tokens)
├── ScaffoldDialog.kt (300 tokens)
├── SettingItem.kt (700 tokens)
├── ShareLogDlg.kt (600 tokens)
├── SubsAppCard.kt (600 tokens)
├── SubsItemCard.kt (1800 tokens)
├── SubsSheet.kt (3.9k tokens)
├── TermsAcceptDialog.kt (700 tokens)
├── TextDialog.kt (300 tokens)
├── TextListDialog.kt (300 tokens)
├── TextMenu.kt (700 tokens)
├── TextSwitch.kt (800 tokens)
├── TooltipIconButtonBox.kt (200 tokens)
├── TowLineText.kt (300 tokens)
├── TriStateIcon.kt (1000 tokens)
├── TriStateSwitch.kt (6.3k tokens)
├── UploadOptions.kt (1100 tokens)
├── home/
├── AppListPage.kt (3.5k tokens)
├── ControlPage.kt (3.6k tokens)
├── HomePage.kt (500 tokens)
├── HomeVm.kt (500 tokens)
├── ScaffoldExt.kt (100 tokens)
├── SettingsPage.kt (5.9k tokens)
├── SubsManagePage.kt (4.4k tokens)
├── icon/
├── BackCloseIcon.kt (1200 tokens)
├── DragPan.kt (400 tokens)
├── LockOpenRight.kt (600 tokens)
├── ResetSettings.kt (600 tokens)
├── SportsBasketball.kt (1000 tokens)
├── ToggleMid.kt (500 tokens)
├── share/
├── AppFilter.kt (1300 tokens)
├── BaseViewModel.kt (400 tokens)
├── FixedWindowInsets.kt (100 tokens)
├── ListPlaceholder.kt
├── LocalExt.kt (100 tokens)
├── ModifierExt.kt (100 tokens)
├── StateExt.kt (400 tokens)
├── style/
├── Color.kt (600 tokens)
├── Padding.kt (300 tokens)
├── TextTransformation.kt (300 tokens)
├── Theme.kt (1200 tokens)
├── util/
├── AndroidTarget.kt (300 tokens)
├── AppInfoState.kt (2000 tokens)
├── BackupUtils.kt (1000 tokens)
├── BarUtils.kt (400 tokens)
├── CollectionExt.kt (100 tokens)
├── Constants.kt (300 tokens)
├── CoroutineExt.kt (400 tokens)
├── FlowExt.kt (100 tokens)
├── FolderExt.kt (900 tokens)
├── Github.kt (1800 tokens)
├── ImageUtils.kt (700 tokens)
├── IntentExt.kt (1000 tokens)
├── KeyboardUtils.kt (500 tokens)
├── LifecycleCallbacks.kt (600 tokens)
├── LinkLoad.kt
├── LoadStatus.kt (100 tokens)
├── LogUtils.kt (800 tokens)
├── MutexState.kt (400 tokens)
├── NetworkExt.kt (200 tokens)
├── NetworkUtils.kt
├── Option.kt (900 tokens)
├── Others.kt (1400 tokens)
├── ScreenUtils.kt (200 tokens)
├── ScreenshotUtil.kt (800 tokens)
├── Singleton.kt (200 tokens)
├── SnapshotExt.kt (2.2k tokens)
├── SubsState.kt (4k tokens)
├── TimeExt.kt (400 tokens)
├── Toast.kt (1300 tokens)
├── Unit.kt (100 tokens)
├── Upgrade.kt (2.1k tokens)
├── UriUtils.kt (100 tokens)
├── ZipUtils.kt (600 tokens)
├── res/
├── drawable/
├── ic_anim_logo.xml (1300 tokens)
├── ic_anim_search_close.xml (800 tokens)
├── ic_capture.xml (200 tokens)
├── ic_event_list.xml (100 tokens)
├── ic_flash_off.xml (100 tokens)
├── ic_flash_on.xml (100 tokens)
├── ic_http.xml (100 tokens)
├── ic_launcher.xml (100 tokens)
├── ic_launcher_background.xml (100 tokens)
├── ic_launcher_foreground.xml (300 tokens)
├── ic_layers.xml (100 tokens)
├── ic_page_info.xml (200 tokens)
├── ic_radio_button.xml (100 tokens)
├── ic_status.xml (300 tokens)
├── mipmap/
├── gkd_black.png
├── gkd_white.png
├── values-night/
├── colors.xml
├── themes.xml
├── values/
├── colors.xml (100 tokens)
├── strings.xml (100 tokens)
├── themes.xml (300 tokens)
├── xml/
├── ab_desc.xml (200 tokens)
├── file_paths.xml
├── network_security_config.xml
├── test/
├── kotlin/
├── li/
├── songe/
├── gkd/
├── ExampleUnitTest.kt
├── build.gradle.kts (700 tokens)
├── gradle.properties
├── gradle/
├── libs.versions.toml (1200 tokens)
├── wrapper/
├── gradle-wrapper.jar
├── gradle-wrapper.properties
├── gradlew (1200 tokens)
├── gradlew.bat (500 tokens)
├── hidden_api/
├── .gitignore
├── build.gradle.kts
├── src/
├── main/
├── AndroidManifest.xml
├── java/
├── android/
├── accessibilityservice/
├── AccessibilityServiceInfoHidden.java (100 tokens)
├── IAccessibilityServiceClient.java (100 tokens)
├── app/
├── AppOpsManagerHidden.java (400 tokens)
├── IActivityManager.java (300 tokens)
├── IActivityTaskManager.java (200 tokens)
├── IApplicationThread.java
├── ITaskStackListener.java (100 tokens)
├── IUiAutomationConnection.java (400 tokens)
├── TaskInfoHidden.java (100 tokens)
├── UiAutomationHidden.java (100 tokens)
├── WindowConfiguration.java (100 tokens)
├── content/
├── ContextHidden.java (100 tokens)
├── pm/
├── IPackageManager.java (300 tokens)
├── PackageInfoHidden.java
├── PackageInfoList.java
├── ParceledListSlice.java (200 tokens)
├── UserInfo.java
├── res/
├── ConfigurationHidden.java (100 tokens)
├── hardware/
├── input/
├── IInputManager.java (100 tokens)
├── InputManagerHidden.java (100 tokens)
├── os/
├── IUserManager.java (100 tokens)
├── view/
├── IWindowManager.java (300 tokens)
├── KeyEventHidden.java (100 tokens)
├── MotionEventHidden.java (200 tokens)
├── SurfaceControlHidden.java (300 tokens)
├── accessibility/
├── AccessibilityNodeInfoHidden.java (100 tokens)
├── IAccessibilityManager.java (200 tokens)
├── window/
├── ScreenCapture.java (200 tokens)
├── ScreenCaptureInternal.java (100 tokens)
├── com/
├── android/
├── internal/
├── R.java (100 tokens)
├── app/
├── IAppOpsService.java (100 tokens)
├── selector/
├── .gitignore
├── build.gradle.kts (100 tokens)
├── src/
├── commonMain/
├── kotlin/
├── li/
├── songe/
├── selector/
├── Exception.kt (400 tokens)
├── FastQuery.kt (200 tokens)
├── MatchOption.kt
├── QueryContext.kt (300 tokens)
├── QueryPath.kt (100 tokens)
├── QueryResult.kt (900 tokens)
├── Selector.kt (300 tokens)
├── Stringify.kt
├── Transform.kt (1500 tokens)
├── TypeInfo.kt (200 tokens)
├── connect/
├── ConnectExpression.kt (100 tokens)
├── ConnectOperator.kt (700 tokens)
├── ConnectSegment.kt (200 tokens)
├── ConnectWrapper.kt (300 tokens)
├── PolynomialExpression.kt (700 tokens)
├── TupleExpression.kt (200 tokens)
├── parser/
├── AstNode.kt (200 tokens)
├── AstParser.kt (1900 tokens)
├── BaseParser.kt (1200 tokens)
├── ConnectParser.kt (1000 tokens)
├── PropertyParser.kt (2k tokens)
├── SelectorParser.kt (1100 tokens)
├── property/
├── BinaryExpression.kt (200 tokens)
├── CompareOperator.kt (2.1k tokens)
├── Expression.kt (100 tokens)
├── LogicalExpression.kt (400 tokens)
├── LogicalOperator.kt (300 tokens)
├── NotExpression.kt (100 tokens)
├── PropertySegment.kt (600 tokens)
├── PropertyUnit.kt (100 tokens)
├── PropertyWrapper.kt (300 tokens)
├── ValueExpression.kt (1400 tokens)
├── toMatches.kt (100 tokens)
├── unit/
├── LogicalSelectorExpression.kt (500 tokens)
├── NotSelectorExpression.kt (300 tokens)
├── SelectorExpression.kt (200 tokens)
├── SelectorLogicalOperator.kt (700 tokens)
├── UnitSelectorExpression.kt (1900 tokens)
├── util.kt (2.2k tokens)
├── jsMain/
├── kotlin/
├── li/
├── songe/
├── selector/
├── toMatches.js.kt (300 tokens)
├── jvmMain/
├── kotlin/
├── li/
├── songe/
├── selector/
├── toMatches.jvm.kt (100 tokens)
├── jvmTest/
├── kotlin/
├── li/
├── songe/
├── selector/
├── ParserUnitTest.kt (1300 tokens)
├── QueryUnitTest.kt (800 tokens)
├── Snapshot.kt (900 tokens)
├── TypeUnitTest.kt
├── settings.gradle.kts (200 tokens)
├── stability_config.conf
```
## /.github/FUNDING.yml
```yml path="/.github/FUNDING.yml"
# These are supported funding model platforms
# github: [lisonge]
custom: ['https://github.com/lisonge/sponsor']
```
## /.github/ISSUE_TEMPLATE/bug_report.yml
```yml path="/.github/ISSUE_TEMPLATE/bug_report.yml"
name: 错误反馈 / Bug report
title: "[BUG] "
description: 反馈你遇到的错误 / Report the bug you encountered
labels: ["pending triage"]
body:
- type: markdown
attributes:
value: |
## 感谢您花时间填写,在提交问题之前,请确保您完成以下操作
1. 请 **确保** 您已经查阅了 [GKD 官方文档](https://gkd.li) 以及 [常见问题](https://gkd.li/guide/faq)
2. 请 **判断** 是不是第三方规则订阅的问题,如果是,你应该向规则提供者反馈,而不是在这里提交,**此处只接受 GKD 应用本体的问题**
3. 请 **确保** 已有的 [问题](https://github.com/gkd-kit/gkd/issues?q=is%3Aissue) 或 [讨论](https://github.com/orgs/gkd-kit/discussions?discussions_q=) 中没有人提交过相似问题,否则请在该问题下进行讨论
4. 请 **确保** 你的问题能在 [releases](https://github.com/gkd-kit/gkd/releases/latest) 发布的最新版本(包含测试版本)上复现 (如果不是请先更新到最新版本复现后再提交问题)
- type: textarea
id: log-file
attributes:
label: |
日志文件
description: |
首页-设置-关于-日志,上传日志文件或生成链接并粘贴到下面的输入框\
任何问题都需要提供日志文件. 否则将直接关闭,请不要纯发文字/截图/视频
validations:
required: true
- type: textarea
id: bug-1
attributes:
label: |
BUG描述(文字/截图/视频)
description: |
请使用尽量准确的描述,否则你的问题将会被直接关闭\
另外如果你的问题是关于快照/选择器的,请必须提供快照链接或者快照文件,否则你的问题将会被直接关闭
validations:
required: true
- type: textarea
id: bug-2
attributes:
label: |
期望行为(文字/截图/视频)
description: |
请使用尽量准确的描述,否则你的问题将会被直接关闭
validations:
required: true
- type: textarea
id: bug-3
attributes:
label: |
实际行为(文字/截图/视频)
description: |
请使用尽量准确的描述,否则你的问题将会被直接关闭
validations:
required: true
```
## /.github/ISSUE_TEMPLATE/config.yml
```yml path="/.github/ISSUE_TEMPLATE/config.yml"
blank_issues_enabled: false
contact_links:
- name: 讨论交流 / Discussions
url: https://github.com/gkd-kit/gkd/discussions
about: '其他提问或请求: 选择器/规则/快照/无障碍/设备/相关问题. 在 issues 搜索不到可前往搜索讨论'
```
## /.github/ISSUE_TEMPLATE/feature_request.yml
```yml path="/.github/ISSUE_TEMPLATE/feature_request.yml"
name: 功能请求 / Feature request
title: "[Feature] "
description: 提出你的功能请求 / Propose your feature request
labels: ["pending triage"]
body:
- type: markdown
attributes:
value: |
## 感谢您花时间填写,在提交问题之前,请确保您完成以下操作
1. GKD 默认不提供任何规则,你可以查看 [GKD 官方文档](https://gkd.li) 后自行编写规则或者导入远程订阅,请不要再提出类似想要XXX规则这种问题
2. 请 **判断** 是不是第三方规则订阅的功能请求,如果是,你应该向规则提供者反馈,而不是在这里提交,**此处只接受 GKD 应用本体的功能请求**
3. 请 **确保** 已有的 [问题](https://github.com/gkd-kit/gkd/issues?q=is%3Aissue) 或 [讨论](https://github.com/orgs/gkd-kit/discussions?discussions_q=) 中没有人提交过相似问题,否则请在该问题下进行讨论
4. 请 **确保** 你想要的功能在 [releases](https://github.com/gkd-kit/gkd/releases/latest) 发布的最新版本(包含测试版本)上没有找到 (如果不是请先更新到最新版本验证后再提交问题)
- type: textarea
id: feature-description
attributes:
label: |
新功能描述
description: |
例如: 我希望在 GKD 中的什么页面添加什么功能,以及这个功能的作用是什么\
或者在规则定义中添加某个字段,以及这个字段的作用是什么\
请使用准确的描述,否则你的问题将会被直接关闭
validations:
required: true
```
## /.github/workflows/Build-Apk.yml
```yml path="/.github/workflows/Build-Apk.yml"
name: Build-Apk
on:
workflow_dispatch:
push:
branches:
- '**'
paths-ignore:
- 'LICENSE'
- '*.md'
- '.github/**'
jobs:
build:
if: ${{ !startsWith(github.event.head_commit.message, 'chore:') && !startsWith(github.event.head_commit.message, 'chore(') }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/setup-java@v5
with:
distribution: 'zulu'
java-version: '21'
- uses: gradle/actions/setup-gradle@v5
with:
cache-encryption-key: ${{ secrets.GRADLE_CACHE_ENCRYPTION_KEY }}
- name: write gkd secrets info
run: |
echo ${{ secrets.GKD_STORE_FILE_BASE64 }} | base64 --decode > ${{ github.workspace }}/gkd.jks
echo GKD_STORE_FILE='${{ github.workspace }}/gkd.jks' >> gradle.properties
echo GKD_STORE_PASSWORD='${{ secrets.GKD_STORE_PASSWORD }}' >> gradle.properties
echo GKD_KEY_ALIAS='${{ secrets.GKD_KEY_ALIAS }}' >> gradle.properties
echo GKD_KEY_PASSWORD='${{ secrets.GKD_KEY_PASSWORD }}' >> gradle.properties
- run: echo GKD_RENAME_APK_FLAG=1 >> gradle.properties
- run: chmod 777 ./gradlew
- run: ./gradlew app:assembleGkdRelease
- uses: actions/upload-artifact@v7
with:
archive: false
path: app/build/outputs/apk/gkd/release/*.apk
- uses: actions/upload-artifact@v7
with:
name: outputs
path: app/build/outputs
```
## /.github/workflows/Build-Release.yml
```yml path="/.github/workflows/Build-Release.yml"
name: Build-Release
on:
push:
tags:
- v*
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-java@v5
with:
distribution: 'zulu'
java-version: '21'
- uses: gradle/actions/setup-gradle@v5
with:
cache-encryption-key: ${{ secrets.GRADLE_CACHE_ENCRYPTION_KEY }}
- name: write gkd secrets info
run: |
echo ${{ secrets.GKD_STORE_FILE_BASE64 }} | base64 --decode > ${{ github.workspace }}/gkd.jks
echo GKD_STORE_FILE='${{ github.workspace }}/gkd.jks' >> gradle.properties
echo GKD_STORE_PASSWORD='${{ secrets.GKD_STORE_PASSWORD }}' >> gradle.properties
echo GKD_KEY_ALIAS='${{ secrets.GKD_KEY_ALIAS }}' >> gradle.properties
echo GKD_KEY_PASSWORD='${{ secrets.GKD_KEY_PASSWORD }}' >> gradle.properties
- name: write play secrets info
run: |
echo ${{ secrets.PLAY_STORE_FILE_BASE64 }} | base64 --decode > ${{ github.workspace }}/play.jks
echo PLAY_STORE_FILE='${{ github.workspace }}/play.jks' >> gradle.properties
echo PLAY_STORE_PASSWORD='${{ secrets.PLAY_STORE_PASSWORD }}' >> gradle.properties
echo PLAY_KEY_ALIAS='${{ secrets.PLAY_KEY_ALIAS }}' >> gradle.properties
echo PLAY_KEY_PASSWORD='${{ secrets.PLAY_KEY_PASSWORD }}' >> gradle.properties
- run: chmod 777 ./gradlew
- run: ./gradlew app:assembleGkdRelease app:bundlePlayRelease
- uses: actions/upload-artifact@v7
with:
name: release
path: app/build/outputs/apk/gkd/release
- uses: actions/upload-artifact@v7
with:
name: playRelease
path: app/build/outputs/bundle/playRelease
- uses: actions/upload-artifact@v7
with:
name: outputs
path: app/build/outputs
- uses: actions/upload-artifact@v7
with:
name: CHANGELOG.md
path: CHANGELOG.md
release:
needs: build
permissions: write-all
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v8
with:
name: outputs
path: outputs
- uses: actions/download-artifact@v8
with:
name: release
path: release
- uses: actions/download-artifact@v8
with:
name: CHANGELOG.md
- run: ls -R
- id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref_name }}
release_name: Release ${{ github.ref_name }}
body_path: ./CHANGELOG.md
prerelease: ${{ contains(github.ref_name, 'beta') }}
- uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: release/app-gkd-release.apk
asset_name: gkd-${{ github.ref_name }}.apk
asset_content_type: application/vnd.android.package-archive
- run: zip -r outputs.zip outputs
- uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: outputs.zip
asset_name: outputs-${{ github.ref_name }}.zip
asset_content_type: application/zip
```
## /.gitignore
```gitignore path="/.gitignore"
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
local.properties
.idea
/kotlin-js-store
.vscode
*.jks
*.keystore
/.cache
/.kotlin
/gradle/libs.versions.updates.toml
```
## /CHANGELOG.md
# 更新内容
- 优化快照结果提示文案
- 修复某些场景无障碍服务状态判断错误
- 修复某些设备新安装应用后对应规则不启用
## 更新方式
- GKD - 设置 - 关于 - 检测更新
- 下列方式之一
<a href="https://gkd.li/guide/"><img src="https://e.gkd.li/f23b704d-d781-494b-9719-393f95683b89" alt="Download from GKD.LI" width="32%" /></a><a href="https://play.google.com/store/apps/details?id=li.songe.gkd"><img src="https://e.gkd.li/f63fabeb-0342-4961-a46d-cac61b0f8856" alt="Download from Google Play" width="32%" /></a><a href="https://github.com/gkd-kit/gkd/releases"><img src="https://e.gkd.li/c1ef2bb9-7472-46d5-9806-81b4c37e5b4d" alt="Download from GitHub releases" width="32%" /></a>
## /README.md
# gkd
<p align="center">
<a href="https://gkd.li/"><img src="https://e.gkd.li/2a0a7787-f2dd-4529-a885-93f3b8c857c3" alt="GKD.LI" width="40%" /></a>
</p>
基于 [高级选择器](https://gkd.li/guide/selector) + [订阅规则](https://gkd.li/guide/subscription) + [快照审查](https://github.com/gkd-kit/inspect) 的自定义屏幕点击 Android 应用
通过自定义规则,在指定界面,满足指定条件(如屏幕上存在特定文字)时,点击特定的节点或位置或执行其他操作
- **快捷操作**
帮助你简化一些重复的流程, 如某些软件自动确认电脑登录
- **跳过流程**
某些软件可能在启动时存在一些烦人的流程, 这个软件可以帮助你点击跳过这个流程
## 免责声明
**本项目遵循 [GPL-3.0](/LICENSE) 开源,项目仅供学习交流,禁止用于商业或非法用途**
## 安装
<a href="https://gkd.li/guide/"><img src="https://e.gkd.li/f23b704d-d781-494b-9719-393f95683b89" alt="Download from GKD.LI" width="32%" /></a><a href="https://play.google.com/store/apps/details?id=li.songe.gkd"><img src="https://e.gkd.li/f63fabeb-0342-4961-a46d-cac61b0f8856" alt="Download from Google Play" width="32%" /></a><a href="https://github.com/gkd-kit/gkd/releases"><img src="https://e.gkd.li/c1ef2bb9-7472-46d5-9806-81b4c37e5b4d" alt="Download from GitHub releases" width="32%" /></a>
如遇问题请先查看 [疑难解答](https://gkd.li/guide/faq)
## 截图
| | | | |
| ------------------------------------------------------------- | ------------------------------------------------------------- | ------------------------------------------------------------- | ------------------------------------------------------------- |
|  |  |  |  |
|  |  |  |  |
## 订阅
GKD **默认不提供规则**,需自行添加本地规则,或者通过订阅链接的方式获取远程规则
也可通过 [subscription-template](https://github.com/gkd-kit/subscription-template) 快速构建自己的远程订阅
第三方订阅列表可在 <https://github.com/topics/gkd-subscription> 查看
要加入此列表, 需点击仓库主页右上角设置图标后在 Topics 中添加 `gkd-subscription`
<details>
<summary>示例图片 - 添加至 Topics (点击展开)</summary>

</details>
## 选择器
一个类似 CSS 选择器的选择器, 能联系节点上下文信息, 更容易也更精确找到目标节点
<https://gkd.li/guide/selector>
[@[vid=\"menu\"] < [vid=\"menu_container\"] - [vid=\"dot_text_layout\"] > [text^=\"广告\"]](https://i.gkd.li/i/14881985?gkd=QFt2aWQ9Im1lbnUiXSA8IFt2aWQ9Im1lbnVfY29udGFpbmVyIl0gLSBbdmlkPSJkb3RfdGV4dF9sYXlvdXQiXSA-IFt0ZXh0Xj0i5bm_5ZGKIl0)
<details>
<summary>示例图片 - 选择器路径视图 (点击展开)</summary>
[](https://i.gkd.li/i/14881985?gkd=QFt2aWQ9Im1lbnUiXSA8IFt2aWQ9Im1lbnVfY29udGFpbmVyIl0gLSBbdmlkPSJkb3RfdGV4dF9sYXlvdXQiXSA-IFt0ZXh0Xj0i5bm_5ZGKIl0)
</details>
## 捐赠
如果 GKD 对你有用, 可以通过以下链接支持该项目
<https://github.com/lisonge/sponsor>
或前往 [Google Play](https://play.google.com/store/apps/details?id=li.songe.gkd) 给个好评
## Star History
[](https://starchart.cc/gkd-kit/gkd)
## /app/.gitignore
```gitignore path="/app/.gitignore"
/build
/release
```
## /app/build.gradle.kts
```kts path="/app/build.gradle.kts"
import com.android.build.api.variant.impl.VariantOutputImpl
import javax.xml.parsers.DocumentBuilderFactory
import kotlin.reflect.full.declaredMemberProperties
fun String.runCommand(): String {
val process = ProcessBuilder(split(" "))
.redirectErrorStream(true)
.start()
val output = process.inputStream.bufferedReader().readText().trim()
val exitCode = process.waitFor()
if (exitCode != 0) {
error("Command failed with exit code $exitCode: $output")
}
return output
}
data class GitInfo(
val commitId: String,
val commitTime: String,
val tagName: String?,
) {
val versionNameSuffix get() = if (tagName == null) ("-" + commitId.take(7)) else null
}
val gitInfo = GitInfo(
commitId = "git rev-parse HEAD".runCommand(),
commitTime = "git log -1 --format=%ct".runCommand() + "000",
tagName = runCatching { "git describe --tags --exact-match".runCommand() }.getOrNull(),
)
val debugSuffixPairList by lazy {
DocumentBuilderFactory
.newInstance()
.newDocumentBuilder()
.parse(file("$projectDir/src/main/res/values/strings.xml"))
.documentElement.getElementsByTagName("string").run {
(0 until length).mapNotNull { i ->
val node = item(i)
if (node.attributes.getNamedItem("debug_suffix") != null) {
val key = node.attributes.getNamedItem("name").nodeValue
val value = node.textContent
key to value
} else {
null
}
}
}
}
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.androidx.room)
alias(libs.plugins.kotlin.parcelize)
alias(libs.plugins.kotlin.serialization)
alias(libs.plugins.kotlin.compose)
alias(libs.plugins.kotlinx.atomicfu)
alias(libs.plugins.google.ksp)
alias(libs.plugins.remap)
alias(libs.plugins.loc)
}
android {
namespace = "li.songe.gkd"
defaultConfig {
applicationId = "li.songe.gkd"
versionCode = 92
versionName = "1.12.1"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
useSupportLibrary = true
}
androidResources {
localeFilters += listOf("zh", "en")
}
ndk {
// noinspection ChromeOsAbiSupport
abiFilters += listOf("arm64-v8a", "x86_64")
}
GitInfo::class.declaredMemberProperties.onEach {
manifestPlaceholders[it.name] = it.get(gitInfo) ?: ""
}
}
buildFeatures {
compose = true
aidl = true
resValues = true
}
val gkdSigningConfig = if (project.hasProperty("GKD_STORE_FILE")) {
signingConfigs.create("gkd") {
storeFile = file(project.properties["GKD_STORE_FILE"] as String)
storePassword = project.findProperty("GKD_STORE_PASSWORD")?.toString()
keyAlias = project.findProperty("GKD_KEY_ALIAS")?.toString()
keyPassword = project.findProperty("GKD_KEY_PASSWORD")?.toString()
}
} else {
signingConfigs.getByName("debug")
}
val playSigningConfig = if (project.hasProperty("PLAY_STORE_FILE")) {
signingConfigs.create("play") {
storeFile = file(project.properties["PLAY_STORE_FILE"].toString())
storePassword = project.properties["PLAY_STORE_PASSWORD"].toString()
keyAlias = project.properties["PLAY_KEY_ALIAS"].toString()
keyPassword = project.properties["PLAY_KEY_PASSWORD"].toString()
}
} else {
gkdSigningConfig
}
buildTypes {
all {
versionNameSuffix = gitInfo.versionNameSuffix
}
release {
isMinifyEnabled = true
isShrinkResources = true
isDebuggable = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro",
)
}
debug {
signingConfig = gkdSigningConfig
applicationIdSuffix = ".debug"
resValue("color", "better_black", "#FF5D92")
debugSuffixPairList.onEach { (key, value) ->
resValue("string", key, "$value-debug")
}
}
}
productFlavors {
flavorDimensions += "channel"
create("gkd") {
isDefault = true
signingConfig = gkdSigningConfig
resValue("bool", "is_accessibility_tool", "true")
}
create("play") {
signingConfig = playSigningConfig
resValue("bool", "is_accessibility_tool", "false")
}
all {
dimension = flavorDimensions.first()
manifestPlaceholders["channel"] = name
}
}
dependenciesInfo.includeInApk = false
packaging.resources.excludes += setOf(
// https://github.com/Kotlin/kotlinx.coroutines/issues/2023
"META-INF/**", "**/attach_hotspot_windows.dll",
"**.properties", "**.bin", "**/*.proto",
"**/kotlin-tooling-metadata.json",
// ktor
"**/custom.config.conf",
"**/custom.config.yaml",
)
}
if (project.hasProperty("GKD_RENAME_APK_FLAG")) {
androidComponents.onVariants { variant ->
variant.outputs.onEach { output ->
output as VariantOutputImpl
output.outputFileName = "gkd-v${output.versionName.get()}.apk"
}
}
}
// https://developer.android.com/jetpack/androidx/releases/room?hl=zh-cn#compiler-options
room {
schemaDirectory("$projectDir/schemas")
}
composeCompiler {
reportsDestination = layout.buildDirectory.dir("compose_compiler")
stabilityConfigurationFiles.addAll(
rootProject.layout.projectDirectory.file("stability_config.conf"),
)
}
loc {
template = "{packageName}.{methodName}({fileName}:{lineNumber})"
}
dependencies {
implementation(libs.kotlin.stdlib)
implementation(project(":selector"))
implementation(libs.androidx.appcompat)
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.lifecycle.service)
implementation(libs.compose.ui)
implementation(libs.compose.ui.graphics)
implementation(libs.compose.animation)
implementation(libs.compose.animation.graphics)
implementation(libs.compose.icons)
implementation(libs.compose.preview)
debugImplementation(libs.compose.tooling)
androidTestImplementation(libs.compose.junit4)
implementation(libs.compose.activity)
implementation(libs.compose.material3)
implementation(libs.androidx.navigation3.ui)
implementation(libs.androidx.navigation3.runtime)
implementation(libs.androidx.lifecycle.viewmodel.navigation3)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso)
compileOnly(project(":hidden_api"))
implementation(libs.rikka.shizuku.api)
implementation(libs.rikka.shizuku.provider)
implementation(libs.lsposed.hiddenapibypass)
implementation(libs.androidx.room.runtime)
implementation(libs.androidx.room.ktx)
implementation(libs.androidx.room.paging)
ksp(libs.androidx.room.compiler)
implementation(libs.androidx.paging.runtime)
implementation(libs.androidx.paging.compose)
implementation(libs.ktor.server.core)
implementation(libs.ktor.server.cio)
implementation(libs.ktor.server.content.negotiation)
implementation(libs.ktor.client.core)
implementation(libs.ktor.client.okhttp)
implementation(libs.ktor.client.content.negotiation)
implementation(libs.ktor.serialization.kotlinx.json)
implementation(libs.google.accompanist.drawablepainter)
implementation(libs.kotlinx.serialization.core)
implementation(libs.kotlinx.serialization.json)
// https://github.com/Kotlin/kotlinx-atomicfu/issues/145
implementation(libs.kotlinx.atomicfu)
implementation(libs.activityResultLauncher)
implementation(libs.reorderable)
implementation(libs.androidx.splashscreen)
implementation(libs.coil.compose)
implementation(libs.coil.network)
implementation(libs.coil.gif)
implementation(libs.telephoto.zoomable)
implementation(libs.exp4j)
implementation(libs.toaster)
implementation(libs.permissions)
implementation(libs.device)
implementation(libs.json5)
compileOnly(libs.loc.annotation)
implementation(libs.kevinnzouWebview)
}
```
## /app/proguard-rules.pro
```pro path="/app/proguard-rules.pro"
# http://developer.android.com/guide/developing/tools/proguard.html
-dontwarn **
```
## /app/schemas/li.songe.gkd.db.AppDb/1.json
```json path="/app/schemas/li.songe.gkd.db.AppDb/1.json"
{
"formatVersion": 1,
"database": {
"version": 1,
"identityHash": "e755c87d937b753ba02d02e8fa9afcec",
"entities": [
{
"tableName": "subs_item",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `ctime` INTEGER NOT NULL, `mtime` INTEGER NOT NULL, `enable` INTEGER NOT NULL, `enable_update` INTEGER NOT NULL, `order` INTEGER NOT NULL, `update_url` TEXT, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "ctime",
"columnName": "ctime",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "mtime",
"columnName": "mtime",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "enable",
"columnName": "enable",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "enableUpdate",
"columnName": "enable_update",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "order",
"columnName": "order",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "updateUrl",
"columnName": "update_url",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "snapshot",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `app_id` TEXT, `activity_id` TEXT, `app_name` TEXT, `app_version_code` INTEGER, `app_version_name` TEXT, `screen_height` INTEGER NOT NULL, `screen_width` INTEGER NOT NULL, `is_landscape` INTEGER NOT NULL, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "appId",
"columnName": "app_id",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "activityId",
"columnName": "activity_id",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "appName",
"columnName": "app_name",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "appVersionCode",
"columnName": "app_version_code",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "appVersionName",
"columnName": "app_version_name",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "screenHeight",
"columnName": "screen_height",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "screenWidth",
"columnName": "screen_width",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "isLandscape",
"columnName": "is_landscape",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "subs_config",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `type` INTEGER NOT NULL, `enable` INTEGER NOT NULL, `subs_item_id` INTEGER NOT NULL, `app_id` TEXT NOT NULL, `group_key` INTEGER NOT NULL, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "enable",
"columnName": "enable",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "subsItemId",
"columnName": "subs_item_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "appId",
"columnName": "app_id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "groupKey",
"columnName": "group_key",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "click_log",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `app_id` TEXT, `activity_id` TEXT, `subs_id` INTEGER NOT NULL, `group_key` INTEGER NOT NULL, `rule_index` INTEGER NOT NULL, `rule_key` INTEGER, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "appId",
"columnName": "app_id",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "activityId",
"columnName": "activity_id",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "subsId",
"columnName": "subs_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "groupKey",
"columnName": "group_key",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "ruleIndex",
"columnName": "rule_index",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "ruleKey",
"columnName": "rule_key",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'e755c87d937b753ba02d02e8fa9afcec')"
]
}
}
```
## /app/schemas/li.songe.gkd.db.AppDb/10.json
```json path="/app/schemas/li.songe.gkd.db.AppDb/10.json"
{
"formatVersion": 1,
"database": {
"version": 10,
"identityHash": "f3e8d3fb1a6de3876a6fceb921a456a2",
"entities": [
{
"tableName": "subs_item",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `ctime` INTEGER NOT NULL, `mtime` INTEGER NOT NULL, `enable` INTEGER NOT NULL, `enable_update` INTEGER NOT NULL, `order` INTEGER NOT NULL, `update_url` TEXT, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "ctime",
"columnName": "ctime",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "mtime",
"columnName": "mtime",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "enable",
"columnName": "enable",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "enableUpdate",
"columnName": "enable_update",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "order",
"columnName": "order",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "updateUrl",
"columnName": "update_url",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "snapshot",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `app_id` TEXT, `activity_id` TEXT, `app_name` TEXT, `app_version_code` INTEGER, `app_version_name` TEXT, `screen_height` INTEGER NOT NULL, `screen_width` INTEGER NOT NULL, `is_landscape` INTEGER NOT NULL, `github_asset_id` INTEGER, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "appId",
"columnName": "app_id",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "activityId",
"columnName": "activity_id",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "appName",
"columnName": "app_name",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "appVersionCode",
"columnName": "app_version_code",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "appVersionName",
"columnName": "app_version_name",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "screenHeight",
"columnName": "screen_height",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "screenWidth",
"columnName": "screen_width",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "isLandscape",
"columnName": "is_landscape",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "githubAssetId",
"columnName": "github_asset_id",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "subs_config",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `type` INTEGER NOT NULL, `enable` INTEGER, `subs_id` INTEGER NOT NULL, `app_id` TEXT NOT NULL, `group_key` INTEGER NOT NULL, `exclude` TEXT NOT NULL DEFAULT '', PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "enable",
"columnName": "enable",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "subsId",
"columnName": "subs_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "appId",
"columnName": "app_id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "groupKey",
"columnName": "group_key",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "exclude",
"columnName": "exclude",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "''"
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "category_config",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `enable` INTEGER, `subs_id` INTEGER NOT NULL, `category_key` INTEGER NOT NULL, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "enable",
"columnName": "enable",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "subsId",
"columnName": "subs_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "categoryKey",
"columnName": "category_key",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "action_log",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `ctime` INTEGER NOT NULL, `app_id` TEXT NOT NULL, `activity_id` TEXT, `subs_id` INTEGER NOT NULL, `subs_version` INTEGER NOT NULL DEFAULT 0, `group_key` INTEGER NOT NULL, `group_type` INTEGER NOT NULL DEFAULT 2, `rule_index` INTEGER NOT NULL, `rule_key` INTEGER)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "ctime",
"columnName": "ctime",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "appId",
"columnName": "app_id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "activityId",
"columnName": "activity_id",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "subsId",
"columnName": "subs_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "subsVersion",
"columnName": "subs_version",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "groupKey",
"columnName": "group_key",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "groupType",
"columnName": "group_type",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "2"
},
{
"fieldPath": "ruleIndex",
"columnName": "rule_index",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "ruleKey",
"columnName": "rule_key",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "activity_log_v2",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `ctime` INTEGER NOT NULL, `app_id` TEXT NOT NULL, `activity_id` TEXT)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "ctime",
"columnName": "ctime",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "appId",
"columnName": "app_id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "activityId",
"columnName": "activity_id",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "app_config",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `enable` INTEGER NOT NULL, `subs_id` INTEGER NOT NULL, `app_id` TEXT NOT NULL, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "enable",
"columnName": "enable",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "subsId",
"columnName": "subs_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "appId",
"columnName": "app_id",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'f3e8d3fb1a6de3876a6fceb921a456a2')"
]
}
}
```
## /app/schemas/li.songe.gkd.db.AppDb/11.json
```json path="/app/schemas/li.songe.gkd.db.AppDb/11.json"
{
"formatVersion": 1,
"database": {
"version": 11,
"identityHash": "8cc58f95916481333213b24587c5d4d3",
"entities": [
{
"tableName": "subs_item",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `ctime` INTEGER NOT NULL, `mtime` INTEGER NOT NULL, `enable` INTEGER NOT NULL, `enable_update` INTEGER NOT NULL, `order` INTEGER NOT NULL, `update_url` TEXT, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "ctime",
"columnName": "ctime",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "mtime",
"columnName": "mtime",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "enable",
"columnName": "enable",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "enableUpdate",
"columnName": "enable_update",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "order",
"columnName": "order",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "updateUrl",
"columnName": "update_url",
"affinity": "TEXT"
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
}
},
{
"tableName": "snapshot",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `app_id` TEXT, `activity_id` TEXT, `screen_height` INTEGER NOT NULL, `screen_width` INTEGER NOT NULL, `is_landscape` INTEGER NOT NULL, `github_asset_id` INTEGER, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "appId",
"columnName": "app_id",
"affinity": "TEXT"
},
{
"fieldPath": "activityId",
"columnName": "activity_id",
"affinity": "TEXT"
},
{
"fieldPath": "screenHeight",
"columnName": "screen_height",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "screenWidth",
"columnName": "screen_width",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "isLandscape",
"columnName": "is_landscape",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "githubAssetId",
"columnName": "github_asset_id",
"affinity": "INTEGER"
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
}
},
{
"tableName": "subs_config",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `type` INTEGER NOT NULL, `enable` INTEGER, `subs_id` INTEGER NOT NULL, `app_id` TEXT NOT NULL, `group_key` INTEGER NOT NULL, `exclude` TEXT NOT NULL DEFAULT '', PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "enable",
"columnName": "enable",
"affinity": "INTEGER"
},
{
"fieldPath": "subsId",
"columnName": "subs_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "appId",
"columnName": "app_id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "groupKey",
"columnName": "group_key",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "exclude",
"columnName": "exclude",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "''"
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
}
},
{
"tableName": "category_config",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `enable` INTEGER, `subs_id` INTEGER NOT NULL, `category_key` INTEGER NOT NULL, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "enable",
"columnName": "enable",
"affinity": "INTEGER"
},
{
"fieldPath": "subsId",
"columnName": "subs_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "categoryKey",
"columnName": "category_key",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
}
},
{
"tableName": "action_log",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `ctime` INTEGER NOT NULL, `app_id` TEXT NOT NULL, `activity_id` TEXT, `subs_id` INTEGER NOT NULL, `subs_version` INTEGER NOT NULL DEFAULT 0, `group_key` INTEGER NOT NULL, `group_type` INTEGER NOT NULL DEFAULT 2, `rule_index` INTEGER NOT NULL, `rule_key` INTEGER)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "ctime",
"columnName": "ctime",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "appId",
"columnName": "app_id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "activityId",
"columnName": "activity_id",
"affinity": "TEXT"
},
{
"fieldPath": "subsId",
"columnName": "subs_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "subsVersion",
"columnName": "subs_version",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "groupKey",
"columnName": "group_key",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "groupType",
"columnName": "group_type",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "2"
},
{
"fieldPath": "ruleIndex",
"columnName": "rule_index",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "ruleKey",
"columnName": "rule_key",
"affinity": "INTEGER"
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
}
},
{
"tableName": "activity_log_v2",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `ctime` INTEGER NOT NULL, `app_id` TEXT NOT NULL, `activity_id` TEXT)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "ctime",
"columnName": "ctime",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "appId",
"columnName": "app_id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "activityId",
"columnName": "activity_id",
"affinity": "TEXT"
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
}
},
{
"tableName": "app_config",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `enable` INTEGER NOT NULL, `subs_id` INTEGER NOT NULL, `app_id` TEXT NOT NULL, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "enable",
"columnName": "enable",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "subsId",
"columnName": "subs_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "appId",
"columnName": "app_id",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
}
}
],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '8cc58f95916481333213b24587c5d4d3')"
]
}
}
```
## /app/schemas/li.songe.gkd.db.AppDb/12.json
```json path="/app/schemas/li.songe.gkd.db.AppDb/12.json"
{
"formatVersion": 1,
"database": {
"version": 12,
"identityHash": "58d6b0ebb55bc58ac6016a2b675e3ac4",
"entities": [
{
"tableName": "subs_item",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `ctime` INTEGER NOT NULL, `mtime` INTEGER NOT NULL, `enable` INTEGER NOT NULL, `enable_update` INTEGER NOT NULL, `order` INTEGER NOT NULL, `update_url` TEXT, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "ctime",
"columnName": "ctime",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "mtime",
"columnName": "mtime",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "enable",
"columnName": "enable",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "enableUpdate",
"columnName": "enable_update",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "order",
"columnName": "order",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "updateUrl",
"columnName": "update_url",
"affinity": "TEXT"
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
}
},
{
"tableName": "snapshot",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `app_id` TEXT NOT NULL, `activity_id` TEXT, `screen_height` INTEGER NOT NULL, `screen_width` INTEGER NOT NULL, `is_landscape` INTEGER NOT NULL, `github_asset_id` INTEGER, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "appId",
"columnName": "app_id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "activityId",
"columnName": "activity_id",
"affinity": "TEXT"
},
{
"fieldPath": "screenHeight",
"columnName": "screen_height",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "screenWidth",
"columnName": "screen_width",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "isLandscape",
"columnName": "is_landscape",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "githubAssetId",
"columnName": "github_asset_id",
"affinity": "INTEGER"
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
}
},
{
"tableName": "subs_config",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `type` INTEGER NOT NULL, `enable` INTEGER, `subs_id` INTEGER NOT NULL, `app_id` TEXT NOT NULL, `group_key` INTEGER NOT NULL, `exclude` TEXT NOT NULL DEFAULT '', PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "enable",
"columnName": "enable",
"affinity": "INTEGER"
},
{
"fieldPath": "subsId",
"columnName": "subs_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "appId",
"columnName": "app_id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "groupKey",
"columnName": "group_key",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "exclude",
"columnName": "exclude",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "''"
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
}
},
{
"tableName": "category_config",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `enable` INTEGER, `subs_id` INTEGER NOT NULL, `category_key` INTEGER NOT NULL, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "enable",
"columnName": "enable",
"affinity": "INTEGER"
},
{
"fieldPath": "subsId",
"columnName": "subs_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "categoryKey",
"columnName": "category_key",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
}
},
{
"tableName": "action_log",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `ctime` INTEGER NOT NULL, `app_id` TEXT NOT NULL, `activity_id` TEXT, `subs_id` INTEGER NOT NULL, `subs_version` INTEGER NOT NULL DEFAULT 0, `group_key` INTEGER NOT NULL, `group_type` INTEGER NOT NULL DEFAULT 2, `rule_index` INTEGER NOT NULL, `rule_key` INTEGER)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "ctime",
"columnName": "ctime",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "appId",
"columnName": "app_id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "activityId",
"columnName": "activity_id",
"affinity": "TEXT"
},
{
"fieldPath": "subsId",
"columnName": "subs_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "subsVersion",
"columnName": "subs_version",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "groupKey",
"columnName": "group_key",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "groupType",
"columnName": "group_type",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "2"
},
{
"fieldPath": "ruleIndex",
"columnName": "rule_index",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "ruleKey",
"columnName": "rule_key",
"affinity": "INTEGER"
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
}
},
{
"tableName": "activity_log_v2",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `ctime` INTEGER NOT NULL, `app_id` TEXT NOT NULL, `activity_id` TEXT)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "ctime",
"columnName": "ctime",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "appId",
"columnName": "app_id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "activityId",
"columnName": "activity_id",
"affinity": "TEXT"
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
}
},
{
"tableName": "app_config",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `enable` INTEGER NOT NULL, `subs_id` INTEGER NOT NULL, `app_id` TEXT NOT NULL, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "enable",
"columnName": "enable",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "subsId",
"columnName": "subs_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "appId",
"columnName": "app_id",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
}
}
],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '58d6b0ebb55bc58ac6016a2b675e3ac4')"
]
}
}
```
## /app/schemas/li.songe.gkd.db.AppDb/13.json
```json path="/app/schemas/li.songe.gkd.db.AppDb/13.json"
{
"formatVersion": 1,
"database": {
"version": 13,
"identityHash": "f57629976fb6ff444f59487622f93814",
"entities": [
{
"tableName": "subs_item",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `ctime` INTEGER NOT NULL, `mtime` INTEGER NOT NULL, `enable` INTEGER NOT NULL, `enable_update` INTEGER NOT NULL, `order` INTEGER NOT NULL, `update_url` TEXT, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "ctime",
"columnName": "ctime",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "mtime",
"columnName": "mtime",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "enable",
"columnName": "enable",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "enableUpdate",
"columnName": "enable_update",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "order",
"columnName": "order",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "updateUrl",
"columnName": "update_url",
"affinity": "TEXT"
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
}
},
{
"tableName": "snapshot",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `app_id` TEXT NOT NULL, `activity_id` TEXT, `screen_height` INTEGER NOT NULL, `screen_width` INTEGER NOT NULL, `is_landscape` INTEGER NOT NULL, `github_asset_id` INTEGER, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "appId",
"columnName": "app_id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "activityId",
"columnName": "activity_id",
"affinity": "TEXT"
},
{
"fieldPath": "screenHeight",
"columnName": "screen_height",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "screenWidth",
"columnName": "screen_width",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "isLandscape",
"columnName": "is_landscape",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "githubAssetId",
"columnName": "github_asset_id",
"affinity": "INTEGER"
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
}
},
{
"tableName": "subs_config",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `type` INTEGER NOT NULL, `enable` INTEGER, `subs_id` INTEGER NOT NULL, `app_id` TEXT NOT NULL, `group_key` INTEGER NOT NULL, `exclude` TEXT NOT NULL DEFAULT '', PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "enable",
"columnName": "enable",
"affinity": "INTEGER"
},
{
"fieldPath": "subsId",
"columnName": "subs_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "appId",
"columnName": "app_id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "groupKey",
"columnName": "group_key",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "exclude",
"columnName": "exclude",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "''"
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
}
},
{
"tableName": "category_config",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `enable` INTEGER, `subs_id` INTEGER NOT NULL, `category_key` INTEGER NOT NULL, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "enable",
"columnName": "enable",
"affinity": "INTEGER"
},
{
"fieldPath": "subsId",
"columnName": "subs_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "categoryKey",
"columnName": "category_key",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
}
},
{
"tableName": "action_log",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `ctime` INTEGER NOT NULL, `app_id` TEXT NOT NULL, `activity_id` TEXT, `subs_id` INTEGER NOT NULL, `subs_version` INTEGER NOT NULL DEFAULT 0, `group_key` INTEGER NOT NULL, `group_type` INTEGER NOT NULL DEFAULT 2, `rule_index` INTEGER NOT NULL, `rule_key` INTEGER)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "ctime",
"columnName": "ctime",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "appId",
"columnName": "app_id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "activityId",
"columnName": "activity_id",
"affinity": "TEXT"
},
{
"fieldPath": "subsId",
"columnName": "subs_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "subsVersion",
"columnName": "subs_version",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "groupKey",
"columnName": "group_key",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "groupType",
"columnName": "group_type",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "2"
},
{
"fieldPath": "ruleIndex",
"columnName": "rule_index",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "ruleKey",
"columnName": "rule_key",
"affinity": "INTEGER"
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
}
},
{
"tableName": "activity_log_v2",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `ctime` INTEGER NOT NULL, `app_id` TEXT NOT NULL, `activity_id` TEXT)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "ctime",
"columnName": "ctime",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "appId",
"columnName": "app_id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "activityId",
"columnName": "activity_id",
"affinity": "TEXT"
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
}
},
{
"tableName": "app_config",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `enable` INTEGER NOT NULL, `subs_id` INTEGER NOT NULL, `app_id` TEXT NOT NULL, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "enable",
"columnName": "enable",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "subsId",
"columnName": "subs_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "appId",
"columnName": "app_id",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
}
},
{
"tableName": "app_visit_log",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `mtime` INTEGER NOT NULL, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "mtime",
"columnName": "mtime",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
}
}
],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'f57629976fb6ff444f59487622f93814')"
]
}
}
```
## /app/schemas/li.songe.gkd.db.AppDb/14.json
```json path="/app/schemas/li.songe.gkd.db.AppDb/14.json"
{
"formatVersion": 1,
"database": {
"version": 14,
"identityHash": "8c34795e4b3ae52bf0188358d7bd3037",
"entities": [
{
"tableName": "subs_item",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `ctime` INTEGER NOT NULL, `mtime` INTEGER NOT NULL, `enable` INTEGER NOT NULL, `enable_update` INTEGER NOT NULL, `order` INTEGER NOT NULL, `update_url` TEXT, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "ctime",
"columnName": "ctime",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "mtime",
"columnName": "mtime",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "enable",
"columnName": "enable",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "enableUpdate",
"columnName": "enable_update",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "order",
"columnName": "order",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "updateUrl",
"columnName": "update_url",
"affinity": "TEXT"
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
}
},
{
"tableName": "snapshot",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `app_id` TEXT NOT NULL, `activity_id` TEXT, `screen_height` INTEGER NOT NULL, `screen_width` INTEGER NOT NULL, `is_landscape` INTEGER NOT NULL, `github_asset_id` INTEGER, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "appId",
"columnName": "app_id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "activityId",
"columnName": "activity_id",
"affinity": "TEXT"
},
{
"fieldPath": "screenHeight",
"columnName": "screen_height",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "screenWidth",
"columnName": "screen_width",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "isLandscape",
"columnName": "is_landscape",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "githubAssetId",
"columnName": "github_asset_id",
"affinity": "INTEGER"
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
}
},
{
"tableName": "subs_config",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `type` INTEGER NOT NULL, `enable` INTEGER, `subs_id` INTEGER NOT NULL, `app_id` TEXT NOT NULL, `group_key` INTEGER NOT NULL, `exclude` TEXT NOT NULL DEFAULT '', PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "enable",
"columnName": "enable",
"affinity": "INTEGER"
},
{
"fieldPath": "subsId",
"columnName": "subs_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "appId",
"columnName": "app_id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "groupKey",
"columnName": "group_key",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "exclude",
"columnName": "exclude",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "''"
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
}
},
{
"tableName": "category_config",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `enable` INTEGER, `subs_id` INTEGER NOT NULL, `category_key` INTEGER NOT NULL, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "enable",
"columnName": "enable",
"affinity": "INTEGER"
},
{
"fieldPath": "subsId",
"columnName": "subs_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "categoryKey",
"columnName": "category_key",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
}
},
{
"tableName": "action_log",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `ctime` INTEGER NOT NULL, `app_id` TEXT NOT NULL, `activity_id` TEXT, `subs_id` INTEGER NOT NULL, `subs_version` INTEGER NOT NULL DEFAULT 0, `group_key` INTEGER NOT NULL, `group_type` INTEGER NOT NULL DEFAULT 2, `rule_index` INTEGER NOT NULL, `rule_key` INTEGER)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "ctime",
"columnName": "ctime",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "appId",
"columnName": "app_id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "activityId",
"columnName": "activity_id",
"affinity": "TEXT"
},
{
"fieldPath": "subsId",
"columnName": "subs_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "subsVersion",
"columnName": "subs_version",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "groupKey",
"columnName": "group_key",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "groupType",
"columnName": "group_type",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "2"
},
{
"fieldPath": "ruleIndex",
"columnName": "rule_index",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "ruleKey",
"columnName": "rule_key",
"affinity": "INTEGER"
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
}
},
{
"tableName": "activity_log_v2",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `ctime` INTEGER NOT NULL, `app_id` TEXT NOT NULL, `activity_id` TEXT)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "ctime",
"columnName": "ctime",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "appId",
"columnName": "app_id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "activityId",
"columnName": "activity_id",
"affinity": "TEXT"
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
}
},
{
"tableName": "app_config",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `enable` INTEGER NOT NULL, `subs_id` INTEGER NOT NULL, `app_id` TEXT NOT NULL, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "enable",
"columnName": "enable",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "subsId",
"columnName": "subs_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "appId",
"columnName": "app_id",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
}
},
{
"tableName": "app_visit_log",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `mtime` INTEGER NOT NULL, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "mtime",
"columnName": "mtime",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
}
},
{
"tableName": "a11y_event_log",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `ctime` INTEGER NOT NULL, `type` INTEGER NOT NULL, `appId` TEXT NOT NULL, `name` TEXT NOT NULL, `desc` TEXT, `text` TEXT NOT NULL, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "ctime",
"columnName": "ctime",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "appId",
"columnName": "appId",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "desc",
"columnName": "desc",
"affinity": "TEXT"
},
{
"fieldPath": "text",
"columnName": "text",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
}
}
],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '8c34795e4b3ae52bf0188358d7bd3037')"
]
}
}
```
## /app/schemas/li.songe.gkd.db.AppDb/2.json
```json path="/app/schemas/li.songe.gkd.db.AppDb/2.json"
{
"formatVersion": 1,
"database": {
"version": 2,
"identityHash": "3e6e28a11589fe6c2d8aff5b9467a489",
"entities": [
{
"tableName": "subs_item",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `ctime` INTEGER NOT NULL, `mtime` INTEGER NOT NULL, `enable` INTEGER NOT NULL, `enable_update` INTEGER NOT NULL, `order` INTEGER NOT NULL, `update_url` TEXT, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "ctime",
"columnName": "ctime",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "mtime",
"columnName": "mtime",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "enable",
"columnName": "enable",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "enableUpdate",
"columnName": "enable_update",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "order",
"columnName": "order",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "updateUrl",
"columnName": "update_url",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "snapshot",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `app_id` TEXT, `activity_id` TEXT, `app_name` TEXT, `app_version_code` INTEGER, `app_version_name` TEXT, `screen_height` INTEGER NOT NULL, `screen_width` INTEGER NOT NULL, `is_landscape` INTEGER NOT NULL, `github_asset_id` INTEGER, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "appId",
"columnName": "app_id",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "activityId",
"columnName": "activity_id",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "appName",
"columnName": "app_name",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "appVersionCode",
"columnName": "app_version_code",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "appVersionName",
"columnName": "app_version_name",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "screenHeight",
"columnName": "screen_height",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "screenWidth",
"columnName": "screen_width",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "isLandscape",
"columnName": "is_landscape",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "githubAssetId",
"columnName": "github_asset_id",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "subs_config",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `type` INTEGER NOT NULL, `enable` INTEGER NOT NULL, `subs_item_id` INTEGER NOT NULL, `app_id` TEXT NOT NULL, `group_key` INTEGER NOT NULL, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "enable",
"columnName": "enable",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "subsItemId",
"columnName": "subs_item_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "appId",
"columnName": "app_id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "groupKey",
"columnName": "group_key",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "click_log",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `app_id` TEXT, `activity_id` TEXT, `subs_id` INTEGER NOT NULL, `group_key` INTEGER NOT NULL, `rule_index` INTEGER NOT NULL, `rule_key` INTEGER, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "appId",
"columnName": "app_id",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "activityId",
"columnName": "activity_id",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "subsId",
"columnName": "subs_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "groupKey",
"columnName": "group_key",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "ruleIndex",
"columnName": "rule_index",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "ruleKey",
"columnName": "rule_key",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '3e6e28a11589fe6c2d8aff5b9467a489')"
]
}
}
```
## /app/schemas/li.songe.gkd.db.AppDb/3.json
```json path="/app/schemas/li.songe.gkd.db.AppDb/3.json"
{
"formatVersion": 1,
"database": {
"version": 3,
"identityHash": "b52c1f25e2052865818be5151b6ac6a0",
"entities": [
{
"tableName": "subs_item",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `ctime` INTEGER NOT NULL, `mtime` INTEGER NOT NULL, `enable` INTEGER NOT NULL, `enable_update` INTEGER NOT NULL, `order` INTEGER NOT NULL, `update_url` TEXT, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "ctime",
"columnName": "ctime",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "mtime",
"columnName": "mtime",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "enable",
"columnName": "enable",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "enableUpdate",
"columnName": "enable_update",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "order",
"columnName": "order",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "updateUrl",
"columnName": "update_url",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "snapshot",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `app_id` TEXT, `activity_id` TEXT, `app_name` TEXT, `app_version_code` INTEGER, `app_version_name` TEXT, `screen_height` INTEGER NOT NULL, `screen_width` INTEGER NOT NULL, `is_landscape` INTEGER NOT NULL, `github_asset_id` INTEGER, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "appId",
"columnName": "app_id",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "activityId",
"columnName": "activity_id",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "appName",
"columnName": "app_name",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "appVersionCode",
"columnName": "app_version_code",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "appVersionName",
"columnName": "app_version_name",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "screenHeight",
"columnName": "screen_height",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "screenWidth",
"columnName": "screen_width",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "isLandscape",
"columnName": "is_landscape",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "githubAssetId",
"columnName": "github_asset_id",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "subs_config",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `type` INTEGER NOT NULL, `enable` INTEGER NOT NULL, `subs_item_id` INTEGER NOT NULL, `app_id` TEXT NOT NULL, `group_key` INTEGER NOT NULL, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "enable",
"columnName": "enable",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "subsItemId",
"columnName": "subs_item_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "appId",
"columnName": "app_id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "groupKey",
"columnName": "group_key",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "click_log",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `app_id` TEXT, `activity_id` TEXT, `subs_id` INTEGER NOT NULL, `group_key` INTEGER NOT NULL, `rule_index` INTEGER NOT NULL, `rule_key` INTEGER, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "appId",
"columnName": "app_id",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "activityId",
"columnName": "activity_id",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "subsId",
"columnName": "subs_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "groupKey",
"columnName": "group_key",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "ruleIndex",
"columnName": "rule_index",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "ruleKey",
"columnName": "rule_key",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "category_config",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `enable` INTEGER, `subs_item_id` INTEGER NOT NULL, `category_key` INTEGER NOT NULL, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "enable",
"columnName": "enable",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "subsItemId",
"columnName": "subs_item_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "categoryKey",
"columnName": "category_key",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'b52c1f25e2052865818be5151b6ac6a0')"
]
}
}
```
## /app/schemas/li.songe.gkd.db.AppDb/4.json
```json path="/app/schemas/li.songe.gkd.db.AppDb/4.json"
{
"formatVersion": 1,
"database": {
"version": 4,
"identityHash": "d1a618bf8475b588793fb1d201815a08",
"entities": [
{
"tableName": "subs_item",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `ctime` INTEGER NOT NULL, `mtime` INTEGER NOT NULL, `enable` INTEGER NOT NULL, `enable_update` INTEGER NOT NULL, `order` INTEGER NOT NULL, `update_url` TEXT, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "ctime",
"columnName": "ctime",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "mtime",
"columnName": "mtime",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "enable",
"columnName": "enable",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "enableUpdate",
"columnName": "enable_update",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "order",
"columnName": "order",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "updateUrl",
"columnName": "update_url",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "snapshot",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `app_id` TEXT, `activity_id` TEXT, `app_name` TEXT, `app_version_code` INTEGER, `app_version_name` TEXT, `screen_height` INTEGER NOT NULL, `screen_width` INTEGER NOT NULL, `is_landscape` INTEGER NOT NULL, `github_asset_id` INTEGER, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "appId",
"columnName": "app_id",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "activityId",
"columnName": "activity_id",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "appName",
"columnName": "app_name",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "appVersionCode",
"columnName": "app_version_code",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "appVersionName",
"columnName": "app_version_name",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "screenHeight",
"columnName": "screen_height",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "screenWidth",
"columnName": "screen_width",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "isLandscape",
"columnName": "is_landscape",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "githubAssetId",
"columnName": "github_asset_id",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "subs_config",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `type` INTEGER NOT NULL, `enable` INTEGER NOT NULL, `subs_item_id` INTEGER NOT NULL, `app_id` TEXT NOT NULL, `group_key` INTEGER NOT NULL, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "enable",
"columnName": "enable",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "subsItemId",
"columnName": "subs_item_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "appId",
"columnName": "app_id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "groupKey",
"columnName": "group_key",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "click_log",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `app_id` TEXT, `activity_id` TEXT, `subs_id` INTEGER NOT NULL, `subs_version` INTEGER NOT NULL DEFAULT 0, `group_key` INTEGER NOT NULL, `group_type` INTEGER NOT NULL DEFAULT 2, `rule_index` INTEGER NOT NULL, `rule_key` INTEGER, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "appId",
"columnName": "app_id",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "activityId",
"columnName": "activity_id",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "subsId",
"columnName": "subs_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "subsVersion",
"columnName": "subs_version",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "groupKey",
"columnName": "group_key",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "groupType",
"columnName": "group_type",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "2"
},
{
"fieldPath": "ruleIndex",
"columnName": "rule_index",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "ruleKey",
"columnName": "rule_key",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "category_config",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `enable` INTEGER, `subs_item_id` INTEGER NOT NULL, `category_key` INTEGER NOT NULL, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "enable",
"columnName": "enable",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "subsItemId",
"columnName": "subs_item_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "categoryKey",
"columnName": "category_key",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'd1a618bf8475b588793fb1d201815a08')"
]
}
}
```
## /app/schemas/li.songe.gkd.db.AppDb/5.json
```json path="/app/schemas/li.songe.gkd.db.AppDb/5.json"
{
"formatVersion": 1,
"database": {
"version": 5,
"identityHash": "4219672d163fce6e91926d9e15fc0e64",
"entities": [
{
"tableName": "subs_item",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `ctime` INTEGER NOT NULL, `mtime` INTEGER NOT NULL, `enable` INTEGER NOT NULL, `enable_update` INTEGER NOT NULL, `order` INTEGER NOT NULL, `update_url` TEXT, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "ctime",
"columnName": "ctime",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "mtime",
"columnName": "mtime",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "enable",
"columnName": "enable",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "enableUpdate",
"columnName": "enable_update",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "order",
"columnName": "order",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "updateUrl",
"columnName": "update_url",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "snapshot",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `app_id` TEXT, `activity_id` TEXT, `app_name` TEXT, `app_version_code` INTEGER, `app_version_name` TEXT, `screen_height` INTEGER NOT NULL, `screen_width` INTEGER NOT NULL, `is_landscape` INTEGER NOT NULL, `github_asset_id` INTEGER, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "appId",
"columnName": "app_id",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "activityId",
"columnName": "activity_id",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "appName",
"columnName": "app_name",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "appVersionCode",
"columnName": "app_version_code",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "appVersionName",
"columnName": "app_version_name",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "screenHeight",
"columnName": "screen_height",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "screenWidth",
"columnName": "screen_width",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "isLandscape",
"columnName": "is_landscape",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "githubAssetId",
"columnName": "github_asset_id",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "subs_config",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `type` INTEGER NOT NULL, `enable` INTEGER NOT NULL, `subs_item_id` INTEGER NOT NULL, `app_id` TEXT NOT NULL, `group_key` INTEGER NOT NULL, `exclude` TEXT NOT NULL DEFAULT '', PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "enable",
"columnName": "enable",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "subsItemId",
"columnName": "subs_item_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "appId",
"columnName": "app_id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "groupKey",
"columnName": "group_key",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "exclude",
"columnName": "exclude",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "''"
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "click_log",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `app_id` TEXT, `activity_id` TEXT, `subs_id` INTEGER NOT NULL, `subs_version` INTEGER NOT NULL DEFAULT 0, `group_key` INTEGER NOT NULL, `group_type` INTEGER NOT NULL DEFAULT 2, `rule_index` INTEGER NOT NULL, `rule_key` INTEGER, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "appId",
"columnName": "app_id",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "activityId",
"columnName": "activity_id",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "subsId",
"columnName": "subs_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "subsVersion",
"columnName": "subs_version",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "groupKey",
"columnName": "group_key",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "groupType",
"columnName": "group_type",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "2"
},
{
"fieldPath": "ruleIndex",
"columnName": "rule_index",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "ruleKey",
"columnName": "rule_key",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "category_config",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `enable` INTEGER, `subs_item_id` INTEGER NOT NULL, `category_key` INTEGER NOT NULL, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "enable",
"columnName": "enable",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "subsItemId",
"columnName": "subs_item_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "categoryKey",
"columnName": "category_key",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '4219672d163fce6e91926d9e15fc0e64')"
]
}
}
```
## /app/schemas/li.songe.gkd.db.AppDb/6.json
```json path="/app/schemas/li.songe.gkd.db.AppDb/6.json"
{
"formatVersion": 1,
"database": {
"version": 6,
"identityHash": "6cbd7772c779598a5448b9d5dc36c524",
"entities": [
{
"tableName": "subs_item",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `ctime` INTEGER NOT NULL, `mtime` INTEGER NOT NULL, `enable` INTEGER NOT NULL, `enable_update` INTEGER NOT NULL, `order` INTEGER NOT NULL, `update_url` TEXT, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "ctime",
"columnName": "ctime",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "mtime",
"columnName": "mtime",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "enable",
"columnName": "enable",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "enableUpdate",
"columnName": "enable_update",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "order",
"columnName": "order",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "updateUrl",
"columnName": "update_url",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "snapshot",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `app_id` TEXT, `activity_id` TEXT, `app_name` TEXT, `app_version_code` INTEGER, `app_version_name` TEXT, `screen_height` INTEGER NOT NULL, `screen_width` INTEGER NOT NULL, `is_landscape` INTEGER NOT NULL, `github_asset_id` INTEGER, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "appId",
"columnName": "app_id",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "activityId",
"columnName": "activity_id",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "appName",
"columnName": "app_name",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "appVersionCode",
"columnName": "app_version_code",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "appVersionName",
"columnName": "app_version_name",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "screenHeight",
"columnName": "screen_height",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "screenWidth",
"columnName": "screen_width",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "isLandscape",
"columnName": "is_landscape",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "githubAssetId",
"columnName": "github_asset_id",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "subs_config",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `type` INTEGER NOT NULL, `enable` INTEGER, `subs_item_id` INTEGER NOT NULL, `app_id` TEXT NOT NULL, `group_key` INTEGER NOT NULL, `exclude` TEXT NOT NULL DEFAULT '', PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "enable",
"columnName": "enable",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "subsItemId",
"columnName": "subs_item_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "appId",
"columnName": "app_id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "groupKey",
"columnName": "group_key",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "exclude",
"columnName": "exclude",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "''"
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "click_log",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `app_id` TEXT, `activity_id` TEXT, `subs_id` INTEGER NOT NULL, `subs_version` INTEGER NOT NULL DEFAULT 0, `group_key` INTEGER NOT NULL, `group_type` INTEGER NOT NULL DEFAULT 2, `rule_index` INTEGER NOT NULL, `rule_key` INTEGER, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "appId",
"columnName": "app_id",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "activityId",
"columnName": "activity_id",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "subsId",
"columnName": "subs_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "subsVersion",
"columnName": "subs_version",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "groupKey",
"columnName": "group_key",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "groupType",
"columnName": "group_type",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "2"
},
{
"fieldPath": "ruleIndex",
"columnName": "rule_index",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "ruleKey",
"columnName": "rule_key",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "category_config",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `enable` INTEGER, `subs_item_id` INTEGER NOT NULL, `category_key` INTEGER NOT NULL, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "enable",
"columnName": "enable",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "subsItemId",
"columnName": "subs_item_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "categoryKey",
"columnName": "category_key",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '6cbd7772c779598a5448b9d5dc36c524')"
]
}
}
```
## /app/schemas/li.songe.gkd.db.AppDb/7.json
```json path="/app/schemas/li.songe.gkd.db.AppDb/7.json"
{
"formatVersion": 1,
"database": {
"version": 7,
"identityHash": "9d5e657834ed630ac5cf00753cf24a55",
"entities": [
{
"tableName": "subs_item",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `ctime` INTEGER NOT NULL, `mtime` INTEGER NOT NULL, `enable` INTEGER NOT NULL, `enable_update` INTEGER NOT NULL, `order` INTEGER NOT NULL, `update_url` TEXT, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "ctime",
"columnName": "ctime",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "mtime",
"columnName": "mtime",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "enable",
"columnName": "enable",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "enableUpdate",
"columnName": "enable_update",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "order",
"columnName": "order",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "updateUrl",
"columnName": "update_url",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "snapshot",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `app_id` TEXT, `activity_id` TEXT, `app_name` TEXT, `app_version_code` INTEGER, `app_version_name` TEXT, `screen_height` INTEGER NOT NULL, `screen_width` INTEGER NOT NULL, `is_landscape` INTEGER NOT NULL, `github_asset_id` INTEGER, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "appId",
"columnName": "app_id",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "activityId",
"columnName": "activity_id",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "appName",
"columnName": "app_name",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "appVersionCode",
"columnName": "app_version_code",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "appVersionName",
"columnName": "app_version_name",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "screenHeight",
"columnName": "screen_height",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "screenWidth",
"columnName": "screen_width",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "isLandscape",
"columnName": "is_landscape",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "githubAssetId",
"columnName": "github_asset_id",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "subs_config",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `type` INTEGER NOT NULL, `enable` INTEGER, `subs_item_id` INTEGER NOT NULL, `app_id` TEXT NOT NULL, `group_key` INTEGER NOT NULL, `exclude` TEXT NOT NULL DEFAULT '', PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "enable",
"columnName": "enable",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "subsItemId",
"columnName": "subs_item_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "appId",
"columnName": "app_id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "groupKey",
"columnName": "group_key",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "exclude",
"columnName": "exclude",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "''"
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "click_log",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `app_id` TEXT, `activity_id` TEXT, `subs_id` INTEGER NOT NULL, `subs_version` INTEGER NOT NULL DEFAULT 0, `group_key` INTEGER NOT NULL, `group_type` INTEGER NOT NULL DEFAULT 2, `rule_index` INTEGER NOT NULL, `rule_key` INTEGER, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "appId",
"columnName": "app_id",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "activityId",
"columnName": "activity_id",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "subsId",
"columnName": "subs_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "subsVersion",
"columnName": "subs_version",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "groupKey",
"columnName": "group_key",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "groupType",
"columnName": "group_type",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "2"
},
{
"fieldPath": "ruleIndex",
"columnName": "rule_index",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "ruleKey",
"columnName": "rule_key",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "category_config",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `enable` INTEGER, `subs_item_id` INTEGER NOT NULL, `category_key` INTEGER NOT NULL, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "enable",
"columnName": "enable",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "subsItemId",
"columnName": "subs_item_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "categoryKey",
"columnName": "category_key",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "activity_log",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `app_id` TEXT NOT NULL, `activity_id` TEXT, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "appId",
"columnName": "app_id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "activityId",
"columnName": "activity_id",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '9d5e657834ed630ac5cf00753cf24a55')"
]
}
}
```
## /app/schemas/li.songe.gkd.db.AppDb/8.json
```json path="/app/schemas/li.songe.gkd.db.AppDb/8.json"
{
"formatVersion": 1,
"database": {
"version": 8,
"identityHash": "409bb51310bcdb55ea721a8e88b6cef6",
"entities": [
{
"tableName": "subs_item",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `ctime` INTEGER NOT NULL, `mtime` INTEGER NOT NULL, `enable` INTEGER NOT NULL, `enable_update` INTEGER NOT NULL, `order` INTEGER NOT NULL, `update_url` TEXT, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "ctime",
"columnName": "ctime",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "mtime",
"columnName": "mtime",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "enable",
"columnName": "enable",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "enableUpdate",
"columnName": "enable_update",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "order",
"columnName": "order",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "updateUrl",
"columnName": "update_url",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "snapshot",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `app_id` TEXT, `activity_id` TEXT, `app_name` TEXT, `app_version_code` INTEGER, `app_version_name` TEXT, `screen_height` INTEGER NOT NULL, `screen_width` INTEGER NOT NULL, `is_landscape` INTEGER NOT NULL, `github_asset_id` INTEGER, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "appId",
"columnName": "app_id",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "activityId",
"columnName": "activity_id",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "appName",
"columnName": "app_name",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "appVersionCode",
"columnName": "app_version_code",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "appVersionName",
"columnName": "app_version_name",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "screenHeight",
"columnName": "screen_height",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "screenWidth",
"columnName": "screen_width",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "isLandscape",
"columnName": "is_landscape",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "githubAssetId",
"columnName": "github_asset_id",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "subs_config",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `type` INTEGER NOT NULL, `enable` INTEGER, `subs_item_id` INTEGER NOT NULL, `app_id` TEXT NOT NULL, `group_key` INTEGER NOT NULL, `exclude` TEXT NOT NULL DEFAULT '', PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "enable",
"columnName": "enable",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "subsItemId",
"columnName": "subs_item_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "appId",
"columnName": "app_id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "groupKey",
"columnName": "group_key",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "exclude",
"columnName": "exclude",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "''"
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "click_log",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `app_id` TEXT, `activity_id` TEXT, `subs_id` INTEGER NOT NULL, `subs_version` INTEGER NOT NULL DEFAULT 0, `group_key` INTEGER NOT NULL, `group_type` INTEGER NOT NULL DEFAULT 2, `rule_index` INTEGER NOT NULL, `rule_key` INTEGER, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "appId",
"columnName": "app_id",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "activityId",
"columnName": "activity_id",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "subsId",
"columnName": "subs_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "subsVersion",
"columnName": "subs_version",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "groupKey",
"columnName": "group_key",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "groupType",
"columnName": "group_type",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "2"
},
{
"fieldPath": "ruleIndex",
"columnName": "rule_index",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "ruleKey",
"columnName": "rule_key",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "category_config",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `enable` INTEGER, `subs_item_id` INTEGER NOT NULL, `category_key` INTEGER NOT NULL, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "enable",
"columnName": "enable",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "subsItemId",
"columnName": "subs_item_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "categoryKey",
"columnName": "category_key",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "activity_log_v2",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `ctime` INTEGER NOT NULL, `app_id` TEXT NOT NULL, `activity_id` TEXT)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "ctime",
"columnName": "ctime",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "appId",
"columnName": "app_id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "activityId",
"columnName": "activity_id",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '409bb51310bcdb55ea721a8e88b6cef6')"
]
}
}
```
## /app/schemas/li.songe.gkd.db.AppDb/9.json
```json path="/app/schemas/li.songe.gkd.db.AppDb/9.json"
{
"formatVersion": 1,
"database": {
"version": 9,
"identityHash": "343aa23eaf071a84fab19c5979d95f13",
"entities": [
{
"tableName": "subs_item",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `ctime` INTEGER NOT NULL, `mtime` INTEGER NOT NULL, `enable` INTEGER NOT NULL, `enable_update` INTEGER NOT NULL, `order` INTEGER NOT NULL, `update_url` TEXT, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "ctime",
"columnName": "ctime",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "mtime",
"columnName": "mtime",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "enable",
"columnName": "enable",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "enableUpdate",
"columnName": "enable_update",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "order",
"columnName": "order",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "updateUrl",
"columnName": "update_url",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "snapshot",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `app_id` TEXT, `activity_id` TEXT, `app_name` TEXT, `app_version_code` INTEGER, `app_version_name` TEXT, `screen_height` INTEGER NOT NULL, `screen_width` INTEGER NOT NULL, `is_landscape` INTEGER NOT NULL, `github_asset_id` INTEGER, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "appId",
"columnName": "app_id",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "activityId",
"columnName": "activity_id",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "appName",
"columnName": "app_name",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "appVersionCode",
"columnName": "app_version_code",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "appVersionName",
"columnName": "app_version_name",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "screenHeight",
"columnName": "screen_height",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "screenWidth",
"columnName": "screen_width",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "isLandscape",
"columnName": "is_landscape",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "githubAssetId",
"columnName": "github_asset_id",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "subs_config",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `type` INTEGER NOT NULL, `enable` INTEGER, `subs_item_id` INTEGER NOT NULL, `app_id` TEXT NOT NULL, `group_key` INTEGER NOT NULL, `exclude` TEXT NOT NULL DEFAULT '', PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "enable",
"columnName": "enable",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "subsItemId",
"columnName": "subs_item_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "appId",
"columnName": "app_id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "groupKey",
"columnName": "group_key",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "exclude",
"columnName": "exclude",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "''"
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "category_config",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `enable` INTEGER, `subs_item_id` INTEGER NOT NULL, `category_key` INTEGER NOT NULL, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "enable",
"columnName": "enable",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "subsItemId",
"columnName": "subs_item_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "categoryKey",
"columnName": "category_key",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "action_log",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `ctime` INTEGER NOT NULL, `app_id` TEXT NOT NULL, `activity_id` TEXT, `subs_id` INTEGER NOT NULL, `subs_version` INTEGER NOT NULL DEFAULT 0, `group_key` INTEGER NOT NULL, `group_type` INTEGER NOT NULL DEFAULT 2, `rule_index` INTEGER NOT NULL, `rule_key` INTEGER)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "ctime",
"columnName": "ctime",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "appId",
"columnName": "app_id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "activityId",
"columnName": "activity_id",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "subsId",
"columnName": "subs_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "subsVersion",
"columnName": "subs_version",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "groupKey",
"columnName": "group_key",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "groupType",
"columnName": "group_type",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "2"
},
{
"fieldPath": "ruleIndex",
"columnName": "rule_index",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "ruleKey",
"columnName": "rule_key",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "activity_log_v2",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `ctime` INTEGER NOT NULL, `app_id` TEXT NOT NULL, `activity_id` TEXT)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "ctime",
"columnName": "ctime",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "appId",
"columnName": "app_id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "activityId",
"columnName": "activity_id",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '343aa23eaf071a84fab19c5979d95f13')"
]
}
}
```
## /app/src/androidTest/kotlin/li/songe/gkd/ExampleInstrumentedTest.kt
```kt path="/app/src/androidTest/kotlin/li/songe/gkd/ExampleInstrumentedTest.kt"
package li.songe.gkd
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.*
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("li.songe.gkd", appContext.packageName)
}
}
```
## /app/src/gkd/AndroidManifest.xml
```xml path="/app/src/gkd/AndroidManifest.xml"
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission
android:name="android.permission.REQUEST_INSTALL_PACKAGES"
tools:ignore="RequestInstallPackagesPolicy" />
</manifest>
```
## /app/src/main/AndroidManifest.xml
```xml path="/app/src/main/AndroidManifest.xml"
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission
android:name="android.permission.FOREGROUND_SERVICE"
tools:ignore="ForegroundServicesPolicy" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
<uses-permission
android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION"
android:maxSdkVersion="29" />
<uses-permission
android:name="android.permission.QUERY_ALL_PACKAGES"
tools:ignore="PackageVisibilityPolicy,QueryAllPackagesPermission" />
<uses-permission android:name="com.android.permission.GET_INSTALLED_APPS" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28" />
<uses-permission
android:name="android.permission.WRITE_SECURE_SETTINGS"
tools:ignore="ProtectedPermissions" />
<uses-permission
android:name="android.permission.GET_APP_OPS_STATS"
tools:ignore="ProtectedPermissions" />
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
<application
android:name=".App"
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:networkSecurityConfig="@xml/network_security_config"
android:roundIcon="@drawable/ic_launcher"
android:supportsRtl="false"
android:theme="@style/AppTheme">
<meta-data
android:name="channel"
android:value="${channel}" />
<meta-data
android:name="commitId"
android:value="${commitId}" />
<meta-data
android:name="commitTime"
android:value="${commitTime}" />
<meta-data
android:name="tagName"
android:value="${tagName}" />
<activity
android:name=".MainActivity"
android:enableOnBackInvokedCallback="false"
android:exported="true"
android:launchMode="singleTop"
android:theme="@style/SplashScreenTheme"
tools:ignore="UnusedAttribute">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".OpenSchemeActivity"
android:exported="true"
android:theme="@style/TransparentTheme">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="gkd" />
</intent-filter>
</activity>
<activity
android:name=".OpenFileActivity"
android:exported="true"
android:theme="@style/TransparentTheme">
<intent-filter android:label="@string/import_backup">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="content" />
<data android:mimeType="application/zip" />
<data android:mimeType="application/x-zip-compressed" />
</intent-filter>
</activity>
<activity
android:name=".OpenTileActivity"
android:exported="true"
android:theme="@style/TransparentTheme">
<intent-filter>
<action android:name="android.service.quicksettings.action.QS_TILE_PREFERENCES" />
</intent-filter>
</activity>
<provider
android:name="rikka.shizuku.ShizukuProvider"
android:authorities="${applicationId}.shizuku"
android:enabled="true"
android:exported="true"
android:multiprocess="false"
android:permission="android.permission.INTERACT_ACROSS_USERS_FULL" />
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
<service
android:name="com.google.android.accessibility.selecttospeak.SelectToSpeakService"
android:exported="false"
android:label="@string/app_name"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
tools:ignore="AccessibilityPolicy">
<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService" />
</intent-filter>
<meta-data
android:name="android.accessibilityservice"
android:resource="@xml/ab_desc" />
</service>
<service
android:name=".service.ScreenshotService"
android:exported="false"
android:foregroundServiceType="mediaProjection" />
<service
android:name=".service.StatusService"
android:exported="false"
android:foregroundServiceType="specialUse">
<property
android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
android:value="Display the running state of the application" />
</service>
<service
android:name=".service.HttpService"
android:exported="false"
android:foregroundServiceType="specialUse">
<property
android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
android:value="Enable the HTTP server to provide external browser connections and debugging" />
</service>
<service
android:name=".service.ButtonService"
android:exported="false"
android:foregroundServiceType="specialUse">
<property
android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
android:value="Display a screenshot button for users to actively save screen information." />
</service>
<service
android:name=".service.ActivityService"
android:exported="false"
android:foregroundServiceType="specialUse">
<property
android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
android:value="Display a text card for users to show current activity info" />
</service>
<service
android:name=".service.EventService"
android:exported="false"
android:foregroundServiceType="specialUse">
<property
android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
android:value="Display a text card for users to show a11y event log" />
</service>
<service
android:name=".service.TrackService"
android:exported="false"
android:foregroundServiceType="specialUse">
<property
android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
android:value="Show Action Track" />
</service>
<service
android:name=".service.ExposeService"
android:exported="true"
android:foregroundServiceType="specialUse"
tools:ignore="ExportedService">
<property
android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
android:value="Provide an interface for third-party applications to actively invoke snapshot capturing." />
</service>
<service
android:name=".service.GkdTileService"
android:exported="true"
android:icon="@drawable/ic_status"
android:label="@string/app_name"
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
<meta-data
android:name="android.service.quicksettings.TOGGLEABLE_TILE"
android:value="true" />
<meta-data
android:name="QS_TILE_URI"
android:value="gkd://page" />
<intent-filter>
<action android:name="android.service.quicksettings.action.QS_TILE" />
</intent-filter>
</service>
<service
android:name=".service.SnapshotTileService"
android:exported="true"
android:icon="@drawable/ic_capture"
android:label="@string/capture_snapshot"
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
<meta-data
android:name="QS_TILE_URI"
android:value="gkd://page/2" />
<intent-filter>
<action android:name="android.service.quicksettings.action.QS_TILE" />
</intent-filter>
</service>
<service
android:name=".service.HttpTileService"
android:exported="true"
android:icon="@drawable/ic_http"
android:label="@string/http_server"
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
<meta-data
android:name="android.service.quicksettings.TOGGLEABLE_TILE"
android:value="true" />
<meta-data
android:name="QS_TILE_URI"
android:value="gkd://page/1" />
<intent-filter>
<action android:name="android.service.quicksettings.action.QS_TILE" />
</intent-filter>
</service>
<service
android:name=".service.ButtonTileService"
android:exported="true"
android:icon="@drawable/ic_radio_button"
android:label="@string/snapshot_button"
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
<meta-data
android:name="android.service.quicksettings.TOGGLEABLE_TILE"
android:value="true" />
<meta-data
android:name="QS_TILE_URI"
android:value="gkd://page/1" />
<intent-filter>
<action android:name="android.service.quicksettings.action.QS_TILE" />
</intent-filter>
</service>
<service
android:name=".service.MatchTileService"
android:exported="true"
android:icon="@drawable/ic_flash_on"
android:label="@string/rule_match"
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
<meta-data
android:name="android.service.quicksettings.TOGGLEABLE_TILE"
android:value="true" />
<meta-data
android:name="QS_TILE_URI"
android:value="gkd://page?tab=1" />
<intent-filter>
<action android:name="android.service.quicksettings.action.QS_TILE" />
</intent-filter>
</service>
<service
android:name=".service.ActivityTileService"
android:exported="true"
android:icon="@drawable/ic_layers"
android:label="@string/record_activity"
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
<meta-data
android:name="android.service.quicksettings.TOGGLEABLE_TILE"
android:value="true" />
<meta-data
android:name="QS_TILE_URI"
android:value="gkd://page/1" />
<intent-filter>
<action android:name="android.service.quicksettings.action.QS_TILE" />
</intent-filter>
</service>
<service
android:name=".service.EventTileService"
android:exported="true"
android:icon="@drawable/ic_event_list"
android:label="@string/record_a11y_event"
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
<meta-data
android:name="android.service.quicksettings.TOGGLEABLE_TILE"
android:value="true" />
<meta-data
android:name="QS_TILE_URI"
android:value="gkd://page/1" />
<intent-filter>
<action android:name="android.service.quicksettings.action.QS_TILE" />
</intent-filter>
</service>
</application>
</manifest>
```
## /app/src/main/aidl/li/songe/gkd/shizuku/CommandResult.aidl
```aidl path="/app/src/main/aidl/li/songe/gkd/shizuku/CommandResult.aidl"
package li.songe.gkd.shizuku;
parcelable CommandResult;
```
## /app/src/main/aidl/li/songe/gkd/shizuku/IUserService.aidl
```aidl path="/app/src/main/aidl/li/songe/gkd/shizuku/IUserService.aidl"
package li.songe.gkd.shizuku;
import android.graphics.Bitmap;
import android.graphics.Rect;
import li.songe.gkd.shizuku.CommandResult;
interface IUserService {
void destroy() = 16777114; // Destroy method defined by Shizuku server
void exit() = 1;
CommandResult execCommand(String command) = 2;
Bitmap takeScreenshot1(int width, int height) = 3;
Bitmap takeScreenshot2(in Rect crop, int rotation) = 4;
Bitmap takeScreenshot3(in Rect crop) = 5;
int killLegacyService() = 6;
}
```
## /app/src/main/kotlin/com/google/android/accessibility/selecttospeak/SelectToSpeakService.kt
```kt path="/app/src/main/kotlin/com/google/android/accessibility/selecttospeak/SelectToSpeakService.kt"
package com.google.android.accessibility.selecttospeak
import li.songe.gkd.service.A11yService
// https://github.com/ven-coder/Assists/issues/12#issuecomment-2684469065
class SelectToSpeakService : A11yService()
```
## /app/src/main/kotlin/li/songe/gkd/App.kt
```kt path="/app/src/main/kotlin/li/songe/gkd/App.kt"
package li.songe.gkd
import android.app.ActivityManager
import android.app.AppOpsManager
import android.app.Application
import android.app.KeyguardManager
import android.content.ClipboardManager
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.pm.ApplicationInfo
import android.content.pm.LauncherApps
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.database.ContentObserver
import android.hardware.display.DisplayManager
import android.hardware.input.InputManager
import android.net.Uri
import android.os.PowerManager
import android.provider.Settings
import android.util.Log
import android.view.Display
import android.view.WindowManager
import android.view.accessibility.AccessibilityManager
import android.view.inputmethod.InputMethodManager
import androidx.core.content.ContextCompat
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.delay
import kotlinx.serialization.Serializable
import li.songe.gkd.a11y.initA11yFeat
import li.songe.gkd.data.CrashData
import li.songe.gkd.data.selfAppInfo
import li.songe.gkd.notif.initChannel
import li.songe.gkd.service.clearHttpSubs
import li.songe.gkd.service.initA11yWhiteAppList
import li.songe.gkd.shizuku.initShizuku
import li.songe.gkd.store.initStore
import li.songe.gkd.util.AndroidTarget
import li.songe.gkd.util.LogUtils
import li.songe.gkd.util.PKG_FLAGS
import li.songe.gkd.util.deviceInfoDesc
import li.songe.gkd.util.initAppState
import li.songe.gkd.util.initSubsState
import li.songe.gkd.util.initToast
import li.songe.gkd.util.launchTry
import li.songe.gkd.util.toast
import org.lsposed.hiddenapibypass.HiddenApiBypass
import kotlin.system.exitProcess
val appScope by lazy { MainScope() }
private lateinit var innerApp: App
val app: App
get() = innerApp
private val applicationInfo by lazy {
app.packageManager.getApplicationInfo(
app.packageName,
PackageManager.GET_META_DATA
)
}
private fun getMetaString(key: String): String {
return applicationInfo.metaData.getString(key) ?: error("Missing meta-data: $key")
}
// https://github.com/android-cs/16/blob/main/packages/SettingsLib/src/com/android/settingslib/accessibility/AccessibilityUtils.java#L41
private const val ENABLED_ACCESSIBILITY_SERVICES_SEPARATOR = ':'
@Serializable
data class AppMeta(
val channel: String = getMetaString("channel"),
val commitId: String = getMetaString("commitId"),
val commitTime: Long = getMetaString("commitTime").toLong(),
val tagName: String? = getMetaString("tagName").takeIf { it.isNotEmpty() },
val debuggable: Boolean = applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE != 0,
val versionCode: Int = selfAppInfo.versionCode,
val versionName: String = selfAppInfo.versionName!!,
val appId: String = app.packageName!!,
val appName: String = app.getString(R.string.app_name)
) {
val commitUrl = "https://github.com/gkd-kit/gkd/".run {
plus(if (tagName != null) "tree/$tagName" else "commit/$commitId")
}
val isGkdChannel get() = channel == "gkd"
val updateEnabled get() = isGkdChannel
val isBeta get() = versionName.contains("beta")
}
val META by lazy { AppMeta() }
fun contentObserver(listener: () -> Unit) = object : ContentObserver(null) {
override fun onChange(selfChange: Boolean) = listener()
}
class App : Application() {
companion object {
const val START_WAIT_TIME = 3000L
}
init {
innerApp = this
}
override fun attachBaseContext(base: Context?) {
super.attachBaseContext(base)
if (AndroidTarget.P) {
HiddenApiBypass.addHiddenApiExemptions("L")
}
}
fun registerObserver(
uri: Uri,
observer: ContentObserver
) {
contentResolver.registerContentObserver(uri, false, observer)
}
fun unregisterObserver(observer: ContentObserver) {
contentResolver.unregisterContentObserver(observer)
}
fun getSecureString(name: String): String? = Settings.Secure.getString(contentResolver, name)
fun putSecureString(name: String, value: String?): Boolean {
return Settings.Secure.putString(contentResolver, name, value)
}
fun putSecureInt(name: String, value: Int): Boolean {
return Settings.Secure.putInt(contentResolver, name, value)
}
fun getSecureA11yServices(): MutableSet<ComponentName> {
val value = getSecureString(Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES)
if (value.isNullOrEmpty()) return mutableSetOf()
return value.split(
ENABLED_ACCESSIBILITY_SERVICES_SEPARATOR
).mapNotNull { ComponentName.unflattenFromString(it) }.toHashSet()
}
fun putSecureA11yServices(services: Set<ComponentName>) {
putSecureString(
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
services.joinToString(ENABLED_ACCESSIBILITY_SERVICES_SEPARATOR.toString()) { it.flattenToShortString() }
)
}
fun resolveAppId(intent: Intent): String? {
return intent.resolveActivity(packageManager)?.packageName
}
fun getPkgInfo(appId: String): PackageInfo? = try {
packageManager.getPackageInfo(appId, PKG_FLAGS)
} catch (_: PackageManager.NameNotFoundException) {
null
}
fun resolveAppId(action: String, category: String? = null): String? {
val intent = Intent(action)
if (category != null) {
intent.addCategory(category)
}
return resolveAppId(intent)
}
fun startLaunchActivity() {
val intent = packageManager.getLaunchIntentForPackage(META.appId)!!
intent.addFlags(
Intent.FLAG_ACTIVITY_NEW_TASK
or Intent.FLAG_ACTIVITY_CLEAR_TOP
or Intent.FLAG_ACTIVITY_CLEAR_TASK
)
startActivity(intent)
}
fun checkGrantedPermission(permission: String) = ContextCompat.checkSelfPermission(
this,
permission,
) == PackageManager.PERMISSION_GRANTED
val startTime = System.currentTimeMillis()
var justStarted: Boolean = true
get() {
if (field) {
field = System.currentTimeMillis() - startTime < START_WAIT_TIME
}
return field
}
val activityManager by lazy { app.getSystemService(ACTIVITY_SERVICE) as ActivityManager }
val appOpsManager by lazy { app.getSystemService(APP_OPS_SERVICE) as AppOpsManager }
val inputMethodManager by lazy { app.getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager }
val inputManager by lazy { app.getSystemService(INPUT_SERVICE) as InputManager }
val windowManager by lazy { app.getSystemService(WINDOW_SERVICE) as WindowManager }
val displayManager by lazy { app.getSystemService(DISPLAY_SERVICE) as DisplayManager }
val keyguardManager by lazy { app.getSystemService(KEYGUARD_SERVICE) as KeyguardManager }
val clipboardManager by lazy { app.getSystemService(CLIPBOARD_SERVICE) as ClipboardManager }
val powerManager by lazy { getSystemService(POWER_SERVICE) as PowerManager }
val a11yManager by lazy { getSystemService(ACCESSIBILITY_SERVICE) as AccessibilityManager }
val launcherApps by lazy { getSystemService(LAUNCHER_APPS_SERVICE) as LauncherApps }
val compatDisplay: Display
get() = if (AndroidTarget.R) {
displayManager.getDisplay(Display.DEFAULT_DISPLAY)
} else {
@Suppress("DEPRECATION")
windowManager.defaultDisplay
}
override fun onCreate() {
super.onCreate()
LogUtils.d()
Thread.setDefaultUncaughtExceptionHandler { t, e ->
toast(e.message ?: e.toString())
LogUtils.d("UncaughtExceptionHandler", t, e)
val mtime = System.currentTimeMillis()
appScope.launchTry(Dispatchers.IO) {
CrashData(
id = mtime,
mtime = mtime,
device = deviceInfoDesc,
androidVersionCode = android.os.Build.VERSION.SDK_INT,
androidVersionName = android.os.Build.VERSION.RELEASE,
versionCode = META.versionCode,
versionName = META.versionName,
name = e::class.java.name,
message = e.message,
thread = t.name,
stackTrace = Log.getStackTraceString(e),
).save()
delay(1500)
if (isActivityVisible) {
startLaunchActivity()
}
android.os.Process.killProcess(android.os.Process.myPid())
exitProcess(0)
}
}
initToast()
initStore()
initChannel()
initAppState()
initA11yFeat()
initShizuku()
initSubsState()
initA11yWhiteAppList()
clearHttpSubs()
syncFixState()
}
}
```
## /app/src/main/kotlin/li/songe/gkd/MainActivity.kt
```kt path="/app/src/main/kotlin/li/songe/gkd/MainActivity.kt"
package li.songe.gkd
import android.app.Activity
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels
import androidx.compose.animation.slideInHorizontally
import androidx.compose.animation.slideOutHorizontally
import androidx.compose.animation.togetherWith
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsAnimationCompat
import androidx.core.view.WindowInsetsCompat
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.viewmodel.navigation3.rememberViewModelStoreNavEntryDecorator
import androidx.navigation3.runtime.entryProvider
import androidx.navigation3.runtime.rememberSaveableStateHolderNavEntryDecorator
import androidx.navigation3.ui.NavDisplay
import com.dylanc.activityresult.launcher.PickContentLauncher
import com.dylanc.activityresult.launcher.StartActivityLauncher
import com.dylanc.activityresult.launcher.launchForResult
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.flow.updateAndGet
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import li.songe.gkd.a11y.topActivityFlow
import li.songe.gkd.a11y.updateSystemDefaultAppId
import li.songe.gkd.a11y.updateTopActivity
import li.songe.gkd.permission.AuthDialog
import li.songe.gkd.permission.updatePermissionState
import li.songe.gkd.service.A11yService
import li.songe.gkd.service.StatusService
import li.songe.gkd.service.fixRestartAutomatorService
import li.songe.gkd.service.updateTopTaskAppId
import li.songe.gkd.shizuku.automationRegisteredExceptionFlow
import li.songe.gkd.shizuku.shizukuContextFlow
import li.songe.gkd.store.storeFlow
import li.songe.gkd.ui.A11YScopeAppListRoute
import li.songe.gkd.ui.A11yEventLogPage
import li.songe.gkd.ui.A11yEventLogRoute
import li.songe.gkd.ui.A11yScopeAppListPage
import li.songe.gkd.ui.AboutPage
import li.songe.gkd.ui.AboutRoute
import li.songe.gkd.ui.ActionLogPage
import li.songe.gkd.ui.ActionLogRoute
import li.songe.gkd.ui.ActivityLogPage
import li.songe.gkd.ui.ActivityLogRoute
import li.songe.gkd.ui.AdvancedPage
import li.songe.gkd.ui.AdvancedPageRoute
import li.songe.gkd.ui.AppConfigPage
import li.songe.gkd.ui.AppConfigRoute
import li.songe.gkd.ui.AppOpsAllowPage
import li.songe.gkd.ui.AppOpsAllowRoute
import li.songe.gkd.ui.AuthA11yPage
import li.songe.gkd.ui.AuthA11yRoute
import li.songe.gkd.ui.BlockA11yAppListPage
import li.songe.gkd.ui.BlockA11yAppListRoute
import li.songe.gkd.ui.CrashReportPage
import li.songe.gkd.ui.CrashReportRoute
import li.songe.gkd.ui.EditBlockAppListPage
import li.songe.gkd.ui.EditBlockAppListRoute
import li.songe.gkd.ui.ImagePreviewPage
import li.songe.gkd.ui.ImagePreviewRoute
import li.songe.gkd.ui.SlowGroupPage
import li.songe.gkd.ui.SlowGroupRoute
import li.songe.gkd.ui.SnapshotPage
import li.songe.gkd.ui.SnapshotPageRoute
import li.songe.gkd.ui.SubsAppGroupListPage
import li.songe.gkd.ui.SubsAppGroupListRoute
import li.songe.gkd.ui.SubsAppListPage
import li.songe.gkd.ui.SubsAppListRoute
import li.songe.gkd.ui.SubsCategoryGroupPage
import li.songe.gkd.ui.SubsCategoryGroupRoute
import li.songe.gkd.ui.SubsCategoryPage
import li.songe.gkd.ui.SubsCategoryRoute
import li.songe.gkd.ui.SubsGlobalGroupExcludePage
import li.songe.gkd.ui.SubsGlobalGroupExcludeRoute
import li.songe.gkd.ui.SubsGlobalGroupListPage
import li.songe.gkd.ui.SubsGlobalGroupListRoute
import li.songe.gkd.ui.UpsertRuleGroupPage
import li.songe.gkd.ui.UpsertRuleGroupRoute
import li.songe.gkd.ui.WebViewPage
import li.songe.gkd.ui.WebViewRoute
import li.songe.gkd.ui.component.BuildDialog
import li.songe.gkd.ui.component.PerfIcon
import li.songe.gkd.ui.component.ShareLogDlg
import li.songe.gkd.ui.component.SubsSheet
import li.songe.gkd.ui.component.TermsAcceptDialog
import li.songe.gkd.ui.component.TextDialog
import li.songe.gkd.ui.home.HomePage
import li.songe.gkd.ui.home.HomeRoute
import li.songe.gkd.ui.share.FixedWindowInsets
import li.songe.gkd.ui.share.LocalMainViewModel
import li.songe.gkd.ui.style.AppTheme
import li.songe.gkd.util.AndroidTarget
import li.songe.gkd.util.BarUtils
import li.songe.gkd.util.EditGithubCookieDlg
import li.songe.gkd.util.KeyboardUtils
import li.songe.gkd.util.LogUtils
import li.songe.gkd.util.ShortUrlSet
import li.songe.gkd.util.appInfoMapFlow
import li.songe.gkd.util.componentName
import li.songe.gkd.util.copyText
import li.songe.gkd.util.fixSomeProblems
import li.songe.gkd.util.launchTry
import li.songe.gkd.util.mapState
import li.songe.gkd.util.openApp
import li.songe.gkd.util.openUri
import li.songe.gkd.util.shizukuAppId
import li.songe.gkd.util.throttle
import li.songe.gkd.util.toast
import kotlin.concurrent.Volatile
import kotlin.reflect.jvm.jvmName
class MainActivity : ComponentActivity() {
val startTime = System.currentTimeMillis()
val mainVm by viewModels<MainViewModel>()
val launcher by lazy { StartActivityLauncher(this) }
val pickContentLauncher by lazy { PickContentLauncher(this) }
val imeFullHiddenFlow = MutableStateFlow(true)
val imePlayingFlow = MutableStateFlow(false)
private val imeVisible: Boolean
get() = ViewCompat.getRootWindowInsets(window.decorView)
?.isVisible(WindowInsetsCompat.Type.ime()) == true // fix #1315
var topBarWindowInsets by mutableStateOf(WindowInsets(top = BarUtils.getStatusBarHeight()))
private fun watchKeyboardVisible() {
if (AndroidTarget.R) {
ViewCompat.setWindowInsetsAnimationCallback(
window.decorView,
object : WindowInsetsAnimationCompat.Callback(DISPATCH_MODE_CONTINUE_ON_SUBTREE) {
override fun onStart(
animation: WindowInsetsAnimationCompat,
bounds: WindowInsetsAnimationCompat.BoundsCompat
): WindowInsetsAnimationCompat.BoundsCompat {
imePlayingFlow.update { imeVisible }
return super.onStart(animation, bounds)
}
override fun onProgress(
insets: WindowInsetsCompat,
runningAnimations: List<WindowInsetsAnimationCompat>
): WindowInsetsCompat {
return insets
}
override fun onEnd(animation: WindowInsetsAnimationCompat) {
imeFullHiddenFlow.update { !imeVisible }
imePlayingFlow.update { false }
super.onEnd(animation)
}
})
} else {
KeyboardUtils.registerSoftInputChangedListener(window) { height ->
// onEnd
imeFullHiddenFlow.update { height == 0 }
}
}
}
suspend fun hideSoftInput(): Boolean {
if (!imeFullHiddenFlow.updateAndGet { !imeVisible }) {
KeyboardUtils.hideSoftInput(this@MainActivity)
imeFullHiddenFlow.drop(1).first()
return true
}
return false
}
fun justHideSoftInput(): Boolean {
if (!imeFullHiddenFlow.updateAndGet { !imeVisible }) {
KeyboardUtils.hideSoftInput(this@MainActivity)
return true
}
return false
}
suspend fun pickFile(contentType: String): Uri? {
val u = launcher.launchForResult(Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = contentType
}).data?.data
if (u == null) {
toast("未选择文件")
}
return u
}
override fun onCreate(savedInstanceState: Bundle?) {
installSplashScreen()
enableEdgeToEdge()
fixSomeProblems()
super.onCreate(savedInstanceState)
LogUtils.d()
mainVm
launcher
pickContentLauncher
lifecycleScope.launch {
storeFlow.mapState(lifecycleScope) { s -> s.excludeFromRecents }.collect {
app.activityManager.appTasks.forEach { task ->
task.setExcludeFromRecents(it)
}
}
}
addOnNewIntentListener {
mainVm.handleIntent(it)
intent = null
}
watchKeyboardVisible()
StatusService.autoStart()
if (storeFlow.value.enableBlockA11yAppList) {
updateTopTaskAppId(META.appId)
}
setContent {
val latestInsets = TopAppBarDefaults.windowInsets
val density = LocalDensity.current
if (latestInsets.getTop(density) > topBarWindowInsets.getTop(density)) {
topBarWindowInsets = FixedWindowInsets(latestInsets)
}
CompositionLocalProvider(
LocalMainViewModel provides mainVm
) {
AppTheme {
NavDisplay(
entryDecorators = listOf(
rememberSaveableStateHolderNavEntryDecorator(),
rememberViewModelStoreNavEntryDecorator(),
),
backStack = mainVm.backStack,
onBack = mainVm::popPage,
entryProvider = entryProvider {
entry<HomeRoute> { HomePage() }
entry<AuthA11yRoute> { AuthA11yPage() }
entry<AboutRoute> { AboutPage() }
entry<BlockA11yAppListRoute> { BlockA11yAppListPage() }
entry<AdvancedPageRoute> { AdvancedPage() }
entry<SnapshotPageRoute> { SnapshotPage() }
entry<AppOpsAllowRoute> { AppOpsAllowPage() }
entry<A11YScopeAppListRoute> { A11yScopeAppListPage() }
entry<ActivityLogRoute> { ActivityLogPage() }
entry<A11yEventLogRoute> { A11yEventLogPage() }
entry<EditBlockAppListRoute> { EditBlockAppListPage() }
entry<SlowGroupRoute> { SlowGroupPage() }
entry<SubsAppListRoute> { SubsAppListPage(it) }
entry<WebViewRoute> { WebViewPage(it) }
entry<SubsCategoryRoute> { SubsCategoryPage(it) }
entry<SubsGlobalGroupListRoute> { SubsGlobalGroupListPage(it) }
entry<SubsGlobalGroupExcludeRoute> { SubsGlobalGroupExcludePage(it) }
entry<ActionLogRoute> { ActionLogPage(it) }
entry<ImagePreviewRoute> { ImagePreviewPage(it) }
entry<UpsertRuleGroupRoute> { UpsertRuleGroupPage(it) }
entry<SubsAppGroupListRoute> { SubsAppGroupListPage(it) }
entry<AppConfigRoute> { AppConfigPage(it) }
entry<CrashReportRoute> { CrashReportPage() }
entry<SubsCategoryGroupRoute> { SubsCategoryGroupPage(it) }
},
transitionSpec = {
slideInHorizontally(initialOffsetX = { it }) togetherWith
slideOutHorizontally(targetOffsetX = { -it })
},
popTransitionSpec = {
slideInHorizontally(initialOffsetX = { -it }) togetherWith
slideOutHorizontally(targetOffsetX = { it })
},
predictivePopTransitionSpec = {
slideInHorizontally(initialOffsetX = { -it }) togetherWith
slideOutHorizontally(targetOffsetX = { it })
},
)
if (!mainVm.termsAcceptedFlow.collectAsState().value) {
TermsAcceptDialog()
} else {
UiAutomationAlreadyRegisteredDlg()
AccessRestrictedSettingsDlg()
ShizukuErrorDialog(mainVm.shizukuErrorFlow)
AuthDialog(mainVm.authReasonFlow)
BuildDialog(mainVm.dialogFlow)
mainVm.uploadOptions.ShowDialog()
EditGithubCookieDlg()
mainVm.updateStatus?.UpgradeDialog()
SubsSheet(mainVm, mainVm.sheetSubsIdFlow)
mainVm.inputSubsLinkOption.ContentDialog()
mainVm.ruleGroupState.Render()
TextDialog(mainVm.textFlow)
ShareLogDlg(mainVm.showShareLogDlgFlow)
}
}
}
LaunchedEffect(null) {
intent?.let {
mainVm.handleIntent(it)
intent = null
}
}
}
}
override fun onStart() {
super.onStart()
LogUtils.d()
activityVisibleState++
if (topActivityFlow.value.appId != META.appId) {
synchronized(topActivityFlow) {
updateTopActivity(
META.appId,
MainActivity::class.jvmName
)
}
}
}
var isFirstResume = true
override fun onResume() {
super.onResume()
LogUtils.d()
if (isFirstResume && startTime - app.startTime < 2000) {
isFirstResume = false
} else {
syncFixState()
}
}
override fun onStop() {
super.onStop()
LogUtils.d()
activityVisibleState--
}
override fun onDestroy() {
super.onDestroy()
LogUtils.d()
}
}
@Volatile
private var activityVisibleState = 0
val isActivityVisible get() = activityVisibleState > 0
val activityNavSourceName by lazy { META.appId + ".activity.nav.source" }
fun Activity.navToMainActivity() {
if (intent != null) {
val navIntent = Intent(intent)
navIntent.component = MainActivity::class.componentName
navIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
navIntent.putExtra(activityNavSourceName, this::class.jvmName)
startActivity(navIntent)
}
finish()
}
private val syncStateMutex = Mutex()
fun syncFixState() {
appScope.launchTry(Dispatchers.IO) {
if (syncStateMutex.isLocked) {
LogUtils.d("syncFixState isLocked")
}
syncStateMutex.withLock {
updateSystemDefaultAppId()
shizukuContextFlow.value.grantSelf()
updatePermissionState()
fixRestartAutomatorService()
}
}
}
@Composable
private fun ShizukuErrorDialog(stateFlow: MutableStateFlow<Throwable?>) {
val state = stateFlow.collectAsState().value
if (state != null) {
val errorText = remember { state.stackTraceToString() }
val appInfoCache = appInfoMapFlow.collectAsState().value
val installed = appInfoCache.contains(shizukuAppId)
AlertDialog(
onDismissRequest = { stateFlow.value = null },
title = { Text(text = "授权错误") },
text = {
Column {
Text(
text = if (installed) {
"Shizuku 授权失败,请检查是否运行"
} else {
"Shizuku 授权失败,检测到 Shizuku 未安装,请先下载后安装,如果你是通过其它方式授权,请忽略此提示自行查找原因"
}
)
Spacer(modifier = Modifier.height(8.dp))
Box(
modifier = Modifier.fillMaxWidth()
) {
SelectionContainer(
modifier = Modifier
.align(Alignment.TopStart)
.fillMaxWidth()
) {
Text(
text = errorText,
modifier = Modifier
.clip(MaterialTheme.shapes.extraSmall)
.background(MaterialTheme.colorScheme.secondaryContainer)
.padding(8.dp)
.heightIn(max = 400.dp)
.verticalScroll(rememberScrollState()),
style = MaterialTheme.typography.bodySmall,
)
}
PerfIcon(
modifier = Modifier
.align(Alignment.TopEnd)
.clickable(onClick = throttle {
copyText(errorText)
})
.padding(4.dp)
.size(20.dp),
imageVector = PerfIcon.ContentCopy,
tint = MaterialTheme.colorScheme.tertiary.copy(alpha = 0.75f),
)
}
}
},
confirmButton = {
if (installed) {
TextButton(onClick = {
stateFlow.value = null
openApp(shizukuAppId)
}) {
Text(text = "打开 Shizuku")
}
} else {
TextButton(onClick = {
stateFlow.value = null
openUri(ShortUrlSet.URL4)
}) {
Text(text = "去下载")
}
}
},
dismissButton = {
TextButton(onClick = { stateFlow.value = null }) {
Text(text = "我知道了")
}
}
)
}
}
val accessRestrictedSettingsShowFlow = MutableStateFlow(false)
@Composable
fun AccessRestrictedSettingsDlg() {
val a11yRunning by A11yService.isRunning.collectAsState()
LaunchedEffect(a11yRunning) {
if (a11yRunning) {
accessRestrictedSettingsShowFlow.value = false
}
}
val accessRestrictedSettingsShow by accessRestrictedSettingsShowFlow.collectAsState()
val mainVm = LocalMainViewModel.current
val isA11yPage = mainVm.topRoute is AuthA11yRoute
LaunchedEffect(isA11yPage, accessRestrictedSettingsShow) {
if (isA11yPage && accessRestrictedSettingsShow && !a11yRunning) {
toast("请重新授权以解除限制")
accessRestrictedSettingsShowFlow.value = false
}
}
if (accessRestrictedSettingsShow && !isA11yPage && !a11yRunning) {
AlertDialog(
title = {
Text(text = "权限受限")
},
text = {
Text(text = "当前操作权限「访问受限设置」已被限制, 请先解除限制")
},
onDismissRequest = {
accessRestrictedSettingsShowFlow.value = false
},
confirmButton = {
TextButton({
accessRestrictedSettingsShowFlow.value = false
mainVm.navigateWebPage(ShortUrlSet.URL2)
}) {
Text(text = "解除")
}
},
dismissButton = {
TextButton({
accessRestrictedSettingsShowFlow.value = false
}) {
Text(text = "关闭")
}
},
)
}
}
@Composable
fun UiAutomationAlreadyRegisteredDlg() {
if (automationRegisteredExceptionFlow.collectAsState().value != null) {
AlertDialog(
onDismissRequest = {
automationRegisteredExceptionFlow.value = null
},
title = { Text(text = "启动失败") },
text = {
Text(text = "自动化服务启动失败,检测到自动化服务已被其他应用占用,请先关闭已有服务后重试\n\n注:自动化服务只能同时运行一个,请确保没有其他应用或测试框架占用后再启动")
},
confirmButton = {
TextButton(onClick = {
automationRegisteredExceptionFlow.value = null
}) {
Text(text = "我知道了")
}
}
)
}
}
```
## /app/src/main/kotlin/li/songe/gkd/MainViewModel.kt
```kt path="/app/src/main/kotlin/li/songe/gkd/MainViewModel.kt"
package li.songe.gkd
import android.app.Activity
import android.content.Intent
import android.net.Uri
import android.webkit.URLUtil
import androidx.lifecycle.viewModelScope
import androidx.navigation3.runtime.NavBackStack
import androidx.navigation3.runtime.NavKey
import io.ktor.client.request.get
import io.ktor.client.statement.bodyAsText
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import li.songe.gkd.a11y.useA11yServiceEnabledFlow
import li.songe.gkd.a11y.useEnabledA11yServicesFlow
import li.songe.gkd.data.CrashData
import li.songe.gkd.data.RawSubscription
import li.songe.gkd.data.SubsItem
import li.songe.gkd.db.DbSet
import li.songe.gkd.permission.AuthReason
import li.songe.gkd.permission.shizukuGrantedState
import li.songe.gkd.service.A11yService
import li.songe.gkd.shizuku.shizukuContextFlow
import li.songe.gkd.shizuku.uiAutomationFlow
import li.songe.gkd.shizuku.updateBinderMutex
import li.songe.gkd.store.createTextFlow
import li.songe.gkd.store.storeFlow
import li.songe.gkd.ui.AdvancedPageRoute
import li.songe.gkd.ui.AppOpsAllowRoute
import li.songe.gkd.ui.CrashReportRoute
import li.songe.gkd.ui.SnapshotPageRoute
import li.songe.gkd.ui.WebViewRoute
import li.songe.gkd.ui.component.AlertDialogOptions
import li.songe.gkd.ui.component.InputSubsLinkOption
import li.songe.gkd.ui.component.RuleGroupState
import li.songe.gkd.ui.component.UploadOptions
import li.songe.gkd.ui.home.BottomNavItem
import li.songe.gkd.ui.home.HomeRoute
import li.songe.gkd.ui.share.BaseViewModel
import li.songe.gkd.util.AutomatorModeOption
import li.songe.gkd.util.BackupUtils
import li.songe.gkd.util.DefaultSimpleLifeImpl
import li.songe.gkd.util.LOCAL_SUBS_ID
import li.songe.gkd.util.LogUtils
import li.songe.gkd.util.OnSimpleLife
import li.songe.gkd.util.ThrottleTimer
import li.songe.gkd.util.UpdateStatus
import li.songe.gkd.util.appIconMapFlow
import li.songe.gkd.util.clearCache
import li.songe.gkd.util.client
import li.songe.gkd.util.crashFolder
import li.songe.gkd.util.crashTempFolder
import li.songe.gkd.util.findOption
import li.songe.gkd.util.json
import li.songe.gkd.util.launchTry
import li.songe.gkd.util.openUri
import li.songe.gkd.util.openWeChatScaner
import li.songe.gkd.util.runMainPost
import li.songe.gkd.util.stopCoroutine
import li.songe.gkd.util.subsFolder
import li.songe.gkd.util.subsItemsFlow
import li.songe.gkd.util.toast
import li.songe.gkd.util.updateSubsMutex
import li.songe.gkd.util.updateSubscription
import li.songe.loc.Loc
import rikka.shizuku.Shizuku
import java.nio.file.Files
import kotlin.reflect.jvm.jvmName
import kotlin.time.Duration.Companion.days
class MainViewModel : BaseViewModel(), OnSimpleLife by DefaultSimpleLifeImpl() {
companion object {
private var _instance: MainViewModel? = null
val instance get() = _instance!!
private var tempTermsAccepted = false
}
init {
LogUtils.d("MainViewModel:init")
_instance = this
addCloseable {
LogUtils.d("MainViewModel:close")
if (_instance == this) { // 可能同时存在 2 个 MainViewModel 实例
_instance = null
}
}
}
override val scope get() = viewModelScope
val backStack: NavBackStack<NavKey> = NavBackStack(HomeRoute)
val topRoute get() = backStack.last()
private val backThrottleTimer = ThrottleTimer()
fun popPage(@Loc loc: String = "") = runMainPost {
if (backThrottleTimer.expired() && backStack.size > 1) {
val old = backStack.last()
backStack.removeAt(backStack.lastIndex)
LogUtils.d("popPage", "$old -> ${backStack.last()}", loc = loc)
}
}
fun navigatePage(
navKey: NavKey,
replaced: Boolean = false,
@Loc loc: String = "",
) = runMainPost {
if (navKey != backStack.last()) {
val old = backStack.last()
if (replaced) {
backStack[backStack.lastIndex] = navKey
} else {
backStack.add(navKey)
}
LogUtils.d("navigatePage", "$old -> ${backStack.last()}", loc = loc)
}
}
fun navigateWebPage(url: String) = navigatePage(WebViewRoute(url))
val dialogFlow = MutableStateFlow<AlertDialogOptions?>(null)
val authReasonFlow = MutableStateFlow<AuthReason?>(null)
val updateStatus = if (META.updateEnabled) UpdateStatus(viewModelScope) else null
val shizukuErrorFlow = MutableStateFlow<Throwable?>(null)
val uploadOptions = UploadOptions(this)
val showEditCookieDlgFlow = MutableStateFlow(false)
val inputSubsLinkOption = InputSubsLinkOption()
val sheetSubsIdFlow = MutableStateFlow<Long?>(null)
val appOrderListFlow = DbSet.actionLogDao.queryLatestUniqueAppIds().stateInit(emptyList())
val appVisitOrderMapFlow = DbSet.appVisitLogDao.query().map {
it.mapIndexed { i, appId -> appId to i }.toMap()
}.debounce(500).stateInit(emptyMap())
fun addOrModifySubs(
url: String,
oldItem: SubsItem? = null,
) = viewModelScope.launchTry(Dispatchers.IO) {
if (updateSubsMutex.mutex.isLocked) return@launchTry
updateSubsMutex.withStateLock {
val subItems = subsItemsFlow.value
val text = try {
client.get(url).bodyAsText()
} catch (e: Exception) {
e.printStackTrace()
LogUtils.d(e)
toast("下载订阅文件失败\n${e.message}".trimEnd())
return@launchTry
}
val newSubsRaw = try {
RawSubscription.parse(text)
} catch (e: Exception) {
e.printStackTrace()
LogUtils.d(e)
toast("解析订阅文件失败\n${e.message}".trimEnd())
return@launchTry
}
if (oldItem == null) {
if (subItems.any { it.id == newSubsRaw.id }) {
toast("订阅已存在")
return@launchTry
}
} else {
if (oldItem.id != newSubsRaw.id) {
toast("订阅id不对应")
return@launchTry
}
}
if (newSubsRaw.id < 0) {
toast("订阅id不可为${newSubsRaw.id}\n负数id为内部使用")
return@launchTry
}
val newItem = oldItem?.copy(updateUrl = url) ?: SubsItem(
id = newSubsRaw.id,
updateUrl = url,
order = if (subItems.isEmpty()) 1 else (subItems.maxBy { it.order }.order + 1)
)
updateSubscription(newSubsRaw)
if (oldItem == null) {
DbSet.subsItemDao.insert(newItem)
toast("成功添加订阅")
} else {
DbSet.subsItemDao.update(newItem)
toast("成功修改订阅")
}
}
}
val ruleGroupState = RuleGroupState(this)
val textFlow = MutableStateFlow<String?>(null)
fun openUrl(url: String) {
if (URLUtil.isNetworkUrl(url)) {
textFlow.value = url
} else {
openUri(url)
}
}
val tabFlow = MutableStateFlow(BottomNavItem.Control.key)
val resetPageScrollEvent = MutableSharedFlow<BottomNavItem>()
private var lastClickTabTime = 0L
fun handleClickTab(navItem: BottomNavItem) {
val t = System.currentTimeMillis()
// double click
if (navItem.key == tabFlow.value && t - lastClickTabTime < 500) {
viewModelScope.launch { resetPageScrollEvent.emit(navItem) }
}
tabFlow.value = navItem.key
lastClickTabTime = t
}
fun handleGkdUri(uri: Uri) {
val notFoundToast = { toast("未知URI\n${uri}") }
when (uri.host) {
"page" -> when (uri.path) {
"" -> {
val tab = uri.getQueryParameter("tab")?.toIntOrNull()
if (tab != null && BottomNavItem.allSubObjects.any { it.key == tab }) {
tabFlow.value = tab
}
}
"/1" -> navigatePage(AdvancedPageRoute)
"/2" -> navigatePage(SnapshotPageRoute)
"/3" -> navigatePage(AppOpsAllowRoute)
else -> notFoundToast()
}
"invoke" -> when (uri.path) {
"/1" -> openWeChatScaner()
else -> notFoundToast()
}
else -> notFoundToast()
}
}
fun handleIntent(intent: Intent) = viewModelScope.launchTry {
LogUtils.d(intent)
val uri = intent.data?.normalizeScheme()
val source = intent.getStringExtra(activityNavSourceName)
if (uri?.scheme == "gkd") {
handleGkdUri(uri)
} else if (source == OpenFileActivity::class.jvmName && uri != null) {
withContext(Dispatchers.IO) { BackupUtils.importBackUpData(uri) }
}
}
val termsAcceptedFlow by lazy {
if (tempTermsAccepted) {
MutableStateFlow(true)
} else {
createTextFlow(
key = "terms_accepted",
decode = { it == "true" },
encode = {
tempTermsAccepted = it
it.toString()
},
scope = viewModelScope,
).apply {
tempTermsAccepted = value
}
}
}
val githubCookieFlow by lazy {
createTextFlow(
key = "github_cookie",
decode = { it ?: "" },
encode = { it },
private = true,
scope = viewModelScope,
)
}
fun switchEnableShizuku(value: Boolean) {
if (updateBinderMutex.mutex.isLocked) {
toast("正在连接中,请稍后")
return
}
storeFlow.update { s -> s.copy(enableShizuku = value) }
}
fun requestShizuku() {
if (shizukuContextFlow.value.ok) return
if (updateBinderMutex.mutex.isLocked) {
toast("正在连接中,请稍后")
return
}
try {
Shizuku.requestPermission(Activity.RESULT_OK)
} catch (e: Throwable) {
shizukuErrorFlow.value = e
}
}
suspend fun guardShizukuContext() {
if (shizukuContextFlow.value.ok) return
if (!storeFlow.value.enableShizuku) {
storeFlow.update { it.copy(enableShizuku = true) }
}
if (!shizukuGrantedState.updateAndGet()) {
requestShizuku()
stopCoroutine()
}
if (shizukuContextFlow.value.ok) return
stopCoroutine()
}
private val a11yServicesFlow = useEnabledA11yServicesFlow()
val a11yServiceEnabledFlow = useA11yServiceEnabledFlow(a11yServicesFlow)
val automatorModeFlow = storeFlow.mapNew {
AutomatorModeOption.objects.findOption(it.automatorMode)
}
fun updateAutomatorMode(option: AutomatorModeOption) {
if (automatorModeFlow.value == option) return
storeFlow.update { it.copy(automatorMode = option.value, enableAutomator = false) }
A11yService.instance?.shutdown()
uiAutomationFlow.value?.shutdown()
}
val showShareLogDlgFlow = MutableStateFlow(false)
var tempCrashDataList = emptyList<CrashData>()
init {
// preload
appIconMapFlow.value
viewModelScope.launchTry(Dispatchers.IO) {
val subsItems = DbSet.subsItemDao.queryAll()
if (!subsItems.any { s -> s.id == LOCAL_SUBS_ID }) {
if (!subsFolder.resolve("${LOCAL_SUBS_ID}.json").exists()) {
updateSubscription(
RawSubscription(
id = LOCAL_SUBS_ID,
name = "本地订阅",
version = 0
)
)
}
DbSet.subsItemDao.insert(
SubsItem(
id = LOCAL_SUBS_ID,
order = subsItems.minByOrNull { it.order }?.order ?: 0,
)
)
}
}
viewModelScope.launchTry(Dispatchers.IO) {
// 每次进入删除缓存
clearCache()
}
if (termsAcceptedFlow.value && updateStatus?.canRecheck == true) {
updateStatus.checkUpdate()
}
viewModelScope.launch(Dispatchers.IO) {
// preload
githubCookieFlow.value
}
viewModelScope.launchTry(Dispatchers.IO) {
val list = (crashTempFolder.listFiles() ?: emptyArray()).mapNotNull {
try {
json.decodeFromString<CrashData>(it.readText())
} catch (e: Exception) {
LogUtils.d("解析崩溃日志失败: ${it.name}", e)
null
}
}.sortedBy { -it.mtime }
crashTempFolder.deleteRecursively()
val t = System.currentTimeMillis()
crashFolder.listFiles()?.filter {
val name = it.name
!list.any { f -> name == f.filename }
}?.forEach {
val mtime = Files.getLastModifiedTime(it.toPath()).toMillis()
if (t - mtime > 30.days.inWholeMilliseconds) {
it.delete()
}
}
tempCrashDataList = list
if (list.isNotEmpty()) {
navigatePage(CrashReportRoute)
}
}
// for OnSimpleLife
onCreated()
addCloseable { onDestroyed() }
}
}
```
## /app/src/main/kotlin/li/songe/gkd/OpenFileActivity.kt
```kt path="/app/src/main/kotlin/li/songe/gkd/OpenFileActivity.kt"
package li.songe.gkd
import android.app.Activity
import android.os.Bundle
class OpenFileActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
navToMainActivity()
}
}
```
## /app/src/main/kotlin/li/songe/gkd/OpenSchemeActivity.kt
```kt path="/app/src/main/kotlin/li/songe/gkd/OpenSchemeActivity.kt"
package li.songe.gkd
import android.app.Activity
import android.os.Bundle
class OpenSchemeActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
navToMainActivity()
}
}
```
## /app/src/main/kotlin/li/songe/gkd/OpenTileActivity.kt
```kt path="/app/src/main/kotlin/li/songe/gkd/OpenTileActivity.kt"
package li.songe.gkd
import android.app.Activity
import android.content.pm.PackageManager
import android.os.Bundle
import androidx.core.net.toUri
import li.songe.gkd.util.extraCptName
class OpenTileActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val qsTileCpt = intent?.extraCptName
if (qsTileCpt != null && intent.data == null) {
val serviceInfo =
app.packageManager.getServiceInfo(qsTileCpt, PackageManager.GET_META_DATA)
val uriValue = serviceInfo.metaData.getString("QS_TILE_URI")
if (uriValue != null) {
intent.data = uriValue.toUri()
}
}
navToMainActivity()
}
}
```
## /app/src/main/kotlin/li/songe/gkd/a11y/A11yCommonImpl.kt
```kt path="/app/src/main/kotlin/li/songe/gkd/a11y/A11yCommonImpl.kt"
package li.songe.gkd.a11y
import android.graphics.Bitmap
import android.view.accessibility.AccessibilityNodeInfo
import android.view.accessibility.AccessibilityWindowInfo
import kotlinx.coroutines.CoroutineScope
import li.songe.gkd.util.AutomatorModeOption
interface A11yCommonImpl {
suspend fun screenshot(): Bitmap?
val windowNodeInfo: AccessibilityNodeInfo?
val windowInfos: List<AccessibilityWindowInfo>
val scope: CoroutineScope
var justStarted: Boolean
val mode: AutomatorModeOption
val ruleEngine: A11yRuleEngine
fun shutdown(temp: Boolean = false)
}
```
## /app/src/main/kotlin/li/songe/gkd/a11y/A11yContext.kt
```kt path="/app/src/main/kotlin/li/songe/gkd/a11y/A11yContext.kt"
package li.songe.gkd.a11y
import android.util.Log
import android.util.LruCache
import android.view.accessibility.AccessibilityNodeInfo
import kotlinx.atomicfu.atomic
import li.songe.gkd.META
import li.songe.gkd.data.ResolvedRule
import li.songe.gkd.shizuku.casted
import li.songe.gkd.util.InterruptRuleMatchException
import li.songe.selector.FastQuery
import li.songe.selector.MatchOption
import li.songe.selector.QueryContext
import li.songe.selector.Selector
import li.songe.selector.Transform
import li.songe.selector.getBooleanInvoke
import li.songe.selector.getCharSequenceAttr
import li.songe.selector.getCharSequenceInvoke
import li.songe.selector.getIntInvoke
private operator fun <K, V> LruCache<K, V>.set(child: K, value: V): V {
return put(child, value)
}
private fun List<Any>.getInt(i: Int = 0) = get(i) as Int
private const val MAX_CACHE_SIZE = MAX_DESCENDANTS_SIZE
private val AccessibilityNodeInfo?.notExpiredNode: AccessibilityNodeInfo?
get() {
if (this != null) {
val expiryMillis = if (text == null) 2000L else 1000L
if (isExpired(expiryMillis)) {
return null
}
}
return this
}
class A11yContext(
private val a11yEngine: A11yRuleEngine,
private val interruptable: Boolean = true,
) {
private var childCache =
LruCache<Pair<AccessibilityNodeInfo, Int>, AccessibilityNodeInfo>(MAX_CACHE_SIZE)
private var indexCache = LruCache<AccessibilityNodeInfo, Int>(MAX_CACHE_SIZE)
private var parentCache = LruCache<AccessibilityNodeInfo, AccessibilityNodeInfo>(MAX_CACHE_SIZE)
val rootCache = atomic<AccessibilityNodeInfo?>(null)
private fun clearChildCache(node: AccessibilityNodeInfo) {
repeat(node.childCount.coerceAtMost(MAX_CHILD_SIZE)) { i ->
childCache.remove(node to i)?.let {
clearChildCache(it)
}
}
}
fun clearNodeCache(eventNode: AccessibilityNodeInfo? = null) {
if (rootCache.value?.packageName != topActivityFlow.value.appId) {
rootCache.value = null
}
if (eventNode != null) {
clearChildCache(eventNode)
parentCache[eventNode]?.let { p ->
getPureIndex(eventNode)?.let { i ->
childCache[p to i] = eventNode
}
}
if (rootCache.value == eventNode) {
rootCache.value = eventNode
} else {
if (META.debuggable) {
Log.d(
"cache",
"clear node cache ${eventNode.packageName}/${eventNode.className}"
)
}
return
}
}
if (META.debuggable) {
val sizeList = listOf(childCache.size(), parentCache.size(), indexCache.size())
if (sizeList.any { it > 0 }) {
Log.d("cache", "clear cache -> $sizeList")
}
}
try {
childCache.evictAll()
parentCache.evictAll()
indexCache.evictAll()
} catch (_: Exception) {
// https://github.com/gkd-kit/gkd/issues/664
// 在某些机型上 未知原因 缓存不一致 导致删除失败
childCache = LruCache(MAX_CACHE_SIZE)
indexCache = LruCache(MAX_CACHE_SIZE)
parentCache = LruCache(MAX_CACHE_SIZE)
}
}
private var lastAppChangeTime = appChangeTime
fun clearOldAppNodeCache(): Boolean {
if (appChangeTime != lastAppChangeTime) {
lastAppChangeTime = appChangeTime
clearNodeCache()
return true
}
return false
}
var currentRule: ResolvedRule? = null
@Volatile
var interruptKey = 0
private var interruptInnerKey = 0
private fun guardInterrupt() {
if (!interruptable) return
if (interruptInnerKey == interruptKey) return
interruptInnerKey = interruptKey
val rule = currentRule ?: return
if (!activityRuleFlow.value.activePriority) return
if (!activityRuleFlow.value.currentRules.any { it === rule }) return
if (rule.isPriority()) return
if (META.debuggable) {
Log.d("guardInterrupt", "中断 rule=${rule.statusText()}")
}
throw InterruptRuleMatchException()
}
private fun getA11Root(): AccessibilityNodeInfo? {
guardInterrupt()
return a11yEngine.safeActiveWindow
}
private fun getA11Child(node: AccessibilityNodeInfo, index: Int): AccessibilityNodeInfo? {
guardInterrupt()
return node.getChild(index)?.setGeneratedTime()
}
private fun getA11Parent(node: AccessibilityNodeInfo): AccessibilityNodeInfo? {
guardInterrupt()
return node.parent?.setGeneratedTime()
}
private fun getA11ByText(
node: AccessibilityNodeInfo,
value: String
): List<AccessibilityNodeInfo> {
guardInterrupt()
return node.findAccessibilityNodeInfosByText(value).apply {
forEach { it.setGeneratedTime() }
}
}
private fun getA11ById(
node: AccessibilityNodeInfo,
value: String
): List<AccessibilityNodeInfo> {
guardInterrupt()
return node.findAccessibilityNodeInfosByViewId(value).apply {
forEach { it.setGeneratedTime() }
}
}
private fun getFastQueryNodes(
node: AccessibilityNodeInfo,
fastQuery: FastQuery
): List<AccessibilityNodeInfo> {
return when (fastQuery) {
is FastQuery.Id -> getA11ById(node, fastQuery.value)
is FastQuery.Text -> getA11ByText(node, fastQuery.value)
is FastQuery.Vid -> getA11ById(node, "${node.packageName}:id/${fastQuery.value}")
}
}
private fun getCacheRoot(node: AccessibilityNodeInfo? = null): AccessibilityNodeInfo? {
if (rootCache.value.notExpiredNode == null) {
rootCache.value = getA11Root()
}
if (node == rootCache.value) return null
return rootCache.value
}
private fun getCacheParent(node: AccessibilityNodeInfo): AccessibilityNodeInfo? {
if (getCacheRoot() == node) {
return null
}
parentCache[node].notExpiredNode?.let { return it }
return getA11Parent(node).apply {
if (this != null) {
parentCache[node] = this
} else {
rootCache.value = node
}
}
}
private fun getCacheChild(node: AccessibilityNodeInfo, index: Int): AccessibilityNodeInfo? {
if (index !in 0 until node.childCount) {
return null
}
return childCache[node to index].notExpiredNode ?: getA11Child(node, index)?.also { child ->
indexCache[child] = index
parentCache[child] = node
childCache[node to index] = child
}
}
private fun getPureIndex(node: AccessibilityNodeInfo): Int? {
return indexCache[node]
}
private fun getCacheIndex(node: AccessibilityNodeInfo): Int {
indexCache[node]?.let { return it }
getCacheChildren(getCacheParent(node)).forEachIndexed { index, child ->
if (child == node) {
indexCache[node] = index
return index
}
}
return 0
}
/**
* 在无缓存时, 此方法小概率造成无限节点片段,底层原因未知
*
* https://github.com/gkd-kit/gkd/issues/28
*/
private fun getCacheDepth(node: AccessibilityNodeInfo): Int {
var p: AccessibilityNodeInfo = node
var depth = 0
while (true) {
val p2 = getCacheParent(p)
if (p2 != null) {
p = p2
depth++
} else {
break
}
}
return depth
}
private fun getCacheChildren(node: AccessibilityNodeInfo?): Sequence<AccessibilityNodeInfo> {
if (node == null) return emptySequence()
return sequence {
repeat(node.childCount.coerceAtMost(MAX_CHILD_SIZE)) { index ->
val child = getCacheChild(node, index) ?: return@sequence
yield(child)
}
}
}
private var tempVid: CharSequence? = null
private var tempVidNode: AccessibilityNodeInfo? = null
private fun getTempVid(n: AccessibilityNodeInfo): CharSequence? {
if (n !== tempVidNode) {
tempVid = n.getVid()
tempVidNode = n
}
return tempVid
}
private fun getCacheAttr(node: AccessibilityNodeInfo, name: String): Any? = when (name) {
"id" -> node.viewIdResourceName
"vid" -> getTempVid(node)
"name" -> node.className
"text" -> node.text
"desc" -> node.contentDescription
"clickable" -> node.isClickable
"focusable" -> node.isFocusable
"checkable" -> node.isCheckable
"checked" -> node.compatChecked
"editable" -> node.isEditable
"longClickable" -> node.isLongClickable
"visibleToUser" -> node.isVisibleToUser
"left" -> node.casted.boundsInScreen.left
"top" -> node.casted.boundsInScreen.top
"right" -> node.casted.boundsInScreen.right
"bottom" -> node.casted.boundsInScreen.bottom
"width" -> node.casted.boundsInScreen.width()
"height" -> node.casted.boundsInScreen.height()
"index" -> getCacheIndex(node)
"depth" -> getCacheDepth(node)
"childCount" -> node.childCount
"parent" -> getCacheParent(node)
else -> null
}
private val transform = Transform(
getAttr = { target, name ->
when (target) {
is QueryContext<*> -> when (name) {
"prev" -> target.prev
"current" -> target.current
else -> getCacheAttr(target.current as AccessibilityNodeInfo, name)
}
is AccessibilityNodeInfo -> getCacheAttr(target, name)
is CharSequence -> getCharSequenceAttr(target, name)
else -> null
}
},
getInvoke = { target, name, args ->
when (target) {
is AccessibilityNodeInfo -> when (name) {
"getChild" -> {
getCacheChild(target, args.getInt())
}
else -> null
}
is QueryContext<*> -> when (name) {
"getPrev" -> {
args.getInt().let { target.getPrev(it) }
}
"getChild" -> {
getCacheChild(target.current as AccessibilityNodeInfo, args.getInt())
}
else -> null
}
is CharSequence -> getCharSequenceInvoke(target, name, args)
is Int -> getIntInvoke(target, name, args)
is Boolean -> getBooleanInvoke(target, name, args)
else -> null
}
},
getName = { node -> node.className },
getChildren = ::getCacheChildren,
getParent = ::getCacheParent,
getRoot = ::getCacheRoot,
getDescendants = { node ->
sequence {
val stack = getCacheChildren(node).toMutableList()
if (stack.isEmpty()) return@sequence
stack.reverse()
val tempNodes = mutableListOf<AccessibilityNodeInfo>()
do {
val top = stack.removeAt(stack.lastIndex)
yield(top)
for (childNode in getCacheChildren(top)) {
tempNodes.add(childNode)
}
if (tempNodes.isNotEmpty()) {
for (i in tempNodes.size - 1 downTo 0) {
stack.add(tempNodes[i])
}
tempNodes.clear()
}
} while (stack.isNotEmpty())
}.take(MAX_DESCENDANTS_SIZE)
},
traverseChildren = { node, connectExpression ->
sequence {
repeat(node.childCount.coerceAtMost(MAX_CHILD_SIZE)) { offset ->
connectExpression.maxOffset?.let { maxOffset ->
if (offset > maxOffset) return@sequence
}
if (connectExpression.checkOffset(offset)) {
val child = getCacheChild(node, offset) ?: return@sequence
yield(child)
}
}
}
},
traverseBeforeBrothers = { node, connectExpression ->
sequence {
val parentVal = getCacheParent(node) ?: return@sequence
// 如果 node 由 fastQuery 得到, 则第一次调用此方法可能得到 cache.index 是空
val index = getPureIndex(node)
if (index != null) {
var i = index - 1
var offset = 0
while (0 <= i && i < parentVal.childCount) {
connectExpression.maxOffset?.let { maxOffset ->
if (offset > maxOffset) return@sequence
}
if (connectExpression.checkOffset(offset)) {
val child = getCacheChild(parentVal, i) ?: return@sequence
yield(child)
}
i--
offset++
}
} else {
val list = getCacheChildren(parentVal).takeWhile { it != node }.toMutableList()
list.reverse()
yieldAll(list.filterIndexed { i, _ ->
connectExpression.checkOffset(
i
)
})
}
}
},
traverseAfterBrothers = { node, connectExpression ->
val parentVal = getCacheParent(node)
if (parentVal != null) {
val index = getPureIndex(node)
if (index != null) {
sequence {
var i = index + 1
var offset = 0
while (0 <= i && i < parentVal.childCount) {
connectExpression.maxOffset?.let { maxOffset ->
if (offset > maxOffset) return@sequence
}
if (connectExpression.checkOffset(offset)) {
val child = getCacheChild(parentVal, i) ?: return@sequence
yield(child)
}
i++
offset++
}
}
} else {
getCacheChildren(parentVal).dropWhile { it != node }
.drop(1)
.let {
if (connectExpression.maxOffset != null) {
it.take(connectExpression.maxOffset!! + 1)
} else {
it
}
}
.filterIndexed { i, _ ->
connectExpression.checkOffset(
i
)
}
}
} else {
emptySequence()
}
},
traverseDescendants = { node, connectExpression ->
sequence {
val stack = getCacheChildren(node).toMutableList()
if (stack.isEmpty()) return@sequence
stack.reverse()
val tempNodes = mutableListOf<AccessibilityNodeInfo>()
var offset = 0
do {
val top = stack.removeAt(stack.lastIndex)
if (connectExpression.checkOffset(offset)) {
yield(top)
}
offset++
if (offset > MAX_DESCENDANTS_SIZE) {
return@sequence
}
connectExpression.maxOffset?.let { maxOffset ->
if (offset > maxOffset) return@sequence
}
for (childNode in getCacheChildren(top)) {
tempNodes.add(childNode)
}
if (tempNodes.isNotEmpty()) {
for (i in tempNodes.size - 1 downTo 0) {
stack.add(tempNodes[i])
}
tempNodes.clear()
}
} while (stack.isNotEmpty())
}
},
traverseFastQueryDescendants = { node, list ->
sequence {
for (fastQuery in list) {
val nodes = getFastQueryNodes(node, fastQuery)
nodes.forEach { childNode ->
yield(childNode)
}
}
}
}
)
fun querySelfOrSelector(
node: AccessibilityNodeInfo,
selector: Selector,
option: MatchOption,
): AccessibilityNodeInfo? {
if (selector.isMatchRoot) {
return selector.match(
getCacheRoot() ?: return null,
transform,
option
)
}
selector.match(node, transform, option)?.let {
return it
}
return transform.querySelector(node, selector, option)
}
fun queryRule(
rule: ResolvedRule,
node: AccessibilityNodeInfo,
): AccessibilityNodeInfo? {
currentRule = rule
try {
val queryNode = if (rule.matchRoot) {
getCacheRoot()
} else {
node
} ?: return null
var resultNode: AccessibilityNodeInfo? = null
if (rule.anyMatches.isNotEmpty()) {
for (selector in rule.anyMatches) {
resultNode = querySelfOrSelector(
queryNode,
selector,
rule.matchOption,
)
if (resultNode != null) break
}
if (resultNode == null) return null
}
for (selector in rule.matches) {
resultNode = querySelfOrSelector(
queryNode,
selector,
rule.matchOption,
) ?: return null
}
for (selector in rule.excludeMatches) {
querySelfOrSelector(
queryNode,
selector,
rule.matchOption,
)?.let { return null }
}
if (rule.excludeAllMatches.isNotEmpty()) {
val allExclude = rule.excludeAllMatches.all {
querySelfOrSelector(
queryNode,
it,
rule.matchOption,
) == null
}
if (!allExclude) {
return null
}
}
return resultNode
} finally {
currentRule = null
}
}
}
```
## /app/src/main/kotlin/li/songe/gkd/a11y/A11yExt.kt
```kt path="/app/src/main/kotlin/li/songe/gkd/a11y/A11yExt.kt"
package li.songe.gkd.a11y
import android.content.ComponentName
import android.provider.Settings
import android.view.accessibility.AccessibilityEvent
import android.view.accessibility.AccessibilityNodeInfo
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import li.songe.gkd.app
import li.songe.gkd.contentObserver
import li.songe.gkd.service.A11yService
import li.songe.gkd.util.AndroidTarget
import li.songe.gkd.util.OnSimpleLife
import li.songe.gkd.util.mapState
import li.songe.selector.initDefaultTypeInfo
import kotlin.contracts.contract
context(context: OnSimpleLife)
fun useEnabledA11yServicesFlow(): StateFlow<Set<ComponentName>> {
val stateFlow = MutableStateFlow(app.getSecureA11yServices())
val contextObserver = contentObserver {
stateFlow.value = app.getSecureA11yServices()
}
app.registerObserver(
Settings.Secure.getUriFor(Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES),
contextObserver
)
context.onDestroyed {
app.unregisterObserver(contextObserver)
}
return stateFlow
}
context(context: OnSimpleLife)
fun useA11yServiceEnabledFlow(servicesFlow: StateFlow<Set<ComponentName>> = useEnabledA11yServicesFlow()): StateFlow<Boolean> {
return servicesFlow.mapState(context.scope) {
it.contains(A11yService.a11yCn)
}
}
const val STATE_CHANGED = AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED
const val CONTENT_CHANGED = AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED
// 某些应用耗时 300ms
private val AccessibilityEvent.safeSource: AccessibilityNodeInfo?
get() = if (className == null) {
null // https://github.com/gkd-kit/gkd/issues/426 event.clear 已被系统调用
} else {
try {
source?.setGeneratedTime()
} catch (_: Exception) {
// 原因未知, 仍然报错 Cannot perform this action on a not sealed instance.
null
}
}
fun AccessibilityNodeInfo.getVid(): CharSequence? {
val id = viewIdResourceName ?: return null
val appId = packageName ?: return null
if (id.startsWith(appId) && id.startsWith(":id/", appId.length)) {
return id.subSequence(
appId.length + ":id/".length,
id.length
)
}
return null
}
// https://github.com/gkd-kit/gkd/issues/115
// https://github.com/gkd-kit/gkd/issues/650
// 限制节点遍历的数量避免内存溢出
const val MAX_CHILD_SIZE = 512
const val MAX_DESCENDANTS_SIZE = 4096
private const val A11Y_NODE_TIME_KEY = "generatedTime"
fun AccessibilityNodeInfo.setGeneratedTime(): AccessibilityNodeInfo {
extras.putLong(A11Y_NODE_TIME_KEY, System.currentTimeMillis())
return this
}
fun AccessibilityNodeInfo.isExpired(expiryMillis: Long): Boolean {
val generatedTime = extras.getLong(A11Y_NODE_TIME_KEY, -1)
if (generatedTime == -1L) {
// https://github.com/gkd-kit/gkd/issues/759
return true
}
return (System.currentTimeMillis() - generatedTime) > expiryMillis
}
val typeInfo by lazy { initDefaultTypeInfo().globalType }
val AccessibilityNodeInfo.compatChecked: Boolean?
get() = if (AndroidTarget.BAKLAVA) {
when (checked) {
AccessibilityNodeInfo.CHECKED_STATE_TRUE -> true
AccessibilityNodeInfo.CHECKED_STATE_FALSE -> false
AccessibilityNodeInfo.CHECKED_STATE_PARTIAL -> null
else -> null
}
} else {
@Suppress("DEPRECATION")
isChecked
}
private const val interestedEvents = STATE_CHANGED or CONTENT_CHANGED
fun AccessibilityEvent?.isUseful(): Boolean {
contract {
returns(true) implies (this@isUseful != null)
}
return (this != null && packageName != null && className != null && eventType and interestedEvents != 0)
}
data class A11yEvent(
val type: Int,
val time: Long,
val appId: String,
val name: String,
val event: AccessibilityEvent,
) {
val safeSource: AccessibilityNodeInfo?
get() = event.safeSource
fun sameAs(other: A11yEvent): Boolean {
if (other === this) return true
return type == other.type && appId == other.appId && name == other.name
}
}
// AccessibilityEvent 的 clear 方法会在后续时间被 某些系统 调用导致内部数据丢失, 导致异步子线程获取到的数据不一致
fun AccessibilityEvent.toA11yEvent(): A11yEvent? {
val appId = packageName ?: return null
val b = className ?: return null
return A11yEvent(
type = eventType,
time = System.currentTimeMillis(),
appId = appId.toString(),
name = b.toString(),
event = this,
)
}
```
## /app/src/main/kotlin/li/songe/gkd/data/AppRule.kt
```kt path="/app/src/main/kotlin/li/songe/gkd/data/AppRule.kt"
package li.songe.gkd.data
class AppRule(
rule: RawSubscription.RawAppRule,
g: ResolvedAppGroup,
appInfo: AppInfo?,
) : ResolvedRule(
rule = rule,
g = g,
) {
val group = g.group
val app = g.app
val enable = appInfo?.let {
if (rule.versionCode?.match(it.versionCode) == false) {
return@let false
}
if (rule.versionName?.match(it.versionName) == false) {
return@let false
}
null
} ?: true
val appId = app.id
private val activityIds = getFixActivityIds(app.id, rule.activityIds ?: group.activityIds)
private val excludeActivityIds =
(getFixActivityIds(
app.id,
rule.excludeActivityIds ?: group.excludeActivityIds
) + (excludeData.activityIds.filter { e -> e.first == appId }
.map { e -> e.second })).distinct()
override val type = "app"
override fun matchActivity(appId: String, activityId: String?): Boolean {
if (!enable) return false
if (appId != app.id) return false
activityId ?: return true
if (excludeActivityIds.any { activityId.startsWith(it) }) return false
return activityIds.isEmpty() || activityIds.any { activityId.startsWith(it) }
}
}
```
The content has been capped at 50000 tokens. The user could consider applying other filters to refine the result. The better and more specific the context, the better the LLM can follow instructions. If the context seems verbose, the user can refine the filter using uithub. Thank you for using https://uithub.com - Perfect LLM context for any GitHub repo.