├── .editorconfig
├── .github
├── FUNDING.yml
├── ISSUE_TEMPLATE
│ ├── bug_report.yml
│ ├── config.yml
│ └── feat_req.yml
├── actions
│ ├── build-apk
│ │ └── action.yml
│ ├── gradle-setup
│ │ └── action.yml
│ ├── run-lints
│ │ └── action.yml
│ └── run-tests
│ │ └── action.yml
├── pull_request_template.md
└── workflows
│ ├── ci.yml
│ ├── codeql.yml
│ ├── daily.yml
│ └── release.yml
├── .gitignore
├── .idea
└── icon.svg
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── SECURITY.md
├── api
├── .gitignore
├── build.gradle.kts
├── consumer-rules.pro
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ └── kotlin
│ └── dev
│ └── shadoe
│ └── delta
│ └── api
│ ├── ACLDevice.kt
│ ├── ConfigFlag.kt
│ ├── LinkAddress.kt
│ ├── MacAddress.kt
│ ├── ShizukuStates.kt
│ ├── SoftApAutoShutdownTimeout.kt
│ ├── SoftApCapabilities.kt
│ ├── SoftApConfiguration.kt
│ ├── SoftApEnabledState.kt
│ ├── SoftApRandomizationSetting.kt
│ ├── SoftApSecurityType.kt
│ ├── SoftApSpeedType.kt
│ ├── SoftApStatus.kt
│ └── TetheredClient.kt
├── app
├── .gitignore
├── build.gradle.kts
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── kotlin
│ │ └── dev
│ │ └── shadoe
│ │ └── delta
│ │ └── settings
│ │ └── components
│ │ └── AutoShutdownFieldTest.kt
│ ├── debug
│ └── AndroidManifest.xml
│ └── main
│ ├── AndroidManifest.xml
│ ├── ic_launcher-playstore.png
│ ├── kotlin
│ └── dev
│ │ └── shadoe
│ │ └── delta
│ │ ├── Application.kt
│ │ ├── MainActivity.kt
│ │ ├── SoftApBroadcastReceiver.kt
│ │ ├── SoftApTile.kt
│ │ ├── common
│ │ ├── Nav.kt
│ │ ├── NavViewModel.kt
│ │ ├── Routes.kt
│ │ ├── SoftApScreen.kt
│ │ ├── SoftApScreenDestinations.kt
│ │ ├── SoftApScreenViewModel.kt
│ │ ├── components
│ │ │ ├── FadeInExpanded.kt
│ │ │ └── FoldableWrapper.kt
│ │ ├── shapes
│ │ │ └── PolygonShape.kt
│ │ └── test
│ │ │ └── RecomposeHighlighter.kt
│ │ ├── control
│ │ ├── ControlScreen.kt
│ │ ├── ControlViewModel.kt
│ │ └── components
│ │ │ ├── AppBarWithDebugAction.kt
│ │ │ ├── BlockedClientComponent.kt
│ │ │ ├── BlocklistComponent.kt
│ │ │ ├── ConnectedClientComponent.kt
│ │ │ ├── ConnectedClientsList.kt
│ │ │ ├── PassphraseDisplay.kt
│ │ │ ├── SoftApControl.kt
│ │ │ ├── SoftApControlButton.kt
│ │ │ └── SoftApControlViewModel.kt
│ │ ├── crash
│ │ ├── CrashHandlerActivity.kt
│ │ └── CrashHandlerUtils.kt
│ │ ├── debug
│ │ ├── DebugScreen.kt
│ │ └── DebugViewModel.kt
│ │ ├── design
│ │ ├── AppTheme.kt
│ │ ├── Typography.kt
│ │ └── VariableFontFamily.kt
│ │ ├── settings
│ │ ├── SettingsScreen.kt
│ │ ├── SettingsViewModel.kt
│ │ ├── UpdateResults.kt
│ │ └── components
│ │ │ ├── AutoShutdownField.kt
│ │ │ ├── AutoShutdownTimeoutField.kt
│ │ │ ├── FrequencyBandField.kt
│ │ │ ├── HiddenHotspotField.kt
│ │ │ ├── MacRandomizationField.kt
│ │ │ ├── MaxClientLimitField.kt
│ │ │ ├── PassphraseField.kt
│ │ │ ├── PresetField.kt
│ │ │ ├── PresetSaveDialog.kt
│ │ │ ├── PresetSheet.kt
│ │ │ ├── SecurityTypeField.kt
│ │ │ ├── SoftApTileField.kt
│ │ │ ├── SsidField.kt
│ │ │ ├── TaskerIntegrationField.kt
│ │ │ └── TaskerIntegrationInfo.kt
│ │ └── setup
│ │ ├── CrashHandlerSetupScreen.kt
│ │ ├── FirstUseScreen.kt
│ │ ├── ShizukuSetupScreen.kt
│ │ ├── ShizukuSetupViewModel.kt
│ │ └── components
│ │ ├── ShizukuConnected.kt
│ │ ├── ShizukuNotConnected.kt
│ │ ├── ShizukuNotInstalled.kt
│ │ └── ShizukuNotRunning.kt
│ └── res
│ ├── drawable
│ ├── ic_launcher_foreground.xml
│ ├── ic_qs_tile.xml
│ └── shizuku_logo_mono.xml
│ ├── font-v26
│ ├── nunitosans_italic_variable.ttf
│ └── nunitosans_variable.ttf
│ ├── mipmap-anydpi-v26
│ ├── ic_launcher.xml
│ └── ic_launcher_round.xml
│ ├── resources.properties
│ ├── values-af-rZA
│ └── strings.xml
│ ├── values-ar-rSA
│ └── strings.xml
│ ├── values-ca-rES
│ └── strings.xml
│ ├── values-cs-rCZ
│ └── strings.xml
│ ├── values-da-rDK
│ └── strings.xml
│ ├── values-de-rDE
│ └── strings.xml
│ ├── values-el-rGR
│ └── strings.xml
│ ├── values-es-rES
│ └── strings.xml
│ ├── values-fi-rFI
│ └── strings.xml
│ ├── values-fr-rFR
│ └── strings.xml
│ ├── values-hu-rHU
│ └── strings.xml
│ ├── values-it-rIT
│ └── strings.xml
│ ├── values-iw-rIL
│ └── strings.xml
│ ├── values-ja-rJP
│ └── strings.xml
│ ├── values-ko-rKR
│ └── strings.xml
│ ├── values-night
│ └── themes.xml
│ ├── values-nl-rNL
│ └── strings.xml
│ ├── values-no-rNO
│ └── strings.xml
│ ├── values-pl-rPL
│ └── strings.xml
│ ├── values-pt-rBR
│ └── strings.xml
│ ├── values-pt-rPT
│ └── strings.xml
│ ├── values-ro-rRO
│ └── strings.xml
│ ├── values-ru-rRU
│ └── strings.xml
│ ├── values-sr-rSP
│ └── strings.xml
│ ├── values-sv-rSE
│ └── strings.xml
│ ├── values-tr-rTR
│ └── strings.xml
│ ├── values-uk-rUA
│ └── strings.xml
│ ├── values-vi-rVN
│ └── strings.xml
│ ├── values-zh-rCN
│ └── strings.xml
│ ├── values-zh-rTW
│ └── strings.xml
│ ├── values
│ ├── colors.xml
│ ├── strings.xml
│ └── themes.xml
│ └── xml
│ ├── backup_rules.xml
│ └── data_extraction_rules.xml
├── build.gradle.kts
├── crowdin.yml
├── data
├── .gitignore
├── build.gradle.kts
├── consumer-rules.pro
├── proguard-rules.pro
├── schema
│ └── dev.shadoe.delta.data.database.ConfigDB
│ │ ├── 1.json
│ │ └── 2.json
└── src
│ ├── main
│ ├── AndroidManifest.xml
│ └── kotlin
│ │ └── dev
│ │ └── shadoe
│ │ └── delta
│ │ └── data
│ │ ├── FlagsRepository.kt
│ │ ├── MacAddressCacheRepository.kt
│ │ ├── database
│ │ ├── ConfigDB.kt
│ │ ├── converters
│ │ │ └── MacAddressConverter.kt
│ │ ├── dao
│ │ │ ├── FlagsDao.kt
│ │ │ ├── HostInfoDao.kt
│ │ │ └── PresetDao.kt
│ │ └── models
│ │ │ ├── Flag.kt
│ │ │ ├── HostInfo.kt
│ │ │ └── Preset.kt
│ │ ├── modules
│ │ ├── DaoModule.kt
│ │ └── SystemServicesModule.kt
│ │ ├── qualifiers
│ │ ├── TetheringSystemService.kt
│ │ └── WifiSystemService.kt
│ │ ├── shizuku
│ │ └── ShizukuRepository.kt
│ │ └── softap
│ │ ├── SoftApBlocklistManager.kt
│ │ ├── SoftApController.kt
│ │ ├── SoftApMonitor.kt
│ │ ├── SoftApStateFacade.kt
│ │ ├── SoftApStateListener.kt
│ │ ├── SoftApStateStore.kt
│ │ ├── callbacks
│ │ ├── SoftApCallback.kt
│ │ └── TetheringEventCallback.kt
│ │ ├── internal
│ │ ├── Extensions.kt
│ │ ├── InternalState.kt
│ │ ├── TetheringEventListener.kt
│ │ └── Utils.kt
│ │ └── validators
│ │ ├── PassphraseValidator.kt
│ │ └── SsidValidator.kt
│ └── test
│ └── kotlin
│ └── dev
│ └── shadoe
│ └── delta
│ └── data
│ └── softap
│ └── validators
│ ├── PassphraseValidatorTest.kt
│ └── SsidValidatorTest.kt
├── fastlane
└── metadata
│ └── android
│ └── en-US
│ ├── changelogs
│ ├── 15.txt
│ ├── 16.txt
│ └── 17.txt
│ ├── full_description.txt
│ ├── images
│ ├── icon.png
│ └── phoneScreenshots
│ │ ├── 1.png
│ │ ├── 2.png
│ │ ├── 3.png
│ │ ├── 4.png
│ │ ├── 5.png
│ │ └── 6.png
│ └── short_description.txt
├── gradle.properties
├── gradle
├── build-logic
│ ├── .gitignore
│ ├── build.gradle.kts
│ ├── settings.gradle.kts
│ └── src
│ │ └── main
│ │ └── kotlin
│ │ └── delta
│ │ └── buildsrc
│ │ ├── Commands.kt
│ │ ├── SigningConfig.kt
│ │ ├── VersionConfig.kt
│ │ ├── delta.app.version.gradle.kts
│ │ └── lint
│ │ ├── delta.lint.java.gradle.kts
│ │ ├── delta.lint.kotlin.gradle.kts
│ │ ├── delta.lint.kts.gradle.kts
│ │ └── delta.lint.text.gradle.kts
├── libs.versions.toml
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── scripts
└── update_ver.sh
├── settings.gradle.kts
└── system-api-stubs
├── .gitignore
├── build.gradle.kts
├── consumer-rules.pro
├── proguard-rules.pro
└── src
└── main
├── AndroidManifest.xml
├── aidl
└── android
│ └── net
│ ├── IIntResultListener.aidl
│ ├── TetherStatesParcel.aidl
│ ├── TetheredClient.aidl
│ ├── TetheringConfigurationParcel.aidl
│ ├── TetheringInterface.aidl
│ ├── TetheringRequestParcel.aidl
│ └── wifi
│ └── SoftApState.aidl
└── java
└── android
└── net
├── ITetheringConnector.java
├── ITetheringEventCallback.java
├── TetheredClient.java
├── TetheringCallbackStartedParcel.java
├── TetheringInterface.java
├── TetheringManagerHidden.java
└── wifi
├── ISoftApCallback.java
├── IStringListener.java
├── IWifiManager.java
├── OuiKeyedData.java
├── SoftApCapability.java
├── SoftApConfigurationHidden.java
├── SoftApInfo.java
├── SoftApState.java
└── WifiClient.java
/.editorconfig:
--------------------------------------------------------------------------------
1 | # https://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | indent_style = space
6 | indent_size = 4
7 | max_line_length = 80
8 |
9 | end_of_line = lf
10 | charset = utf-8
11 | trim_trailing_whitespace = true
12 | insert_final_newline = true
13 |
14 | [*.{yaml,yml}]
15 | indent_size = 2
16 |
17 | [*.{kt,kts}]
18 | indent_size = 2
19 | ij_kotlin_packages_to_use_import_on_demand = unset
20 | ij_kotlin_name_count_to_use_star_import = 65535
21 | ij_kotlin_name_count_to_use_star_import_for_members = 65535
22 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: [supershadoe]
2 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.yml:
--------------------------------------------------------------------------------
1 | name: Bug report
2 | description: To report a bug or issue with the app.
3 | labels: ["bug", "user"]
4 | body:
5 | - type: textarea
6 | id: reproduction-steps
7 | attributes:
8 | label: Steps to reproduce
9 | description: Describe the steps to be taken to reproduce the bug.
10 | value: |
11 | 1.
12 | 2.
13 | 3.
14 | 4.
15 | validations:
16 | required: true
17 |
18 | - type: textarea
19 | id: expected-behavior
20 | attributes:
21 | label: Expected behavior
22 | description: Describe what should have happened if the bug wasn't there.
23 | validations:
24 | required: true
25 |
26 | - type: textarea
27 | id: actual-behavior
28 | attributes:
29 | label: Actual behavior
30 | description: The erroneous behavior that is manifested instead of the expected behavior.
31 | validations:
32 | required: true
33 |
34 | - type: input
35 | id: os-version
36 | attributes:
37 | label: Android version
38 | description: Mostly in Settings -> About phone.
39 | placeholder: |
40 | Example: "Android 15"
41 | validations:
42 | required: true
43 |
44 | - type: input
45 | id: device-model
46 | attributes:
47 | label: Device model
48 | description: Provide your device model.
49 | placeholder: |
50 | Example: "Pixel 6A"
51 | validations:
52 | required: true
53 |
54 | - type: textarea
55 | id: crash-logs
56 | attributes:
57 | label: Crash logs
58 | description: Can obtain from adb logcat or through android studio.
59 | placeholder: |
60 | You can either paste the crash log here or attach it as an attachment.
61 |
62 | - type: textarea
63 | id: other-info
64 | attributes:
65 | label: Other info
66 | description: Any other info or images to share with us goes here.
67 |
68 | - type: checkboxes
69 | id: checklist
70 | attributes:
71 | label: Checklist
72 | description: Make sure to tick all the following boxes.
73 | options:
74 | - label: I have searched through the existing issues and am pretty sure that this is not a duplicate.
75 | required: true
76 | - label: I am using the latest version of the app, linked [here](https://github.com/supershadoe/delta/releases/latest)
77 | required: true
78 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: true
2 | contact_links:
3 | - name: Discussions
4 | url: https://github.com/supershadoe/delta/discussions
5 | about: Ask questions about the app in general or any other help related to the app.
6 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feat_req.yml:
--------------------------------------------------------------------------------
1 | name: Feature request
2 | description: To suggest a new feature for the app.
3 | labels: ["enhancement", "user"]
4 | body:
5 | - type: textarea
6 | id: prob-statement
7 | attributes:
8 | label: Why is this feature needed?
9 | description: Describe what problem(s) would this feature solve.
10 | validations:
11 | required: true
12 |
13 | - type: textarea
14 | id: feat-desc
15 | attributes:
16 | label: Potential solution
17 | description: Describe how the problem might be tackled here.
18 | validations:
19 | required: true
20 |
21 | - type: checkboxes
22 | id: checklist
23 | attributes:
24 | label: Checklist
25 | description: Make sure to tick all the following boxes.
26 | options:
27 | - label: I have searched through the existing issues and am pretty sure that this is not a duplicate.
28 | required: true
29 |
--------------------------------------------------------------------------------
/.github/actions/build-apk/action.yml:
--------------------------------------------------------------------------------
1 | name: Build APK
2 | description: Builds debug and release APKs
3 | inputs:
4 | cache-encryption-key:
5 | description: 'Encryption key for the configuration cache'
6 | required: false
7 | sign-release:
8 | description: 'Whether to configure signing for a release build'
9 | required: true
10 | default: 'false'
11 | keystore-base64:
12 | description: 'Base64 encoded keystore for signing'
13 | required: false
14 | sign-base64:
15 | description: 'Base64 encoded signing configuration (sign.json)'
16 | required: false
17 | runs:
18 | using: 'composite'
19 | steps:
20 | - name: Custom gradle setup
21 | uses: ./.github/actions/gradle-setup
22 | with:
23 | cache-encryption-key: ${{ inputs.cache-encryption-key || '' }}
24 | - name: Decode signing keystore
25 | if: inputs.sign-release == 'true'
26 | shell: bash
27 | env:
28 | KEYSTORE_BASE64: ${{ inputs.keystore-base64 }}
29 | run: |
30 | echo "Decoding keystore..."
31 | if [ -z "$KEYSTORE_BASE64" ]; then
32 | echo "Error: KEYSTORE_BASE64 input is missing for signed release."
33 | exit 1
34 | fi
35 | echo $KEYSTORE_BASE64 | base64 -d > app/sign.keystore
36 | - name: Deserialize signing configuration
37 | if: inputs.sign-release == 'true'
38 | shell: bash
39 | env:
40 | SIGN_BASE64: ${{ inputs.sign-base64 }}
41 | run: |
42 | echo "Configuring signing key..."
43 | if [ -z "$SIGN_BASE64" ]; then
44 | echo "Error: SIGN_BASE64 input is missing for signed release."
45 | exit 1
46 | fi
47 | echo $SIGN_BASE64 | base64 -d > app/sign.json
48 | - name: Build debug APK
49 | shell: bash
50 | run: ./gradlew assembleDebugKeySignedDebug
51 | - name: Build debug key signed release APK
52 | if: inputs.sign-release == 'false'
53 | shell: bash
54 | run: ./gradlew assembleDebugKeySignedRelease
55 | - name: Build signed release APK
56 | if: inputs.sign-release == 'true'
57 | shell: bash
58 | run: ./gradlew assembleDefaultKeySignedRelease
59 | - name: Move APKs to root
60 | shell: bash
61 | run: |
62 | DEBUG_APK_PATH="app/build/outputs/apk/debugKeySigned/debug/app-debugKeySigned-debug.apk"
63 | UNSIGNED_RELEASE_APK_PATH="app/build/outputs/apk/debugKeySigned/release/app-debugKeySigned-release.apk"
64 | SIGNED_RELEASE_APK_PATH="app/build/outputs/apk/defaultKeySigned/release/app-defaultKeySigned-release.apk"
65 | [ -e "$DEBUG_APK_PATH" ] && mv "$DEBUG_APK_PATH" "app-debug.apk"
66 | [ -e "$UNSIGNED_RELEASE_APK_PATH" ] && mv "$UNSIGNED_RELEASE_APK_PATH" \
67 | "app-release.apk"
68 | [ -e "$SIGNED_RELEASE_APK_PATH" ] && mv "$SIGNED_RELEASE_APK_PATH" \
69 | "app-release.apk"
70 | - name: Upload APK
71 | uses: actions/upload-artifact@v4
72 | with:
73 | name: app
74 | path: app-*.apk
75 | retention-days: 7
76 | if-no-files-found: error
77 |
--------------------------------------------------------------------------------
/.github/actions/gradle-setup/action.yml:
--------------------------------------------------------------------------------
1 | name: Custom gradle setup
2 | description: 'Gradle & Java setup for this project'
3 | inputs:
4 | cache-encryption-key:
5 | description: 'Encryption key for the configuration cache'
6 | required: false
7 | gradle-action:
8 | description: 'Gradle action to use for this step'
9 | required: false
10 | default: 'setup-gradle'
11 | read-only-cache:
12 | description: 'Whether to use a read-only cache for this step'
13 | required: false
14 | default: 'false'
15 | runs:
16 | using: 'composite'
17 | steps:
18 | - name: Setup JDK
19 | uses: actions/setup-java@v4
20 | with:
21 | distribution: temurin
22 | java-version: 21
23 | - name: Setup composite build caching
24 | uses: actions/cache@v4
25 | with:
26 | path: gradle/build-logic/build
27 | key: build-logic-${{ hashFiles('gradle/build-logic/src/**', 'gradle/build-logic/*.kts') }}
28 | - name: Setup gradle
29 | uses: gradle/actions/setup-gradle@v4
30 | if: inputs.gradle-action == 'setup-gradle'
31 | with:
32 | cache-encryption-key: ${{ inputs.cache-encryption-key }}
33 | cache-read-only: ${{ inputs.read-only-cache }}
34 | - name: Generate and submit dependency graph
35 | uses: gradle/actions/dependency-submission@v4
36 | if: inputs.gradle-action == 'dependency-submission'
37 | with:
38 | cache-encryption-key: ${{ inputs.cache-encryption-key }}
39 | dependency-graph-include-configurations: '.*RuntimeClasspath'
40 |
--------------------------------------------------------------------------------
/.github/actions/run-lints/action.yml:
--------------------------------------------------------------------------------
1 | name: Linting/Formatting
2 | description: Runs lint and format checks
3 | inputs:
4 | cache-encryption-key:
5 | description: 'Encryption key for the configuration cache'
6 | required: false
7 | read-only-cache:
8 | description: 'Whether to use a read-only cache for this step'
9 | required: false
10 | default: 'false'
11 | runs:
12 | using: composite
13 | steps:
14 | - name: Custom gradle setup
15 | uses: ./.github/actions/gradle-setup
16 | with:
17 | cache-encryption-key: ${{ inputs.cache-encryption-key || '' }}
18 | - name: Check for formatting issues
19 | shell: bash
20 | run: ./gradlew spotlessCheck
21 |
--------------------------------------------------------------------------------
/.github/actions/run-tests/action.yml:
--------------------------------------------------------------------------------
1 | name: Testing
2 | description: Runs unit and integration tests
3 | inputs:
4 | cache-encryption-key:
5 | description: 'Encryption key for the configuration cache'
6 | required: false
7 | read-only-cache:
8 | description: 'Whether to use a read-only cache for this step'
9 | required: false
10 | default: 'false'
11 | runs:
12 | using: composite
13 | steps:
14 | - name: Custom gradle setup
15 | uses: ./.github/actions/gradle-setup
16 | with:
17 | cache-encryption-key: ${{ inputs.cache-encryption-key || '' }}
18 | - name: Run unit tests
19 | shell: bash
20 | run: ./gradlew testReleaseUnitTest
21 |
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 | ### Summary
2 |
3 |
6 |
7 | ### Related issue(s)
8 |
9 |
18 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: delta-ci
2 | on:
3 | workflow_call:
4 | push:
5 | branches:
6 | - main
7 | paths-ignore:
8 | - '*.md'
9 | - '*.txt'
10 | - '.github/*'
11 | - 'docs/*'
12 | - 'fastlane/*'
13 | pull_request:
14 | types:
15 | - ready_for_review
16 | - review_requested
17 | - synchronize
18 | paths-ignore:
19 | - '*.md'
20 | - '*.txt'
21 | - '.github/*'
22 | - 'docs/*'
23 | - 'fastlane/*'
24 | concurrency:
25 | group: ${{ github.workflow }}-${{ github.ref }}
26 | cancel-in-progress: true
27 | jobs:
28 | lint:
29 | name: Lint check
30 | runs-on: ubuntu-latest
31 | steps:
32 | - name: Checkout repo
33 | uses: actions/checkout@v4
34 | - name: Run lint check
35 | uses: ./.github/actions/run-lints
36 | with:
37 | cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }}
38 | read-only-cache: ${{ github.event_name == 'pull_request' }}
39 | test:
40 | name: Testing
41 | runs-on: ubuntu-latest
42 | steps:
43 | - name: Checkout repo
44 | uses: actions/checkout@v4
45 | - name: Run unit tests
46 | uses: ./.github/actions/run-tests
47 | with:
48 | cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }}
49 | read-only-cache: ${{ github.event_name == 'pull_request' }}
50 | dependency-graph-submission:
51 | name: Dependency graph submission
52 | runs-on: ubuntu-latest
53 | if: github.event_name != 'pull_request'
54 | permissions:
55 | contents: write
56 | steps:
57 | - name: Checkout repo
58 | uses: actions/checkout@v4
59 | - name: Generate dependency graph
60 | uses: ./.github/actions/gradle-setup
61 | with:
62 | cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }}
63 | gradle-action: dependency-submission
64 | upload-strings-to-crowdin:
65 | name: Upload string to crowdin
66 | runs-on: ubuntu-latest
67 | if: github.event_name != 'pull_request'
68 | steps:
69 | - name: Checkout repo
70 | uses: actions/checkout@v4
71 | - name: Sync with crowdin
72 | uses: crowdin/github-action@v2
73 | with:
74 | upload_sources: true
75 | upload_translations: false
76 | download_translations: false
77 | create_pull_request: false
78 | env:
79 | CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
80 | CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PAT }}
81 |
--------------------------------------------------------------------------------
/.github/workflows/codeql.yml:
--------------------------------------------------------------------------------
1 | name: CodeQL analysis
2 | on:
3 | schedule:
4 | - cron: '0 0 * * 1'
5 | jobs:
6 | analyze:
7 | name: Analyze java-kotlin
8 | runs-on: ubuntu-latest
9 | permissions:
10 | security-events: write
11 | steps:
12 | - name: Checkout repo
13 | uses: actions/checkout@v4
14 | - name: Initialize CodeQL
15 | uses: github/codeql-action/init@v3
16 | with:
17 | build-mode: manual
18 | - name: Custom gradle setup
19 | uses: ./.github/actions/gradle-setup
20 | with:
21 | cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }}
22 | - name: Build app
23 | run: ./gradlew assembleDebugKeySignedRelease
24 | - name: Perform CodeQL Analysis
25 | uses: github/codeql-action/analyze@v3
26 | with:
27 | category: "/language:java-kotlin"
28 |
--------------------------------------------------------------------------------
/.github/workflows/daily.yml:
--------------------------------------------------------------------------------
1 | name: Daily jobs
2 | on:
3 | schedule:
4 | - cron: '0 0 * * *'
5 | jobs:
6 | stale:
7 | name: Mark issues/PRs stale after 60 days
8 | runs-on: ubuntu-latest
9 | permissions:
10 | issues: write
11 | pull-requests: write
12 | steps:
13 | - uses: actions/stale@v5
14 | with:
15 | days-before-stale: 60
16 | days-before-close: 7
17 | stale-issue-message: |
18 | Due to no activity on this issue for over 2 months, this issue has been marked stale.
19 |
20 | Please chime in with a response if the issue still persists.
21 | close-issue-message: |
22 | This issue has been closed by the stale bot as no activity was observed.
23 |
24 | Please create a new issue if the issue still persists.
25 | stale-pr-message: |
26 | Due to no activity on this PR for over 2 months, this PR has been marked stale.
27 |
28 | Stale PRs increase overhead on maintainer's side and also quickly grow outdated as the
29 | codebase changes, thus making merging harder.
30 |
31 | If this PR is still relevant and required, make sure the PR is rebased over the current
32 | code.
33 | close-pr-message: |
34 | This PR has been closed by the stale bot as no activity was observed.
35 |
36 | Please create a new issue if the issue still persists.
37 | stale-issue-label: 'stale'
38 | stale-pr-label: 'stale'
39 | download-translations-from-crowdin:
40 | name: Download translations from crowdin
41 | runs-on: ubuntu-latest
42 | permissions:
43 | contents: write
44 | pull-requests: write
45 | steps:
46 | - name: Checkout repo
47 | uses: actions/checkout@v4
48 | - name: Sync with crowdin
49 | uses: crowdin/github-action@v2
50 | with:
51 | upload_sources: false
52 | upload_translations: false
53 | download_translations: true
54 | skip_untranslated_strings: true
55 | localization_branch_name: crowdin-sync
56 | create_pull_request: true
57 | commit_message: Sync translations
58 | pull_request_title: Sync translations
59 | pull_request_body: '_Synced by GitHub actions from [Crowdin](https://crowdin.com/project/delta-app)_'
60 | pull_request_reviewers: supershadoe
61 | env:
62 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
63 | CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
64 | CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PAT }}
65 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release tasks
2 | on:
3 | push:
4 | tags-ignore:
5 | - norel-*
6 | workflow_dispatch:
7 | concurrency:
8 | group: release
9 | cancel-in-progress: true
10 | jobs:
11 | # TODO: check if CI passed for tag
12 | build-apk:
13 | name: Build app
14 | runs-on: ubuntu-latest
15 | permissions:
16 | contents: write
17 | steps:
18 | - name: Checkout repo
19 | uses: actions/checkout@v4
20 | - name: Build APK
21 | uses: ./.github/actions/build-apk
22 | with:
23 | cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }}
24 | sign-release: true
25 | keystore-base64: ${{ secrets.KEYSTORE_BASE64 }}
26 | sign-base64: ${{ secrets.SIGN_BASE64 }}
27 | create-release:
28 | name: Create release
29 | runs-on: ubuntu-latest
30 | needs: [build-apk]
31 | permissions:
32 | contents: write
33 | discussions: write
34 | steps:
35 | # TODO: use metadata from fastlane
36 | - name: Download APK
37 | uses: actions/download-artifact@v4
38 | with:
39 | name: app
40 | - name: Calculate checksum and generate release notes
41 | run: |
42 | cat << "EOF" >> release_notes.txt
43 | ### Message
44 |
45 | To be filled.
46 |
47 | ### Checksum
48 |
49 | ```sh
50 | $ sha256sum app-release.apk
51 | EOF
52 |
53 | sha256sum app-release.apk >> release_notes.txt
54 |
55 | cat << "EOF" >> release_notes.txt
56 | ```
57 |
58 | Check the checksum after downloading the APK using `sha256sum` to
59 | ensure integrity of the file.
60 | EOF
61 | - name: Create release
62 | uses: softprops/action-gh-release@v2
63 | with:
64 | generate_release_notes: true
65 | draft: true
66 | prerelease: false
67 | tag_name: ${{ needs.pre-release-job.outputs.version }}
68 | files: |
69 | app-release.apk
70 | discussion_category_name: Announcements
71 | body_path: release_notes.txt
72 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | .idea/*
4 | !.idea/icon.svg
5 | /local.properties
6 | .DS_Store
7 | /build
8 | /captures
9 | .externalNativeBuild
10 | .cxx
11 | local.properties
12 | .kotlin
13 |
--------------------------------------------------------------------------------
/.idea/icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | ## Contribution guide
2 |
3 | Thank you for taking the time to contribute to Delta! :sparkles:
4 |
5 | ### Steps to follow while reporting a bug
6 |
7 | > [!WARNING]
8 | > Follow the steps in [security policy](https://github.com/supershadoe/delta/security/policy)
9 | > if it is a security vulnerability.
10 |
11 | - Check if a relevant issue exists in the [issue tracker](https://github.com/supershadoe/delta/issues).
12 | - If an issue doesn't exist, choose the relevant [issue form](https://github.com/supershadoe/delta/issues/new/choose)
13 | and open a new issue.
14 |
15 | ### Developer guide
16 |
17 | The project has the following modules.
18 | - `:api` - Contains all the classes and interfaces used by all modules in the
19 | app.
20 | - `:app` - Contains all the UI components and the ViewModels used by the app.
21 | - `:data` - Contains the core logic of the app that interfaces with the hidden
22 | Android API. This is what you want to modify if you want to fix something in
23 | the backend of the app.
24 | - `:system-api-stubs` - These are only to be modified when adding new
25 | functionality by using any other hidden API.
26 | - `:buildSrc` - Contains the shared convention plugins and functions to be
27 | reused in other build scripts.
28 |
29 | ### New contributors
30 |
31 | If you do not have an idea where to start, ping `@supershadoe` on Discord or
32 | mail me at [shadoe@shadoe.dev](mailto:shadoe@shadoe.dev)
33 |
34 | This is only for getting to know what's the need of the hour and what to get
35 | started with, not for asking to be coached through the process of developing.
36 |
37 | Some good resources to start with the project are as follows.
38 | (the links might be broken as Android docs changes up the links often)
39 |
40 | - [About app architecture | Android docs](https://developer.android.com/topic/architecture)
41 | - [Localization | Android docs](https://developer.android.com/guide/topics/resources/localization#creating-alternatives)
42 | - [Flows | Kotlin docs](https://kotlinlang.org/docs/flow.html)
43 | - [Flow | Android docs](https://developer.android.com/kotlin/flow)
44 | - [StateFlow and SharedFlow | Android docs](https://developer.android.com/kotlin/flow/stateflow-and-sharedflow)
45 | - [Jetpack compose](https://developer.android.com/compose)
46 | - [Shizuku-API](https://github.com/RikkaApps/Shizuku-API/)
47 |
48 | If you want to utilize some other hidden API for a new feature in the app, make
49 | sure you know what you are doing because there are no specific guides to help
50 | you out with that; the only sources of truth are [Android Code Search](https://cs.android.com/)
51 | and your device.
52 |
53 | ### Steps to send in a patch for an issue
54 |
55 | > [!WARNING]
56 | > - Do NOT create a pull request without creating an issue first.
57 | > - Do NOT make a cosmetic change alone as the formatter setup in the repo will
58 | > take of enforcing formatting and other standards.
59 |
60 | - Create a pull request after pushing your patch to GitHub.
61 | - Mark the PR as ready for review (if opened as draft) to start the automated
62 | testing.
63 | - If all the tests do not pass, fix whatever broke the tests and push again.
64 | - PR will not be accepted without the tests passing.
65 |
66 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2024, 2025 supershadoe
2 |
3 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
4 |
5 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
6 |
7 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
8 |
9 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
10 |
11 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
12 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## delta
2 |
3 | This app allows accessing advanced hotspot settings not exposed through the
4 | system settings app on devices that use Stock Android (Pixel, Moto, etc.)
5 |
6 | It provides various information and features such as:
7 | - Usual info provided by settings app such as hotspot name, passphrase, security
8 | type, frequency bands, quick connect using QR, etc.
9 | - List of connected devices.
10 | - Modifying amount of devices that can connect.
11 | - Changing MAC randomization settings.
12 | - Setting auto turn-off timeout.
13 | - Blocklist to prevent certain devices from connecting.
14 | - Make hotspot's SSID hidden (device doesn't appear on WiFi scans)
15 | - Quick setting tile to easily turn hotspot on/off from notification shade.
16 | - Control hotspot using Tasker/other automation apps.
17 |
18 | It uses Shizuku to obtain hotspot related details and to modify hotspot
19 | settings. It is also useful on devices like Samsung Tabs (WiFi-only) where
20 | hotspot settings are hidden from Settings app and SystemUI.
21 |
22 | ### Installation
23 |
24 | 1. Install Shizuku from [Play Store](https://play.google.com/store/apps/details?id=moe.shizuku.privileged.api)
25 | or other places like GitHub/IzzyOnDroid [(official site)](https://shizuku.rikka.app/download/)
26 | 2. Get Delta from either GitHub releases directly, using [Obtainium](https://github.com/ImranR98/Obtainium)
27 | or from [IzzyOnDroid](https://apt.izzysoft.de/fdroid/index/apk/dev.shadoe.delta)
28 | F-Droid repo.
29 |
30 | [ ](https://github.com/ImranR98/Obtainium)
32 | [ ](https://apt.izzysoft.de/fdroid/index/apk/dev.shadoe.delta)
34 |
35 | ### Localization
36 |
37 | If you wish to translate the project to a language that you are fluent in, you
38 | can contribute through [Crowdin](https://crowdin.com/project/delta-app)
39 |
40 | Open up a discussion on [GitHub](https://github.com/supershadoe/delta/discussions/new?category=ideas)
41 | or [mail me](mailto:shadoe@shadoe.dev) to enable any language on Crowdin other
42 | than the ones already enabled.
43 |
44 | ### Want to contribute?
45 |
46 | Check out this [page](https://github.com/supershadoe/delta/contribute) on how
47 | to start! :hugs:
48 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Security Policy
2 |
3 | ## Supported Versions
4 |
5 | Only the most recent stable release of Delta (referred to as "the app") will be maintained and patched in case
6 | of any security issues.
7 |
8 | ## Reporting a Vulnerability
9 |
10 | Posting on public channels like issues/discussions may cause bad actors to take advantage of
11 | the vulnerabilities and thus, it is not advisable.
12 |
13 | To report a vulnerability, mail @supershadoe through this [mail ID](mailto:security@shadoe.dev) and not through
14 | any other channels.
15 |
--------------------------------------------------------------------------------
/api/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/api/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | kotlin("jvm")
3 | id("delta.lint.kotlin")
4 | id("delta.lint.kts")
5 | }
6 |
7 | kotlin { jvmToolchain(21) }
8 |
9 | dependencies { implementation(libs.androidx.annotation) }
10 |
--------------------------------------------------------------------------------
/api/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/supershadoe/delta/2cc85f133800794b6056a77b430367e0f1a973bf/api/consumer-rules.pro
--------------------------------------------------------------------------------
/api/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/api/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/api/src/main/kotlin/dev/shadoe/delta/api/ACLDevice.kt:
--------------------------------------------------------------------------------
1 | package dev.shadoe.delta.api
2 |
3 | data class ACLDevice(val hostname: String?, val macAddress: MacAddress)
4 |
--------------------------------------------------------------------------------
/api/src/main/kotlin/dev/shadoe/delta/api/ConfigFlag.kt:
--------------------------------------------------------------------------------
1 | package dev.shadoe.delta.api
2 |
3 | /**
4 | * Holds certain flags used to determine the app behavior.
5 | *
6 | * Possible usage: Determining first run, app version migrations, etc.
7 | */
8 | enum class ConfigFlag {
9 | /** True if the app has been run before. */
10 | NOT_FIRST_RUN,
11 |
12 | /**
13 | * True if the app uses Room for config.
14 | *
15 | * This is set either after migrating app from Datastore when upgrading from
16 | * older versions or when app is installed afresh.
17 | */
18 | @Deprecated("Not used anymore", level = DeprecationLevel.HIDDEN) USES_ROOM_DB,
19 |
20 | /**
21 | * Adding a permission to the receiver cannot let arbitrary apps send intents
22 | * to this app. Thus, a setting is added in the app for power users to enable
23 | * insecure receivers.
24 | */
25 | INSECURE_RECEIVER_ENABLED,
26 | }
27 |
--------------------------------------------------------------------------------
/api/src/main/kotlin/dev/shadoe/delta/api/LinkAddress.kt:
--------------------------------------------------------------------------------
1 | package dev.shadoe.delta.api
2 |
3 | import java.net.InetAddress
4 |
5 | data class LinkAddress(val address: InetAddress)
6 |
--------------------------------------------------------------------------------
/api/src/main/kotlin/dev/shadoe/delta/api/MacAddress.kt:
--------------------------------------------------------------------------------
1 | package dev.shadoe.delta.api
2 |
3 | data class MacAddress(val macAddress: String) {
4 | override fun toString() = macAddress
5 | }
6 |
--------------------------------------------------------------------------------
/api/src/main/kotlin/dev/shadoe/delta/api/ShizukuStates.kt:
--------------------------------------------------------------------------------
1 | package dev.shadoe.delta.api
2 |
3 | import androidx.annotation.IntDef
4 |
5 | object ShizukuStates {
6 | const val NOT_READY = -1
7 | const val NOT_AVAILABLE = 0
8 | const val NOT_RUNNING = 1
9 | const val NOT_CONNECTED = 2
10 | const val CONNECTED = 3
11 |
12 | @IntDef(
13 | value = [NOT_READY, NOT_AVAILABLE, NOT_RUNNING, NOT_CONNECTED, CONNECTED]
14 | )
15 | annotation class ShizukuStateType
16 | }
17 |
--------------------------------------------------------------------------------
/api/src/main/kotlin/dev/shadoe/delta/api/SoftApAutoShutdownTimeout.kt:
--------------------------------------------------------------------------------
1 | package dev.shadoe.delta.api
2 |
3 | import androidx.annotation.LongDef
4 |
5 | object SoftApAutoShutdownTimeout {
6 | const val DEFAULT = -1L
7 | const val FIVE_MINUTES = 5 * 60 * 1000L
8 | const val TEN_MINUTES = 10 * 60 * 1000L
9 | const val TWENTY_MINUTES = 20 * 60 * 1000L
10 | const val THIRTY_MINUTES = 30 * 60 * 1000L
11 | const val ONE_HOUR = 60 * 60 * 1000L
12 |
13 | @Retention(AnnotationRetention.SOURCE)
14 | @LongDef(
15 | value =
16 | [
17 | DEFAULT,
18 | FIVE_MINUTES,
19 | TEN_MINUTES,
20 | TWENTY_MINUTES,
21 | THIRTY_MINUTES,
22 | ONE_HOUR,
23 | ]
24 | )
25 | annotation class AutoShutdownTimeoutType
26 |
27 | val supportedShutdownTimeouts =
28 | listOf(
29 | DEFAULT,
30 | FIVE_MINUTES,
31 | TEN_MINUTES,
32 | TWENTY_MINUTES,
33 | THIRTY_MINUTES,
34 | ONE_HOUR,
35 | )
36 | }
37 |
--------------------------------------------------------------------------------
/api/src/main/kotlin/dev/shadoe/delta/api/SoftApCapabilities.kt:
--------------------------------------------------------------------------------
1 | package dev.shadoe.delta.api
2 |
3 | import dev.shadoe.delta.api.SoftApSecurityType.SecurityType
4 | import dev.shadoe.delta.api.SoftApSpeedType.BandType
5 |
6 | data class SoftApCapabilities(
7 | val maxSupportedClients: Int,
8 | val clientForceDisconnectSupported: Boolean,
9 | val isMacAddressCustomizationSupported: Boolean,
10 | @BandType val supportedFrequencyBands: List,
11 | @SecurityType val supportedSecurityTypes: List,
12 | )
13 |
--------------------------------------------------------------------------------
/api/src/main/kotlin/dev/shadoe/delta/api/SoftApConfiguration.kt:
--------------------------------------------------------------------------------
1 | package dev.shadoe.delta.api
2 |
3 | data class SoftApConfiguration(
4 | val ssid: String?,
5 | val passphrase: String,
6 | @SoftApSecurityType.SecurityType val securityType: Int,
7 | @SoftApRandomizationSetting.RandomizationType
8 | val macRandomizationSetting: Int,
9 | val isHidden: Boolean,
10 | @SoftApSpeedType.BandType val speedType: Int,
11 | val blockedDevices: List,
12 | val allowedClients: List,
13 | val isAutoShutdownEnabled: Boolean,
14 | /** Timeout in milliseconds for auto shutdown. */
15 | val autoShutdownTimeout: Long,
16 | val maxClientLimit: Int,
17 | )
18 |
--------------------------------------------------------------------------------
/api/src/main/kotlin/dev/shadoe/delta/api/SoftApEnabledState.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Adopted from source of android.net.wifi.WifiManager
3 | *
4 | * Copyright (C) 2019 The Android Open Source Project
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | *
18 | */
19 |
20 | package dev.shadoe.delta.api
21 |
22 | import androidx.annotation.IntDef
23 |
24 | object SoftApEnabledState {
25 | /**
26 | * Wi-Fi AP is currently being disabled. The state will change to
27 | * {@link #WIFI_AP_STATE_DISABLED} if it finishes successfully.
28 | */
29 | const val WIFI_AP_STATE_DISABLING = 10
30 |
31 | /** Wi-Fi AP is disabled. */
32 | const val WIFI_AP_STATE_DISABLED = 11
33 |
34 | /**
35 | * Wi-Fi AP is currently being enabled. The state will change to
36 | * {@link #WIFI_AP_STATE_ENABLED} if it finishes successfully.
37 | */
38 | const val WIFI_AP_STATE_ENABLING = 12
39 |
40 | /** Wi-Fi AP is enabled. */
41 | const val WIFI_AP_STATE_ENABLED = 13
42 |
43 | /**
44 | * Wi-Fi AP is in a failed state. This state will occur when an error occurs
45 | * during enabling or disabling
46 | */
47 | const val WIFI_AP_STATE_FAILED = 14
48 |
49 | @Retention(AnnotationRetention.SOURCE)
50 | @IntDef(
51 | value =
52 | [
53 | WIFI_AP_STATE_DISABLING,
54 | WIFI_AP_STATE_DISABLED,
55 | WIFI_AP_STATE_ENABLING,
56 | WIFI_AP_STATE_ENABLED,
57 | WIFI_AP_STATE_FAILED,
58 | ]
59 | )
60 | annotation class EnabledStateType
61 | }
62 |
--------------------------------------------------------------------------------
/api/src/main/kotlin/dev/shadoe/delta/api/SoftApRandomizationSetting.kt:
--------------------------------------------------------------------------------
1 | package dev.shadoe.delta.api
2 |
3 | import androidx.annotation.IntDef
4 |
5 | object SoftApRandomizationSetting {
6 | const val RANDOMIZATION_NONE = 0
7 | const val RANDOMIZATION_PERSISTENT = 1
8 | const val RANDOMIZATION_NON_PERSISTENT = 2
9 |
10 | @Retention(AnnotationRetention.SOURCE)
11 | @IntDef(
12 | value =
13 | [
14 | RANDOMIZATION_NONE,
15 | RANDOMIZATION_PERSISTENT,
16 | RANDOMIZATION_NON_PERSISTENT,
17 | ]
18 | )
19 | annotation class RandomizationType
20 |
21 | val supportedRandomizationSettings =
22 | listOf(
23 | RANDOMIZATION_NONE,
24 | RANDOMIZATION_PERSISTENT,
25 | RANDOMIZATION_NON_PERSISTENT,
26 | )
27 | }
28 |
--------------------------------------------------------------------------------
/api/src/main/kotlin/dev/shadoe/delta/api/SoftApSecurityType.kt:
--------------------------------------------------------------------------------
1 | package dev.shadoe.delta.api
2 |
3 | import androidx.annotation.IntDef
4 |
5 | object SoftApSecurityType {
6 | const val SECURITY_TYPE_OPEN = 0
7 | const val SECURITY_TYPE_WPA2_PSK = 1
8 | const val SECURITY_TYPE_WPA3_SAE_TRANSITION = 2
9 | const val SECURITY_TYPE_WPA3_SAE = 3
10 | const val SECURITY_TYPE_WPA3_OWE_TRANSITION = 4
11 | const val SECURITY_TYPE_WPA3_OWE = 5
12 |
13 | @Retention(AnnotationRetention.SOURCE)
14 | @IntDef(
15 | value =
16 | [
17 | SECURITY_TYPE_OPEN,
18 | SECURITY_TYPE_WPA2_PSK,
19 | SECURITY_TYPE_WPA3_SAE_TRANSITION,
20 | SECURITY_TYPE_WPA3_SAE,
21 | SECURITY_TYPE_WPA3_OWE_TRANSITION,
22 | SECURITY_TYPE_WPA3_OWE,
23 | ]
24 | )
25 | annotation class SecurityType
26 | }
27 |
--------------------------------------------------------------------------------
/api/src/main/kotlin/dev/shadoe/delta/api/SoftApSpeedType.kt:
--------------------------------------------------------------------------------
1 | package dev.shadoe.delta.api
2 |
3 | import androidx.annotation.IntDef
4 |
5 | object SoftApSpeedType {
6 | /** Wi-Fi hotspot band unknown. */
7 | const val BAND_UNKNOWN = 0
8 |
9 | const val BAND_2GHZ = 1 shl 0
10 | const val BAND_5GHZ = 1 shl 1
11 | const val BAND_6GHZ = 1 shl 2
12 | const val BAND_60GHZ = 1 shl 3
13 |
14 | @Retention(AnnotationRetention.SOURCE)
15 | @IntDef(value = [BAND_2GHZ, BAND_5GHZ, BAND_6GHZ, BAND_60GHZ])
16 | annotation class BandType
17 | }
18 |
--------------------------------------------------------------------------------
/api/src/main/kotlin/dev/shadoe/delta/api/SoftApStatus.kt:
--------------------------------------------------------------------------------
1 | package dev.shadoe.delta.api
2 |
3 | data class SoftApStatus(
4 | @SoftApEnabledState.EnabledStateType val enabledState: Int,
5 | val tetheredClients: List,
6 | val capabilities: SoftApCapabilities,
7 | val isSoftApSupported: Boolean,
8 | )
9 |
--------------------------------------------------------------------------------
/api/src/main/kotlin/dev/shadoe/delta/api/TetheredClient.kt:
--------------------------------------------------------------------------------
1 | package dev.shadoe.delta.api
2 |
3 | data class TetheredClient(
4 | val macAddress: MacAddress,
5 | val address: LinkAddress?,
6 | val hostname: String?,
7 | val tetheringType: Int,
8 | )
9 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 | sign.json
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/app/src/androidTest/kotlin/dev/shadoe/delta/settings/components/AutoShutdownFieldTest.kt:
--------------------------------------------------------------------------------
1 | package dev.shadoe.delta.settings.components
2 |
3 | import androidx.compose.runtime.getValue
4 | import androidx.compose.runtime.mutableStateOf
5 | import androidx.compose.runtime.remember
6 | import androidx.compose.runtime.setValue
7 | import androidx.compose.ui.test.assertIsOff
8 | import androidx.compose.ui.test.assertIsOn
9 | import androidx.compose.ui.test.isToggleable
10 | import androidx.compose.ui.test.junit4.createComposeRule
11 | import androidx.compose.ui.test.performClick
12 | import androidx.test.ext.junit.runners.AndroidJUnit4
13 | import kotlin.test.Test
14 | import org.junit.Rule
15 | import org.junit.runner.RunWith
16 |
17 | @RunWith(AndroidJUnit4::class)
18 | class AutoShutdownFieldTest {
19 | @get:Rule val composeTestRule = createComposeRule()
20 |
21 | @Test
22 | fun `Check if the switch invokes the callback on tap`() {
23 | composeTestRule.run {
24 | setContent {
25 | var state by remember { mutableStateOf(false) }
26 | AutoShutdownField(
27 | isAutoShutdownEnabled = state,
28 | onAutoShutdownChange = { state = !state },
29 | )
30 | }
31 | onNode(isToggleable()).run {
32 | performClick()
33 | assertIsOn()
34 | performClick()
35 | assertIsOff()
36 | }
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/app/src/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
8 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
25 |
30 |
31 |
32 |
33 |
35 |
36 |
41 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/app/src/main/ic_launcher-playstore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/supershadoe/delta/2cc85f133800794b6056a77b430367e0f1a973bf/app/src/main/ic_launcher-playstore.png
--------------------------------------------------------------------------------
/app/src/main/kotlin/dev/shadoe/delta/Application.kt:
--------------------------------------------------------------------------------
1 | package dev.shadoe.delta
2 |
3 | import android.app.Application
4 | import android.util.Log
5 | import dagger.hilt.android.HiltAndroidApp
6 | import dev.shadoe.delta.crash.CrashHandlerUtils
7 | import kotlin.system.exitProcess
8 | import org.lsposed.hiddenapibypass.HiddenApiBypass
9 |
10 | @HiltAndroidApp
11 | class Application : Application() {
12 | override fun onCreate() {
13 | super.onCreate()
14 |
15 | Thread.setDefaultUncaughtExceptionHandler { _, throwable ->
16 | Log.e(packageName, "Uncaught exception", throwable)
17 | CrashHandlerUtils.sendCrashNotification(applicationContext, throwable)
18 | exitProcess(1)
19 | }
20 |
21 | HiddenApiBypass.setHiddenApiExemptions("L")
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/dev/shadoe/delta/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package dev.shadoe.delta
2 |
3 | import android.os.Bundle
4 | import androidx.activity.ComponentActivity
5 | import androidx.activity.compose.setContent
6 | import androidx.activity.enableEdgeToEdge
7 | import androidx.hilt.navigation.compose.hiltViewModel
8 | import dagger.hilt.android.AndroidEntryPoint
9 | import dev.shadoe.delta.common.Nav
10 | import dev.shadoe.delta.common.NavViewModel
11 | import dev.shadoe.delta.design.AppTheme
12 |
13 | @AndroidEntryPoint
14 | class MainActivity : ComponentActivity() {
15 | override fun onCreate(savedInstanceState: Bundle?) {
16 | enableEdgeToEdge()
17 | super.onCreate(savedInstanceState)
18 | setContent { AppTheme { Nav(hiltViewModel()) } }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/dev/shadoe/delta/common/NavViewModel.kt:
--------------------------------------------------------------------------------
1 | package dev.shadoe.delta.common
2 |
3 | import android.content.Context
4 | import androidx.lifecycle.ViewModel
5 | import androidx.lifecycle.viewModelScope
6 | import dagger.hilt.android.lifecycle.HiltViewModel
7 | import dagger.hilt.android.qualifiers.ApplicationContext
8 | import dev.shadoe.delta.api.ShizukuStates
9 | import dev.shadoe.delta.crash.CrashHandlerUtils
10 | import dev.shadoe.delta.data.FlagsRepository
11 | import dev.shadoe.delta.data.shizuku.ShizukuRepository
12 | import dev.shadoe.delta.data.softap.SoftApStateFacade
13 | import javax.inject.Inject
14 | import kotlinx.coroutines.flow.MutableStateFlow
15 | import kotlinx.coroutines.flow.asStateFlow
16 | import kotlinx.coroutines.flow.update
17 | import kotlinx.coroutines.launch
18 |
19 | @HiltViewModel
20 | class NavViewModel
21 | @Inject
22 | constructor(
23 | @ApplicationContext private val applicationContext: Context,
24 | private val shizukuRepository: ShizukuRepository,
25 | private val softApStateFacade: SoftApStateFacade,
26 | private val flagsRepository: FlagsRepository,
27 | ) : ViewModel() {
28 | private val _startScreen = MutableStateFlow(Routes.BlankScreen)
29 | val startScreen = _startScreen.asStateFlow()
30 |
31 | private suspend fun determineStartScreen(): Route {
32 | val isFirstRun = flagsRepository.isFirstRun()
33 | val shizukuConnected =
34 | shizukuRepository.shizukuState.value != ShizukuStates.CONNECTED
35 | val crashHandlerSetup =
36 | CrashHandlerUtils.shouldShowNotificationPermissionRequest(
37 | applicationContext
38 | )
39 | return when {
40 | isFirstRun -> Routes.Setup.FirstUseScreen
41 | shizukuConnected -> Routes.Setup.ShizukuSetupScreen
42 | crashHandlerSetup -> Routes.Setup.CrashHandlerSetupScreen
43 | else -> Routes.SoftApScreen
44 | }
45 | }
46 |
47 | init {
48 | addCloseable(shizukuRepository.callbackSubscriber)
49 | softApStateFacade.subscribe()
50 | addCloseable { softApStateFacade.unsubscribe() }
51 | viewModelScope.launch {
52 | shizukuRepository.shizukuState.collect {
53 | _startScreen.update { determineStartScreen() }
54 | }
55 | }
56 | }
57 |
58 | fun onSetupStarted() =
59 | viewModelScope.launch { flagsRepository.setNotFirstRun() }
60 |
61 | fun onSetupFinished() =
62 | viewModelScope.launch { _startScreen.update { determineStartScreen() } }
63 | }
64 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/dev/shadoe/delta/common/Routes.kt:
--------------------------------------------------------------------------------
1 | package dev.shadoe.delta.common
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | sealed interface Route {}
6 |
7 | object Routes {
8 | @Serializable object BlankScreen : Route
9 |
10 | @Serializable
11 | object Setup {
12 | @Serializable object FirstUseScreen : Route
13 |
14 | @Serializable object ShizukuSetupScreen : Route
15 |
16 | @Serializable object CrashHandlerSetupScreen : Route
17 | }
18 |
19 | @Serializable object SoftApScreen : Route
20 |
21 | @Serializable object BlocklistScreen : Route
22 |
23 | @Serializable object DebugScreen : Route
24 | }
25 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/dev/shadoe/delta/common/SoftApScreenDestinations.kt:
--------------------------------------------------------------------------------
1 | package dev.shadoe.delta.common
2 |
3 | import androidx.annotation.StringRes
4 | import androidx.compose.material.icons.Icons
5 | import androidx.compose.material.icons.rounded.Dashboard
6 | import androidx.compose.material.icons.rounded.Settings
7 | import androidx.compose.ui.graphics.vector.ImageVector
8 | import dev.shadoe.delta.R
9 |
10 | enum class SoftApScreenDestinations(
11 | @StringRes val label: Int,
12 | val icon: ImageVector,
13 | @StringRes val contentDescription: Int,
14 | ) {
15 | CONTROL(
16 | label = R.string.controls_icon,
17 | Icons.Rounded.Dashboard,
18 | contentDescription = R.string.controls_icon,
19 | ),
20 | SETTINGS(
21 | label = R.string.settings,
22 | Icons.Rounded.Settings,
23 | contentDescription = R.string.settings,
24 | ),
25 | }
26 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/dev/shadoe/delta/common/SoftApScreenViewModel.kt:
--------------------------------------------------------------------------------
1 | package dev.shadoe.delta.common
2 |
3 | import androidx.lifecycle.ViewModel
4 | import dagger.hilt.android.lifecycle.HiltViewModel
5 | import dev.shadoe.delta.data.softap.SoftApStateStore
6 | import javax.inject.Inject
7 | import kotlinx.coroutines.ExperimentalCoroutinesApi
8 | import kotlinx.coroutines.flow.mapLatest
9 |
10 | @HiltViewModel
11 | class SoftApScreenViewModel @Inject constructor(state: SoftApStateStore) :
12 | ViewModel() {
13 | @OptIn(ExperimentalCoroutinesApi::class)
14 | val isSoftApSupported = state.status.mapLatest { it.isSoftApSupported }
15 | }
16 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/dev/shadoe/delta/common/components/FadeInExpanded.kt:
--------------------------------------------------------------------------------
1 | package dev.shadoe.delta.common.components
2 |
3 | import androidx.compose.animation.AnimatedVisibility
4 | import androidx.compose.animation.AnimatedVisibilityScope
5 | import androidx.compose.animation.expandVertically
6 | import androidx.compose.animation.fadeIn
7 | import androidx.compose.animation.fadeOut
8 | import androidx.compose.animation.shrinkVertically
9 | import androidx.compose.runtime.Composable
10 |
11 | @Composable
12 | fun FadeInExpanded(
13 | visible: Boolean,
14 | content: @Composable AnimatedVisibilityScope.() -> Unit,
15 | ) {
16 | AnimatedVisibility(
17 | visible = visible,
18 | enter = fadeIn() + expandVertically(),
19 | exit = fadeOut() + shrinkVertically(),
20 | content = content,
21 | )
22 | }
23 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/dev/shadoe/delta/common/components/FoldableWrapper.kt:
--------------------------------------------------------------------------------
1 | package dev.shadoe.delta.common.components
2 |
3 | import androidx.compose.foundation.clickable
4 | import androidx.compose.foundation.layout.Arrangement
5 | import androidx.compose.foundation.layout.Row
6 | import androidx.compose.foundation.layout.fillMaxWidth
7 | import androidx.compose.foundation.layout.padding
8 | import androidx.compose.material.icons.Icons
9 | import androidx.compose.material.icons.rounded.KeyboardArrowDown
10 | import androidx.compose.material.icons.rounded.KeyboardArrowUp
11 | import androidx.compose.material3.Icon
12 | import androidx.compose.material3.MaterialTheme
13 | import androidx.compose.material3.Text
14 | import androidx.compose.runtime.Composable
15 | import androidx.compose.ui.Alignment
16 | import androidx.compose.ui.Modifier
17 | import androidx.compose.ui.res.stringResource
18 | import androidx.compose.ui.unit.dp
19 | import dev.shadoe.delta.R
20 |
21 | @Composable
22 | fun FoldableWrapper(
23 | text: String,
24 | foldableState: Boolean,
25 | onFoldableToggled: () -> Unit,
26 | modifier: Modifier = Modifier,
27 | ) {
28 | Row(
29 | modifier =
30 | Modifier.fillMaxWidth()
31 | .clickable { onFoldableToggled() }
32 | .padding(vertical = 16.dp)
33 | .then(modifier),
34 | horizontalArrangement = Arrangement.SpaceBetween,
35 | ) {
36 | Text(
37 | text = text,
38 | color = MaterialTheme.colorScheme.onSurfaceVariant,
39 | style = MaterialTheme.typography.titleLarge,
40 | )
41 | Icon(
42 | modifier = Modifier.align(Alignment.CenterVertically),
43 | imageVector =
44 | if (foldableState) {
45 | Icons.Rounded.KeyboardArrowUp
46 | } else {
47 | Icons.Rounded.KeyboardArrowDown
48 | },
49 | contentDescription =
50 | if (foldableState) {
51 | stringResource(R.string.collapse_icon)
52 | } else {
53 | stringResource(R.string.expand_icon)
54 | },
55 | )
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/dev/shadoe/delta/common/shapes/PolygonShape.kt:
--------------------------------------------------------------------------------
1 | package dev.shadoe.delta.common.shapes
2 |
3 | import androidx.compose.ui.geometry.Rect
4 | import androidx.compose.ui.geometry.Size
5 | import androidx.compose.ui.graphics.Matrix
6 | import androidx.compose.ui.graphics.Outline
7 | import androidx.compose.ui.graphics.Shape
8 | import androidx.compose.ui.graphics.asComposePath
9 | import androidx.compose.ui.unit.Density
10 | import androidx.compose.ui.unit.LayoutDirection
11 | import androidx.graphics.shapes.RoundedPolygon
12 | import androidx.graphics.shapes.toPath
13 | import kotlin.math.max
14 |
15 | class PolygonShape(private val polygon: RoundedPolygon) : Shape {
16 | // 4x4 xyzw matrix for transformations on the polygon.
17 | private val matrix = Matrix()
18 |
19 | override fun createOutline(
20 | size: Size,
21 | layoutDirection: LayoutDirection,
22 | density: Density,
23 | ): Outline {
24 | // Generate the cubic path for the polygon to draw at every frame
25 | // during the animation.
26 | val path = polygon.toPath().asComposePath()
27 |
28 | // The polygon generated by graphics lib is not necessarily
29 | // the same size as whatever box we are clipping/drawing in.
30 | // So, we need to find the scaling factor to scale the polygon
31 | // to fit the box without distorting it.
32 |
33 | // calculateBounds() returns the axis-aligned
34 | // bounding box (just the dimensions of the rectangle wrapping the
35 | // polygon) for the polygon.
36 |
37 | // Calculate the bounds of the polygon.
38 | val bounds =
39 | polygon.calculateBounds().let { Rect(it[0], it[1], it[2], it[3]) }
40 |
41 | // Take the bigger side out of the two
42 | // (it's usually almost same in this app as it's all circles
43 | // of radius 1f/2f)
44 | val maxDimension = max(bounds.width, bounds.height)
45 |
46 | // Calculate scaling factors
47 | val scaleX = size.width / maxDimension
48 | val scaleY = size.height / maxDimension
49 |
50 | matrix.scale(scaleX, scaleY)
51 |
52 | // The polygon is centered at origin (0, 0) so for a circle of
53 | // radius = 1, you can only see a quarter of the shape in view.
54 |
55 | // The bounds for a circle of radius 1f would be (-1f, -1f, 1f, 1f)
56 | // So, translating by (-bounds.left, -bounds.top) would move the polygon
57 | // to the center of the viewport
58 | matrix.translate(-bounds.left, -bounds.top)
59 |
60 | // Transform the generated path using this matrix.
61 | path.transform(matrix)
62 |
63 | // Generate outline based on the cubic path.
64 | return Outline.Generic(path)
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/dev/shadoe/delta/control/ControlViewModel.kt:
--------------------------------------------------------------------------------
1 | package dev.shadoe.delta.control
2 |
3 | import androidx.lifecycle.ViewModel
4 | import dagger.hilt.android.lifecycle.HiltViewModel
5 | import dev.shadoe.delta.api.ACLDevice
6 | import dev.shadoe.delta.data.softap.SoftApBlocklistManager
7 | import dev.shadoe.delta.data.softap.SoftApStateStore
8 | import javax.inject.Inject
9 | import kotlinx.coroutines.ExperimentalCoroutinesApi
10 | import kotlinx.coroutines.flow.mapLatest
11 |
12 | @HiltViewModel
13 | class ControlViewModel
14 | @Inject
15 | constructor(
16 | state: SoftApStateStore,
17 | private val softApBlocklistManager: SoftApBlocklistManager,
18 | ) : ViewModel() {
19 | @OptIn(ExperimentalCoroutinesApi::class)
20 | val enabledState = state.status.mapLatest { it.enabledState }
21 |
22 | @OptIn(ExperimentalCoroutinesApi::class)
23 | val connectedClients = state.status.mapLatest { it.tetheredClients }
24 |
25 | @OptIn(ExperimentalCoroutinesApi::class)
26 | val supportsBlocklist =
27 | state.status.mapLatest { it.capabilities.clientForceDisconnectSupported }
28 |
29 | val blockedClients = softApBlocklistManager.blockedClients
30 |
31 | fun blockDevices(devices: Iterable) =
32 | softApBlocklistManager.blockDevices(devices)
33 |
34 | fun unblockDevices(devices: Iterable) =
35 | softApBlocklistManager.unblockDevices(devices)
36 | }
37 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/dev/shadoe/delta/control/components/AppBarWithDebugAction.kt:
--------------------------------------------------------------------------------
1 | package dev.shadoe.delta.control.components
2 |
3 | import android.widget.Toast
4 | import androidx.compose.foundation.clickable
5 | import androidx.compose.material3.Text
6 | import androidx.compose.runtime.Composable
7 | import androidx.compose.runtime.getValue
8 | import androidx.compose.runtime.mutableIntStateOf
9 | import androidx.compose.runtime.remember
10 | import androidx.compose.runtime.setValue
11 | import androidx.compose.ui.Modifier
12 | import androidx.compose.ui.platform.LocalContext
13 | import androidx.compose.ui.res.stringResource
14 | import dev.shadoe.delta.R
15 |
16 | @Composable
17 | fun AppBarWithDebugAction(onNavigateToDebug: () -> Unit) {
18 | val context = LocalContext.current
19 | var debugTaps by remember { mutableIntStateOf(0) }
20 | val stringTriggeredDebugScreen =
21 | stringResource(R.string.triggered_debug_screen)
22 | Text(
23 | text = stringResource(R.string.app_name),
24 | modifier =
25 | Modifier.clickable {
26 | debugTaps += 1
27 | if (debugTaps % 5 == 0) {
28 | onNavigateToDebug()
29 | Toast.makeText(
30 | context,
31 | stringTriggeredDebugScreen,
32 | Toast.LENGTH_SHORT,
33 | )
34 | .show()
35 | }
36 | },
37 | )
38 | }
39 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/dev/shadoe/delta/control/components/BlockedClientComponent.kt:
--------------------------------------------------------------------------------
1 | package dev.shadoe.delta.control.components
2 |
3 | import androidx.compose.foundation.layout.Column
4 | import androidx.compose.foundation.layout.Row
5 | import androidx.compose.foundation.layout.padding
6 | import androidx.compose.material3.Button
7 | import androidx.compose.material3.Checkbox
8 | import androidx.compose.material3.Text
9 | import androidx.compose.runtime.Composable
10 | import androidx.compose.ui.Alignment
11 | import androidx.compose.ui.Modifier
12 | import androidx.compose.ui.res.stringResource
13 | import androidx.compose.ui.unit.dp
14 | import dev.shadoe.delta.R
15 | import dev.shadoe.delta.api.ACLDevice
16 |
17 | data class BlockedClientComponentState(
18 | val device: ACLDevice,
19 | val isEditingBlocklist: Boolean,
20 | val isChosenForUnblock: Boolean,
21 | )
22 |
23 | data class BlockedClientComponentActions(
24 | val startEditing: () -> Unit,
25 | val onEditToggled: (Boolean) -> Unit,
26 | )
27 |
28 | @Composable
29 | fun BlockedClientComponent(
30 | state: BlockedClientComponentState,
31 | actions: BlockedClientComponentActions,
32 | modifier: Modifier = Modifier,
33 | ) {
34 | Row(
35 | modifier =
36 | modifier.then(Modifier.padding(horizontal = 16.dp, vertical = 4.dp)),
37 | verticalAlignment = Alignment.CenterVertically,
38 | ) {
39 | if (state.isEditingBlocklist) {
40 | Checkbox(
41 | checked = state.isChosenForUnblock,
42 | onCheckedChange = actions.onEditToggled,
43 | )
44 | }
45 | Column(modifier = Modifier.weight(1f)) {
46 | Text(
47 | text =
48 | state.device.hostname ?: stringResource(R.string.no_client_hostname)
49 | )
50 | Text(text = state.device.macAddress.toString())
51 | }
52 | if (!state.isEditingBlocklist) {
53 | Button(onClick = actions.startEditing) {
54 | Text(text = stringResource(R.string.unblock_button))
55 | }
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/dev/shadoe/delta/control/components/ConnectedClientsList.kt:
--------------------------------------------------------------------------------
1 | package dev.shadoe.delta.control.components
2 |
3 | import androidx.compose.foundation.layout.Box
4 | import androidx.compose.foundation.layout.fillMaxWidth
5 | import androidx.compose.foundation.layout.padding
6 | import androidx.compose.foundation.lazy.LazyListScope
7 | import androidx.compose.foundation.lazy.items
8 | import androidx.compose.material3.Button
9 | import androidx.compose.material3.MaterialTheme
10 | import androidx.compose.material3.Text
11 | import androidx.compose.ui.Alignment
12 | import androidx.compose.ui.Modifier
13 | import androidx.compose.ui.res.stringResource
14 | import androidx.compose.ui.unit.dp
15 | import dev.shadoe.delta.R
16 | import dev.shadoe.delta.api.TetheredClient
17 |
18 | internal data class ConnectedClientsListState(
19 | val tetheredClients: List,
20 | val supportsBlocklist: Boolean,
21 | val devicesToBlock: Set,
22 | )
23 |
24 | internal data class ConnectedClientsListActions(
25 | val addToBlockList: (TetheredClient) -> Unit,
26 | val removeFromBlockList: (TetheredClient) -> Unit,
27 | val onBlockClients: () -> Unit,
28 | )
29 |
30 | internal fun LazyListScope.connectedClientsList(
31 | state: ConnectedClientsListState,
32 | actions: ConnectedClientsListActions,
33 | ) {
34 | item {
35 | Text(
36 | text = stringResource(R.string.connected_devices),
37 | color = MaterialTheme.colorScheme.onSurfaceVariant,
38 | style = MaterialTheme.typography.titleLarge,
39 | modifier = Modifier.padding(16.dp),
40 | )
41 | }
42 | if (state.tetheredClients.isEmpty()) {
43 | item {
44 | Box(
45 | modifier = Modifier.fillMaxWidth(),
46 | contentAlignment = Alignment.Center,
47 | ) {
48 | Text(text = stringResource(R.string.no_connected_devices))
49 | }
50 | }
51 | }
52 | items(state.tetheredClients) { client ->
53 | ConnectedClientComponent(
54 | state =
55 | ConnectedClientComponentState(
56 | client = client,
57 | isEditingBlocklist = state.devicesToBlock.isNotEmpty(),
58 | supportsBlocklist = state.supportsBlocklist,
59 | isChosenForBlocking = state.devicesToBlock.contains(client),
60 | ),
61 | actions =
62 | ConnectedClientComponentActions(
63 | startEditing = { actions.addToBlockList(client) },
64 | onEditToggled = { isChecked ->
65 | if (isChecked) {
66 | actions.addToBlockList(client)
67 | } else {
68 | actions.removeFromBlockList(client)
69 | }
70 | },
71 | ),
72 | )
73 | }
74 | if (state.devicesToBlock.isNotEmpty()) {
75 | item {
76 | Button(
77 | onClick = actions.onBlockClients,
78 | modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp),
79 | ) {
80 | Text(text = stringResource(R.string.block_button))
81 | }
82 | }
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/dev/shadoe/delta/control/components/PassphraseDisplay.kt:
--------------------------------------------------------------------------------
1 | package dev.shadoe.delta.control.components
2 |
3 | import androidx.compose.foundation.clickable
4 | import androidx.compose.foundation.layout.Row
5 | import androidx.compose.foundation.layout.padding
6 | import androidx.compose.foundation.layout.size
7 | import androidx.compose.material.icons.Icons
8 | import androidx.compose.material.icons.rounded.Visibility
9 | import androidx.compose.material.icons.rounded.VisibilityOff
10 | import androidx.compose.material3.Icon
11 | import androidx.compose.material3.LocalTextStyle
12 | import androidx.compose.material3.MaterialTheme
13 | import androidx.compose.material3.Text
14 | import androidx.compose.runtime.Composable
15 | import androidx.compose.runtime.mutableStateOf
16 | import androidx.compose.runtime.remember
17 | import androidx.compose.ui.Alignment
18 | import androidx.compose.ui.Modifier
19 | import androidx.compose.ui.platform.LocalDensity
20 | import androidx.compose.ui.res.stringResource
21 | import androidx.compose.ui.text.font.FontStyle
22 | import androidx.compose.ui.unit.dp
23 | import dev.shadoe.delta.R
24 |
25 | @Composable
26 | internal fun PassphraseDisplay(passphrase: String?) {
27 | val density = LocalDensity.current
28 | val isPassphraseShown = remember { mutableStateOf(false) }
29 |
30 | Row(
31 | modifier =
32 | Modifier.clickable { isPassphraseShown.value = !isPassphraseShown.value },
33 | verticalAlignment = Alignment.CenterVertically,
34 | ) {
35 | Text(
36 | text =
37 | if (passphrase == null) {
38 | stringResource(R.string.no_passphrase)
39 | } else if (isPassphraseShown.value) {
40 | passphrase
41 | } else {
42 | stringResource(R.string.passphrase_hidden)
43 | },
44 | style =
45 | MaterialTheme.typography.bodyMedium.copy(
46 | fontStyle =
47 | if (isPassphraseShown.value && passphrase != null) {
48 | FontStyle.Normal
49 | } else {
50 | FontStyle.Italic
51 | }
52 | ),
53 | )
54 | Icon(
55 | imageVector =
56 | if (isPassphraseShown.value) {
57 | Icons.Rounded.VisibilityOff
58 | } else {
59 | Icons.Rounded.Visibility
60 | },
61 | contentDescription =
62 | if (isPassphraseShown.value) {
63 | stringResource(R.string.passphrase_hide)
64 | } else {
65 | stringResource(R.string.passphrase_show)
66 | },
67 | modifier =
68 | Modifier.padding(start = 8.dp)
69 | .size(with(density) { LocalTextStyle.current.fontSize.toDp() }),
70 | )
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/dev/shadoe/delta/debug/DebugViewModel.kt:
--------------------------------------------------------------------------------
1 | package dev.shadoe.delta.debug
2 |
3 | import androidx.lifecycle.ViewModel
4 | import androidx.lifecycle.viewModelScope
5 | import dagger.hilt.android.lifecycle.HiltViewModel
6 | import dev.shadoe.delta.api.ConfigFlag
7 | import dev.shadoe.delta.data.FlagsRepository
8 | import dev.shadoe.delta.data.MacAddressCacheRepository
9 | import dev.shadoe.delta.data.softap.SoftApStateStore
10 | import javax.inject.Inject
11 | import kotlinx.coroutines.Dispatchers
12 | import kotlinx.coroutines.flow.MutableStateFlow
13 | import kotlinx.coroutines.launch
14 | import kotlinx.coroutines.withContext
15 |
16 | @HiltViewModel
17 | class DebugViewModel
18 | @Inject
19 | constructor(
20 | softApStateStore: SoftApStateStore,
21 | private val flagsRepository: FlagsRepository,
22 | private val macAddressCacheRepository: MacAddressCacheRepository,
23 | ) : ViewModel() {
24 | val config = softApStateStore.config
25 | val status = softApStateStore.status
26 | val flags = MutableStateFlow("")
27 | val macAddressCache = MutableStateFlow("")
28 |
29 | init {
30 | viewModelScope.launch {
31 | withContext(Dispatchers.IO) {
32 | flags.value =
33 | flagsRepository
34 | .debugDumpFlags()
35 | .map { ConfigFlag.entries[it.flag].name to it.value }
36 | .toString()
37 | macAddressCache.value =
38 | macAddressCacheRepository.debugDumpCache().toString()
39 | }
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/dev/shadoe/delta/design/AppTheme.kt:
--------------------------------------------------------------------------------
1 | package dev.shadoe.delta.design
2 |
3 | import android.os.Build
4 | import androidx.compose.foundation.isSystemInDarkTheme
5 | import androidx.compose.material3.MaterialTheme
6 | import androidx.compose.material3.darkColorScheme
7 | import androidx.compose.material3.dynamicDarkColorScheme
8 | import androidx.compose.material3.dynamicLightColorScheme
9 | import androidx.compose.material3.lightColorScheme
10 | import androidx.compose.runtime.Composable
11 | import androidx.compose.ui.platform.LocalContext
12 |
13 | @Composable
14 | fun AppTheme(content: @Composable () -> Unit) {
15 | val colorScheme =
16 | if (isSystemInDarkTheme()) {
17 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
18 | dynamicDarkColorScheme(LocalContext.current)
19 | } else {
20 | darkColorScheme()
21 | }
22 | } else {
23 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
24 | dynamicLightColorScheme(LocalContext.current)
25 | } else {
26 | lightColorScheme()
27 | }
28 | }
29 | MaterialTheme(colorScheme = colorScheme, typography = Typography.value) {
30 | content()
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/dev/shadoe/delta/design/Typography.kt:
--------------------------------------------------------------------------------
1 | package dev.shadoe.delta.design
2 |
3 | import androidx.compose.material3.Typography
4 |
5 | object Typography {
6 | private val defaultTypography = Typography()
7 |
8 | private val displayLarge =
9 | defaultTypography.displayLarge.copy(fontFamily = VariableFontFamily)
10 | private val displayMedium =
11 | defaultTypography.displayMedium.copy(fontFamily = VariableFontFamily)
12 | private val displaySmall =
13 | defaultTypography.displaySmall.copy(fontFamily = VariableFontFamily)
14 | private val headlineLarge =
15 | defaultTypography.headlineLarge.copy(fontFamily = VariableFontFamily)
16 | private val headlineMedium =
17 | defaultTypography.headlineMedium.copy(fontFamily = VariableFontFamily)
18 | private val headlineSmall =
19 | defaultTypography.headlineSmall.copy(fontFamily = VariableFontFamily)
20 | private val titleLarge =
21 | defaultTypography.titleLarge.copy(fontFamily = VariableFontFamily)
22 | private val titleMedium =
23 | defaultTypography.titleMedium.copy(fontFamily = VariableFontFamily)
24 | private val titleSmall =
25 | defaultTypography.titleSmall.copy(fontFamily = VariableFontFamily)
26 | private val bodyLarge =
27 | defaultTypography.bodyLarge.copy(fontFamily = VariableFontFamily)
28 | private val bodyMedium =
29 | defaultTypography.bodyMedium.copy(fontFamily = VariableFontFamily)
30 | private val bodySmall =
31 | defaultTypography.bodySmall.copy(fontFamily = VariableFontFamily)
32 | private val labelLarge =
33 | defaultTypography.labelLarge.copy(fontFamily = VariableFontFamily)
34 | private val labelMedium =
35 | defaultTypography.labelMedium.copy(fontFamily = VariableFontFamily)
36 | private val labelSmall =
37 | defaultTypography.labelSmall.copy(fontFamily = VariableFontFamily)
38 |
39 | val value =
40 | Typography(
41 | displayLarge = displayLarge,
42 | displayMedium = displayMedium,
43 | displaySmall = displaySmall,
44 | headlineLarge = headlineLarge,
45 | headlineMedium = headlineMedium,
46 | headlineSmall = headlineSmall,
47 | titleLarge = titleLarge,
48 | titleMedium = titleMedium,
49 | titleSmall = titleSmall,
50 | bodyLarge = bodyLarge,
51 | bodyMedium = bodyMedium,
52 | bodySmall = bodySmall,
53 | labelLarge = labelLarge,
54 | labelMedium = labelMedium,
55 | labelSmall = labelSmall,
56 | )
57 | }
58 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/dev/shadoe/delta/settings/UpdateResults.kt:
--------------------------------------------------------------------------------
1 | package dev.shadoe.delta.settings
2 |
3 | import dev.shadoe.delta.data.softap.validators.PassphraseValidator
4 | import dev.shadoe.delta.data.softap.validators.SsidValidator
5 |
6 | data class UpdateResults(
7 | val ssidResult: SsidValidator.Result = SsidValidator.Result.Success,
8 | val passphraseResult: PassphraseValidator.Result =
9 | PassphraseValidator.Result.Success,
10 | )
11 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/dev/shadoe/delta/settings/components/AutoShutdownField.kt:
--------------------------------------------------------------------------------
1 | package dev.shadoe.delta.settings.components
2 |
3 | import androidx.compose.foundation.layout.Column
4 | import androidx.compose.foundation.layout.Row
5 | import androidx.compose.foundation.layout.fillMaxWidth
6 | import androidx.compose.foundation.layout.padding
7 | import androidx.compose.material.icons.Icons
8 | import androidx.compose.material.icons.rounded.SettingsPower
9 | import androidx.compose.material3.Icon
10 | import androidx.compose.material3.MaterialTheme
11 | import androidx.compose.material3.Switch
12 | import androidx.compose.material3.Text
13 | import androidx.compose.runtime.Composable
14 | import androidx.compose.ui.Alignment
15 | import androidx.compose.ui.Modifier
16 | import androidx.compose.ui.res.stringResource
17 | import androidx.compose.ui.unit.dp
18 | import dev.shadoe.delta.R
19 |
20 | @Composable
21 | internal fun AutoShutdownField(
22 | isAutoShutdownEnabled: Boolean,
23 | onAutoShutdownChange: (Boolean) -> Unit,
24 | ) {
25 | Row(
26 | modifier = Modifier.fillMaxWidth().padding(vertical = 16.dp),
27 | verticalAlignment = Alignment.CenterVertically,
28 | ) {
29 | Icon(
30 | imageVector = Icons.Rounded.SettingsPower,
31 | contentDescription = stringResource(R.string.auto_shutdown_field_icon),
32 | )
33 | Column(
34 | modifier = Modifier.weight(1f).padding(horizontal = 16.dp),
35 | horizontalAlignment = Alignment.Start,
36 | ) {
37 | Text(
38 | text = stringResource(R.string.auto_shutdown_field_title),
39 | style = MaterialTheme.typography.titleLarge,
40 | )
41 | Text(
42 | text = stringResource(R.string.auto_shutdown_field_desc),
43 | style = MaterialTheme.typography.bodyMedium,
44 | color = MaterialTheme.colorScheme.onSurfaceVariant,
45 | )
46 | }
47 | Switch(
48 | checked = isAutoShutdownEnabled,
49 | onCheckedChange = { onAutoShutdownChange(it) },
50 | )
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/dev/shadoe/delta/settings/components/HiddenHotspotField.kt:
--------------------------------------------------------------------------------
1 | package dev.shadoe.delta.settings.components
2 |
3 | import androidx.compose.foundation.layout.Column
4 | import androidx.compose.foundation.layout.Row
5 | import androidx.compose.foundation.layout.fillMaxWidth
6 | import androidx.compose.foundation.layout.padding
7 | import androidx.compose.material.icons.Icons
8 | import androidx.compose.material.icons.rounded.WifiFind
9 | import androidx.compose.material3.Icon
10 | import androidx.compose.material3.MaterialTheme
11 | import androidx.compose.material3.Switch
12 | import androidx.compose.material3.Text
13 | import androidx.compose.runtime.Composable
14 | import androidx.compose.ui.Alignment
15 | import androidx.compose.ui.Modifier
16 | import androidx.compose.ui.res.stringResource
17 | import androidx.compose.ui.unit.dp
18 | import dev.shadoe.delta.R
19 |
20 | @Composable
21 | internal fun HiddenHotspotField(
22 | isHiddenHotspotEnabled: Boolean,
23 | onHiddenHotspotChange: (Boolean) -> Unit,
24 | ) {
25 | Row(
26 | modifier = Modifier.fillMaxWidth().padding(vertical = 16.dp),
27 | verticalAlignment = Alignment.CenterVertically,
28 | ) {
29 | Icon(
30 | imageVector = Icons.Rounded.WifiFind,
31 | contentDescription = stringResource(R.string.hidden_network_field_icon),
32 | )
33 | Column(
34 | modifier = Modifier.weight(1f).padding(horizontal = 16.dp),
35 | horizontalAlignment = Alignment.Start,
36 | ) {
37 | Text(
38 | text = stringResource(R.string.hidden_network_field_label),
39 | style = MaterialTheme.typography.titleLarge,
40 | )
41 | Text(
42 | text = stringResource(R.string.hidden_network_field_desc),
43 | style = MaterialTheme.typography.bodyMedium,
44 | color = MaterialTheme.colorScheme.onSurfaceVariant,
45 | )
46 | }
47 | Switch(
48 | checked = isHiddenHotspotEnabled,
49 | onCheckedChange = { onHiddenHotspotChange(it) },
50 | )
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/dev/shadoe/delta/settings/components/MaxClientLimitField.kt:
--------------------------------------------------------------------------------
1 | package dev.shadoe.delta.settings.components
2 |
3 | import androidx.compose.foundation.layout.Column
4 | import androidx.compose.foundation.layout.Row
5 | import androidx.compose.foundation.layout.fillMaxWidth
6 | import androidx.compose.foundation.layout.padding
7 | import androidx.compose.material.icons.Icons
8 | import androidx.compose.material.icons.rounded.DesktopAccessDisabled
9 | import androidx.compose.material3.Icon
10 | import androidx.compose.material3.MaterialTheme
11 | import androidx.compose.material3.Slider
12 | import androidx.compose.material3.SliderDefaults
13 | import androidx.compose.material3.Text
14 | import androidx.compose.runtime.Composable
15 | import androidx.compose.ui.Alignment
16 | import androidx.compose.ui.Modifier
17 | import androidx.compose.ui.res.stringResource
18 | import androidx.compose.ui.unit.dp
19 | import dev.shadoe.delta.R
20 |
21 | @Composable
22 | internal fun MaxClientLimitField(
23 | allowedLimit: Int,
24 | maxClient: Int,
25 | onMaxClientChange: (Float) -> Unit,
26 | ) {
27 | Row(
28 | modifier = Modifier.fillMaxWidth().padding(vertical = 16.dp),
29 | verticalAlignment = Alignment.CenterVertically,
30 | ) {
31 | Icon(
32 | imageVector = Icons.Rounded.DesktopAccessDisabled,
33 | contentDescription = stringResource(R.string.max_client_limit_field_icon),
34 | )
35 | Column(
36 | modifier = Modifier.weight(1f).padding(horizontal = 16.dp),
37 | horizontalAlignment = Alignment.Start,
38 | ) {
39 | Text(
40 | text = stringResource(R.string.max_client_limit_field_label),
41 | style = MaterialTheme.typography.titleLarge,
42 | )
43 | Slider(
44 | value = allowedLimit.toFloat(),
45 | onValueChange = { onMaxClientChange(it) },
46 | valueRange = 1f..maxClient.toFloat(),
47 | steps = maxClient.coerceIn(1, 5),
48 | colors =
49 | SliderDefaults.colors(
50 | thumbColor = MaterialTheme.colorScheme.secondary,
51 | activeTrackColor = MaterialTheme.colorScheme.secondary,
52 | inactiveTrackColor = MaterialTheme.colorScheme.secondaryContainer,
53 | ),
54 | )
55 | Text(
56 | text =
57 | stringResource(R.string.max_client_limit_field_count, allowedLimit),
58 | modifier = Modifier.align(alignment = Alignment.CenterHorizontally),
59 | color = MaterialTheme.colorScheme.onSurfaceVariant,
60 | )
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/dev/shadoe/delta/settings/components/PresetField.kt:
--------------------------------------------------------------------------------
1 | package dev.shadoe.delta.settings.components
2 |
3 | import androidx.compose.foundation.layout.Arrangement
4 | import androidx.compose.foundation.layout.Column
5 | import androidx.compose.foundation.layout.ExperimentalLayoutApi
6 | import androidx.compose.foundation.layout.FlowRow
7 | import androidx.compose.foundation.layout.Row
8 | import androidx.compose.foundation.layout.fillMaxWidth
9 | import androidx.compose.foundation.layout.padding
10 | import androidx.compose.material.icons.Icons
11 | import androidx.compose.material.icons.rounded.History
12 | import androidx.compose.material3.Button
13 | import androidx.compose.material3.Icon
14 | import androidx.compose.material3.MaterialTheme
15 | import androidx.compose.material3.Text
16 | import androidx.compose.runtime.Composable
17 | import androidx.compose.ui.Alignment
18 | import androidx.compose.ui.Modifier
19 | import androidx.compose.ui.res.stringResource
20 | import androidx.compose.ui.unit.dp
21 | import dev.shadoe.delta.R
22 |
23 | @Composable
24 | internal fun PresetField(onShowPresets: () -> Unit, onSaveConfig: () -> Unit) {
25 | Row(
26 | modifier = Modifier.fillMaxWidth().padding(vertical = 16.dp),
27 | verticalAlignment = Alignment.CenterVertically,
28 | ) {
29 | Icon(
30 | imageVector = Icons.Rounded.History,
31 | contentDescription = stringResource(R.string.presets_field_icon),
32 | )
33 | Column(
34 | modifier = Modifier.weight(1f).padding(horizontal = 16.dp),
35 | horizontalAlignment = Alignment.Start,
36 | ) {
37 | Text(
38 | text = stringResource(R.string.presets_setting),
39 | style = MaterialTheme.typography.titleLarge,
40 | )
41 | Text(
42 | text = stringResource(R.string.presets_field_desc),
43 | modifier = Modifier.padding(vertical = 4.dp),
44 | style = MaterialTheme.typography.bodyMedium,
45 | color = MaterialTheme.colorScheme.onSurfaceVariant,
46 | )
47 | @OptIn(ExperimentalLayoutApi::class)
48 | FlowRow(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
49 | Button(onClick = onShowPresets) {
50 | Text(text = stringResource(R.string.presets_show_button))
51 | }
52 | Button(modifier = Modifier, onClick = onSaveConfig) {
53 | Text(text = stringResource(R.string.presets_save_config_button))
54 | }
55 | }
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/dev/shadoe/delta/settings/components/PresetSaveDialog.kt:
--------------------------------------------------------------------------------
1 | package dev.shadoe.delta.settings.components
2 |
3 | import androidx.compose.material3.AlertDialog
4 | import androidx.compose.material3.Text
5 | import androidx.compose.material3.TextButton
6 | import androidx.compose.material3.TextField
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.runtime.getValue
9 | import androidx.compose.runtime.mutableStateOf
10 | import androidx.compose.runtime.remember
11 | import androidx.compose.runtime.setValue
12 | import androidx.compose.ui.res.stringResource
13 | import dev.shadoe.delta.R
14 |
15 | @Composable
16 | fun PresetSaveDialog(onSave: (String) -> Unit, onDismiss: () -> Unit) {
17 | var presetName by remember { mutableStateOf("") }
18 | AlertDialog(
19 | onDismissRequest = onDismiss,
20 | confirmButton = {
21 | TextButton(
22 | onClick = {
23 | if (presetName.isNotEmpty()) {
24 | onSave(presetName)
25 | }
26 | }
27 | ) {
28 | Text(stringResource(R.string.save_button))
29 | }
30 | },
31 | dismissButton = {
32 | TextButton(onClick = onDismiss) {
33 | Text(stringResource(R.string.cancel_button))
34 | }
35 | },
36 | title = { Text(stringResource(R.string.preset_save_title)) },
37 | text = {
38 | TextField(
39 | value = presetName,
40 | onValueChange = { presetName = it },
41 | singleLine = true,
42 | supportingText = {
43 | if (presetName.isEmpty()) {
44 | Text(stringResource(R.string.preset_save_empty))
45 | }
46 | },
47 | isError = presetName.isEmpty(),
48 | )
49 | },
50 | )
51 | }
52 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/dev/shadoe/delta/settings/components/SsidField.kt:
--------------------------------------------------------------------------------
1 | package dev.shadoe.delta.settings.components
2 |
3 | import androidx.compose.foundation.layout.Column
4 | import androidx.compose.foundation.layout.Row
5 | import androidx.compose.foundation.layout.fillMaxWidth
6 | import androidx.compose.foundation.layout.padding
7 | import androidx.compose.foundation.text.KeyboardOptions
8 | import androidx.compose.material.icons.Icons
9 | import androidx.compose.material.icons.outlined.Error
10 | import androidx.compose.material.icons.rounded.Wifi
11 | import androidx.compose.material3.Icon
12 | import androidx.compose.material3.MaterialTheme
13 | import androidx.compose.material3.OutlinedTextField
14 | import androidx.compose.material3.Text
15 | import androidx.compose.runtime.Composable
16 | import androidx.compose.ui.Alignment
17 | import androidx.compose.ui.Modifier
18 | import androidx.compose.ui.res.stringResource
19 | import androidx.compose.ui.text.input.ImeAction
20 | import androidx.compose.ui.text.input.KeyboardCapitalization
21 | import androidx.compose.ui.text.input.KeyboardType
22 | import androidx.compose.ui.unit.dp
23 | import dev.shadoe.delta.R
24 | import dev.shadoe.delta.data.softap.validators.SsidValidator
25 |
26 | @Composable
27 | internal fun SsidField(
28 | ssid: String,
29 | onSsidChange: (String) -> Unit,
30 | currentResult: SsidValidator.Result,
31 | ) {
32 | Row(
33 | verticalAlignment = Alignment.CenterVertically,
34 | modifier = Modifier.padding(vertical = 8.dp),
35 | ) {
36 | Icon(
37 | imageVector = Icons.Rounded.Wifi,
38 | contentDescription = stringResource(R.string.ssid_field_icon),
39 | )
40 | Column(modifier = Modifier.padding(horizontal = 16.dp).fillMaxWidth()) {
41 | OutlinedTextField(
42 | value = ssid,
43 | onValueChange = { onSsidChange(it) },
44 | singleLine = true,
45 | keyboardOptions =
46 | KeyboardOptions(
47 | capitalization = KeyboardCapitalization.None,
48 | autoCorrectEnabled = false,
49 | keyboardType = KeyboardType.Text,
50 | imeAction = ImeAction.Next,
51 | ),
52 | modifier = Modifier.fillMaxWidth(),
53 | label = { Text(text = stringResource(R.string.ssid_field_label)) },
54 | )
55 | if (currentResult !is SsidValidator.Result.Success) {
56 | Row(modifier = Modifier.padding(top = 8.dp)) {
57 | Icon(
58 | imageVector = Icons.Outlined.Error,
59 | contentDescription = stringResource(R.string.error_icon),
60 | tint = MaterialTheme.colorScheme.error,
61 | )
62 | Text(
63 | text =
64 | when (currentResult) {
65 | is SsidValidator.Result.SsidTooShort ->
66 | stringResource(R.string.ssid_too_short_warning)
67 | is SsidValidator.Result.SsidTooLong ->
68 | stringResource(R.string.ssid_too_long_warning)
69 | else -> ""
70 | },
71 | color = MaterialTheme.colorScheme.error,
72 | modifier = Modifier.padding(start = 4.dp),
73 | )
74 | }
75 | }
76 | }
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/dev/shadoe/delta/settings/components/TaskerIntegrationField.kt:
--------------------------------------------------------------------------------
1 | package dev.shadoe.delta.settings.components
2 |
3 | import androidx.compose.foundation.layout.Column
4 | import androidx.compose.foundation.layout.Row
5 | import androidx.compose.foundation.layout.fillMaxWidth
6 | import androidx.compose.foundation.layout.padding
7 | import androidx.compose.material.icons.Icons
8 | import androidx.compose.material.icons.rounded.Autorenew
9 | import androidx.compose.material3.Button
10 | import androidx.compose.material3.Icon
11 | import androidx.compose.material3.MaterialTheme
12 | import androidx.compose.material3.Switch
13 | import androidx.compose.material3.Text
14 | import androidx.compose.runtime.Composable
15 | import androidx.compose.ui.Alignment
16 | import androidx.compose.ui.Modifier
17 | import androidx.compose.ui.res.stringResource
18 | import androidx.compose.ui.unit.dp
19 | import dev.shadoe.delta.R
20 |
21 | @Composable
22 | internal fun TaskerIntegrationField(
23 | isTaskerIntegrationEnabled: Boolean,
24 | onTaskerIntegrationChange: (Boolean) -> Unit,
25 | onShowTaskerIntegrationInfo: () -> Unit,
26 | ) {
27 | Row(
28 | modifier = Modifier.fillMaxWidth().padding(vertical = 16.dp),
29 | verticalAlignment = Alignment.CenterVertically,
30 | ) {
31 | Icon(
32 | imageVector = Icons.Rounded.Autorenew,
33 | contentDescription =
34 | stringResource(R.string.tasker_integration_field_icon),
35 | )
36 | Column(
37 | modifier = Modifier.weight(1f).padding(horizontal = 16.dp),
38 | horizontalAlignment = Alignment.Start,
39 | ) {
40 | Text(
41 | text = stringResource(R.string.tasker_integration_field_label),
42 | style = MaterialTheme.typography.titleLarge,
43 | )
44 | Text(
45 | text = stringResource(R.string.tasker_integration_field_desc),
46 | style = MaterialTheme.typography.bodyMedium,
47 | color = MaterialTheme.colorScheme.onSurfaceVariant,
48 | )
49 | Button(onClick = onShowTaskerIntegrationInfo) {
50 | Text(text = stringResource(R.string.tasker_integration_info_button))
51 | }
52 | }
53 | Switch(
54 | checked = isTaskerIntegrationEnabled,
55 | onCheckedChange = onTaskerIntegrationChange,
56 | )
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/dev/shadoe/delta/setup/ShizukuSetupViewModel.kt:
--------------------------------------------------------------------------------
1 | package dev.shadoe.delta.setup
2 |
3 | import androidx.lifecycle.ViewModel
4 | import dagger.hilt.android.lifecycle.HiltViewModel
5 | import dev.shadoe.delta.data.shizuku.ShizukuRepository
6 | import javax.inject.Inject
7 |
8 | @HiltViewModel
9 | class ShizukuSetupViewModel
10 | @Inject
11 | constructor(private val shizukuRepository: ShizukuRepository) : ViewModel() {
12 | val shizukuState = shizukuRepository.shizukuState
13 |
14 | fun requestPermission() {
15 | shizukuRepository.requestPermission()
16 | }
17 |
18 | companion object {
19 | const val SHIZUKU_APP_ID = ShizukuRepository.SHIZUKU_APP_ID
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/dev/shadoe/delta/setup/components/ShizukuConnected.kt:
--------------------------------------------------------------------------------
1 | package dev.shadoe.delta.setup.components
2 |
3 | import androidx.compose.foundation.layout.Arrangement
4 | import androidx.compose.foundation.layout.Box
5 | import androidx.compose.foundation.layout.Column
6 | import androidx.compose.foundation.layout.fillMaxWidth
7 | import androidx.compose.foundation.layout.padding
8 | import androidx.compose.material3.Button
9 | import androidx.compose.material3.MaterialTheme
10 | import androidx.compose.material3.Text
11 | import androidx.compose.runtime.Composable
12 | import androidx.compose.ui.Alignment
13 | import androidx.compose.ui.Modifier
14 | import androidx.compose.ui.res.stringResource
15 | import androidx.compose.ui.unit.dp
16 | import dev.shadoe.delta.R
17 |
18 | @Composable
19 | internal fun ShizukuConnected(
20 | continueAction: () -> Unit,
21 | modifier: Modifier = Modifier,
22 | ) {
23 | Column(modifier = modifier) {
24 | Column(
25 | modifier = Modifier.weight(0.5f),
26 | verticalArrangement = Arrangement.Center,
27 | ) {
28 | Text(
29 | text = stringResource(R.string.shizuku_setup_title),
30 | style = MaterialTheme.typography.displaySmall,
31 | )
32 | Text(
33 | text = stringResource(R.string.shizuku_setup_connected),
34 | style = MaterialTheme.typography.titleLarge,
35 | )
36 | }
37 | Box(
38 | modifier = Modifier.weight(0.7f).fillMaxWidth(),
39 | contentAlignment = Alignment.CenterEnd,
40 | ) {
41 | Button(onClick = continueAction) {
42 | Text(text = stringResource(R.string.setup_continue_button))
43 | }
44 | }
45 | Text(
46 | text = stringResource(R.string.shizuku_connected_desc),
47 | style = MaterialTheme.typography.bodySmall,
48 | color = MaterialTheme.colorScheme.onSurfaceVariant,
49 | modifier = Modifier.padding(bottom = 32.dp),
50 | )
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/dev/shadoe/delta/setup/components/ShizukuNotConnected.kt:
--------------------------------------------------------------------------------
1 | package dev.shadoe.delta.setup.components
2 |
3 | import androidx.compose.foundation.layout.Arrangement
4 | import androidx.compose.foundation.layout.Box
5 | import androidx.compose.foundation.layout.Column
6 | import androidx.compose.foundation.layout.fillMaxWidth
7 | import androidx.compose.foundation.layout.padding
8 | import androidx.compose.material3.Button
9 | import androidx.compose.material3.MaterialTheme
10 | import androidx.compose.material3.Text
11 | import androidx.compose.runtime.Composable
12 | import androidx.compose.ui.Alignment
13 | import androidx.compose.ui.Modifier
14 | import androidx.compose.ui.res.stringResource
15 | import androidx.compose.ui.unit.dp
16 | import dev.shadoe.delta.R
17 |
18 | @Composable
19 | internal fun ShizukuNotConnected(
20 | onRequestPermission: () -> Unit,
21 | modifier: Modifier = Modifier,
22 | ) {
23 | Column(modifier = modifier) {
24 | Column(
25 | modifier = Modifier.weight(0.5f),
26 | verticalArrangement = Arrangement.Center,
27 | ) {
28 | Text(
29 | text = stringResource(R.string.shizuku_setup_title),
30 | style = MaterialTheme.typography.displaySmall,
31 | )
32 | Text(
33 | text = stringResource(R.string.shizuku_setup_not_connected),
34 | style = MaterialTheme.typography.titleLarge,
35 | )
36 | }
37 | Box(
38 | modifier = Modifier.weight(0.7f).fillMaxWidth(),
39 | contentAlignment = Alignment.CenterEnd,
40 | ) {
41 | Button(onClick = onRequestPermission) {
42 | Text(text = stringResource(R.string.shizuku_grant_access))
43 | }
44 | }
45 | Text(
46 | text = stringResource(R.string.setup_note),
47 | style = MaterialTheme.typography.bodySmall,
48 | color = MaterialTheme.colorScheme.onSurfaceVariant,
49 | modifier = Modifier.padding(bottom = 32.dp),
50 | )
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/dev/shadoe/delta/setup/components/ShizukuNotRunning.kt:
--------------------------------------------------------------------------------
1 | package dev.shadoe.delta.setup.components
2 |
3 | import android.content.Intent
4 | import androidx.compose.foundation.layout.Arrangement
5 | import androidx.compose.foundation.layout.Box
6 | import androidx.compose.foundation.layout.Column
7 | import androidx.compose.foundation.layout.fillMaxWidth
8 | import androidx.compose.foundation.layout.padding
9 | import androidx.compose.material3.Button
10 | import androidx.compose.material3.MaterialTheme
11 | import androidx.compose.material3.Text
12 | import androidx.compose.runtime.Composable
13 | import androidx.compose.ui.Alignment
14 | import androidx.compose.ui.Modifier
15 | import androidx.compose.ui.platform.LocalConfiguration
16 | import androidx.compose.ui.platform.LocalContext
17 | import androidx.compose.ui.res.stringResource
18 | import androidx.compose.ui.unit.dp
19 | import dev.shadoe.delta.R
20 | import dev.shadoe.delta.setup.ShizukuSetupViewModel
21 |
22 | @Composable
23 | internal fun ShizukuNotRunning(modifier: Modifier = Modifier) {
24 | val context = LocalContext.current
25 | val isBigScreen = LocalConfiguration.current.screenWidthDp >= 700
26 | Column(modifier = modifier) {
27 | Column(
28 | modifier = Modifier.weight(0.5f),
29 | verticalArrangement = Arrangement.Center,
30 | ) {
31 | Text(
32 | text = stringResource(R.string.shizuku_setup_title),
33 | style = MaterialTheme.typography.displaySmall,
34 | )
35 | Text(
36 | text = stringResource(R.string.shizuku_setup_not_running),
37 | style = MaterialTheme.typography.titleLarge,
38 | )
39 | }
40 | Box(
41 | modifier = Modifier.weight(0.7f).fillMaxWidth(),
42 | contentAlignment = Alignment.CenterEnd,
43 | ) {
44 | Button(
45 | onClick = {
46 | with(context) {
47 | startActivity(
48 | packageManager
49 | .getLaunchIntentForPackage(ShizukuSetupViewModel.SHIZUKU_APP_ID)
50 | ?.apply {
51 | if (isBigScreen) {
52 | addFlags(
53 | Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT or
54 | Intent.FLAG_ACTIVITY_NEW_TASK
55 | )
56 | }
57 | }
58 | )
59 | }
60 | }
61 | ) {
62 | Text(text = stringResource(R.string.shizuku_start))
63 | }
64 | }
65 | Text(
66 | text = stringResource(R.string.setup_note),
67 | style = MaterialTheme.typography.bodySmall,
68 | color = MaterialTheme.colorScheme.onSurfaceVariant,
69 | modifier = Modifier.padding(bottom = 32.dp),
70 | )
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
6 |
10 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_qs_tile.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/shizuku_logo_mono.xml:
--------------------------------------------------------------------------------
1 |
6 |
10 |
15 |
18 |
--------------------------------------------------------------------------------
/app/src/main/res/font-v26/nunitosans_italic_variable.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/supershadoe/delta/2cc85f133800794b6056a77b430367e0f1a973bf/app/src/main/res/font-v26/nunitosans_italic_variable.ttf
--------------------------------------------------------------------------------
/app/src/main/res/font-v26/nunitosans_variable.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/supershadoe/delta/2cc85f133800794b6056a77b430367e0f1a973bf/app/src/main/res/font-v26/nunitosans_variable.ttf
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/resources.properties:
--------------------------------------------------------------------------------
1 | unqualifiedResLocale=en-US
2 |
--------------------------------------------------------------------------------
/app/src/main/res/values-af-rZA/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/app/src/main/res/values-ar-rSA/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/app/src/main/res/values-ca-rES/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/app/src/main/res/values-cs-rCZ/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/app/src/main/res/values-da-rDK/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/app/src/main/res/values-el-rGR/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/app/src/main/res/values-fi-rFI/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/app/src/main/res/values-fr-rFR/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/app/src/main/res/values-hu-rHU/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/app/src/main/res/values-it-rIT/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/app/src/main/res/values-iw-rIL/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/app/src/main/res/values-ko-rKR/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/app/src/main/res/values-night/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values-nl-rNL/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/app/src/main/res/values-no-rNO/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/app/src/main/res/values-pl-rPL/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/app/src/main/res/values-pt-rBR/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/app/src/main/res/values-pt-rPT/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/app/src/main/res/values-ro-rRO/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/app/src/main/res/values-sv-rSE/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/app/src/main/res/values-uk-rUA/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/app/src/main/res/values-vi-rVN/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/app/src/main/res/values-zh-rCN/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/app/src/main/res/values-zh-rTW/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFBB86FC
4 | #FF6200EE
5 | #FF3700B3
6 | #FF03DAC5
7 | #FF018786
8 | #FF000000
9 | #FFFFFFFF
10 | #FFE79AF3
11 |
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/backup_rules.xml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/data_extraction_rules.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
12 |
13 |
19 |
--------------------------------------------------------------------------------
/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.android.application) apply false
3 | alias(libs.plugins.android.library) apply false
4 | alias(libs.plugins.compose.compiler) apply false
5 | alias(libs.plugins.hilt) apply false
6 | alias(libs.plugins.kotlin.android) apply false
7 | alias(libs.plugins.kotlin.serialization) apply false
8 | alias(libs.plugins.ksp) apply false
9 | alias(libs.plugins.refine) apply false
10 | id("delta.lint.kts")
11 | id("delta.lint.text")
12 | }
13 |
--------------------------------------------------------------------------------
/crowdin.yml:
--------------------------------------------------------------------------------
1 | "project_id_env": "CROWDIN_PROJECT_ID"
2 | "api_token_env": "CROWDIN_PERSONAL_TOKEN"
3 | "base_path": "./app/src/main/res"
4 |
5 | "preserve_hierarchy": true
6 |
7 | "files": [
8 | {
9 | "source": "values/strings.xml",
10 | "translation": "values-%android_code%/strings.xml"
11 | }
12 | ]
13 |
--------------------------------------------------------------------------------
/data/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/data/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.android.library)
3 | alias(libs.plugins.kotlin.android)
4 | alias(libs.plugins.kotlin.serialization)
5 | alias(libs.plugins.refine)
6 | alias(libs.plugins.ksp)
7 | alias(libs.plugins.hilt)
8 | alias(libs.plugins.room)
9 | id("delta.lint.kotlin")
10 | id("delta.lint.kts")
11 | }
12 |
13 | android {
14 | namespace = "dev.shadoe.delta.data"
15 | compileSdk = 36
16 |
17 | defaultConfig {
18 | minSdk = 30
19 | consumerProguardFiles("consumer-rules.pro")
20 | }
21 |
22 | buildTypes {
23 | release {
24 | isMinifyEnabled = false
25 | proguardFiles(
26 | getDefaultProguardFile("proguard-android-optimize.txt"),
27 | "proguard-rules.pro",
28 | )
29 | }
30 | }
31 |
32 | compileOptions {
33 | sourceCompatibility = JavaVersion.VERSION_21
34 | targetCompatibility = JavaVersion.VERSION_21
35 | }
36 |
37 | kotlinOptions { jvmTarget = "21" }
38 | }
39 |
40 | room { schemaDirectory("$projectDir/schema") }
41 |
42 | dependencies {
43 | compileOnly(project(path = ":system-api-stubs"))
44 | implementation(project(path = ":api"))
45 | implementation(libs.shizuku.api)
46 | implementation(libs.hilt.android)
47 | implementation(libs.kotlinx.coroutines.android)
48 | implementation(libs.kotlinx.serialization.json)
49 | implementation(libs.refine.runtime)
50 | implementation(libs.room.runtime)
51 | implementation(libs.room.ktx)
52 | implementation(libs.shizuku.api)
53 | implementation(libs.shizuku.provider)
54 | ksp(libs.hilt.compiler)
55 | ksp(libs.room.compiler)
56 | testImplementation(libs.kotlin.test.junit)
57 | testImplementation(libs.mockk)
58 | testImplementation(libs.kotlinx.coroutines.test)
59 | testImplementation(project(path = ":system-api-stubs"))
60 | }
61 |
--------------------------------------------------------------------------------
/data/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/supershadoe/delta/2cc85f133800794b6056a77b430367e0f1a973bf/data/consumer-rules.pro
--------------------------------------------------------------------------------
/data/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/data/schema/dev.shadoe.delta.data.database.ConfigDB/1.json:
--------------------------------------------------------------------------------
1 | {
2 | "formatVersion": 1,
3 | "database": {
4 | "version": 1,
5 | "identityHash": "21c2c119df4bf4fdb2e904952095f3d6",
6 | "entities": [
7 | {
8 | "tableName": "Flag",
9 | "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`flag` INTEGER NOT NULL, `value` INTEGER NOT NULL, PRIMARY KEY(`flag`))",
10 | "fields": [
11 | {
12 | "fieldPath": "flag",
13 | "columnName": "flag",
14 | "affinity": "INTEGER",
15 | "notNull": true
16 | },
17 | {
18 | "fieldPath": "value",
19 | "columnName": "value",
20 | "affinity": "INTEGER",
21 | "notNull": true
22 | }
23 | ],
24 | "primaryKey": {
25 | "autoGenerate": false,
26 | "columnNames": [
27 | "flag"
28 | ]
29 | },
30 | "indices": [],
31 | "foreignKeys": []
32 | },
33 | {
34 | "tableName": "HostInfo",
35 | "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`macAddress` TEXT NOT NULL, `hostname` TEXT NOT NULL, PRIMARY KEY(`macAddress`))",
36 | "fields": [
37 | {
38 | "fieldPath": "macAddress",
39 | "columnName": "macAddress",
40 | "affinity": "TEXT",
41 | "notNull": true
42 | },
43 | {
44 | "fieldPath": "hostname",
45 | "columnName": "hostname",
46 | "affinity": "TEXT",
47 | "notNull": true
48 | }
49 | ],
50 | "primaryKey": {
51 | "autoGenerate": false,
52 | "columnNames": [
53 | "macAddress"
54 | ]
55 | },
56 | "indices": [],
57 | "foreignKeys": []
58 | },
59 | {
60 | "tableName": "Preset",
61 | "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
62 | "fields": [
63 | {
64 | "fieldPath": "id",
65 | "columnName": "id",
66 | "affinity": "INTEGER",
67 | "notNull": true
68 | }
69 | ],
70 | "primaryKey": {
71 | "autoGenerate": true,
72 | "columnNames": [
73 | "id"
74 | ]
75 | },
76 | "indices": [],
77 | "foreignKeys": []
78 | }
79 | ],
80 | "views": [],
81 | "setupQueries": [
82 | "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
83 | "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '21c2c119df4bf4fdb2e904952095f3d6')"
84 | ]
85 | }
86 | }
--------------------------------------------------------------------------------
/data/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/data/src/main/kotlin/dev/shadoe/delta/data/FlagsRepository.kt:
--------------------------------------------------------------------------------
1 | package dev.shadoe.delta.data
2 |
3 | import dev.shadoe.delta.api.ConfigFlag
4 | import dev.shadoe.delta.data.database.dao.FlagsDao
5 | import dev.shadoe.delta.data.database.models.Flag
6 | import javax.inject.Inject
7 |
8 | class FlagsRepository @Inject constructor(private val flagsDao: FlagsDao) {
9 | suspend fun isFirstRun() =
10 | flagsDao.getFlag(ConfigFlag.NOT_FIRST_RUN.ordinal) != true
11 |
12 | suspend fun setNotFirstRun() =
13 | flagsDao.setFlag(
14 | Flag(flag = ConfigFlag.NOT_FIRST_RUN.ordinal, value = true)
15 | )
16 |
17 | suspend fun isInsecureReceiverEnabled() =
18 | flagsDao.getFlag(ConfigFlag.INSECURE_RECEIVER_ENABLED.ordinal) == true
19 |
20 | suspend fun setInsecureReceiverStatus(enabled: Boolean) =
21 | flagsDao.setFlag(
22 | Flag(flag = ConfigFlag.INSECURE_RECEIVER_ENABLED.ordinal, value = enabled)
23 | )
24 |
25 | suspend fun debugDumpFlags() = flagsDao.dump()
26 | }
27 |
--------------------------------------------------------------------------------
/data/src/main/kotlin/dev/shadoe/delta/data/MacAddressCacheRepository.kt:
--------------------------------------------------------------------------------
1 | package dev.shadoe.delta.data
2 |
3 | import dev.shadoe.delta.api.MacAddress
4 | import dev.shadoe.delta.api.TetheredClient
5 | import dev.shadoe.delta.data.database.dao.HostInfoDao
6 | import dev.shadoe.delta.data.database.models.HostInfo
7 | import javax.inject.Inject
8 |
9 | class MacAddressCacheRepository
10 | @Inject
11 | constructor(private val hostInfoDao: HostInfoDao) {
12 | internal suspend fun updateHostInfoInCache(clients: List) {
13 | hostInfoDao.addHostInfo(
14 | *clients
15 | .filter { it.hostname != null }
16 | .map { HostInfo(macAddress = it.macAddress, hostname = it.hostname!!) }
17 | .toTypedArray()
18 | )
19 | }
20 |
21 | suspend fun getHostnamesFromCache(macAddressList: List) =
22 | hostInfoDao.resolveMacAddressesToHostNames(macAddressList).associate {
23 | it.macAddress to it.hostname
24 | }
25 |
26 | suspend fun debugDumpCache() = hostInfoDao.dump()
27 | }
28 |
--------------------------------------------------------------------------------
/data/src/main/kotlin/dev/shadoe/delta/data/database/ConfigDB.kt:
--------------------------------------------------------------------------------
1 | package dev.shadoe.delta.data.database
2 |
3 | import androidx.room.AutoMigration
4 | import androidx.room.Database
5 | import androidx.room.RoomDatabase
6 | import dev.shadoe.delta.data.database.dao.FlagsDao
7 | import dev.shadoe.delta.data.database.dao.HostInfoDao
8 | import dev.shadoe.delta.data.database.dao.PresetDao
9 | import dev.shadoe.delta.data.database.models.Flag
10 | import dev.shadoe.delta.data.database.models.HostInfo
11 | import dev.shadoe.delta.data.database.models.Preset
12 |
13 | @Database(
14 | entities = [Flag::class, HostInfo::class, Preset::class],
15 | autoMigrations = [AutoMigration(from = 1, to = 2)],
16 | version = 2,
17 | )
18 | abstract class ConfigDB : RoomDatabase() {
19 | abstract fun flagsDao(): FlagsDao
20 |
21 | abstract fun hostInfoDao(): HostInfoDao
22 |
23 | abstract fun presetDao(): PresetDao
24 | }
25 |
--------------------------------------------------------------------------------
/data/src/main/kotlin/dev/shadoe/delta/data/database/converters/MacAddressConverter.kt:
--------------------------------------------------------------------------------
1 | package dev.shadoe.delta.data.database.converters
2 |
3 | import androidx.room.TypeConverter
4 | import dev.shadoe.delta.api.MacAddress
5 |
6 | class MacAddressConverter {
7 | @TypeConverter
8 | fun macAddressListToString(macAddressList: List) =
9 | macAddressList.joinToString(",") { macAddressToString(it) }
10 |
11 | @TypeConverter
12 | fun stringToMacAddressList(serialized: String) =
13 | serialized.split(",").map { stringToMacAddress(it) }
14 |
15 | @TypeConverter
16 | fun macAddressToString(macAddress: MacAddress) = macAddress.macAddress
17 |
18 | @TypeConverter
19 | fun stringToMacAddress(serialized: String) = MacAddress(serialized)
20 | }
21 |
--------------------------------------------------------------------------------
/data/src/main/kotlin/dev/shadoe/delta/data/database/dao/FlagsDao.kt:
--------------------------------------------------------------------------------
1 | package dev.shadoe.delta.data.database.dao
2 |
3 | import androidx.room.Dao
4 | import androidx.room.Query
5 | import androidx.room.Upsert
6 | import dev.shadoe.delta.data.database.models.Flag
7 |
8 | @Dao
9 | interface FlagsDao {
10 | @Query("SELECT value FROM Flag WHERE flag = :flag LIMIT 1")
11 | suspend fun getFlag(flag: Int): Boolean?
12 |
13 | @Query("SELECT * FROM Flag") suspend fun dump(): List
14 |
15 | @Upsert suspend fun setFlag(flag: Flag)
16 | }
17 |
--------------------------------------------------------------------------------
/data/src/main/kotlin/dev/shadoe/delta/data/database/dao/HostInfoDao.kt:
--------------------------------------------------------------------------------
1 | package dev.shadoe.delta.data.database.dao
2 |
3 | import androidx.room.Dao
4 | import androidx.room.Query
5 | import androidx.room.TypeConverters
6 | import androidx.room.Upsert
7 | import dev.shadoe.delta.api.MacAddress
8 | import dev.shadoe.delta.data.database.converters.MacAddressConverter
9 | import dev.shadoe.delta.data.database.models.HostInfo
10 |
11 | @TypeConverters(MacAddressConverter::class)
12 | @Dao
13 | interface HostInfoDao {
14 | @Query("SELECT * FROM HostInfo WHERE macAddress IN (:macAddress)")
15 | suspend fun resolveMacAddressesToHostNames(
16 | macAddress: List
17 | ): List
18 |
19 | @Query("SELECT * FROM HostInfo") suspend fun dump(): List
20 |
21 | @Upsert suspend fun addHostInfo(vararg hostInfo: HostInfo)
22 | }
23 |
--------------------------------------------------------------------------------
/data/src/main/kotlin/dev/shadoe/delta/data/database/dao/PresetDao.kt:
--------------------------------------------------------------------------------
1 | package dev.shadoe.delta.data.database.dao
2 |
3 | import androidx.room.Dao
4 | import androidx.room.Delete
5 | import androidx.room.Insert
6 | import androidx.room.Query
7 | import dev.shadoe.delta.data.database.models.Preset
8 | import kotlinx.coroutines.flow.Flow
9 |
10 | @Dao
11 | interface PresetDao {
12 | @Query("SELECT * FROM Preset") fun observePresets(): Flow>
13 |
14 | @Insert suspend fun insert(preset: Preset)
15 |
16 | @Delete suspend fun delete(preset: Preset)
17 | }
18 |
--------------------------------------------------------------------------------
/data/src/main/kotlin/dev/shadoe/delta/data/database/models/Flag.kt:
--------------------------------------------------------------------------------
1 | package dev.shadoe.delta.data.database.models
2 |
3 | import androidx.room.Entity
4 | import androidx.room.PrimaryKey
5 |
6 | @Entity
7 | data class Flag(
8 | @PrimaryKey(autoGenerate = false) val flag: Int,
9 | val value: Boolean,
10 | )
11 |
--------------------------------------------------------------------------------
/data/src/main/kotlin/dev/shadoe/delta/data/database/models/HostInfo.kt:
--------------------------------------------------------------------------------
1 | package dev.shadoe.delta.data.database.models
2 |
3 | import androidx.room.Entity
4 | import androidx.room.PrimaryKey
5 | import androidx.room.TypeConverters
6 | import dev.shadoe.delta.api.MacAddress
7 | import dev.shadoe.delta.data.database.converters.MacAddressConverter
8 |
9 | @TypeConverters(MacAddressConverter::class)
10 | @Entity
11 | data class HostInfo(
12 | @PrimaryKey(autoGenerate = false) val macAddress: MacAddress,
13 | val hostname: String,
14 | )
15 |
--------------------------------------------------------------------------------
/data/src/main/kotlin/dev/shadoe/delta/data/database/models/Preset.kt:
--------------------------------------------------------------------------------
1 | package dev.shadoe.delta.data.database.models
2 |
3 | import androidx.room.ColumnInfo
4 | import androidx.room.Entity
5 | import androidx.room.PrimaryKey
6 | import androidx.room.TypeConverters
7 | import dev.shadoe.delta.api.MacAddress
8 | import dev.shadoe.delta.api.SoftApAutoShutdownTimeout
9 | import dev.shadoe.delta.api.SoftApRandomizationSetting
10 | import dev.shadoe.delta.api.SoftApSecurityType
11 | import dev.shadoe.delta.api.SoftApSpeedType
12 | import dev.shadoe.delta.data.database.converters.MacAddressConverter
13 |
14 | @TypeConverters(MacAddressConverter::class)
15 | @Entity
16 | data class Preset(
17 | @PrimaryKey(autoGenerate = true) val id: Int = 0,
18 | @ColumnInfo(defaultValue = "") val passphrase: String = "",
19 | val ssid: String?,
20 | @ColumnInfo(defaultValue = SoftApSecurityType.SECURITY_TYPE_OPEN.toString())
21 | @SoftApSecurityType.SecurityType
22 | val securityType: Int,
23 | @ColumnInfo(
24 | defaultValue = SoftApRandomizationSetting.RANDOMIZATION_NONE.toString()
25 | )
26 | @SoftApRandomizationSetting.RandomizationType
27 | val macRandomizationSetting: Int,
28 | @ColumnInfo(defaultValue = "0") val isHidden: Boolean,
29 | @ColumnInfo(defaultValue = SoftApSpeedType.BAND_2GHZ.toString())
30 | @SoftApSpeedType.BandType
31 | val speedType: Int,
32 | @ColumnInfo(defaultValue = "") val blockedDevices: List,
33 | @ColumnInfo(defaultValue = "") val allowedClients: List,
34 | @ColumnInfo(defaultValue = "0") val isAutoShutdownEnabled: Boolean,
35 | /** Timeout in milliseconds for auto shutdown. */
36 | @ColumnInfo(defaultValue = SoftApAutoShutdownTimeout.DEFAULT.toString())
37 | val autoShutdownTimeout: Long,
38 | @ColumnInfo(defaultValue = "0") val maxClientLimit: Int,
39 | @ColumnInfo(defaultValue = "") val presetName: String,
40 | @ColumnInfo(defaultValue = "0") val timestamp: Long,
41 | )
42 |
--------------------------------------------------------------------------------
/data/src/main/kotlin/dev/shadoe/delta/data/modules/DaoModule.kt:
--------------------------------------------------------------------------------
1 | package dev.shadoe.delta.data.modules
2 |
3 | import android.content.Context
4 | import androidx.room.Room
5 | import dagger.Module
6 | import dagger.Provides
7 | import dagger.hilt.InstallIn
8 | import dagger.hilt.android.qualifiers.ApplicationContext
9 | import dagger.hilt.components.SingletonComponent
10 | import dev.shadoe.delta.data.database.ConfigDB
11 | import javax.inject.Qualifier
12 | import javax.inject.Singleton
13 |
14 | @Qualifier
15 | @Retention(AnnotationRetention.BINARY)
16 | private annotation class ConfigDatabase
17 |
18 | @Module
19 | @InstallIn(SingletonComponent::class)
20 | object DaoModule {
21 | @Singleton
22 | @ConfigDatabase
23 | @Provides
24 | fun provideConfigDatabase(@ApplicationContext applicationContext: Context) =
25 | Room.databaseBuilder(
26 | applicationContext,
27 | ConfigDB::class.java,
28 | "config_database",
29 | )
30 | .build()
31 |
32 | @Singleton
33 | @Provides
34 | fun provideFlagsDao(@ConfigDatabase configDB: ConfigDB) = configDB.flagsDao()
35 |
36 | @Singleton
37 | @Provides
38 | fun provideHostInfoDao(@ConfigDatabase configDB: ConfigDB) =
39 | configDB.hostInfoDao()
40 |
41 | @Singleton
42 | @Provides
43 | fun providePresetDao(@ConfigDatabase configDB: ConfigDB) =
44 | configDB.presetDao()
45 | }
46 |
--------------------------------------------------------------------------------
/data/src/main/kotlin/dev/shadoe/delta/data/modules/SystemServicesModule.kt:
--------------------------------------------------------------------------------
1 | package dev.shadoe.delta.data.modules
2 |
3 | import android.net.ITetheringConnector
4 | import android.net.wifi.IWifiManager
5 | import android.os.IBinder
6 | import dagger.Module
7 | import dagger.Provides
8 | import dagger.hilt.InstallIn
9 | import dagger.hilt.components.SingletonComponent
10 | import dev.shadoe.delta.data.qualifiers.TetheringSystemService
11 | import dev.shadoe.delta.data.qualifiers.WifiSystemService
12 | import javax.inject.Singleton
13 | import rikka.shizuku.ShizukuBinderWrapper
14 | import rikka.shizuku.SystemServiceHelper
15 |
16 | @Module
17 | @InstallIn(SingletonComponent::class)
18 | object SystemServicesModule {
19 | class BinderAcquisitionException(message: String) : Exception(message)
20 |
21 | private fun getSystemService(name: String): IBinder {
22 | return SystemServiceHelper.getSystemService(name)?.let {
23 | ShizukuBinderWrapper(it)
24 | } ?: throw BinderAcquisitionException("Unable to get service: $name")
25 | }
26 |
27 | @Singleton
28 | @TetheringSystemService
29 | @Provides
30 | fun provideTetheringManager(): ITetheringConnector =
31 | ITetheringConnector.Stub.asInterface(getSystemService("tethering"))
32 |
33 | @Singleton
34 | @WifiSystemService
35 | @Provides
36 | fun provideWifiManager(): IWifiManager =
37 | IWifiManager.Stub.asInterface(getSystemService("wifi"))
38 | }
39 |
--------------------------------------------------------------------------------
/data/src/main/kotlin/dev/shadoe/delta/data/qualifiers/TetheringSystemService.kt:
--------------------------------------------------------------------------------
1 | package dev.shadoe.delta.data.qualifiers
2 |
3 | import javax.inject.Qualifier
4 |
5 | @Qualifier
6 | @Retention(AnnotationRetention.BINARY)
7 | internal annotation class TetheringSystemService
8 |
--------------------------------------------------------------------------------
/data/src/main/kotlin/dev/shadoe/delta/data/qualifiers/WifiSystemService.kt:
--------------------------------------------------------------------------------
1 | package dev.shadoe.delta.data.qualifiers
2 |
3 | import javax.inject.Qualifier
4 |
5 | @Qualifier
6 | @Retention(AnnotationRetention.BINARY)
7 | internal annotation class WifiSystemService
8 |
--------------------------------------------------------------------------------
/data/src/main/kotlin/dev/shadoe/delta/data/softap/SoftApBlocklistManager.kt:
--------------------------------------------------------------------------------
1 | package dev.shadoe.delta.data.softap
2 |
3 | import dev.shadoe.delta.api.ACLDevice
4 | import dev.shadoe.delta.data.MacAddressCacheRepository
5 | import javax.inject.Inject
6 | import kotlinx.coroutines.ExperimentalCoroutinesApi
7 | import kotlinx.coroutines.flow.mapLatest
8 |
9 | class SoftApBlocklistManager
10 | @Inject
11 | constructor(
12 | private val macAddressCacheRepository: MacAddressCacheRepository,
13 | private val softApController: SoftApController,
14 | private val softApStateStore: SoftApStateStore,
15 | ) {
16 | @OptIn(ExperimentalCoroutinesApi::class)
17 | val blockedClients =
18 | softApStateStore.config
19 | .mapLatest { c -> c.blockedDevices }
20 | .mapLatest { macAddresses ->
21 | val cache =
22 | macAddressCacheRepository.getHostnamesFromCache(macAddresses)
23 | macAddresses.map { ACLDevice(hostname = cache[it], macAddress = it) }
24 | }
25 |
26 | fun blockDevices(devices: Iterable) {
27 | softApStateStore.config.value.let { c ->
28 | softApController.updateSoftApConfiguration(
29 | c.copy(
30 | blockedDevices = c.blockedDevices.plus(devices.map { it.macAddress })
31 | )
32 | )
33 | }
34 | }
35 |
36 | fun unblockDevices(devices: Iterable) {
37 | softApStateStore.config.value.let { c ->
38 | softApController.updateSoftApConfiguration(
39 | c.copy(
40 | blockedDevices = c.blockedDevices.minus(devices.map { it.macAddress })
41 | )
42 | )
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/data/src/main/kotlin/dev/shadoe/delta/data/softap/SoftApStateFacade.kt:
--------------------------------------------------------------------------------
1 | package dev.shadoe.delta.data.softap
2 |
3 | import dev.shadoe.delta.api.ShizukuStates
4 | import dev.shadoe.delta.data.shizuku.ShizukuRepository
5 | import javax.inject.Inject
6 | import javax.inject.Provider
7 | import javax.inject.Singleton
8 | import kotlin.concurrent.atomics.AtomicInt
9 | import kotlin.concurrent.atomics.ExperimentalAtomicApi
10 | import kotlin.concurrent.atomics.decrementAndFetch
11 | import kotlin.concurrent.atomics.incrementAndFetch
12 | import kotlinx.coroutines.CoroutineScope
13 | import kotlinx.coroutines.Dispatchers
14 | import kotlinx.coroutines.ExperimentalCoroutinesApi
15 | import kotlinx.coroutines.Job
16 | import kotlinx.coroutines.flow.collectLatest
17 | import kotlinx.coroutines.flow.distinctUntilChanged
18 | import kotlinx.coroutines.flow.mapLatest
19 | import kotlinx.coroutines.launch
20 |
21 | @OptIn(ExperimentalAtomicApi::class)
22 | @Singleton
23 | class SoftApStateFacade
24 | @Inject
25 | constructor(
26 | private val shizukuRepository: ShizukuRepository,
27 | private val listenerProvider: Provider,
28 | private val backgroundJobsProvider: Provider,
29 | state: SoftApStateStore,
30 | ) {
31 | private var softApStateListener: SoftApStateListener? = null
32 | private var softApMonitor: SoftApMonitor? = null
33 | private var shizukuSubscriber: AutoCloseable? = null
34 | private var shizukuStateCollector: Job? = null
35 |
36 | private var isRunning = false
37 | private var subscribers = AtomicInt(0)
38 |
39 | val status = state.status
40 | val config = state.config
41 |
42 | private fun startListening() {
43 | softApStateListener?.close()
44 | softApStateListener = listenerProvider.get()
45 | softApMonitor?.close()
46 | softApMonitor = backgroundJobsProvider.get()
47 | }
48 |
49 | private fun stopListening() {
50 | runCatching {
51 | softApStateListener?.close()
52 | softApMonitor?.close()
53 | }
54 | softApMonitor = null
55 | }
56 |
57 | private fun start() {
58 | shizukuSubscriber?.close()
59 | shizukuSubscriber = shizukuRepository.callbackSubscriber
60 | shizukuStateCollector?.cancel()
61 | shizukuStateCollector =
62 | CoroutineScope(Dispatchers.Default).launch {
63 | @OptIn(ExperimentalCoroutinesApi::class)
64 | shizukuRepository.shizukuState
65 | .mapLatest { it == ShizukuStates.CONNECTED }
66 | .distinctUntilChanged()
67 | .collectLatest { if (it) startListening() else stopListening() }
68 | }
69 | isRunning = true
70 | }
71 |
72 | private fun stop() {
73 | stopListening()
74 | shizukuSubscriber?.close()
75 | shizukuSubscriber = null
76 | shizukuStateCollector?.cancel()
77 | shizukuStateCollector = null
78 | isRunning = false
79 | }
80 |
81 | fun subscribe() {
82 | val current = subscribers.incrementAndFetch()
83 | if (current == 1 && !isRunning) {
84 | start()
85 | }
86 | }
87 |
88 | fun unsubscribe() {
89 | val current = subscribers.decrementAndFetch()
90 | if (current < 0) subscribers.store(0)
91 | if (current == 0 && isRunning) stop()
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/data/src/main/kotlin/dev/shadoe/delta/data/softap/SoftApStateStore.kt:
--------------------------------------------------------------------------------
1 | package dev.shadoe.delta.data.softap
2 |
3 | import dev.shadoe.delta.api.SoftApAutoShutdownTimeout
4 | import dev.shadoe.delta.api.SoftApCapabilities
5 | import dev.shadoe.delta.api.SoftApConfiguration
6 | import dev.shadoe.delta.api.SoftApEnabledState
7 | import dev.shadoe.delta.api.SoftApRandomizationSetting
8 | import dev.shadoe.delta.api.SoftApSecurityType
9 | import dev.shadoe.delta.api.SoftApSpeedType
10 | import dev.shadoe.delta.api.SoftApStatus
11 | import dev.shadoe.delta.data.softap.internal.InternalState
12 | import dev.shadoe.delta.data.softap.internal.Utils.generateRandomPassword
13 | import javax.inject.Inject
14 | import javax.inject.Singleton
15 | import kotlinx.coroutines.flow.MutableStateFlow
16 | import kotlinx.coroutines.flow.asStateFlow
17 |
18 | @Singleton
19 | class SoftApStateStore @Inject constructor() {
20 | internal val internalState =
21 | MutableStateFlow(
22 | InternalState(fallbackPassphrase = generateRandomPassword())
23 | )
24 |
25 | internal val mStatus =
26 | MutableStateFlow(
27 | SoftApStatus(
28 | enabledState = SoftApEnabledState.WIFI_AP_STATE_DISABLED,
29 | tetheredClients = emptyList(),
30 | capabilities =
31 | SoftApCapabilities(
32 | maxSupportedClients = 0,
33 | clientForceDisconnectSupported = false,
34 | isMacAddressCustomizationSupported = false,
35 | supportedFrequencyBands = emptyList(),
36 | supportedSecurityTypes = emptyList(),
37 | ),
38 | isSoftApSupported = true,
39 | )
40 | )
41 |
42 | internal val mConfig =
43 | MutableStateFlow(
44 | SoftApConfiguration(
45 | ssid = "not configured",
46 | passphrase = "none",
47 | securityType = SoftApSecurityType.SECURITY_TYPE_OPEN,
48 | macRandomizationSetting = SoftApRandomizationSetting.RANDOMIZATION_NONE,
49 | isHidden = false,
50 | speedType = SoftApSpeedType.BAND_2GHZ,
51 | blockedDevices = listOf(),
52 | allowedClients = listOf(),
53 | isAutoShutdownEnabled = false,
54 | autoShutdownTimeout = SoftApAutoShutdownTimeout.DEFAULT,
55 | maxClientLimit = 0,
56 | )
57 | )
58 |
59 | val config = mConfig.asStateFlow()
60 |
61 | val status = mStatus.asStateFlow()
62 | }
63 |
--------------------------------------------------------------------------------
/data/src/main/kotlin/dev/shadoe/delta/data/softap/internal/InternalState.kt:
--------------------------------------------------------------------------------
1 | package dev.shadoe.delta.data.softap.internal
2 |
3 | internal data class InternalState(val fallbackPassphrase: String = "")
4 |
--------------------------------------------------------------------------------
/data/src/main/kotlin/dev/shadoe/delta/data/softap/internal/TetheringEventListener.kt:
--------------------------------------------------------------------------------
1 | package dev.shadoe.delta.data.softap.internal
2 |
3 | import dev.shadoe.delta.api.SoftApCapabilities
4 | import dev.shadoe.delta.api.SoftApEnabledState.EnabledStateType
5 | import dev.shadoe.delta.api.TetheredClient
6 |
7 | internal interface TetheringEventListener {
8 | /** Callback to update the state of Soft AP. */
9 | fun onEnabledStateChanged(@EnabledStateType state: Int)
10 |
11 | /** Callback to update the list of tethered clients. */
12 | fun onTetheredClientsChanged(clients: List)
13 |
14 | /**
15 | * Callback to update the soft AP capabilities obtained from the device
16 | * networking driver.
17 | *
18 | * This value should almost never change after start.
19 | */
20 | fun onSoftApCapabilitiesChanged(capabilities: SoftApCapabilities)
21 |
22 | /** Callback that indicates whether the device supports Soft AP. */
23 | fun onSoftApSupported(isSupported: Boolean)
24 | }
25 |
--------------------------------------------------------------------------------
/data/src/main/kotlin/dev/shadoe/delta/data/softap/internal/Utils.kt:
--------------------------------------------------------------------------------
1 | package dev.shadoe.delta.data.softap.internal
2 |
3 | import java.util.UUID
4 |
5 | internal object Utils {
6 | const val ADB_PACKAGE_NAME = "com.android.shell"
7 |
8 | /*
9 | * Copyright (C) 2023 The Android Open Source Project
10 | * SPDX-License-Identifier: Apache-2.0
11 | * From https://cs.android.com/android/platform/superproject/main/+/29fbb69343c063b65d71180d04f5d2acaf4f050c:packages/apps/Settings/src/com/android/settings/wifi/repository/WifiHotspotRepository.java;l=168
12 | */
13 | fun generateRandomPassword(): String {
14 | val randomUUID = UUID.randomUUID().toString()
15 | // first 12 chars from xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
16 | return randomUUID.substring(0, 8) + randomUUID.substring(9, 13)
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/data/src/main/kotlin/dev/shadoe/delta/data/softap/validators/PassphraseValidator.kt:
--------------------------------------------------------------------------------
1 | package dev.shadoe.delta.data.softap.validators
2 |
3 | import dev.shadoe.delta.api.SoftApSecurityType
4 | import dev.shadoe.delta.api.SoftApSecurityType.SecurityType
5 |
6 | object PassphraseValidator {
7 | const val PSK_PASSPHRASE_ASCII_MIN_LENGTH = 8
8 | const val PSK_PASSPHRASE_ASCII_MAX_LENGTH = 63
9 |
10 | sealed class Result {
11 | object Success : Result()
12 |
13 | object PskTooShort : Result()
14 |
15 | object PskTooLong : Result()
16 | }
17 |
18 | fun validate(passphrase: String, @SecurityType securityType: Int): Result {
19 | if (securityType == SoftApSecurityType.SECURITY_TYPE_OPEN)
20 | return Result.Success
21 | if (passphrase.isEmpty()) return Result.PskTooShort
22 | val usesPsk =
23 | securityType == SoftApSecurityType.SECURITY_TYPE_WPA2_PSK ||
24 | securityType == SoftApSecurityType.SECURITY_TYPE_WPA3_SAE_TRANSITION
25 | if (!usesPsk) return Result.Success
26 | return when {
27 | passphrase.length < PSK_PASSPHRASE_ASCII_MIN_LENGTH -> Result.PskTooShort
28 | passphrase.length > PSK_PASSPHRASE_ASCII_MAX_LENGTH -> Result.PskTooLong
29 | else -> Result.Success
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/data/src/main/kotlin/dev/shadoe/delta/data/softap/validators/SsidValidator.kt:
--------------------------------------------------------------------------------
1 | package dev.shadoe.delta.data.softap.validators
2 |
3 | object SsidValidator {
4 | const val SSID_ASCII_MIN_LENGTH = 1
5 | const val SSID_ASCII_MAX_LENGTH = 32
6 |
7 | sealed class Result {
8 | object Success : Result()
9 |
10 | object SsidTooShort : Result()
11 |
12 | object SsidTooLong : Result()
13 | }
14 |
15 | fun validate(ssid: String): Result =
16 | when {
17 | ssid.length < SSID_ASCII_MIN_LENGTH -> Result.SsidTooShort
18 | ssid.encodeToByteArray().size > SSID_ASCII_MAX_LENGTH ->
19 | Result.SsidTooLong
20 | else -> Result.Success
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/data/src/test/kotlin/dev/shadoe/delta/data/softap/validators/PassphraseValidatorTest.kt:
--------------------------------------------------------------------------------
1 | package dev.shadoe.delta.data.softap.validators
2 |
3 | import dev.shadoe.delta.api.SoftApSecurityType
4 | import kotlin.test.Test
5 | import kotlin.test.assertIs
6 |
7 | class PassphraseValidatorTest {
8 | companion object {
9 | private const val SHORT_PASS = "short"
10 | private const val NORMAL_PASS = "this is a good passphrase"
11 | private const val LONG_PASS =
12 | "bruh max length is quite big for a normal human to exhaust the limit easily tbf."
13 | }
14 |
15 | @Test
16 | fun `When security type is WPA2-PSK or SAE-transition`() {
17 | val securityTypes =
18 | listOf(
19 | SoftApSecurityType.SECURITY_TYPE_WPA2_PSK,
20 | SoftApSecurityType.SECURITY_TYPE_WPA3_SAE_TRANSITION,
21 | )
22 | for (securityType in securityTypes) {
23 | val normal = PassphraseValidator.validate(NORMAL_PASS, securityType)
24 | val tooShort = PassphraseValidator.validate(SHORT_PASS, securityType)
25 | val tooLong = PassphraseValidator.validate(LONG_PASS, securityType)
26 | assertIs(normal)
27 | assertIs(tooShort)
28 | assertIs(tooLong)
29 | }
30 | }
31 |
32 | @Test
33 | fun `When security type is open`() {
34 | val securityType = SoftApSecurityType.SECURITY_TYPE_OPEN
35 | val results =
36 | listOf(
37 | PassphraseValidator.validate(NORMAL_PASS, securityType),
38 | PassphraseValidator.validate(SHORT_PASS, securityType),
39 | PassphraseValidator.validate(LONG_PASS, securityType),
40 | PassphraseValidator.validate("", securityType),
41 | )
42 | for (r in results) {
43 | assertIs(r)
44 | }
45 | }
46 |
47 | @Test
48 | fun `When security type is SAE and passphrase is not empty`() {
49 | val securityType = SoftApSecurityType.SECURITY_TYPE_WPA3_SAE
50 | val results =
51 | listOf(
52 | PassphraseValidator.validate(NORMAL_PASS, securityType),
53 | PassphraseValidator.validate(SHORT_PASS, securityType),
54 | PassphraseValidator.validate(LONG_PASS, securityType),
55 | )
56 | for (r in results) {
57 | assertIs(r)
58 | }
59 | }
60 |
61 | @Test
62 | fun `When security type is SAE and passphrase is empty`() {
63 | val emptyPass =
64 | PassphraseValidator.validate(
65 | "",
66 | SoftApSecurityType.SECURITY_TYPE_WPA3_SAE,
67 | )
68 | assertIs(emptyPass)
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/data/src/test/kotlin/dev/shadoe/delta/data/softap/validators/SsidValidatorTest.kt:
--------------------------------------------------------------------------------
1 | package dev.shadoe.delta.data.softap.validators
2 |
3 | import kotlin.test.Test
4 | import kotlin.test.assertIs
5 |
6 | class SsidValidatorTest {
7 | @Test
8 | fun `Validation of ASCII SSIDs`() {
9 | val normal = SsidValidator.validate("test")
10 | val tooShort = SsidValidator.validate("")
11 | val tooLong = SsidValidator.validate("a".repeat(33))
12 | assertIs(normal)
13 | assertIs(tooShort)
14 | assertIs(tooLong)
15 | }
16 |
17 | @Test
18 | fun `Multi-byte chars in SSID`() {
19 | // len = 14 normally, len = 14 * 3 when encoded as byte array
20 | // Above the limit
21 | val fail = SsidValidator.validate("オール・ヘイル・ルルーシュ!")
22 | // len = 10 normally, len = 10 * 3 when encoded as byte array
23 | // Barely around the limit
24 | val pass = SsidValidator.validate("シュタインズ・ゲート")
25 | assertIs(fail)
26 | assertIs(pass)
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelogs/15.txt:
--------------------------------------------------------------------------------
1 | ### 2025.05+0
2 |
3 | BREAKING CHANGE: Update to previous version before this
4 |
5 | - Presets support for saving/restoring settings
6 | - Quick setting tile for devices where SystemUI doesn't show one
7 | - Proper checking of blocklist/tethering support on devices
8 | - Automatically fix tethering if it fails due to bad config
9 | - Tap to copy IP address
10 | - Redesigned UI
11 | - Can change language for Delta alone in System settings
12 | - Tasker integration
13 | - New languages
14 | - Fully translated: German
15 | - Partial: Serbian & Spanish
16 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelogs/16.txt:
--------------------------------------------------------------------------------
1 | ### 2025.05+1
2 |
3 | BREAKING CHANGE: Update to previous version before this
4 |
5 | - Presets support for saving/restoring settings
6 | - Quick setting tile for devices where SystemUI doesn't show one
7 | - Proper checking of blocklist/tethering support on devices
8 | - Automatically fix tethering if it fails due to bad config
9 | - Tap to copy IP address
10 | - Redesigned UI
11 | - Can change language for Delta alone in System settings
12 | - Tasker integration
13 | - New languages
14 | - Fully translated: German
15 | - Partial: Serbian & Spanish
16 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelogs/17.txt:
--------------------------------------------------------------------------------
1 | ### 2025.05+2
2 |
3 | Hotfix update: fixes crashes on Android 11 due to a new check
4 | added in app for availability of hotspot feature.
5 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/full_description.txt:
--------------------------------------------------------------------------------
1 | This app allows accessing advanced hotspot settings not exposed through the system settings app on devices that use Stock Android (Pixel, Moto, etc.) It provides various information and features such as:
Usual info provided by settings app such as hotspot name, passphrase, security type, frequency bands, quick connect using QR, etc. List of connected devices. Modifying amount of devices that can connect. Changing MAC randomization settings. Setting auto turn-off timeout. Blocklist to prevent certain devices from connecting. Make hotspot's SSID hidden (device doesn't appear on WiFi scans) Quick setting tile to easily turn hotspot on/off from notification shade. Control hotspot using Tasker/other automation apps.
It uses Shizuku to obtain hotspot related details and to modify hotspot settings. It is also useful on devices like Samsung Tabs (WiFi-only) where hotspot settings are hidden from Settings app and SystemUI.
2 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/supershadoe/delta/2cc85f133800794b6056a77b430367e0f1a973bf/fastlane/metadata/android/en-US/images/icon.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/supershadoe/delta/2cc85f133800794b6056a77b430367e0f1a973bf/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/supershadoe/delta/2cc85f133800794b6056a77b430367e0f1a973bf/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/supershadoe/delta/2cc85f133800794b6056a77b430367e0f1a973bf/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/supershadoe/delta/2cc85f133800794b6056a77b430367e0f1a973bf/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/supershadoe/delta/2cc85f133800794b6056a77b430367e0f1a973bf/fastlane/metadata/android/en-US/images/phoneScreenshots/5.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/supershadoe/delta/2cc85f133800794b6056a77b430367e0f1a973bf/fastlane/metadata/android/en-US/images/phoneScreenshots/6.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/short_description.txt:
--------------------------------------------------------------------------------
1 | Advanced hotspot settings for Pixel-like devices
2 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 |
11 | # Configuration cache skips the configuration phase entirely if the build
12 | # configuration doesn't change between builds
13 | # https://docs.gradle.org/current/userguide/performance.html#enable_configuration_cache
14 | org.gradle.configuration-cache=true
15 | # In case some of the plugins are not fully compatible, warn.
16 | org.gradle.configuration-cache.problems=warn
17 |
18 | # Only configure whatever subproject task is being run (and its dependencies)
19 | # https://docs.gradle.org/current/userguide/multi_project_configuration_and_execution.html
20 | org.gradle.configureondemand=true
21 |
22 | # Evaluate tasks of different modules parallely
23 | # Each subproject are linked only through declared dependencies and thus,
24 | # we can use this optimization
25 | # https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects
26 | org.gradle.parallel=true
27 |
28 | # Use build cache for tasks with same input
29 | # https://docs.gradle.org/current/userguide/performance.html#enable_the_build_cache
30 | org.gradle.caching=true
31 |
32 | # AndroidX package structure to make it clearer which packages are bundled with the
33 | # Android operating system, and which are packaged with your app's APK
34 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
35 | android.useAndroidX=true
36 |
37 | # Kotlin code style for this project: "official" or "obsolete"
38 | # Not necessary tbh because ktlint runs its own formatting anyways
39 | kotlin.code.style=official
40 |
41 | # Enables namespacing of each library's R class so that its R class includes
42 | # only the resources declared in the library itself and none from the library's
43 | # dependencies, thereby reducing the size of the R class for that library
44 | # https://developer.android.com/build/optimize-your-build#use-non-transitive-r-classes
45 | android.nonTransitiveRClass=true
46 |
--------------------------------------------------------------------------------
/gradle/build-logic/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/gradle/build-logic/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | `kotlin-dsl`
3 | alias(libs.plugins.kotlin.serialization)
4 | }
5 |
6 | dependencies {
7 | implementation(libs.kotlinx.serialization.json)
8 | implementation(libs.spotless)
9 | }
10 |
--------------------------------------------------------------------------------
/gradle/build-logic/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | gradlePluginPortal()
4 | mavenCentral()
5 | }
6 | }
7 |
8 | @Suppress("UnstableApiUsage")
9 | dependencyResolutionManagement {
10 | versionCatalogs { create("libs") { from(files("../libs.versions.toml")) } }
11 | repositories {
12 | google()
13 | mavenCentral()
14 | }
15 | }
16 |
17 | rootProject.name = "delta-build"
18 |
--------------------------------------------------------------------------------
/gradle/build-logic/src/main/kotlin/delta/buildsrc/Commands.kt:
--------------------------------------------------------------------------------
1 | package delta.buildsrc
2 |
3 | import kotlinx.serialization.json.Json
4 | import org.gradle.api.Project
5 | import java.io.File
6 | import java.nio.charset.Charset
7 |
8 | fun Project.readSigningConfig(file: File) = file
9 | .takeIf { it.exists() }
10 | ?.runCatching {
11 | Json.decodeFromString(
12 | string = readText(charset = Charset.defaultCharset()),
13 | )
14 | }?.getOrNull()
15 |
--------------------------------------------------------------------------------
/gradle/build-logic/src/main/kotlin/delta/buildsrc/SigningConfig.kt:
--------------------------------------------------------------------------------
1 | package delta.buildsrc
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | @Serializable
6 | data class SigningConfig(
7 | val keyAlias: String,
8 | val keyPassword: String,
9 | val storeFile: String,
10 | val storePassword: String,
11 | )
12 |
--------------------------------------------------------------------------------
/gradle/build-logic/src/main/kotlin/delta/buildsrc/VersionConfig.kt:
--------------------------------------------------------------------------------
1 | package delta.buildsrc
2 |
3 | data class VersionConfig(
4 | val versionCode: Int,
5 | val versionName: String,
6 | )
7 |
8 | val versionConfig
9 | get() = VersionConfig(
10 | versionCode = 17,
11 | versionName = "2025.05+2",
12 | )
13 |
--------------------------------------------------------------------------------
/gradle/build-logic/src/main/kotlin/delta/buildsrc/delta.app.version.gradle.kts:
--------------------------------------------------------------------------------
1 | import delta.buildsrc.versionConfig
2 |
3 | tasks.register("getAppVersionCode") {
4 | group = "Help"
5 | description = "Prints the app version code"
6 | doLast { println(versionConfig.versionCode) }
7 | }
8 |
9 | tasks.register("getAppVersionName") {
10 | group = "Help"
11 | description = "Prints the app version name"
12 | doLast { println(versionConfig.versionName) }
13 | }
14 |
15 | tasks.register("getAppVersion") {
16 | group = "Help"
17 | description = "Prints the app version config"
18 | doLast { println(versionConfig) }
19 | }
20 |
--------------------------------------------------------------------------------
/gradle/build-logic/src/main/kotlin/delta/buildsrc/lint/delta.lint.java.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins { id("com.diffplug.spotless") }
2 |
3 | spotless {
4 | java {
5 | importOrder()
6 | removeUnusedImports()
7 | googleJavaFormat().aosp().reflowLongStrings()
8 | formatAnnotations()
9 | target("**/*.java")
10 | targetExclude("build/**")
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/gradle/build-logic/src/main/kotlin/delta/buildsrc/lint/delta.lint.kotlin.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins { id("com.diffplug.spotless") }
2 |
3 | spotless {
4 | kotlin {
5 | target("**/*.kt")
6 | targetExclude("**/build/**/*.kt")
7 | ktfmt("0.54").googleStyle().configure {
8 | it.setMaxWidth(80)
9 | it.setManageTrailingCommas(true)
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/gradle/build-logic/src/main/kotlin/delta/buildsrc/lint/delta.lint.kts.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins { id("com.diffplug.spotless") }
2 |
3 | spotless {
4 | kotlinGradle {
5 | target("**/*.gradle.kts")
6 | targetExclude("**/build/**/*.gradle.kts")
7 | ktfmt("0.54").googleStyle().configure {
8 | it.setMaxWidth(80)
9 | it.setManageTrailingCommas(true)
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/gradle/build-logic/src/main/kotlin/delta/buildsrc/lint/delta.lint.text.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins { id("com.diffplug.spotless") }
2 |
3 | spotless {
4 | format("text") {
5 | target(
6 | "*.gradle",
7 | ".gitattributes",
8 | ".gitignore",
9 | "*.txt",
10 | "*.yml",
11 | "*.yaml",
12 | "*.toml",
13 | )
14 | trimTrailingWhitespace()
15 | leadingTabsToSpaces()
16 | endWithNewline()
17 | }
18 | flexmark {
19 | target("**/*.md")
20 | flexmark()
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/supershadoe/delta/2cc85f133800794b6056a77b430367e0f1a973bf/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Fri Nov 22 23:33:27 IST 2024
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.12.1-bin.zip
5 | zipStoreBase=GRADLE_USER_HOME
6 | zipStorePath=wrapper/dists
7 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/scripts/update_ver.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | edit_version_code() {
4 | echo "Determining version code..." >&2
5 | versionCode=$(grep "versionCode =" \
6 | gradle/build-logic/src/main/kotlin/delta/buildsrc/VersionConfig.kt |\
7 | cut -d= -f2 |\
8 | cut -d, -f1 |\
9 | xargs \
10 | )
11 | echo "Current version code is $versionCode" >&2
12 | versionCode=$((versionCode + 1))
13 | echo "Updating it to $versionCode" >&2
14 | sed -i "s/versionCode = [0-9]\+/versionCode = $versionCode/" \
15 | gradle/build-logic/src/main/kotlin/delta/buildsrc/VersionConfig.kt
16 | echo "versionCode=$versionCode"
17 | }
18 |
19 | edit_version_name() {
20 | echo "Determining version name..." >&2
21 | oldVersionName=$(grep "versionName =" \
22 | gradle/build-logic/src/main/kotlin/delta/buildsrc/VersionConfig.kt |\
23 | cut -d= -f2 |\
24 | cut -d\" -f2 |\
25 | xargs \
26 | )
27 | echo "Current version name is $oldVersionName" >&2
28 | oldDate=$(echo $oldVersionName | cut -d+ -f1)
29 | buildNum=$(echo $oldVersionName | cut -d+ -f2)
30 | newDate=$(date +%Y.%m)
31 | echo "Current date is $newDate" >&2
32 | if [ $oldDate != $newDate ]; then
33 | echo "Date doesn't match current date, resetting build number..." >&2
34 | buildNum=0
35 | elif [ "$1" = "pre" ]; then
36 | if [ $buildNum -ge 0 ]; then
37 | echo "Pre-release call, not updating build number..." >&2
38 | else
39 | echo "Unknown build number, resetting it..." >&2
40 | buildNum=0
41 | fi
42 | else
43 | echo "Date matches the current date, incrementing build number..." >&2
44 | buildNum=$((buildNum + 1))
45 | fi
46 | versionName="$newDate+$buildNum"
47 | echo "New version name is $versionName" >&2
48 | sed -i "s/versionName = \"\S\+\"/versionName = \"$versionName\"/" \
49 | gradle/build-logic/src/main/kotlin/delta/buildsrc/VersionConfig.kt
50 | echo "versionName=$versionName"
51 | }
52 |
53 | case $1 in
54 | "pre")
55 | edit_version_name $1
56 | ;;
57 | "post")
58 | edit_version_code
59 | edit_version_name $1
60 | ;;
61 | *)
62 | echo "Enter a valid argument - pre|post" >&2
63 | ;;
64 | esac
65 |
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | google {
4 | content {
5 | includeGroupByRegex("com\\.android.*")
6 | includeGroupByRegex("com\\.google.*")
7 | includeGroupByRegex("androidx.*")
8 | }
9 | }
10 | mavenCentral()
11 | gradlePluginPortal()
12 | }
13 | }
14 |
15 | @Suppress("UnstableApiUsage")
16 | dependencyResolutionManagement {
17 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
18 | repositories {
19 | google()
20 | mavenCentral()
21 | }
22 | }
23 |
24 | rootProject.name = "Delta"
25 |
26 | includeBuild("gradle/build-logic")
27 |
28 | include(":api", ":app", ":data", ":system-api-stubs")
29 |
--------------------------------------------------------------------------------
/system-api-stubs/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/system-api-stubs/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import com.android.build.gradle.tasks.AidlCompile
2 |
3 | plugins {
4 | alias(libs.plugins.android.library)
5 | id("delta.lint.java")
6 | id("delta.lint.kts")
7 | }
8 |
9 | android {
10 | namespace = "dev.shadoe.systemapistubs"
11 | compileSdk = 36
12 |
13 | defaultConfig {
14 | minSdk = 30
15 | consumerProguardFiles("consumer-rules.pro")
16 | }
17 |
18 | buildTypes {
19 | release {
20 | isMinifyEnabled = false
21 | proguardFiles(
22 | getDefaultProguardFile("proguard-android-optimize.txt"),
23 | "proguard-rules.pro",
24 | )
25 | }
26 | }
27 |
28 | buildFeatures { aidl = true }
29 |
30 | compileOptions {
31 | sourceCompatibility = JavaVersion.VERSION_11
32 | targetCompatibility = JavaVersion.VERSION_21
33 | }
34 |
35 | afterEvaluate {
36 | tasks.withType().configureEach {
37 | doLast {
38 | outputs.files.forEach { fileOrFolder ->
39 | fileOrFolder.walkTopDown().forEach { file ->
40 | file
41 | .takeIf { file.extension == "java" }
42 | ?.apply {
43 | val sanitized = readText().replace("\\", "\\\\")
44 | writeText(sanitized)
45 | }
46 | }
47 | }
48 | }
49 | }
50 | }
51 | }
52 |
53 | dependencies {
54 | implementation(libs.androidx.annotation)
55 | annotationProcessor(libs.refine.processor)
56 | implementation(libs.refine.annotation)
57 | }
58 |
--------------------------------------------------------------------------------
/system-api-stubs/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/supershadoe/delta/2cc85f133800794b6056a77b430367e0f1a973bf/system-api-stubs/consumer-rules.pro
--------------------------------------------------------------------------------
/system-api-stubs/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/system-api-stubs/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/system-api-stubs/src/main/aidl/android/net/IIntResultListener.aidl:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2019 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package android.net;
18 |
19 | /**
20 | * Listener interface allowing objects to listen to various module event.
21 | * {@hide}
22 | */
23 | oneway interface IIntResultListener {
24 | void onResult(int resultCode);
25 | }
--------------------------------------------------------------------------------
/system-api-stubs/src/main/aidl/android/net/TetherStatesParcel.aidl:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2019 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package android.net;
18 |
19 | import android.net.TetheringInterface;
20 |
21 | /**
22 | * Status details for tethering downstream interfaces.
23 | * {@hide}
24 | */
25 | parcelable TetherStatesParcel {
26 | TetheringInterface[] availableList;
27 | TetheringInterface[] tetheredList;
28 | TetheringInterface[] localOnlyList;
29 | TetheringInterface[] erroredIfaceList;
30 | // List of Last error code corresponding to each errored iface in erroredIfaceList. */
31 | // TODO: Improve this as b/143122247.
32 | int[] lastErrorList;
33 | }
--------------------------------------------------------------------------------
/system-api-stubs/src/main/aidl/android/net/TetheredClient.aidl:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2020 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package android.net;
17 |
18 | @JavaOnlyStableParcelable parcelable TetheredClient;
--------------------------------------------------------------------------------
/system-api-stubs/src/main/aidl/android/net/TetheringConfigurationParcel.aidl:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2019 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package android.net;
18 |
19 | /**
20 | * Configuration details for tethering.
21 | * @hide
22 | */
23 | parcelable TetheringConfigurationParcel {
24 | String[] tetherableUsbRegexs;
25 | String[] tetherableWifiRegexs;
26 | String[] tetherableBluetoothRegexs;
27 | String[] legacyDhcpRanges;
28 | String[] provisioningApp;
29 | String provisioningAppNoUi;
30 | }
--------------------------------------------------------------------------------
/system-api-stubs/src/main/aidl/android/net/TetheringInterface.aidl:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2021 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package android.net;
17 |
18 | @JavaOnlyStableParcelable parcelable TetheringInterface;
19 |
--------------------------------------------------------------------------------
/system-api-stubs/src/main/aidl/android/net/TetheringRequestParcel.aidl:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package android.net;
18 |
19 | import android.net.LinkAddress;
20 | import android.net.wifi.SoftApConfiguration;
21 |
22 | /**
23 | * Configuration details for requesting tethering.
24 | */
25 | parcelable TetheringRequestParcel {
26 | int tetheringType;
27 | LinkAddress localIPv4Address;
28 | LinkAddress staticClientAddress;
29 | boolean exemptFromEntitlementCheck;
30 | boolean showProvisioningUi;
31 | int connectivityScope;
32 | SoftApConfiguration softApConfig;
33 | int uid;
34 | String packageName;
35 | String interfaceName;
36 | }
--------------------------------------------------------------------------------
/system-api-stubs/src/main/aidl/android/net/wifi/SoftApState.aidl:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2024 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package android.net.wifi;
18 |
19 | import android.net.wifi.SoftApState;
20 |
21 | parcelable SoftApState;
22 |
--------------------------------------------------------------------------------
/system-api-stubs/src/main/java/android/net/ITetheringEventCallback.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2019 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package android.net;
18 |
19 | import android.os.Binder;
20 | import android.os.IBinder;
21 | import android.os.IInterface;
22 | import android.os.Parcel;
23 | import android.os.RemoteException;
24 | import androidx.annotation.NonNull;
25 | import java.util.List;
26 |
27 | /** Callback class for receiving tethering changed events. */
28 | public interface ITetheringEventCallback extends IInterface {
29 | /** Local-side IPC implementation stub class. */
30 | abstract class Stub extends Binder implements ITetheringEventCallback {
31 | public Stub() {
32 | throw new RuntimeException("stub!");
33 | }
34 |
35 | /**
36 | * Cast an IBinder object into an android.net.wifi.ISoftApCallback interface, generating a
37 | * proxy if needed.
38 | */
39 | public static ITetheringEventCallback asInterface(IBinder obj) {
40 | throw new RuntimeException("stub!");
41 | }
42 |
43 | @Override
44 | public IBinder asBinder() {
45 | throw new RuntimeException("stub!");
46 | }
47 |
48 | @Override
49 | public boolean onTransact(int code, @NonNull Parcel data, Parcel reply, int flags)
50 | throws RemoteException {
51 | throw new RuntimeException("stub!");
52 | }
53 |
54 | public static final java.lang.String DESCRIPTOR = "android.net.ITetheringEventCallback";
55 | }
56 |
57 | /** Called immediately after the callbacks are registered */
58 | void onCallbackStarted(TetheringCallbackStartedParcel parcel);
59 |
60 | void onCallbackStopped(int errorCode);
61 |
62 | void onUpstreamChanged(Network network);
63 |
64 | void onConfigurationChanged(TetheringConfigurationParcel config);
65 |
66 | void onTetherStatesChanged(TetherStatesParcel states);
67 |
68 | void onTetherClientsChanged(List clients);
69 |
70 | void onOffloadStatusChanged(int status);
71 |
72 | void onSupportedTetheringTypes(long supportedBitmap);
73 | }
74 |
--------------------------------------------------------------------------------
/system-api-stubs/src/main/java/android/net/TetheringCallbackStartedParcel.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package android.net;
18 |
19 | import android.os.Build;
20 | import android.os.Parcel;
21 | import android.os.Parcelable;
22 | import androidx.annotation.NonNull;
23 | import androidx.annotation.RequiresApi;
24 | import java.util.List;
25 |
26 | /** Initial information reported by tethering upon callback registration. */
27 | public class TetheringCallbackStartedParcel implements Parcelable {
28 | @RequiresApi(Build.VERSION_CODES.TIRAMISU)
29 | public long supportedTypes = 0L;
30 |
31 | public Network upstreamNetwork;
32 | public TetheringConfigurationParcel config;
33 | public TetherStatesParcel states;
34 | public List tetheredClients;
35 | public int offloadStatus = 0;
36 |
37 | public static final Creator CREATOR =
38 | new Creator() {
39 | @Override
40 | public TetheringCallbackStartedParcel createFromParcel(Parcel in) {
41 | throw new RuntimeException("stub!");
42 | }
43 |
44 | @Override
45 | public TetheringCallbackStartedParcel[] newArray(int size) {
46 | throw new RuntimeException("stub!");
47 | }
48 | };
49 |
50 | @Override
51 | public final void writeToParcel(@NonNull Parcel _aidl_parcel, int _aidl_flag) {
52 | throw new RuntimeException("stub!");
53 | }
54 |
55 | public final void readFromParcel(Parcel _aidl_parcel) {
56 | throw new RuntimeException("stub!");
57 | }
58 |
59 | @Override
60 | public int describeContents() {
61 | throw new RuntimeException("stub!");
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/system-api-stubs/src/main/java/android/net/wifi/IStringListener.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2022 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package android.net.wifi;
18 |
19 | import android.os.Binder;
20 | import android.os.Build;
21 | import android.os.IBinder;
22 | import android.os.IInterface;
23 | import android.os.Parcel;
24 | import android.os.RemoteException;
25 | import androidx.annotation.NonNull;
26 | import androidx.annotation.RequiresApi;
27 |
28 | /** Interface for IStringListener. */
29 | public interface IStringListener extends IInterface {
30 | @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
31 | abstract class Stub extends Binder implements IStringListener {
32 | /** Construct the stub at attach it to the interface. */
33 | public Stub() {
34 | throw new RuntimeException("stub!");
35 | }
36 |
37 | /**
38 | * Cast an IBinder object into an android.net.wifi.IStringListener interface, generating a
39 | * proxy if needed.
40 | */
41 | public static IStringListener asInterface(IBinder obj) {
42 | throw new RuntimeException("stub!");
43 | }
44 |
45 | @Override
46 | public android.os.IBinder asBinder() {
47 | throw new RuntimeException("stub!");
48 | }
49 |
50 | @Override
51 | public boolean onTransact(int code, @NonNull Parcel data, Parcel reply, int flags)
52 | throws RemoteException {
53 | throw new RuntimeException("stub!");
54 | }
55 | }
56 |
57 | void onResult(String value) throws RemoteException;
58 | }
59 |
--------------------------------------------------------------------------------
/system-api-stubs/src/main/java/android/net/wifi/OuiKeyedData.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2023 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package android.net.wifi;
18 |
19 | import android.os.Parcel;
20 | import android.os.Parcelable;
21 | import android.os.PersistableBundle;
22 | import androidx.annotation.NonNull;
23 |
24 | public final class OuiKeyedData implements Parcelable {
25 |
26 | public int getOui() {
27 | throw new RuntimeException("stub!");
28 | }
29 |
30 | public @NonNull PersistableBundle getData() {
31 | throw new RuntimeException("stub!");
32 | }
33 |
34 | public boolean validate() {
35 | throw new RuntimeException("stub!");
36 | }
37 |
38 | @Override
39 | public int describeContents() {
40 | return 0;
41 | }
42 |
43 | @Override
44 | public void writeToParcel(@NonNull Parcel dest, int flags) {
45 | throw new RuntimeException("stub!");
46 | }
47 |
48 | OuiKeyedData(@NonNull Parcel in) {
49 | throw new RuntimeException("stub!");
50 | }
51 |
52 | @NonNull public static final Parcelable.Creator CREATOR =
53 | new Parcelable.Creator<>() {
54 | @Override
55 | public OuiKeyedData createFromParcel(Parcel in) {
56 | throw new RuntimeException("stub!");
57 | }
58 |
59 | @Override
60 | public OuiKeyedData[] newArray(int size) {
61 | throw new RuntimeException("stub!");
62 | }
63 | };
64 |
65 | public static final class Builder {
66 | public Builder(int oui, @NonNull PersistableBundle data) {
67 | throw new RuntimeException("stub!");
68 | }
69 |
70 | @NonNull public OuiKeyedData build() {
71 | throw new RuntimeException("stub!");
72 | }
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/system-api-stubs/src/main/java/android/net/wifi/SoftApState.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2024 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package android.net.wifi;
18 |
19 | import android.net.TetheringManager;
20 | import android.os.Parcel;
21 | import android.os.Parcelable;
22 | import androidx.annotation.NonNull;
23 | import androidx.annotation.Nullable;
24 |
25 | public final class SoftApState implements Parcelable {
26 |
27 | public SoftApState(
28 | int state,
29 | int failureReason,
30 | @Nullable TetheringManager.TetheringRequest tetheringRequest,
31 | @Nullable String iface) {
32 | throw new RuntimeException("stub!");
33 | }
34 |
35 | @Override
36 | public void writeToParcel(@NonNull Parcel dest, int flags) {
37 | throw new RuntimeException("stub!");
38 | }
39 |
40 | @Override
41 | public int describeContents() {
42 | return 0;
43 | }
44 |
45 | @NonNull public static final Creator CREATOR =
46 | new Creator<>() {
47 | @Override
48 | @NonNull public SoftApState createFromParcel(Parcel in) {
49 | throw new RuntimeException("stub!");
50 | }
51 |
52 | @Override
53 | @NonNull public SoftApState[] newArray(int size) {
54 | throw new RuntimeException("stub!");
55 | }
56 | };
57 |
58 | public int getState() {
59 | throw new RuntimeException("stub!");
60 | }
61 |
62 | public int getFailureReason() {
63 | throw new RuntimeException("stub!");
64 | }
65 |
66 | public int getFailureReasonInternal() {
67 | throw new RuntimeException("stub!");
68 | }
69 |
70 | @Nullable public TetheringManager.TetheringRequest getTetheringRequest() {
71 | throw new RuntimeException("stub!");
72 | }
73 |
74 | @Nullable public String getIface() {
75 | throw new RuntimeException("stub!");
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/system-api-stubs/src/main/java/android/net/wifi/WifiClient.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2019 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package android.net.wifi;
18 |
19 | import android.net.MacAddress;
20 | import android.os.Parcel;
21 | import android.os.Parcelable;
22 | import androidx.annotation.NonNull;
23 |
24 | public final class WifiClient implements Parcelable {
25 |
26 | @NonNull public MacAddress getMacAddress() {
27 | throw new RuntimeException("stub!");
28 | }
29 |
30 | @NonNull public String getApInstanceIdentifier() {
31 | throw new RuntimeException("stub!");
32 | }
33 |
34 | public WifiClient(@NonNull MacAddress macAddress, @NonNull String apInstanceIdentifier) {
35 | throw new RuntimeException("stub!");
36 | }
37 |
38 | @Override
39 | public int describeContents() {
40 | return 0;
41 | }
42 |
43 | @Override
44 | public void writeToParcel(@NonNull Parcel dest, int flags) {
45 | throw new RuntimeException("stub!");
46 | }
47 |
48 | @NonNull public static final Creator CREATOR =
49 | new Creator<>() {
50 | public WifiClient createFromParcel(Parcel in) {
51 | throw new RuntimeException("stub!");
52 | }
53 |
54 | public WifiClient[] newArray(int size) {
55 | throw new RuntimeException("stub!");
56 | }
57 | };
58 | }
59 |
--------------------------------------------------------------------------------