mirror of
https://github.com/wgtunnel/android.git
synced 2026-07-03 14:07:49 +02:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b3cb7a7988 | |||
| 6415f49377 | |||
| baed8ff2e7 |
@@ -1,97 +0,0 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_size = 4
|
||||
indent_style = tab
|
||||
max_line_length = 150
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[{*.kt,*.kts}]
|
||||
ij_continuation_indent_size = 4
|
||||
ij_java_names_count_to_use_import_on_demand = 9999
|
||||
ij_kotlin_align_in_columns_case_branch = false
|
||||
ij_kotlin_align_multiline_binary_operation = false
|
||||
ij_kotlin_align_multiline_extends_list = false
|
||||
ij_kotlin_align_multiline_method_parentheses = false
|
||||
ij_kotlin_align_multiline_parameters = true
|
||||
ij_kotlin_align_multiline_parameters_in_calls = false
|
||||
ij_kotlin_assignment_wrap = normal
|
||||
ij_kotlin_blank_lines_after_class_header = 0
|
||||
ij_kotlin_blank_lines_around_block_when_branches = 0
|
||||
ij_kotlin_blank_lines_before_declaration_with_comment_or_annotation_on_separate_line = 1
|
||||
ij_kotlin_block_comment_at_first_column = true
|
||||
ij_kotlin_call_parameters_new_line_after_left_paren = true
|
||||
ij_kotlin_call_parameters_right_paren_on_new_line = false
|
||||
ij_kotlin_catch_on_new_line = false
|
||||
ij_kotlin_continuation_indent_for_chained_calls = true
|
||||
ij_kotlin_continuation_indent_for_expression_bodies = true
|
||||
ij_kotlin_continuation_indent_in_argument_lists = true
|
||||
ij_kotlin_continuation_indent_in_elvis = false
|
||||
ij_kotlin_continuation_indent_in_if_conditions = false
|
||||
ij_kotlin_continuation_indent_in_parameter_lists = false
|
||||
ij_kotlin_continuation_indent_in_supertype_lists = false
|
||||
ij_kotlin_else_on_new_line = false
|
||||
ij_kotlin_enum_constants_wrap = off
|
||||
ij_kotlin_extends_list_wrap = normal
|
||||
ij_kotlin_field_annotation_wrap = split_into_lines
|
||||
ij_kotlin_finally_on_new_line = false
|
||||
ij_kotlin_if_rparen_on_new_line = false
|
||||
ij_kotlin_import_nested_classes = false
|
||||
ij_kotlin_insert_whitespaces_in_simple_one_line_method = true
|
||||
ij_kotlin_keep_blank_lines_before_right_brace = 2
|
||||
ij_kotlin_keep_blank_lines_in_code = 2
|
||||
ij_kotlin_keep_blank_lines_in_declarations = 2
|
||||
ij_kotlin_keep_first_column_comment = true
|
||||
ij_kotlin_keep_indents_on_empty_lines = false
|
||||
ij_kotlin_keep_line_breaks = true
|
||||
ij_kotlin_lbrace_on_next_line = false
|
||||
ij_kotlin_line_comment_add_space = false
|
||||
ij_kotlin_line_comment_at_first_column = true
|
||||
ij_kotlin_method_annotation_wrap = split_into_lines
|
||||
ij_kotlin_method_call_chain_wrap = normal
|
||||
ij_kotlin_method_parameters_new_line_after_left_paren = true
|
||||
ij_kotlin_method_parameters_right_paren_on_new_line = true
|
||||
ij_kotlin_name_count_to_use_star_import = 9999
|
||||
ij_kotlin_name_count_to_use_star_import_for_members = 9999
|
||||
ij_kotlin_parameter_annotation_wrap = off
|
||||
ij_kotlin_space_after_comma = true
|
||||
ij_kotlin_space_after_extend_colon = true
|
||||
ij_kotlin_space_after_type_colon = true
|
||||
ij_kotlin_space_before_catch_parentheses = true
|
||||
ij_kotlin_space_before_comma = false
|
||||
ij_kotlin_space_before_extend_colon = true
|
||||
ij_kotlin_space_before_for_parentheses = true
|
||||
ij_kotlin_space_before_if_parentheses = true
|
||||
ij_kotlin_space_before_lambda_arrow = true
|
||||
ij_kotlin_space_before_type_colon = false
|
||||
ij_kotlin_space_before_when_parentheses = true
|
||||
ij_kotlin_space_before_while_parentheses = true
|
||||
ij_kotlin_spaces_around_additive_operators = true
|
||||
ij_kotlin_spaces_around_assignment_operators = true
|
||||
ij_kotlin_spaces_around_equality_operators = true
|
||||
ij_kotlin_spaces_around_function_type_arrow = true
|
||||
ij_kotlin_spaces_around_logical_operators = true
|
||||
ij_kotlin_spaces_around_multiplicative_operators = true
|
||||
ij_kotlin_spaces_around_range = false
|
||||
ij_kotlin_spaces_around_relational_operators = true
|
||||
ij_kotlin_spaces_around_unary_operator = false
|
||||
ij_kotlin_spaces_around_when_arrow = true
|
||||
ij_kotlin_variable_annotation_wrap = off
|
||||
ij_kotlin_while_on_new_line = false
|
||||
ij_kotlin_wrap_elvis_expressions = 1
|
||||
ij_kotlin_wrap_expression_body_functions = 1
|
||||
ij_kotlin_wrap_first_method_in_call_chain = false
|
||||
#compose
|
||||
ktlint_standard_filename = disabled
|
||||
ktlint_standard_no-wildcard-imports = disabled
|
||||
ktlint_standard_function-naming = disabled
|
||||
ktlint_standard_property-naming = disabled
|
||||
ktlint_standard_package-naming = disabled
|
||||
ktlint_function_naming_ignore_when_annotated_with = Composable
|
||||
ktlint_code_style = android_studio
|
||||
ktlint_standard_import-ordering = disabled
|
||||
ktlint_standard_package-naming = disabled
|
||||
ij_kotlin_allow_trailing_comma = true
|
||||
ij_kotlin_allow_trailing_comma_on_call_site = true
|
||||
@@ -1,22 +0,0 @@
|
||||
# Contributor Code of Conduct
|
||||
|
||||
## Pledge
|
||||
|
||||
We as individuals involved in this project, pledge to participate in this
|
||||
community in a respectful, constructive, and civil manner as we work towards a common goal
|
||||
of delivering free, open source, and value adding software for all.
|
||||
|
||||
## Standard
|
||||
|
||||
The standard for this community is the Golden Rule.
|
||||
|
||||
> “Do unto others as you would have them do unto you.”
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies to all spaces related to WG Tunnel.
|
||||
|
||||
## Incidents or Concerns
|
||||
|
||||
For any incidents or concerns, reach out to Zane at
|
||||
<support@zaneschepke.com>.
|
||||
@@ -1,2 +0,0 @@
|
||||
ko_fi: zaneschepke
|
||||
liberapay: zaneschepke
|
||||
@@ -1,33 +0,0 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: "[BUG] - Problem with app"
|
||||
labels: bug
|
||||
assignees: zaneschepke
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**Smartphone (please complete the following information):**
|
||||
|
||||
- Device: [e.g. Pixel 4a]
|
||||
- Android Version: [e.g. Android 13]
|
||||
- App Version [e.g. 3.3.3]
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots (Only if necessary)**
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
@@ -1,20 +0,0 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: "[FEATURE] - New feature request"
|
||||
labels: enhancement
|
||||
assignees: zaneschepke
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
@@ -1,17 +0,0 @@
|
||||
# Support
|
||||
|
||||
If you are experiencing issues with the app, the following resources are available to help you.
|
||||
|
||||
<ol>
|
||||
<li>
|
||||
See the app docs site <a href="https://zaneschepke.com/wgtunnel-docs/overview.html">here</a> (work in progress).
|
||||
</li>
|
||||
<li>
|
||||
Chat with me and our community on Discord <a href="https://discord.gg/rbRRNh6H7V">here</a>, or open an issue on GitHub <a href="https://github.com/zaneschepke/wgtunnel/issues/new/choose">here</a>.
|
||||
</li>
|
||||
<li>
|
||||
Send me an email <a href="mailto:zanecschepke@gmail.com">here</a>.
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
Thank you for using WG Tunnel.
|
||||
@@ -1,10 +0,0 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: github-actions
|
||||
directory: /
|
||||
schedule:
|
||||
interval: daily
|
||||
- package-ecosystem: gradle
|
||||
directory: /
|
||||
schedule:
|
||||
interval: daily
|
||||
@@ -1,23 +0,0 @@
|
||||
name: ci-android
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
format:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '17'
|
||||
cache: gradle
|
||||
|
||||
- name: Grant execute permission for gradlew
|
||||
run: chmod +x gradlew
|
||||
|
||||
- name: Run ktlint
|
||||
run: ./gradlew ktlintCheck
|
||||
@@ -1,20 +0,0 @@
|
||||
name: Issue Updates Workflow
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [ opened, closed, reopened ]
|
||||
|
||||
|
||||
jobs:
|
||||
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Send Telegram Message
|
||||
run: |
|
||||
msg_text='${{ github.actor }} updated an issue:
|
||||
status: ${{ github.event.issue.state }} - #${{ github.event.issue.number }} ${{ github.event.issue.title }}
|
||||
https://github.com/zaneschepke/wgtunnel/issues/${{ github.event.issue.number }}'
|
||||
curl -s -X POST 'https://api.telegram.org/bot${{ secrets.TELEGRAM_TOKEN }}/sendMessage' \
|
||||
-d "chat_id=${{ secrets.TELEGRAM_TO }}&text=${msg_text}&message_thread_id=${{ secrets.TELEGRAM_TOPIC }}"
|
||||
@@ -1,21 +0,0 @@
|
||||
name: Release Updates Workflow
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [ published ]
|
||||
|
||||
|
||||
jobs:
|
||||
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Send Telegram Message
|
||||
run: |
|
||||
msg_text='${{ github.actor }} published a new release:
|
||||
Release: ${{ github.event.release.tag_name }}
|
||||
${{ github.event.release.body }}
|
||||
https://github.com/zaneschepke/wgtunnel/releases/tag/${{ github.event.release.tag_name }}'
|
||||
curl -s -X POST 'https://api.telegram.org/bot${{ secrets.TELEGRAM_TOKEN }}/sendMessage' \
|
||||
-d "chat_id=${{ secrets.TELEGRAM_TO }}&text=${msg_text}&message_thread_id=${{ secrets.TELEGRAM_TOPIC }}"
|
||||
@@ -1,236 +0,0 @@
|
||||
name: release-android
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "4 3 * * *"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
track:
|
||||
type: choice
|
||||
description: "Google play release track"
|
||||
options:
|
||||
- none
|
||||
- internal
|
||||
- alpha
|
||||
- beta
|
||||
- production
|
||||
default: alpha
|
||||
required: true
|
||||
release_type:
|
||||
type: choice
|
||||
description: "GitHub release type"
|
||||
options:
|
||||
- none
|
||||
- prerelease
|
||||
- nightly
|
||||
- release
|
||||
default: release
|
||||
required: true
|
||||
tag_name:
|
||||
description: "Tag name for release"
|
||||
required: false
|
||||
default: nightly
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build Signed APK
|
||||
if: ${{ inputs.release_type != 'none' }}
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
env:
|
||||
SIGNING_KEY_ALIAS: ${{ secrets.SIGNING_KEY_ALIAS }}
|
||||
SIGNING_KEY_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD }}
|
||||
SIGNING_STORE_PASSWORD: ${{ secrets.SIGNING_STORE_PASSWORD }}
|
||||
KEY_STORE_FILE: 'android_keystore.jks'
|
||||
KEY_STORE_LOCATION: ${{ github.workspace }}/app/keystore/
|
||||
GH_USER: ${{ secrets.GH_USER }}
|
||||
# GH needed for gh cli
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
GH_REPO: ${{ github.repository }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '17'
|
||||
cache: gradle
|
||||
- name: Grant execute permission for gradlew
|
||||
run: chmod +x gradlew
|
||||
|
||||
- name: Install system dependencies
|
||||
run: |
|
||||
sudo apt update && sudo apt install -y gh apksigner
|
||||
|
||||
# Here we need to decode keystore.jks from base64 string and place it
|
||||
# in the folder specified in the release signing configuration
|
||||
- name: Decode Keystore
|
||||
id: decode_keystore
|
||||
uses: timheuer/base64-to-file@v1.2
|
||||
with:
|
||||
fileName: ${{ env.KEY_STORE_FILE }}
|
||||
fileDir: ${{ env.KEY_STORE_LOCATION }}
|
||||
encodedString: ${{ secrets.KEYSTORE }}
|
||||
|
||||
# create keystore path for gradle to read
|
||||
- name: Create keystore path env var
|
||||
run: |
|
||||
store_path=${{ env.KEY_STORE_LOCATION }}${{ env.KEY_STORE_FILE }}
|
||||
echo "KEY_STORE_PATH=$store_path" >> $GITHUB_ENV
|
||||
|
||||
- name: Create service_account.json
|
||||
id: createServiceAccount
|
||||
run: echo '${{ secrets.SERVICE_ACCOUNT_JSON }}' > service_account.json
|
||||
|
||||
# Build and sign APK ("-x test" argument is used to skip tests)
|
||||
# add fdroid flavor for apk upload
|
||||
- name: Build Fdroid Release APK
|
||||
if: ${{ inputs.release_type != '' && inputs.release_type == 'release' }}
|
||||
run: ./gradlew :app:assembleFdroidRelease -x test
|
||||
|
||||
- name: Build Fdroid Prerelease APK
|
||||
if: ${{ inputs.release_type != '' && inputs.release_type == 'prerelease' }}
|
||||
run: ./gradlew :app:assembleFdroidPrerelease -x test
|
||||
|
||||
- name: Build Fdroid Nightly APK
|
||||
if: ${{ inputs.release_type == '' || inputs.release_type == 'nightly' }}
|
||||
run: ./gradlew :app:assembleFdroidNightly -x test
|
||||
|
||||
- if: ${{ inputs.release_type == '' || inputs.release_type == 'nightly' }}
|
||||
run: echo "APK_PATH=$(find . -regex '^.*/build/outputs/apk/fdroid/nightly/.*\.apk$' -type f | head -1)" >> $GITHUB_ENV
|
||||
- if: ${{ inputs.release_type != '' && inputs.release_type == 'release' }}
|
||||
run: echo "APK_PATH=$(find . -regex '^.*/build/outputs/apk/fdroid/release/.*\.apk$' -type f | head -1)" >> $GITHUB_ENV
|
||||
- if: ${{ inputs.release_type != '' && inputs.release_type == 'prerelease' }}
|
||||
run: echo "APK_PATH=$(find . -regex '^.*/build/outputs/apk/fdroid/prerelease/.*\.apk$' -type f | head -1)" >> $GITHUB_ENV
|
||||
|
||||
- name: Get version code
|
||||
if: ${{ inputs.release_type == 'release' }}
|
||||
run: |
|
||||
version_code=$(grep "VERSION_CODE" buildSrc/src/main/kotlin/Constants.kt | awk '{print $5}' | tr -d '\n')
|
||||
echo "VERSION_CODE=$version_code" >> $GITHUB_ENV
|
||||
|
||||
# Save the APK after the Build job is complete to publish it as a Github release in the next job
|
||||
- name: Upload APK
|
||||
uses: actions/upload-artifact@v4.3.6
|
||||
with:
|
||||
name: wgtunnel
|
||||
path: ${{ env.APK_PATH }}
|
||||
|
||||
- name: Download APK from build
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: wgtunnel
|
||||
|
||||
- name: Repository Dispatch for my F-Droid repo
|
||||
uses: peter-evans/repository-dispatch@v3
|
||||
if: ${{ inputs.release_type == 'release' }}
|
||||
with:
|
||||
token: ${{ secrets.PAT }}
|
||||
repository: zaneschepke/fdroid
|
||||
event-type: fdroid-update
|
||||
|
||||
# Setup TAG_NAME, which is used as a general "name"
|
||||
- if: github.event_name == 'workflow_dispatch'
|
||||
run: echo "TAG_NAME=${{ github.event.inputs.tag_name }}" >> $GITHUB_ENV
|
||||
- if: github.event_name == 'schedule'
|
||||
run: echo "TAG_NAME=nightly" >> $GITHUB_ENV
|
||||
|
||||
- name: Set version release notes
|
||||
if: ${{ inputs.release_type == 'release' }}
|
||||
run: |
|
||||
RELEASE_NOTES="$(cat ${{ github.workspace }}/fastlane/metadata/android/en-US/changelogs/${{ env.VERSION_CODE }}.txt)"
|
||||
echo "RELEASE_NOTES<<EOF" >> $GITHUB_ENV
|
||||
echo "$RELEASE_NOTES" >> $GITHUB_ENV
|
||||
echo "EOF" >> $GITHUB_ENV
|
||||
|
||||
- name: On nightly release notes
|
||||
if: ${{ contains(env.TAG_NAME, 'nightly') }}
|
||||
run: |
|
||||
echo "RELEASE_NOTES=Nightly build for the latest development version of the app." >> $GITHUB_ENV
|
||||
gh release delete nightly --yes || true
|
||||
|
||||
- name: On prerelease release notes
|
||||
if: ${{ inputs.release_type == 'prerelease' }}
|
||||
run: |
|
||||
echo "RELEASE_NOTES=Testing version of app for specific feature." >> $GITHUB_ENV
|
||||
gh release delete ${{ github.event.inputs.tag_name }} --yes || true
|
||||
|
||||
- name: Get checksum
|
||||
id: checksum
|
||||
run: echo "checksum=$(apksigner verify -print-certs ${{ env.APK_PATH }} | grep -Po "(?<=SHA-256 digest:) .*" | tr -d "[:blank:]")" >> $GITHUB_OUTPUT
|
||||
|
||||
|
||||
- name: Create Release with Fastlane changelog notes
|
||||
id: create_release
|
||||
uses: softprops/action-gh-release@v2
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
body: |
|
||||
${{ env.RELEASE_NOTES }}
|
||||
|
||||
SHA256 fingerprint:
|
||||
```${{ steps.checksum.outputs.checksum }}```
|
||||
tag_name: ${{ env.TAG_NAME }}
|
||||
name: ${{ env.TAG_NAME }}
|
||||
draft: false
|
||||
prerelease: ${{ inputs.release_type == 'prerelease' || inputs.release_type == '' || inputs.release_type == 'nightly' }}
|
||||
make_latest: ${{ inputs.release_type == 'release' }}
|
||||
files: ${{ github.workspace }}/${{ env.APK_PATH }}
|
||||
|
||||
publish-play:
|
||||
if: ${{ inputs.track != 'none' && inputs.track != '' }}
|
||||
name: Publish to Google Play
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
env:
|
||||
SIGNING_KEY_ALIAS: ${{ secrets.SIGNING_KEY_ALIAS }}
|
||||
SIGNING_KEY_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD }}
|
||||
SIGNING_STORE_PASSWORD: ${{ secrets.SIGNING_STORE_PASSWORD }}
|
||||
KEY_STORE_FILE: 'android_keystore.jks'
|
||||
KEY_STORE_LOCATION: ${{ github.workspace }}/app/keystore/
|
||||
GH_USER: ${{ secrets.GH_USER }}
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '17'
|
||||
cache: gradle
|
||||
|
||||
- name: Grant execute permission for gradlew
|
||||
run: chmod +x gradlew
|
||||
|
||||
# Here we need to decode keystore.jks from base64 string and place it
|
||||
# in the folder specified in the release signing configuration
|
||||
- name: Decode Keystore
|
||||
id: decode_keystore
|
||||
uses: timheuer/base64-to-file@v1.2
|
||||
with:
|
||||
fileName: ${{ env.KEY_STORE_FILE }}
|
||||
fileDir: ${{ env.KEY_STORE_LOCATION }}
|
||||
encodedString: ${{ secrets.KEYSTORE }}
|
||||
|
||||
# create keystore path for gradle to read
|
||||
- name: Create keystore path env var
|
||||
run: |
|
||||
store_path=${{ env.KEY_STORE_LOCATION }}${{ env.KEY_STORE_FILE }}
|
||||
echo "KEY_STORE_PATH=$store_path" >> $GITHUB_ENV
|
||||
|
||||
- name: Create service_account.json
|
||||
id: createServiceAccount
|
||||
run: echo '${{ secrets.SERVICE_ACCOUNT_JSON }}' > service_account.json
|
||||
|
||||
- name: Deploy with fastlane
|
||||
uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: '3.2' # Not needed with a .ruby-version file
|
||||
bundler-cache: true
|
||||
|
||||
- name: Distribute app to Prod track 🚀
|
||||
run: (cd ${{ github.workspace }} && bundle install && bundle exec fastlane ${{ inputs.track }})
|
||||
|
||||
@@ -69,5 +69,3 @@ lint/tmp/
|
||||
# App Specific cases
|
||||
app/release/output.json
|
||||
.idea/codeStyles/
|
||||
# where we keep our signing secrets locally
|
||||
app/signing.properties
|
||||
|
||||
@@ -2,109 +2,61 @@
|
||||
WG Tunnel
|
||||
</h1>
|
||||
|
||||
<div align="center">
|
||||
<span align="center">
|
||||
|
||||
[](https://discord.gg/rbRRNh6H7V)
|
||||
[](https://twitter.com/i/communities/1780655267685736818)
|
||||
[](https://t.me/wgtunnel)
|
||||
[](https://opensource.org/licenses/MIT)
|
||||
[](https://discord.gg/rbRRNh6H7V)
|
||||
|
||||
</div>
|
||||
</span>
|
||||
|
||||
<div align="center">
|
||||
<span align="center">
|
||||
|
||||
|
||||
[](https://play.google.com/store/apps/details?id=com.zaneschepke.wireguardautotunnel)
|
||||
[](https://f-droid.org/packages/com.zaneschepke.wireguardautotunnel/)
|
||||
|
||||
</span>
|
||||
|
||||
</div>
|
||||
<span align="left">
|
||||
|
||||
This is an alternative Android Application for [WireGuard](https://www.wireguard.com/) with added features. Built using the [wireguard-android](https://github.com/WireGuard/wireguard-android) library and [Jetpack Compose](https://developer.android.com/jetpack/compose), this application was inspired by the official [WireGuard Android](https://github.com/WireGuard/wireguard-android) app.
|
||||
|
||||
<div align="left">
|
||||
</span>
|
||||
|
||||
This is an alternative Android Application for [WireGuard](https://www.wireguard.com/)
|
||||
and [AmneziaWG](https://docs.amnezia.org/documentation/amnezia-wg/) with added
|
||||
features. Built using the [wireguard-android](https://github.com/WireGuard/wireguard-android)
|
||||
library and [Jetpack Compose](https://developer.android.com/jetpack/compose), this application was
|
||||
inspired by the official [WireGuard Android](https://github.com/WireGuard/wireguard-android) app.
|
||||
|
||||
</div>
|
||||
|
||||
<div align="center">
|
||||
<span align="center">
|
||||
|
||||
## Screenshots
|
||||
|
||||
<p float="center">
|
||||
<img label="Main" style="padding-right:25px" src="fastlane/metadata/android/en-US/images/phoneScreenshots/main_screen.png" width="200" />
|
||||
<img label="Config" style="padding-left:25px" src="fastlane/metadata/android/en-US/images/phoneScreenshots/config_screen.png" width="200" />
|
||||
<img label="Settings" style="padding-left:25px" src="fastlane/metadata/android/en-US/images/phoneScreenshots/settings_screen.png" width="200" />
|
||||
<img label="Support" style="padding-left:25px" src="fastlane/metadata/android/en-US/images/phoneScreenshots/support_screen.png" width="200" />
|
||||
<img label="Main" style="padding-right:25px" src="asset/main_screen.png" width="200" />
|
||||
<img label="Config" style="padding-left:25px" src="./asset/config_screen.png" width="200" />
|
||||
<img label="Settings" style="padding-left:25px" src="asset/settings_screen.png" width="200" />
|
||||
<img label="Support" style="padding-left:25px" src="asset/support_screen.png" width="200" />
|
||||
</p>
|
||||
|
||||
<div align="left">
|
||||
<span align="left">
|
||||
|
||||
## Inspiration
|
||||
|
||||
The original inspiration for this app came from the inconvenience of having to manually turn VPN off
|
||||
and on while on different networks. This app was created to offer a free solution to this problem.
|
||||
The inspiration for this app came from the inconvenience of constantly having to turn VPN off and on while on different networks. With there being no free solution to this problem, this app was created to meet that need.
|
||||
|
||||
## Features
|
||||
|
||||
* Add tunnels via .conf file, zip, manual entry, or QR code
|
||||
* Auto connect to tunnels based on Wi-Fi SSID, ethernet, or mobile data
|
||||
* Split tunneling by application with search
|
||||
* WireGuard support for kernel and userspace modes
|
||||
* Amnezia support for userspace mode for DPI/censorship protection
|
||||
* Always-On VPN support
|
||||
* Export Amnezia and WireGuard tunnels to zip
|
||||
* Quick tile support for tunnel toggling, auto-tunneling
|
||||
* Static shortcuts support for tunnel toggling, auto-tunneling
|
||||
* Intent automation support for all tunnels
|
||||
* Automatic auto-tunneling service restart after reboot
|
||||
* Automatic tunnel restart after reboot
|
||||
* Battery preservation measures
|
||||
* Restart tunnel on ping failure (beta)
|
||||
* Add tunnels via .conf file
|
||||
* Auto connect to VPN based on Wi-Fi SSID
|
||||
* Split tunneling by application
|
||||
* Always-on VPN for Android support
|
||||
* Configurable Trusted Network list
|
||||
* Optional auto connect on mobile data
|
||||
* Automatic service restart after reboot
|
||||
* Service will stay running in background after app has been closed
|
||||
|
||||
## Fdroid
|
||||
|
||||
Want updates faster?
|
||||
|
||||
Check out my personal [fdroid repository](https://github.com/zaneschepke/fdroid) to get updates the
|
||||
moment they are released.
|
||||
|
||||
## Docs
|
||||
|
||||
Information about features, behaviors, and answers to common questions can be found in the
|
||||
app [documentation](https://zaneschepke.com/wgtunnel-docs/overview.html).
|
||||
|
||||
The repository for these docs can be found [here](https://github.com/zaneschepke/wgtunnel-docs).
|
||||
|
||||
## Translation
|
||||
|
||||
This app is using [Weblate](https://weblate.org) to assist with translations.
|
||||
|
||||
Help translate WG Tunnel into your language
|
||||
at [Hosted Weblate](https://hosted.weblate.org/engage/wg-tunnel/).\
|
||||
[](https://hosted.weblate.org/engage/wg-tunnel/)
|
||||
|
||||
## Building
|
||||
|
||||
|
||||
```
|
||||
$ git clone https://github.com/zaneschepke/wgtunnel
|
||||
$ cd wgtunnel
|
||||
$ ./gradlew assembleRelease
|
||||
```
|
||||
|
||||
And then build the app:
|
||||
|
||||
```
|
||||
$ ./gradlew assembleDebug
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
Any contributions in the form of feedback, issues, code, or translations are welcome and much
|
||||
appreciated!
|
||||
|
||||
Please read
|
||||
the [code of conduct](https://github.com/zaneschepke/wgtunnel?tab=coc-ov-file#contributor-code-of-conduct)
|
||||
before contributing.
|
||||
</span>
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
# Security Policy
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Please report security issues to `support@zaneschepke.com`
|
||||
+103
-179
@@ -1,209 +1,133 @@
|
||||
val rExtra = rootProject.extra
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.android.application)
|
||||
alias(libs.plugins.kotlin.android)
|
||||
alias(libs.plugins.hilt.android)
|
||||
alias(libs.plugins.kotlinxSerialization)
|
||||
alias(libs.plugins.ksp)
|
||||
alias(libs.plugins.compose.compiler)
|
||||
alias(libs.plugins.grgit)
|
||||
id("com.android.application")
|
||||
id("org.jetbrains.kotlin.android")
|
||||
kotlin("kapt")
|
||||
id("com.google.dagger.hilt.android")
|
||||
id("com.google.gms.google-services")
|
||||
id("com.google.firebase.crashlytics")
|
||||
id("org.jetbrains.kotlin.plugin.serialization")
|
||||
id("io.objectbox")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = Constants.APP_ID
|
||||
compileSdk = Constants.TARGET_SDK
|
||||
namespace = "com.zaneschepke.wireguardautotunnel"
|
||||
compileSdk = 34
|
||||
|
||||
androidResources {
|
||||
generateLocaleConfig = true
|
||||
}
|
||||
val versionMajor = 2
|
||||
val versionMinor = 2
|
||||
val versionPatch = 0
|
||||
val versionBuild = 0
|
||||
|
||||
defaultConfig {
|
||||
applicationId = Constants.APP_ID
|
||||
minSdk = Constants.MIN_SDK
|
||||
targetSdk = Constants.TARGET_SDK
|
||||
versionCode = determineVersionCode()
|
||||
versionName = determineVersionName()
|
||||
defaultConfig {
|
||||
applicationId = "com.zaneschepke.wireguardautotunnel"
|
||||
minSdk = 29
|
||||
targetSdk = 34
|
||||
versionCode = versionMajor * 10000 + versionMinor * 1000 + versionPatch * 100 + versionBuild
|
||||
versionName = "${versionMajor}.${versionMinor}.${versionPatch}"
|
||||
|
||||
ksp { arg("room.schemaLocation", "$projectDir/schemas") }
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
vectorDrawables {
|
||||
useSupportLibrary = true
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
getByName("debug").assets.srcDirs(files("$projectDir/schemas")) // Room
|
||||
}
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
vectorDrawables { useSupportLibrary = true }
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
create(Constants.RELEASE) {
|
||||
storeFile = getStoreFile()
|
||||
storePassword = getSigningProperty(Constants.STORE_PASS_VAR)
|
||||
keyAlias = getSigningProperty(Constants.KEY_ALIAS_VAR)
|
||||
keyPassword = getSigningProperty(Constants.KEY_PASS_VAR)
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
// don't strip
|
||||
packaging.jniLibs.keepDebugSymbols.addAll(
|
||||
listOf("libwg-go.so", "libwg-quick.so", "libwg.so"),
|
||||
)
|
||||
|
||||
release {
|
||||
isDebuggable = false
|
||||
isMinifyEnabled = true
|
||||
isShrinkResources = true
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro",
|
||||
)
|
||||
signingConfig = signingConfigs.getByName(Constants.RELEASE)
|
||||
}
|
||||
debug { isDebuggable = true }
|
||||
|
||||
create(Constants.PRERELEASE) {
|
||||
initWith(buildTypes.getByName(Constants.RELEASE))
|
||||
}
|
||||
|
||||
create(Constants.NIGHTLY) {
|
||||
initWith(buildTypes.getByName(Constants.RELEASE))
|
||||
}
|
||||
|
||||
applicationVariants.all {
|
||||
val variant = this
|
||||
variant.outputs
|
||||
.map { it as com.android.build.gradle.internal.api.BaseVariantOutputImpl }
|
||||
.forEach { output ->
|
||||
val outputFileName =
|
||||
"${Constants.APP_NAME}-${variant.flavorName}-" +
|
||||
"${variant.buildType.name}-${variant.versionName}.apk"
|
||||
output.outputFileName = outputFileName
|
||||
}
|
||||
}
|
||||
}
|
||||
flavorDimensions.add(Constants.TYPE)
|
||||
productFlavors {
|
||||
create("fdroid") {
|
||||
dimension = Constants.TYPE
|
||||
proguardFile("fdroid-rules.pro")
|
||||
}
|
||||
create("general") {
|
||||
dimension = Constants.TYPE
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
isCoreLibraryDesugaringEnabled = true
|
||||
}
|
||||
kotlinOptions { jvmTarget = Constants.JVM_TARGET }
|
||||
buildFeatures {
|
||||
compose = true
|
||||
buildConfig = true
|
||||
}
|
||||
packaging { resources { excludes += "/META-INF/{AL2.0,LGPL2.1}" } }
|
||||
buildTypes {
|
||||
release {
|
||||
isDebuggable = false
|
||||
isMinifyEnabled = false
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro"
|
||||
)
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = "17"
|
||||
}
|
||||
buildFeatures {
|
||||
compose = true
|
||||
}
|
||||
composeOptions {
|
||||
kotlinCompilerExtensionVersion = "1.4.8"
|
||||
}
|
||||
packaging {
|
||||
resources {
|
||||
excludes += "/META-INF/{AL2.0,LGPL2.1}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val generalImplementation by configurations
|
||||
|
||||
dependencies {
|
||||
implementation("androidx.core:core-ktx:1.10.1")
|
||||
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.1")
|
||||
implementation("androidx.activity:activity-compose:1.7.2")
|
||||
implementation(platform("androidx.compose:compose-bom:2023.03.00"))
|
||||
implementation("androidx.compose.ui:ui")
|
||||
implementation("androidx.compose.ui:ui-graphics")
|
||||
implementation("androidx.compose.ui:ui-tooling-preview")
|
||||
implementation("androidx.compose.material3:material3:1.1.1")
|
||||
implementation("androidx.appcompat:appcompat:1.6.1")
|
||||
|
||||
implementation(project(":logcatter"))
|
||||
testImplementation("junit:junit:4.13.2")
|
||||
androidTestImplementation("androidx.test.ext:junit:1.1.5")
|
||||
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
|
||||
androidTestImplementation(platform("androidx.compose:compose-bom:2023.03.00"))
|
||||
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
|
||||
debugImplementation("androidx.compose.ui:ui-tooling")
|
||||
debugImplementation("androidx.compose.ui:ui-test-manifest")
|
||||
|
||||
implementation(libs.androidx.core.ktx)
|
||||
implementation(libs.androidx.lifecycle.runtime.ktx)
|
||||
//wireguard tunnel
|
||||
implementation("com.wireguard.android:tunnel:1.0.20230427")
|
||||
|
||||
// helpers for implementing LifecycleOwner in a Service
|
||||
implementation(libs.androidx.lifecycle.service)
|
||||
implementation(libs.androidx.activity.compose)
|
||||
implementation(platform(libs.androidx.compose.bom))
|
||||
implementation(libs.androidx.compose.ui)
|
||||
implementation(libs.androidx.compose.ui.graphics)
|
||||
implementation(libs.androidx.compose.ui.tooling.preview)
|
||||
implementation(libs.androidx.material3)
|
||||
implementation(libs.androidx.appcompat)
|
||||
//logging
|
||||
implementation("com.jakewharton.timber:timber:5.0.1")
|
||||
|
||||
// test
|
||||
testImplementation(libs.junit)
|
||||
testImplementation(libs.androidx.junit)
|
||||
androidTestImplementation(libs.androidx.junit)
|
||||
androidTestImplementation(libs.androidx.espresso.core)
|
||||
androidTestImplementation(platform(libs.androidx.compose.bom))
|
||||
androidTestImplementation(libs.androidx.compose.ui.test)
|
||||
androidTestImplementation(libs.androidx.room.testing)
|
||||
debugImplementation(libs.androidx.compose.ui.tooling)
|
||||
debugImplementation(libs.androidx.compose.manifest)
|
||||
// compose navigation
|
||||
implementation("androidx.navigation:navigation-compose:2.6.0")
|
||||
implementation("androidx.hilt:hilt-navigation-compose:1.0.0")
|
||||
|
||||
// get tunnel lib from github packages or mavenLocal
|
||||
implementation(libs.tunnel)
|
||||
implementation(libs.amneziawg.android)
|
||||
coreLibraryDesugaring(libs.desugar.jdk.libs)
|
||||
// hilt
|
||||
implementation("com.google.dagger:hilt-android:${rExtra.get("hiltVersion")}")
|
||||
kapt("com.google.dagger:hilt-android-compiler:${rExtra.get("hiltVersion")}")
|
||||
|
||||
// logging
|
||||
implementation(libs.timber)
|
||||
//accompanist
|
||||
implementation("com.google.accompanist:accompanist-systemuicontroller:${rExtra.get("accompanistVersion")}")
|
||||
implementation("com.google.accompanist:accompanist-permissions:${rExtra.get("accompanistVersion")}")
|
||||
implementation("com.google.accompanist:accompanist-flowlayout:${rExtra.get("accompanistVersion")}")
|
||||
implementation("com.google.accompanist:accompanist-navigation-animation:${rExtra.get("accompanistVersion")}")
|
||||
implementation("com.google.accompanist:accompanist-drawablepainter:${rExtra.get("accompanistVersion")}")
|
||||
|
||||
// compose navigation
|
||||
implementation(libs.androidx.navigation.compose)
|
||||
implementation(libs.androidx.hilt.navigation.compose)
|
||||
//db
|
||||
implementation("io.objectbox:objectbox-kotlin:${rExtra.get("objectBoxVersion")}")
|
||||
|
||||
implementation(libs.zaneschepke.multifab)
|
||||
//lifecycle
|
||||
implementation("androidx.lifecycle:lifecycle-runtime-compose:2.6.1")
|
||||
|
||||
// hilt
|
||||
implementation(libs.hilt.android)
|
||||
ksp(libs.hilt.android.compiler)
|
||||
//icons
|
||||
implementation("androidx.compose.material:material-icons-extended:1.4.3")
|
||||
|
||||
// accompanist
|
||||
implementation(libs.accompanist.permissions)
|
||||
implementation(libs.accompanist.flowlayout)
|
||||
implementation(libs.accompanist.drawablepainter)
|
||||
|
||||
// storage
|
||||
implementation(libs.androidx.room.runtime)
|
||||
ksp(libs.androidx.room.compiler)
|
||||
implementation(libs.androidx.room.ktx)
|
||||
implementation(libs.androidx.datastore.preferences)
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1")
|
||||
|
||||
// lifecycle
|
||||
implementation(libs.lifecycle.runtime.compose)
|
||||
implementation(libs.androidx.lifecycle.runtime.ktx)
|
||||
implementation(libs.androidx.lifecycle.process)
|
||||
|
||||
// icons
|
||||
implementation(libs.material.icons.extended)
|
||||
// serialization
|
||||
implementation(libs.kotlinx.serialization.json)
|
||||
//firebase crashlytics
|
||||
implementation(platform("com.google.firebase:firebase-bom:32.0.0"))
|
||||
implementation("com.google.firebase:firebase-crashlytics-ktx")
|
||||
implementation("com.google.firebase:firebase-analytics-ktx")
|
||||
|
||||
// barcode scanning
|
||||
implementation(libs.zxing.android.embedded)
|
||||
//barcode scanning
|
||||
implementation("com.google.android.gms:play-services-code-scanner:16.0.0")
|
||||
|
||||
// bio
|
||||
implementation(libs.androidx.biometric.ktx)
|
||||
implementation(libs.pin.lock.compose)
|
||||
|
||||
// shortcuts
|
||||
implementation(libs.androidx.core)
|
||||
implementation(libs.androidx.core.google.shortcuts)
|
||||
|
||||
// splash
|
||||
implementation(libs.androidx.core.splashscreen)
|
||||
}
|
||||
|
||||
fun determineVersionCode(): Int {
|
||||
return with(getBuildTaskName().lowercase()) {
|
||||
when {
|
||||
contains(Constants.NIGHTLY) -> Constants.VERSION_CODE + Constants.NIGHTLY_CODE
|
||||
contains(Constants.PRERELEASE) -> Constants.VERSION_CODE + Constants.PRERELEASE_CODE
|
||||
else -> Constants.VERSION_CODE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun determineVersionName(): String {
|
||||
return with(getBuildTaskName().lowercase()) {
|
||||
when {
|
||||
contains(Constants.NIGHTLY) || contains(Constants.PRERELEASE) ->
|
||||
Constants.VERSION_NAME +
|
||||
"-${grgitService.service.get().grgit.head().abbreviatedId}"
|
||||
else -> Constants.VERSION_NAME
|
||||
}
|
||||
}
|
||||
}
|
||||
kapt {
|
||||
correctErrorTypes = true
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
-dontwarn com.google.errorprone.annotations.**
|
||||
|
||||
-keepclassmembers class * extends androidx.datastore.preferences.protobuf.GeneratedMessageLite {
|
||||
<fields>;
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"project_info": {
|
||||
"project_number": "328300975830",
|
||||
"project_id": "wireguard-auto-tunnel",
|
||||
"storage_bucket": "wireguard-auto-tunnel.appspot.com"
|
||||
},
|
||||
"client": [
|
||||
{
|
||||
"client_info": {
|
||||
"mobilesdk_app_id": "1:328300975830:android:82cd774598ccb7234b1b77",
|
||||
"android_client_info": {
|
||||
"package_name": "com.zaneschepke.wireguardautotunnel"
|
||||
}
|
||||
},
|
||||
"oauth_client": [
|
||||
{
|
||||
"client_id": "328300975830-m72lc3hr69ddhdqh9ngr27rvc8o0jb2d.apps.googleusercontent.com",
|
||||
"client_type": 3
|
||||
}
|
||||
],
|
||||
"api_key": [
|
||||
{
|
||||
"current_key": "AIzaSyBsSMY0LlckizXDnuYBy7nXWGSdl8zZedI"
|
||||
}
|
||||
],
|
||||
"services": {
|
||||
"appinvite_service": {
|
||||
"other_platform_oauth_client": [
|
||||
{
|
||||
"client_id": "328300975830-m72lc3hr69ddhdqh9ngr27rvc8o0jb2d.apps.googleusercontent.com",
|
||||
"client_type": 3
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"configuration_version": "1"
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
{
|
||||
"_note1": "KEEP THIS FILE! Check it into a version control system (VCS) like git.",
|
||||
"_note2": "ObjectBox manages crucial IDs for your object model. See docs for details.",
|
||||
"_note3": "If you have VCS merge conflicts, you must resolve them according to ObjectBox docs.",
|
||||
"entities": [
|
||||
{
|
||||
"id": "1:2692736974585027589",
|
||||
"lastPropertyId": "15:5057486545428188436",
|
||||
"name": "TunnelConfig",
|
||||
"properties": [
|
||||
{
|
||||
"id": "1:1985347930017457084",
|
||||
"name": "id",
|
||||
"type": 6,
|
||||
"flags": 1
|
||||
},
|
||||
{
|
||||
"id": "12:2409068226744965585",
|
||||
"name": "name",
|
||||
"indexId": "1:4811206443952699137",
|
||||
"type": 9,
|
||||
"flags": 34848
|
||||
},
|
||||
{
|
||||
"id": "13:8987443291286312275",
|
||||
"name": "wgQuick",
|
||||
"type": 9
|
||||
}
|
||||
],
|
||||
"relations": []
|
||||
},
|
||||
{
|
||||
"id": "2:8887605597748372702",
|
||||
"lastPropertyId": "9:4468844863383145378",
|
||||
"name": "Settings",
|
||||
"properties": [
|
||||
{
|
||||
"id": "1:7485739868216068651",
|
||||
"name": "id",
|
||||
"type": 6,
|
||||
"flags": 1
|
||||
},
|
||||
{
|
||||
"id": "2:5814013113141456749",
|
||||
"name": "isAutoTunnelEnabled",
|
||||
"type": 1
|
||||
},
|
||||
{
|
||||
"id": "4:5645665441196906014",
|
||||
"name": "trustedNetworkSSIDs",
|
||||
"type": 30
|
||||
},
|
||||
{
|
||||
"id": "5:4989886999117763881",
|
||||
"name": "isTunnelOnMobileDataEnabled",
|
||||
"type": 1
|
||||
},
|
||||
{
|
||||
"id": "6:3370284381040192129",
|
||||
"name": "defaultTunnel",
|
||||
"type": 9
|
||||
},
|
||||
{
|
||||
"id": "9:4468844863383145378",
|
||||
"name": "isAlwaysOnVpnEnabled",
|
||||
"type": 1
|
||||
}
|
||||
],
|
||||
"relations": []
|
||||
}
|
||||
],
|
||||
"lastEntityId": "2:8887605597748372702",
|
||||
"lastIndexId": "1:4811206443952699137",
|
||||
"lastRelationId": "0:0",
|
||||
"lastSequenceId": "0:0",
|
||||
"modelVersion": 5,
|
||||
"modelVersionParserMinimum": 5,
|
||||
"retiredEntityUids": [],
|
||||
"retiredIndexUids": [],
|
||||
"retiredPropertyUids": [
|
||||
1763475292291320186,
|
||||
6483820955437198310,
|
||||
8323071516033820771,
|
||||
5904440563612311217,
|
||||
1408037976996390989,
|
||||
7737847485212546994,
|
||||
8215616901775229364,
|
||||
8021610768066328637,
|
||||
6174306582797008721,
|
||||
2175939938544485767,
|
||||
7555225587864607050,
|
||||
969146862000617878,
|
||||
5057486545428188436,
|
||||
2814640993034665120,
|
||||
4981008812459251156
|
||||
],
|
||||
"retiredRelationUids": [],
|
||||
"version": 1
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
{
|
||||
"_note1": "KEEP THIS FILE! Check it into a version control system (VCS) like git.",
|
||||
"_note2": "ObjectBox manages crucial IDs for your object model. See docs for details.",
|
||||
"_note3": "If you have VCS merge conflicts, you must resolve them according to ObjectBox docs.",
|
||||
"entities": [
|
||||
{
|
||||
"id": "1:2692736974585027589",
|
||||
"lastPropertyId": "15:5057486545428188436",
|
||||
"name": "TunnelConfig",
|
||||
"properties": [
|
||||
{
|
||||
"id": "1:1985347930017457084",
|
||||
"name": "id",
|
||||
"type": 6,
|
||||
"flags": 1
|
||||
},
|
||||
{
|
||||
"id": "12:2409068226744965585",
|
||||
"name": "name",
|
||||
"indexId": "1:4811206443952699137",
|
||||
"type": 9,
|
||||
"flags": 34848
|
||||
},
|
||||
{
|
||||
"id": "13:8987443291286312275",
|
||||
"name": "wgQuick",
|
||||
"type": 9
|
||||
}
|
||||
],
|
||||
"relations": []
|
||||
},
|
||||
{
|
||||
"id": "2:8887605597748372702",
|
||||
"lastPropertyId": "8:4981008812459251156",
|
||||
"name": "Settings",
|
||||
"properties": [
|
||||
{
|
||||
"id": "1:7485739868216068651",
|
||||
"name": "id",
|
||||
"type": 6,
|
||||
"flags": 1
|
||||
},
|
||||
{
|
||||
"id": "2:5814013113141456749",
|
||||
"name": "isAutoTunnelEnabled",
|
||||
"type": 1
|
||||
},
|
||||
{
|
||||
"id": "4:5645665441196906014",
|
||||
"name": "trustedNetworkSSIDs",
|
||||
"type": 30
|
||||
},
|
||||
{
|
||||
"id": "5:4989886999117763881",
|
||||
"name": "isTunnelOnMobileDataEnabled",
|
||||
"type": 1
|
||||
},
|
||||
{
|
||||
"id": "6:3370284381040192129",
|
||||
"name": "defaultTunnel",
|
||||
"type": 9
|
||||
}
|
||||
],
|
||||
"relations": []
|
||||
}
|
||||
],
|
||||
"lastEntityId": "2:8887605597748372702",
|
||||
"lastIndexId": "1:4811206443952699137",
|
||||
"lastRelationId": "0:0",
|
||||
"lastSequenceId": "0:0",
|
||||
"modelVersion": 5,
|
||||
"modelVersionParserMinimum": 5,
|
||||
"retiredEntityUids": [],
|
||||
"retiredIndexUids": [],
|
||||
"retiredPropertyUids": [
|
||||
1763475292291320186,
|
||||
6483820955437198310,
|
||||
8323071516033820771,
|
||||
5904440563612311217,
|
||||
1408037976996390989,
|
||||
7737847485212546994,
|
||||
8215616901775229364,
|
||||
8021610768066328637,
|
||||
6174306582797008721,
|
||||
2175939938544485767,
|
||||
7555225587864607050,
|
||||
969146862000617878,
|
||||
5057486545428188436,
|
||||
2814640993034665120,
|
||||
4981008812459251156
|
||||
],
|
||||
"retiredRelationUids": [],
|
||||
"version": 1
|
||||
}
|
||||
Vendored
+1
-4
@@ -18,7 +18,4 @@
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
-keepclassmembers class * extends androidx.datastore.preferences.protobuf.GeneratedMessageLite {
|
||||
<fields>;
|
||||
}
|
||||
#-renamesourcefileattribute SourceFile
|
||||
@@ -1,112 +0,0 @@
|
||||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 1,
|
||||
"identityHash": "ba86153e6fb0b823197b987239b03e64",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "Settings",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_tunnel_enabled` INTEGER NOT NULL, `is_tunnel_on_mobile_data_enabled` INTEGER NOT NULL, `trusted_network_ssids` TEXT NOT NULL, `default_tunnel` TEXT, `is_always_on_vpn_enabled` INTEGER NOT NULL, `is_tunnel_on_ethernet_enabled` INTEGER NOT NULL)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "isAutoTunnelEnabled",
|
||||
"columnName": "is_tunnel_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "isTunnelOnMobileDataEnabled",
|
||||
"columnName": "is_tunnel_on_mobile_data_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "trustedNetworkSSIDs",
|
||||
"columnName": "trusted_network_ssids",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "defaultTunnel",
|
||||
"columnName": "default_tunnel",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "isAlwaysOnVpnEnabled",
|
||||
"columnName": "is_always_on_vpn_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "isTunnelOnEthernetEnabled",
|
||||
"columnName": "is_tunnel_on_ethernet_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": true,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "TunnelConfig",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `wg_quick` TEXT NOT NULL)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "wgQuick",
|
||||
"columnName": "wg_quick",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": true,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_TunnelConfig_name",
|
||||
"unique": true,
|
||||
"columnNames": [
|
||||
"name"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_TunnelConfig_name` ON `${TABLE_NAME}` (`name`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": []
|
||||
}
|
||||
],
|
||||
"views": [],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'ba86153e6fb0b823197b987239b03e64')"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,126 +0,0 @@
|
||||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 2,
|
||||
"identityHash": "65b1c9efff61712231fa64d1f19f3915",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "Settings",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_tunnel_enabled` INTEGER NOT NULL, `is_tunnel_on_mobile_data_enabled` INTEGER NOT NULL, `trusted_network_ssids` TEXT NOT NULL, `default_tunnel` TEXT, `is_always_on_vpn_enabled` INTEGER NOT NULL, `is_tunnel_on_ethernet_enabled` INTEGER NOT NULL, `is_shortcuts_enabled` INTEGER NOT NULL DEFAULT false, `is_battery_saver_enabled` INTEGER NOT NULL DEFAULT false)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "isAutoTunnelEnabled",
|
||||
"columnName": "is_tunnel_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "isTunnelOnMobileDataEnabled",
|
||||
"columnName": "is_tunnel_on_mobile_data_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "trustedNetworkSSIDs",
|
||||
"columnName": "trusted_network_ssids",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "defaultTunnel",
|
||||
"columnName": "default_tunnel",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "isAlwaysOnVpnEnabled",
|
||||
"columnName": "is_always_on_vpn_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "isTunnelOnEthernetEnabled",
|
||||
"columnName": "is_tunnel_on_ethernet_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "isShortcutsEnabled",
|
||||
"columnName": "is_shortcuts_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isBatterySaverEnabled",
|
||||
"columnName": "is_battery_saver_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": true,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "TunnelConfig",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `wg_quick` TEXT NOT NULL)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "wgQuick",
|
||||
"columnName": "wg_quick",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": true,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_TunnelConfig_name",
|
||||
"unique": true,
|
||||
"columnNames": [
|
||||
"name"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_TunnelConfig_name` ON `${TABLE_NAME}` (`name`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": []
|
||||
}
|
||||
],
|
||||
"views": [],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '65b1c9efff61712231fa64d1f19f3915')"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,133 +0,0 @@
|
||||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 3,
|
||||
"identityHash": "6b30daba29bb95f8ddc4d26206329d4f",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "Settings",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_tunnel_enabled` INTEGER NOT NULL, `is_tunnel_on_mobile_data_enabled` INTEGER NOT NULL, `trusted_network_ssids` TEXT NOT NULL, `default_tunnel` TEXT, `is_always_on_vpn_enabled` INTEGER NOT NULL, `is_tunnel_on_ethernet_enabled` INTEGER NOT NULL, `is_shortcuts_enabled` INTEGER NOT NULL DEFAULT false, `is_battery_saver_enabled` INTEGER NOT NULL DEFAULT false, `is_tunnel_on_wifi_enabled` INTEGER NOT NULL DEFAULT false)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "isAutoTunnelEnabled",
|
||||
"columnName": "is_tunnel_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "isTunnelOnMobileDataEnabled",
|
||||
"columnName": "is_tunnel_on_mobile_data_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "trustedNetworkSSIDs",
|
||||
"columnName": "trusted_network_ssids",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "defaultTunnel",
|
||||
"columnName": "default_tunnel",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "isAlwaysOnVpnEnabled",
|
||||
"columnName": "is_always_on_vpn_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "isTunnelOnEthernetEnabled",
|
||||
"columnName": "is_tunnel_on_ethernet_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "isShortcutsEnabled",
|
||||
"columnName": "is_shortcuts_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isBatterySaverEnabled",
|
||||
"columnName": "is_battery_saver_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isTunnelOnWifiEnabled",
|
||||
"columnName": "is_tunnel_on_wifi_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": true,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "TunnelConfig",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `wg_quick` TEXT NOT NULL)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "wgQuick",
|
||||
"columnName": "wg_quick",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": true,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_TunnelConfig_name",
|
||||
"unique": true,
|
||||
"columnNames": [
|
||||
"name"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_TunnelConfig_name` ON `${TABLE_NAME}` (`name`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": []
|
||||
}
|
||||
],
|
||||
"views": [],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '6b30daba29bb95f8ddc4d26206329d4f')"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,154 +0,0 @@
|
||||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 4,
|
||||
"identityHash": "aee55639422df8dadfe74c3bad204477",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "Settings",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_tunnel_enabled` INTEGER NOT NULL, `is_tunnel_on_mobile_data_enabled` INTEGER NOT NULL, `trusted_network_ssids` TEXT NOT NULL, `default_tunnel` TEXT, `is_always_on_vpn_enabled` INTEGER NOT NULL, `is_tunnel_on_ethernet_enabled` INTEGER NOT NULL, `is_shortcuts_enabled` INTEGER NOT NULL DEFAULT false, `is_battery_saver_enabled` INTEGER NOT NULL DEFAULT false, `is_tunnel_on_wifi_enabled` INTEGER NOT NULL DEFAULT false, `is_kernel_enabled` INTEGER NOT NULL DEFAULT false, `is_restore_on_boot_enabled` INTEGER NOT NULL DEFAULT false, `is_multi_tunnel_enabled` INTEGER NOT NULL DEFAULT false)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "isAutoTunnelEnabled",
|
||||
"columnName": "is_tunnel_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "isTunnelOnMobileDataEnabled",
|
||||
"columnName": "is_tunnel_on_mobile_data_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "trustedNetworkSSIDs",
|
||||
"columnName": "trusted_network_ssids",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "defaultTunnel",
|
||||
"columnName": "default_tunnel",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "isAlwaysOnVpnEnabled",
|
||||
"columnName": "is_always_on_vpn_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "isTunnelOnEthernetEnabled",
|
||||
"columnName": "is_tunnel_on_ethernet_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "isShortcutsEnabled",
|
||||
"columnName": "is_shortcuts_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isBatterySaverEnabled",
|
||||
"columnName": "is_battery_saver_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isTunnelOnWifiEnabled",
|
||||
"columnName": "is_tunnel_on_wifi_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isKernelEnabled",
|
||||
"columnName": "is_kernel_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isRestoreOnBootEnabled",
|
||||
"columnName": "is_restore_on_boot_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isMultiTunnelEnabled",
|
||||
"columnName": "is_multi_tunnel_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": true,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "TunnelConfig",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `wg_quick` TEXT NOT NULL)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "wgQuick",
|
||||
"columnName": "wg_quick",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": true,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_TunnelConfig_name",
|
||||
"unique": true,
|
||||
"columnNames": [
|
||||
"name"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_TunnelConfig_name` ON `${TABLE_NAME}` (`name`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": []
|
||||
}
|
||||
],
|
||||
"views": [],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'aee55639422df8dadfe74c3bad204477')"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,161 +0,0 @@
|
||||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 5,
|
||||
"identityHash": "bc15003a44746e18b9c260ec49737089",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "Settings",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_tunnel_enabled` INTEGER NOT NULL, `is_tunnel_on_mobile_data_enabled` INTEGER NOT NULL, `trusted_network_ssids` TEXT NOT NULL, `default_tunnel` TEXT, `is_always_on_vpn_enabled` INTEGER NOT NULL, `is_tunnel_on_ethernet_enabled` INTEGER NOT NULL, `is_shortcuts_enabled` INTEGER NOT NULL DEFAULT false, `is_battery_saver_enabled` INTEGER NOT NULL DEFAULT false, `is_tunnel_on_wifi_enabled` INTEGER NOT NULL DEFAULT false, `is_kernel_enabled` INTEGER NOT NULL DEFAULT false, `is_restore_on_boot_enabled` INTEGER NOT NULL DEFAULT false, `is_multi_tunnel_enabled` INTEGER NOT NULL DEFAULT false, `is_auto_tunnel_paused` INTEGER NOT NULL DEFAULT false)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "isAutoTunnelEnabled",
|
||||
"columnName": "is_tunnel_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "isTunnelOnMobileDataEnabled",
|
||||
"columnName": "is_tunnel_on_mobile_data_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "trustedNetworkSSIDs",
|
||||
"columnName": "trusted_network_ssids",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "defaultTunnel",
|
||||
"columnName": "default_tunnel",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "isAlwaysOnVpnEnabled",
|
||||
"columnName": "is_always_on_vpn_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "isTunnelOnEthernetEnabled",
|
||||
"columnName": "is_tunnel_on_ethernet_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "isShortcutsEnabled",
|
||||
"columnName": "is_shortcuts_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isBatterySaverEnabled",
|
||||
"columnName": "is_battery_saver_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isTunnelOnWifiEnabled",
|
||||
"columnName": "is_tunnel_on_wifi_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isKernelEnabled",
|
||||
"columnName": "is_kernel_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isRestoreOnBootEnabled",
|
||||
"columnName": "is_restore_on_boot_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isMultiTunnelEnabled",
|
||||
"columnName": "is_multi_tunnel_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isAutoTunnelPaused",
|
||||
"columnName": "is_auto_tunnel_paused",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": true,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "TunnelConfig",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `wg_quick` TEXT NOT NULL)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "wgQuick",
|
||||
"columnName": "wg_quick",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": true,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_TunnelConfig_name",
|
||||
"unique": true,
|
||||
"columnNames": [
|
||||
"name"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_TunnelConfig_name` ON `${TABLE_NAME}` (`name`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": []
|
||||
}
|
||||
],
|
||||
"views": [],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'bc15003a44746e18b9c260ec49737089')"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,168 +0,0 @@
|
||||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 6,
|
||||
"identityHash": "625820076477aca948536f7bccccc7ca",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "Settings",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_tunnel_enabled` INTEGER NOT NULL, `is_tunnel_on_mobile_data_enabled` INTEGER NOT NULL, `trusted_network_ssids` TEXT NOT NULL, `default_tunnel` TEXT, `is_always_on_vpn_enabled` INTEGER NOT NULL, `is_tunnel_on_ethernet_enabled` INTEGER NOT NULL, `is_shortcuts_enabled` INTEGER NOT NULL DEFAULT false, `is_battery_saver_enabled` INTEGER NOT NULL DEFAULT false, `is_tunnel_on_wifi_enabled` INTEGER NOT NULL DEFAULT false, `is_kernel_enabled` INTEGER NOT NULL DEFAULT false, `is_restore_on_boot_enabled` INTEGER NOT NULL DEFAULT false, `is_multi_tunnel_enabled` INTEGER NOT NULL DEFAULT false, `is_auto_tunnel_paused` INTEGER NOT NULL DEFAULT false, `is_ping_enabled` INTEGER NOT NULL DEFAULT false)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "isAutoTunnelEnabled",
|
||||
"columnName": "is_tunnel_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "isTunnelOnMobileDataEnabled",
|
||||
"columnName": "is_tunnel_on_mobile_data_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "trustedNetworkSSIDs",
|
||||
"columnName": "trusted_network_ssids",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "defaultTunnel",
|
||||
"columnName": "default_tunnel",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "isAlwaysOnVpnEnabled",
|
||||
"columnName": "is_always_on_vpn_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "isTunnelOnEthernetEnabled",
|
||||
"columnName": "is_tunnel_on_ethernet_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "isShortcutsEnabled",
|
||||
"columnName": "is_shortcuts_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isBatterySaverEnabled",
|
||||
"columnName": "is_battery_saver_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isTunnelOnWifiEnabled",
|
||||
"columnName": "is_tunnel_on_wifi_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isKernelEnabled",
|
||||
"columnName": "is_kernel_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isRestoreOnBootEnabled",
|
||||
"columnName": "is_restore_on_boot_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isMultiTunnelEnabled",
|
||||
"columnName": "is_multi_tunnel_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isAutoTunnelPaused",
|
||||
"columnName": "is_auto_tunnel_paused",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isPingEnabled",
|
||||
"columnName": "is_ping_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": true,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "TunnelConfig",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `wg_quick` TEXT NOT NULL)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "wgQuick",
|
||||
"columnName": "wg_quick",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": true,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_TunnelConfig_name",
|
||||
"unique": true,
|
||||
"columnNames": [
|
||||
"name"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_TunnelConfig_name` ON `${TABLE_NAME}` (`name`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": []
|
||||
}
|
||||
],
|
||||
"views": [],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '625820076477aca948536f7bccccc7ca')"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,176 +0,0 @@
|
||||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 7,
|
||||
"identityHash": "e65e4e7cf01f50fb03196d47b54288b1",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "Settings",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_tunnel_enabled` INTEGER NOT NULL, `is_tunnel_on_mobile_data_enabled` INTEGER NOT NULL, `trusted_network_ssids` TEXT NOT NULL, `is_always_on_vpn_enabled` INTEGER NOT NULL, `is_tunnel_on_ethernet_enabled` INTEGER NOT NULL, `is_shortcuts_enabled` INTEGER NOT NULL DEFAULT false, `is_tunnel_on_wifi_enabled` INTEGER NOT NULL DEFAULT false, `is_kernel_enabled` INTEGER NOT NULL DEFAULT false, `is_restore_on_boot_enabled` INTEGER NOT NULL DEFAULT false, `is_multi_tunnel_enabled` INTEGER NOT NULL DEFAULT false, `is_auto_tunnel_paused` INTEGER NOT NULL DEFAULT false, `is_ping_enabled` INTEGER NOT NULL DEFAULT false)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "isAutoTunnelEnabled",
|
||||
"columnName": "is_tunnel_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "isTunnelOnMobileDataEnabled",
|
||||
"columnName": "is_tunnel_on_mobile_data_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "trustedNetworkSSIDs",
|
||||
"columnName": "trusted_network_ssids",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "isAlwaysOnVpnEnabled",
|
||||
"columnName": "is_always_on_vpn_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "isTunnelOnEthernetEnabled",
|
||||
"columnName": "is_tunnel_on_ethernet_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "isShortcutsEnabled",
|
||||
"columnName": "is_shortcuts_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isTunnelOnWifiEnabled",
|
||||
"columnName": "is_tunnel_on_wifi_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isKernelEnabled",
|
||||
"columnName": "is_kernel_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isRestoreOnBootEnabled",
|
||||
"columnName": "is_restore_on_boot_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isMultiTunnelEnabled",
|
||||
"columnName": "is_multi_tunnel_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isAutoTunnelPaused",
|
||||
"columnName": "is_auto_tunnel_paused",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isPingEnabled",
|
||||
"columnName": "is_ping_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": true,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "TunnelConfig",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `wg_quick` TEXT NOT NULL, `tunnel_networks` TEXT NOT NULL DEFAULT '', `is_mobile_data_tunnel` INTEGER NOT NULL DEFAULT false, `is_primary_tunnel` INTEGER NOT NULL DEFAULT false)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "wgQuick",
|
||||
"columnName": "wg_quick",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "tunnelNetworks",
|
||||
"columnName": "tunnel_networks",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true,
|
||||
"defaultValue": "''"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isMobileDataTunnel",
|
||||
"columnName": "is_mobile_data_tunnel",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isPrimaryTunnel",
|
||||
"columnName": "is_primary_tunnel",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": true,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_TunnelConfig_name",
|
||||
"unique": true,
|
||||
"columnNames": [
|
||||
"name"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_TunnelConfig_name` ON `${TABLE_NAME}` (`name`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": []
|
||||
}
|
||||
],
|
||||
"views": [],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'e65e4e7cf01f50fb03196d47b54288b1')"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,190 +0,0 @@
|
||||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 8,
|
||||
"identityHash": "b4d4a7c489f6b2f0d3aa4fa6f37b4935",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "Settings",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_tunnel_enabled` INTEGER NOT NULL, `is_tunnel_on_mobile_data_enabled` INTEGER NOT NULL, `trusted_network_ssids` TEXT NOT NULL, `is_always_on_vpn_enabled` INTEGER NOT NULL, `is_tunnel_on_ethernet_enabled` INTEGER NOT NULL, `is_shortcuts_enabled` INTEGER NOT NULL DEFAULT false, `is_tunnel_on_wifi_enabled` INTEGER NOT NULL DEFAULT false, `is_kernel_enabled` INTEGER NOT NULL DEFAULT false, `is_restore_on_boot_enabled` INTEGER NOT NULL DEFAULT false, `is_multi_tunnel_enabled` INTEGER NOT NULL DEFAULT false, `is_auto_tunnel_paused` INTEGER NOT NULL DEFAULT false, `is_ping_enabled` INTEGER NOT NULL DEFAULT false, `is_amnezia_enabled` INTEGER NOT NULL DEFAULT false)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "isAutoTunnelEnabled",
|
||||
"columnName": "is_tunnel_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "isTunnelOnMobileDataEnabled",
|
||||
"columnName": "is_tunnel_on_mobile_data_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "trustedNetworkSSIDs",
|
||||
"columnName": "trusted_network_ssids",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "isAlwaysOnVpnEnabled",
|
||||
"columnName": "is_always_on_vpn_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "isTunnelOnEthernetEnabled",
|
||||
"columnName": "is_tunnel_on_ethernet_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "isShortcutsEnabled",
|
||||
"columnName": "is_shortcuts_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isTunnelOnWifiEnabled",
|
||||
"columnName": "is_tunnel_on_wifi_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isKernelEnabled",
|
||||
"columnName": "is_kernel_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isRestoreOnBootEnabled",
|
||||
"columnName": "is_restore_on_boot_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isMultiTunnelEnabled",
|
||||
"columnName": "is_multi_tunnel_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isAutoTunnelPaused",
|
||||
"columnName": "is_auto_tunnel_paused",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isPingEnabled",
|
||||
"columnName": "is_ping_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isAmneziaEnabled",
|
||||
"columnName": "is_amnezia_enabled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": true,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "TunnelConfig",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `wg_quick` TEXT NOT NULL, `tunnel_networks` TEXT NOT NULL DEFAULT '', `is_mobile_data_tunnel` INTEGER NOT NULL DEFAULT false, `is_primary_tunnel` INTEGER NOT NULL DEFAULT false, `am_quick` TEXT NOT NULL DEFAULT '')",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "wgQuick",
|
||||
"columnName": "wg_quick",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "tunnelNetworks",
|
||||
"columnName": "tunnel_networks",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true,
|
||||
"defaultValue": "''"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isMobileDataTunnel",
|
||||
"columnName": "is_mobile_data_tunnel",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isPrimaryTunnel",
|
||||
"columnName": "is_primary_tunnel",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "amQuick",
|
||||
"columnName": "am_quick",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true,
|
||||
"defaultValue": "''"
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": true,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_TunnelConfig_name",
|
||||
"unique": true,
|
||||
"columnNames": [
|
||||
"name"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_TunnelConfig_name` ON `${TABLE_NAME}` (`name`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": []
|
||||
}
|
||||
],
|
||||
"views": [],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'b4d4a7c489f6b2f0d3aa4fa6f37b4935')"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
SIGNING_STORE_PASSWORD=
|
||||
SIGNING_KEY_ALIAS=
|
||||
SIGNING_KEY_PASSWORD=
|
||||
KEY_STORE_PATH=/
|
||||
+11
-9
@@ -1,11 +1,13 @@
|
||||
package com.zaneschepke.wireguardautotunnel
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import org.junit.Assert.assertEquals
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
@@ -13,10 +15,10 @@ import org.junit.runner.RunWith
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ExampleInstrumentedTest {
|
||||
@Test
|
||||
fun useAppContext() {
|
||||
// Context of the app under test.
|
||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
assertEquals("com.zaneschepke.wireguardautotunnel", appContext.packageName)
|
||||
}
|
||||
}
|
||||
@Test
|
||||
fun useAppContext() {
|
||||
// Context of the app under test.
|
||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
assertEquals("com.zaneschepke.wireguardautotunnel", appContext.packageName)
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel
|
||||
|
||||
import androidx.room.testing.MigrationTestHelper
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import com.zaneschepke.wireguardautotunnel.data.AppDatabase
|
||||
import com.zaneschepke.wireguardautotunnel.data.Queries
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import java.io.IOException
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class MigrationTest {
|
||||
private val dbName = "migration-test"
|
||||
|
||||
@get:Rule
|
||||
val helper: MigrationTestHelper =
|
||||
MigrationTestHelper(
|
||||
InstrumentationRegistry.getInstrumentation(),
|
||||
AppDatabase::class.java,
|
||||
)
|
||||
|
||||
@Test
|
||||
@Throws(IOException::class)
|
||||
fun migrate6To7() {
|
||||
helper.createDatabase(dbName, 6).apply {
|
||||
// Database has schema version 1. Insert some data using SQL queries.
|
||||
// You can't use DAO classes because they expect the latest schema.
|
||||
execSQL(Queries.createDefaultSettings())
|
||||
execSQL(
|
||||
Queries.createTunnelConfig(),
|
||||
)
|
||||
// Prepare for the next version.
|
||||
close()
|
||||
}
|
||||
|
||||
// Re-open the database with version 2 and provide
|
||||
// MIGRATION_1_2 as the migration process.
|
||||
helper.runMigrationsAndValidate(dbName, 7, true)
|
||||
// MigrationTestHelper automatically verifies the schema changes,
|
||||
// but you need to validate that the data was migrated properly.
|
||||
}
|
||||
}
|
||||
@@ -1,181 +1,98 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission
|
||||
android:name="android.permission.READ_EXTERNAL_STORAGE"
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="32" />
|
||||
<uses-permission
|
||||
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="32"
|
||||
tools:ignore="ScopedStorage" />
|
||||
<uses-permission
|
||||
android:name="android.permission.ACCESS_WIFI_STATE"
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"
|
||||
android:maxSdkVersion="30"
|
||||
tools:ignore="LeanbackUsesWifi" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
|
||||
<!--foreground service exempt android 14-->
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SYSTEM_EXEMPTED" />
|
||||
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
|
||||
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
||||
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION"/>
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_REMOTE_MESSAGING"/>
|
||||
<!--foreground service permissions-->
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
|
||||
<!--start service on boot permission-->
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
|
||||
<!--android tv support-->
|
||||
<uses-feature
|
||||
android:name="android.software.leanback"
|
||||
<uses-feature android:name="android.software.leanback"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.touchscreen"
|
||||
<uses-feature android:name="android.hardware.touchscreen"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.location.gps"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.screen.portrait"
|
||||
android:required="false" />
|
||||
|
||||
<queries>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
</intent>
|
||||
</queries>
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:name=".WireGuardAutoTunnel"
|
||||
android:allowBackup="false"
|
||||
android:banner="@drawable/ic_banner"
|
||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||
android:enableOnBackInvokedCallback="true"
|
||||
android:fullBackupContent="@xml/backup_rules"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:banner="@mipmap/ic_launcher_foreground"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.AppSplashScreen"
|
||||
tools:targetApi="tiramisu">
|
||||
<activity
|
||||
android:name=".ui.SplashActivity"
|
||||
android:exported="true"
|
||||
android:theme="@style/Theme.AppSplashScreen">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
|
||||
<action android:name="android.service.quicksettings.action.QS_TILE_PREFERENCES" />
|
||||
</intent-filter>
|
||||
<meta-data
|
||||
android:name="android.app.shortcuts"
|
||||
android:resource="@xml/shortcuts" />
|
||||
</activity>
|
||||
android:theme="@style/Theme.WireguardAutoTunnel"
|
||||
tools:targetApi="31">
|
||||
<activity
|
||||
android:name=".ui.MainActivity"
|
||||
android:exported="true"
|
||||
android:theme="@style/Theme.WireguardAutoTunnel">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name="com.journeyapps.barcodescanner.CaptureActivity"
|
||||
android:screenOrientation="portrait"
|
||||
tools:replace="screenOrientation" />
|
||||
|
||||
<activity
|
||||
android:name=".service.shortcut.ShortcutsActivity"
|
||||
android:enabled="true"
|
||||
android:exported="true"
|
||||
android:noHistory="true"
|
||||
android:excludeFromRecents="true"
|
||||
android:finishOnTaskLaunch="true"
|
||||
android:launchMode="singleInstance"
|
||||
android:theme="@android:style/Theme.NoDisplay" />
|
||||
|
||||
<service
|
||||
android:name=".service.foreground.ForegroundService"
|
||||
android:enabled="true"
|
||||
android:exported="false"
|
||||
android:foregroundServiceType="systemExempted"
|
||||
tools:node="merge" />
|
||||
<service
|
||||
android:name=".service.tile.TunnelControlTile"
|
||||
android:exported="true"
|
||||
android:icon="@drawable/ic_launcher"
|
||||
android:label="Tunnel control"
|
||||
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
|
||||
<meta-data
|
||||
android:name="android.service.quicksettings.ACTIVE_TILE"
|
||||
android:value="true" />
|
||||
<meta-data
|
||||
android:name="android.service.quicksettings.TOGGLEABLE_TILE"
|
||||
android:value="true" />
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.service.quicksettings.action.QS_TILE" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
<service
|
||||
android:name=".service.tile.AutoTunnelControlTile"
|
||||
android:exported="true"
|
||||
android:icon="@drawable/ic_launcher"
|
||||
android:label="Auto-tunnel"
|
||||
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
|
||||
<meta-data
|
||||
android:name="android.service.quicksettings.ACTIVE_TILE"
|
||||
android:value="true" />
|
||||
<meta-data
|
||||
android:name="android.service.quicksettings.TOGGLEABLE_TILE"
|
||||
android:value="true" />
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.service.quicksettings.action.QS_TILE" />
|
||||
</intent-filter>
|
||||
android:foregroundServiceType="remoteMessaging"
|
||||
android:exported="false">
|
||||
</service>
|
||||
<service
|
||||
android:name=".service.foreground.WireGuardTunnelService"
|
||||
android:enabled="true"
|
||||
android:exported="false"
|
||||
android:foregroundServiceType="systemExempted"
|
||||
android:permission="android.permission.BIND_VPN_SERVICE"
|
||||
android:persistent="true"
|
||||
tools:node="merge">
|
||||
android:enabled="true"
|
||||
android:foregroundServiceType="remoteMessaging"
|
||||
android:exported="false">
|
||||
<intent-filter>
|
||||
<action android:name="android.net.VpnService" />
|
||||
<action android:name="android.net.VpnService"/>
|
||||
</intent-filter>
|
||||
<meta-data
|
||||
android:name="android.net.VpnService.SUPPORTS_ALWAYS_ON"
|
||||
android:value="true" />
|
||||
<meta-data android:name="android.net.VpnService.SUPPORTS_ALWAYS_ON"
|
||||
android:value="true"/>
|
||||
</service>
|
||||
<service
|
||||
android:name=".service.foreground.WireGuardConnectivityWatcherService"
|
||||
android:enabled="true"
|
||||
android:exported="false"
|
||||
android:foregroundServiceType="systemExempted"
|
||||
android:persistent="true"
|
||||
android:stopWithTask="false"
|
||||
tools:node="merge" />
|
||||
|
||||
<receiver
|
||||
android:name=".receiver.BootReceiver"
|
||||
android:enabled="true"
|
||||
android:foregroundServiceType="location"
|
||||
android:permission=""
|
||||
android:exported="false">
|
||||
</service>
|
||||
<receiver android:enabled="true" android:name=".receiver.BootReceiver"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||
<action android:name="android.intent.action.ACTION_BOOT_COMPLETED" />
|
||||
<action android:name="android.intent.action.QUICKBOOT_POWERON" />
|
||||
<action android:name="com.htc.intent.action.QUICKBOOT_POWERON" />
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED"/>
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
<receiver
|
||||
android:name=".receiver.NotificationActionReceiver"
|
||||
android:exported="false" />
|
||||
<receiver android:exported="false" android:name=".receiver.NotificationActionReceiver"/>
|
||||
<meta-data
|
||||
android:name="com.google.mlkit.vision.DEPENDENCIES"
|
||||
android:value="barcode_ui"/>
|
||||
<meta-data
|
||||
android:name="firebase_crashlytics_collection_enabled"
|
||||
android:value="true" />
|
||||
</application>
|
||||
</manifest>
|
||||
</manifest>
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 38 KiB |
@@ -1,57 +1,25 @@
|
||||
package com.zaneschepke.wireguardautotunnel
|
||||
|
||||
import android.app.Application
|
||||
import android.content.ComponentName
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.StrictMode
|
||||
import android.os.StrictMode.ThreadPolicy
|
||||
import android.service.quicksettings.TileService
|
||||
import com.zaneschepke.wireguardautotunnel.service.tile.AutoTunnelControlTile
|
||||
import com.zaneschepke.wireguardautotunnel.service.tile.TunnelControlTile
|
||||
import com.zaneschepke.wireguardautotunnel.util.ReleaseTree
|
||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||
import com.zaneschepke.wireguardautotunnel.repository.Repository
|
||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.model.Settings
|
||||
import dagger.hilt.android.HiltAndroidApp
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltAndroidApp
|
||||
class WireGuardAutoTunnel : Application() {
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
instance = this
|
||||
if (BuildConfig.DEBUG) {
|
||||
Timber.plant(Timber.DebugTree())
|
||||
StrictMode.setThreadPolicy(
|
||||
ThreadPolicy.Builder()
|
||||
.detectDiskReads()
|
||||
.detectDiskWrites()
|
||||
.detectNetwork()
|
||||
.penaltyLog()
|
||||
.build(),
|
||||
)
|
||||
} else {
|
||||
Timber.plant(ReleaseTree())
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
lateinit var instance: WireGuardAutoTunnel
|
||||
private set
|
||||
@Inject
|
||||
lateinit var settingsRepo : Repository<Settings>
|
||||
|
||||
fun isRunningOnAndroidTv(): Boolean {
|
||||
return instance.packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)
|
||||
}
|
||||
|
||||
fun requestTunnelTileServiceStateUpdate() {
|
||||
TileService.requestListeningState(
|
||||
instance,
|
||||
ComponentName(instance, TunnelControlTile::class.java),
|
||||
)
|
||||
}
|
||||
|
||||
fun requestAutoTunnelTileServiceUpdate() {
|
||||
TileService.requestListeningState(
|
||||
instance,
|
||||
ComponentName(instance, AutoTunnelControlTile::class.java),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
if(BuildConfig.DEBUG) {
|
||||
FirebaseCrashlytics.getInstance().setCrashlyticsCollectionEnabled(false);
|
||||
Timber.plant(Timber.DebugTree())
|
||||
}
|
||||
settingsRepo.init()
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.data
|
||||
|
||||
import androidx.room.AutoMigration
|
||||
import androidx.room.Database
|
||||
import androidx.room.DeleteColumn
|
||||
import androidx.room.RoomDatabase
|
||||
import androidx.room.TypeConverters
|
||||
import androidx.room.migration.AutoMigrationSpec
|
||||
import com.zaneschepke.wireguardautotunnel.data.domain.Settings
|
||||
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
||||
|
||||
@Database(
|
||||
entities = [Settings::class, TunnelConfig::class],
|
||||
version = 8,
|
||||
autoMigrations =
|
||||
[
|
||||
AutoMigration(from = 1, to = 2),
|
||||
AutoMigration(from = 2, to = 3),
|
||||
AutoMigration(
|
||||
from = 3,
|
||||
to = 4,
|
||||
),
|
||||
AutoMigration(
|
||||
from = 4,
|
||||
to = 5,
|
||||
),
|
||||
AutoMigration(
|
||||
from = 5,
|
||||
to = 6,
|
||||
),
|
||||
AutoMigration(
|
||||
from = 6,
|
||||
to = 7,
|
||||
spec = RemoveLegacySettingColumnsMigration::class,
|
||||
),
|
||||
AutoMigration(7, 8),
|
||||
],
|
||||
exportSchema = true,
|
||||
)
|
||||
@TypeConverters(DatabaseListConverters::class)
|
||||
abstract class AppDatabase : RoomDatabase() {
|
||||
abstract fun settingDao(): SettingsDao
|
||||
|
||||
abstract fun tunnelConfigDoa(): TunnelConfigDao
|
||||
}
|
||||
|
||||
@DeleteColumn(
|
||||
tableName = "Settings",
|
||||
columnName = "default_tunnel",
|
||||
)
|
||||
@DeleteColumn(
|
||||
tableName = "Settings",
|
||||
columnName = "is_battery_saver_enabled",
|
||||
)
|
||||
class RemoveLegacySettingColumnsMigration : AutoMigrationSpec
|
||||
@@ -1,21 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.data
|
||||
|
||||
import androidx.room.RoomDatabase
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||
import timber.log.Timber
|
||||
|
||||
class DatabaseCallback : RoomDatabase.Callback() {
|
||||
override fun onCreate(db: SupportSQLiteDatabase) = db.run {
|
||||
// Notice non-ui thread is here
|
||||
beginTransaction()
|
||||
try {
|
||||
execSQL(Queries.createDefaultSettings())
|
||||
Timber.i("Bootstrapping settings data")
|
||||
setTransactionSuccessful()
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e)
|
||||
} finally {
|
||||
endTransaction()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.data
|
||||
|
||||
import androidx.room.TypeConverter
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
class DatabaseListConverters {
|
||||
@TypeConverter
|
||||
fun listToString(value: MutableList<String>): String {
|
||||
return Json.encodeToString(value)
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
fun stringToList(value: String): MutableList<String> {
|
||||
if (value.isBlank() || value.isEmpty()) return mutableListOf()
|
||||
return try {
|
||||
Json.decodeFromString<MutableList<String>>(value)
|
||||
} catch (e: Exception) {
|
||||
val list = value.split(",").toMutableList()
|
||||
val json = listToString(list)
|
||||
Json.decodeFromString<MutableList<String>>(json)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.data
|
||||
|
||||
object Queries {
|
||||
fun createDefaultSettings(): String {
|
||||
return """
|
||||
INSERT INTO Settings (is_tunnel_enabled,
|
||||
is_tunnel_on_mobile_data_enabled,
|
||||
trusted_network_ssids,
|
||||
is_always_on_vpn_enabled,
|
||||
is_tunnel_on_ethernet_enabled,
|
||||
is_shortcuts_enabled,
|
||||
is_tunnel_on_wifi_enabled,
|
||||
is_kernel_enabled,
|
||||
is_restore_on_boot_enabled,
|
||||
is_multi_tunnel_enabled)
|
||||
VALUES
|
||||
('false',
|
||||
'false',
|
||||
'sampleSSID1,sampleSSID2',
|
||||
'false',
|
||||
'false',
|
||||
'false',
|
||||
'false',
|
||||
'false',
|
||||
'false',
|
||||
'false')
|
||||
""".trimIndent()
|
||||
}
|
||||
|
||||
fun createTunnelConfig(): String {
|
||||
return """
|
||||
INSERT INTO TunnelConfig (name, wg_quick) VALUES ('test', 'test')
|
||||
""".trimIndent()
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.data
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Delete
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
import com.zaneschepke.wireguardautotunnel.data.domain.Settings
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Dao
|
||||
interface SettingsDao {
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
suspend fun save(t: Settings)
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
suspend fun saveAll(t: List<Settings>)
|
||||
|
||||
@Query("SELECT * FROM settings WHERE id=:id")
|
||||
suspend fun getById(id: Long): Settings?
|
||||
|
||||
@Query("SELECT * FROM settings")
|
||||
suspend fun getAll(): List<Settings>
|
||||
|
||||
@Query("SELECT * FROM settings LIMIT 1")
|
||||
fun getSettingsFlow(): Flow<Settings>
|
||||
|
||||
@Query("SELECT * FROM settings")
|
||||
fun getAllFlow(): Flow<MutableList<Settings>>
|
||||
|
||||
@Delete
|
||||
suspend fun delete(t: Settings)
|
||||
|
||||
@Query("SELECT COUNT('id') FROM settings")
|
||||
suspend fun count(): Long
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.data
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Delete
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
||||
import com.zaneschepke.wireguardautotunnel.util.TunnelConfigs
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Dao
|
||||
interface TunnelConfigDao {
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
suspend fun save(t: TunnelConfig)
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
suspend fun saveAll(t: TunnelConfigs)
|
||||
|
||||
@Query("SELECT * FROM TunnelConfig WHERE id=:id")
|
||||
suspend fun getById(id: Long): TunnelConfig?
|
||||
|
||||
@Query("SELECT * FROM TunnelConfig WHERE name=:name")
|
||||
suspend fun getByName(name: String): TunnelConfig?
|
||||
|
||||
@Query("SELECT * FROM TunnelConfig")
|
||||
suspend fun getAll(): TunnelConfigs
|
||||
|
||||
@Delete
|
||||
suspend fun delete(t: TunnelConfig)
|
||||
|
||||
@Query("SELECT COUNT('id') FROM TunnelConfig")
|
||||
suspend fun count(): Long
|
||||
|
||||
@Query("SELECT * FROM TunnelConfig WHERE tunnel_networks LIKE '%' || :name || '%'")
|
||||
suspend fun findByTunnelNetworkName(name: String): TunnelConfigs
|
||||
|
||||
@Query("UPDATE TunnelConfig SET is_primary_tunnel = 0 WHERE is_primary_tunnel =1")
|
||||
suspend fun resetPrimaryTunnel()
|
||||
|
||||
@Query("UPDATE TunnelConfig SET is_mobile_data_tunnel = 0 WHERE is_mobile_data_tunnel =1")
|
||||
suspend fun resetMobileDataTunnel()
|
||||
|
||||
@Query("SELECT * FROM TUNNELCONFIG WHERE is_primary_tunnel=1")
|
||||
suspend fun findByPrimary(): TunnelConfigs
|
||||
|
||||
@Query("SELECT * FROM TUNNELCONFIG WHERE is_mobile_data_tunnel=1")
|
||||
suspend fun findByMobileDataTunnel(): TunnelConfigs
|
||||
|
||||
@Query("SELECT * FROM tunnelconfig")
|
||||
fun getAllFlow(): Flow<MutableList<TunnelConfig>>
|
||||
}
|
||||
-81
@@ -1,81 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.data.datastore
|
||||
|
||||
import android.content.Context
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import androidx.datastore.preferences.core.booleanPreferencesKey
|
||||
import androidx.datastore.preferences.core.edit
|
||||
import androidx.datastore.preferences.core.intPreferencesKey
|
||||
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||
import androidx.datastore.preferences.preferencesDataStore
|
||||
import com.zaneschepke.wireguardautotunnel.module.IoDispatcher
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
import java.io.IOException
|
||||
|
||||
class DataStoreManager(
|
||||
private val context: Context,
|
||||
@IoDispatcher private val ioDispatcher: CoroutineDispatcher,
|
||||
) {
|
||||
companion object {
|
||||
val LOCATION_DISCLOSURE_SHOWN = booleanPreferencesKey("LOCATION_DISCLOSURE_SHOWN")
|
||||
val BATTERY_OPTIMIZE_DISABLE_SHOWN = booleanPreferencesKey("BATTERY_OPTIMIZE_DISABLE_SHOWN")
|
||||
val TUNNEL_RUNNING_FROM_MANUAL_START =
|
||||
booleanPreferencesKey("TUNNEL_RUNNING_FROM_MANUAL_START")
|
||||
val ACTIVE_TUNNEL = intPreferencesKey("ACTIVE_TUNNEL")
|
||||
val CURRENT_SSID = stringPreferencesKey("CURRENT_SSID")
|
||||
val IS_PIN_LOCK_ENABLED = booleanPreferencesKey("PIN_LOCK_ENABLED")
|
||||
}
|
||||
|
||||
// preferences
|
||||
private val preferencesKey = "preferences"
|
||||
private val Context.dataStore by
|
||||
preferencesDataStore(
|
||||
name = preferencesKey,
|
||||
)
|
||||
|
||||
suspend fun init() {
|
||||
withContext(ioDispatcher) {
|
||||
try {
|
||||
context.dataStore.data.first()
|
||||
} catch (e: IOException) {
|
||||
Timber.e(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun <T> saveToDataStore(key: Preferences.Key<T>, value: T) {
|
||||
withContext(ioDispatcher) {
|
||||
try {
|
||||
context.dataStore.edit { it[key] = value }
|
||||
} catch (e: IOException) {
|
||||
Timber.e(e)
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> getFromStoreFlow(key: Preferences.Key<T>) = context.dataStore.data.map { it[key] }
|
||||
|
||||
suspend fun <T> getFromStore(key: Preferences.Key<T>): T? {
|
||||
return withContext(ioDispatcher) {
|
||||
try {
|
||||
context.dataStore.data.map { it[key] }.first()
|
||||
} catch (e: IOException) {
|
||||
Timber.e(e)
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> getFromStoreBlocking(key: Preferences.Key<T>) = runBlocking {
|
||||
context.dataStore.data.map { it[key] }.first()
|
||||
}
|
||||
|
||||
val preferencesFlow: Flow<Preferences?> = context.dataStore.data
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.data.domain
|
||||
|
||||
data class GeneralState(
|
||||
val isLocationDisclosureShown: Boolean = LOCATION_DISCLOSURE_SHOWN_DEFAULT,
|
||||
val isBatteryOptimizationDisableShown: Boolean = BATTERY_OPTIMIZATION_DISABLE_SHOWN_DEFAULT,
|
||||
val isTunnelRunningFromManualStart: Boolean = TUNNELING_RUNNING_FROM_MANUAL_START_DEFAULT,
|
||||
val isPinLockEnabled: Boolean = PIN_LOCK_ENABLED_DEFAULT,
|
||||
val activeTunnelId: Int? = null,
|
||||
) {
|
||||
companion object {
|
||||
const val LOCATION_DISCLOSURE_SHOWN_DEFAULT = false
|
||||
const val BATTERY_OPTIMIZATION_DISABLE_SHOWN_DEFAULT = false
|
||||
const val TUNNELING_RUNNING_FROM_MANUAL_START_DEFAULT = false
|
||||
const val PIN_LOCK_ENABLED_DEFAULT = false
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.data.domain
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
|
||||
@Entity
|
||||
data class Settings(
|
||||
@PrimaryKey(autoGenerate = true) val id: Int = 0,
|
||||
@ColumnInfo(name = "is_tunnel_enabled") val isAutoTunnelEnabled: Boolean = false,
|
||||
@ColumnInfo(name = "is_tunnel_on_mobile_data_enabled")
|
||||
val isTunnelOnMobileDataEnabled: Boolean = false,
|
||||
@ColumnInfo(name = "trusted_network_ssids")
|
||||
val trustedNetworkSSIDs: MutableList<String> = mutableListOf(),
|
||||
@ColumnInfo(name = "is_always_on_vpn_enabled") val isAlwaysOnVpnEnabled: Boolean = false,
|
||||
@ColumnInfo(name = "is_tunnel_on_ethernet_enabled")
|
||||
val isTunnelOnEthernetEnabled: Boolean = false,
|
||||
@ColumnInfo(
|
||||
name = "is_shortcuts_enabled",
|
||||
defaultValue = "false",
|
||||
)
|
||||
val isShortcutsEnabled: Boolean = false,
|
||||
@ColumnInfo(
|
||||
name = "is_tunnel_on_wifi_enabled",
|
||||
defaultValue = "false",
|
||||
)
|
||||
val isTunnelOnWifiEnabled: Boolean = false,
|
||||
@ColumnInfo(
|
||||
name = "is_kernel_enabled",
|
||||
defaultValue = "false",
|
||||
)
|
||||
val isKernelEnabled: Boolean = false,
|
||||
@ColumnInfo(
|
||||
name = "is_restore_on_boot_enabled",
|
||||
defaultValue = "false",
|
||||
)
|
||||
val isRestoreOnBootEnabled: Boolean = false,
|
||||
@ColumnInfo(
|
||||
name = "is_multi_tunnel_enabled",
|
||||
defaultValue = "false",
|
||||
)
|
||||
val isMultiTunnelEnabled: Boolean = false,
|
||||
@ColumnInfo(
|
||||
name = "is_auto_tunnel_paused",
|
||||
defaultValue = "false",
|
||||
)
|
||||
val isAutoTunnelPaused: Boolean = false,
|
||||
@ColumnInfo(
|
||||
name = "is_ping_enabled",
|
||||
defaultValue = "false",
|
||||
)
|
||||
val isPingEnabled: Boolean = false,
|
||||
@ColumnInfo(
|
||||
name = "is_amnezia_enabled",
|
||||
defaultValue = "false",
|
||||
)
|
||||
val isAmneziaEnabled: Boolean = false,
|
||||
)
|
||||
@@ -1,53 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.data.domain
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.Index
|
||||
import androidx.room.PrimaryKey
|
||||
import com.wireguard.config.Config
|
||||
import java.io.InputStream
|
||||
|
||||
@Entity(indices = [Index(value = ["name"], unique = true)])
|
||||
data class TunnelConfig(
|
||||
@PrimaryKey(autoGenerate = true) val id: Int = 0,
|
||||
@ColumnInfo(name = "name") val name: String,
|
||||
@ColumnInfo(name = "wg_quick") val wgQuick: String,
|
||||
@ColumnInfo(
|
||||
name = "tunnel_networks",
|
||||
defaultValue = "",
|
||||
)
|
||||
val tunnelNetworks: MutableList<String> = mutableListOf(),
|
||||
@ColumnInfo(
|
||||
name = "is_mobile_data_tunnel",
|
||||
defaultValue = "false",
|
||||
)
|
||||
val isMobileDataTunnel: Boolean = false,
|
||||
@ColumnInfo(
|
||||
name = "is_primary_tunnel",
|
||||
defaultValue = "false",
|
||||
)
|
||||
val isPrimaryTunnel: Boolean = false,
|
||||
@ColumnInfo(
|
||||
name = "am_quick",
|
||||
defaultValue = "",
|
||||
)
|
||||
val amQuick: String = AM_QUICK_DEFAULT,
|
||||
) {
|
||||
companion object {
|
||||
fun configFromWgQuick(wgQuick: String): Config {
|
||||
val inputStream: InputStream = wgQuick.byteInputStream()
|
||||
return inputStream.bufferedReader(Charsets.UTF_8).use {
|
||||
Config.parse(it)
|
||||
}
|
||||
}
|
||||
|
||||
fun configFromAmQuick(amQuick: String): org.amnezia.awg.config.Config {
|
||||
val inputStream: InputStream = amQuick.byteInputStream()
|
||||
return inputStream.bufferedReader(Charsets.UTF_8).use {
|
||||
org.amnezia.awg.config.Config.parse(it)
|
||||
}
|
||||
}
|
||||
|
||||
const val AM_QUICK_DEFAULT = ""
|
||||
}
|
||||
}
|
||||
-15
@@ -1,15 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.data.repository
|
||||
|
||||
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
||||
|
||||
interface AppDataRepository {
|
||||
suspend fun getPrimaryOrFirstTunnel(): TunnelConfig?
|
||||
|
||||
suspend fun getStartTunnelConfig(): TunnelConfig?
|
||||
|
||||
suspend fun toggleWatcherServicePause()
|
||||
|
||||
val settings: SettingsRepository
|
||||
val tunnels: TunnelConfigRepository
|
||||
val appState: AppStateRepository
|
||||
}
|
||||
-38
@@ -1,38 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.data.repository
|
||||
|
||||
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
||||
import javax.inject.Inject
|
||||
|
||||
class AppDataRoomRepository
|
||||
@Inject
|
||||
constructor(
|
||||
override val settings: SettingsRepository,
|
||||
override val tunnels: TunnelConfigRepository,
|
||||
override val appState: AppStateRepository,
|
||||
) : AppDataRepository {
|
||||
override suspend fun getPrimaryOrFirstTunnel(): TunnelConfig? {
|
||||
return tunnels.findPrimary().firstOrNull() ?: tunnels.getAll().firstOrNull()
|
||||
}
|
||||
|
||||
override suspend fun getStartTunnelConfig(): TunnelConfig? {
|
||||
return if (appState.isTunnelRunningFromManualStart()) {
|
||||
appState.getActiveTunnelId()?.let {
|
||||
tunnels.getById(it)
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun toggleWatcherServicePause() {
|
||||
val settings = settings.getSettings()
|
||||
if (settings.isAutoTunnelEnabled) {
|
||||
val pauseAutoTunnel = !settings.isAutoTunnelPaused
|
||||
this.settings.save(
|
||||
settings.copy(
|
||||
isAutoTunnelPaused = pauseAutoTunnel,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
-32
@@ -1,32 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.data.repository
|
||||
|
||||
import com.zaneschepke.wireguardautotunnel.data.domain.GeneralState
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface AppStateRepository {
|
||||
suspend fun isLocationDisclosureShown(): Boolean
|
||||
|
||||
suspend fun setLocationDisclosureShown(shown: Boolean)
|
||||
|
||||
suspend fun isPinLockEnabled(): Boolean
|
||||
|
||||
suspend fun setPinLockEnabled(enabled: Boolean)
|
||||
|
||||
suspend fun isBatteryOptimizationDisableShown(): Boolean
|
||||
|
||||
suspend fun setBatteryOptimizationDisableShown(shown: Boolean)
|
||||
|
||||
suspend fun isTunnelRunningFromManualStart(): Boolean
|
||||
|
||||
suspend fun setTunnelRunningFromManualStart(id: Int)
|
||||
|
||||
suspend fun setManualStop()
|
||||
|
||||
suspend fun getActiveTunnelId(): Int?
|
||||
|
||||
suspend fun getCurrentSsid(): String?
|
||||
|
||||
suspend fun setCurrentSsid(ssid: String)
|
||||
|
||||
val generalStateFlow: Flow<GeneralState>
|
||||
}
|
||||
-96
@@ -1,96 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.data.repository
|
||||
|
||||
import com.zaneschepke.wireguardautotunnel.data.datastore.DataStoreManager
|
||||
import com.zaneschepke.wireguardautotunnel.data.domain.GeneralState
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import timber.log.Timber
|
||||
|
||||
class DataStoreAppStateRepository(private val dataStoreManager: DataStoreManager) :
|
||||
AppStateRepository {
|
||||
override suspend fun isLocationDisclosureShown(): Boolean {
|
||||
return dataStoreManager.getFromStore(DataStoreManager.LOCATION_DISCLOSURE_SHOWN)
|
||||
?: GeneralState.LOCATION_DISCLOSURE_SHOWN_DEFAULT
|
||||
}
|
||||
|
||||
override suspend fun setLocationDisclosureShown(shown: Boolean) {
|
||||
dataStoreManager.saveToDataStore(DataStoreManager.LOCATION_DISCLOSURE_SHOWN, shown)
|
||||
}
|
||||
|
||||
override suspend fun isPinLockEnabled(): Boolean {
|
||||
return dataStoreManager.getFromStore(DataStoreManager.IS_PIN_LOCK_ENABLED)
|
||||
?: GeneralState.PIN_LOCK_ENABLED_DEFAULT
|
||||
}
|
||||
|
||||
override suspend fun setPinLockEnabled(enabled: Boolean) {
|
||||
dataStoreManager.saveToDataStore(DataStoreManager.IS_PIN_LOCK_ENABLED, enabled)
|
||||
}
|
||||
|
||||
override suspend fun isBatteryOptimizationDisableShown(): Boolean {
|
||||
return dataStoreManager.getFromStore(DataStoreManager.BATTERY_OPTIMIZE_DISABLE_SHOWN)
|
||||
?: GeneralState.BATTERY_OPTIMIZATION_DISABLE_SHOWN_DEFAULT
|
||||
}
|
||||
|
||||
override suspend fun setBatteryOptimizationDisableShown(shown: Boolean) {
|
||||
dataStoreManager.saveToDataStore(DataStoreManager.BATTERY_OPTIMIZE_DISABLE_SHOWN, shown)
|
||||
}
|
||||
|
||||
override suspend fun isTunnelRunningFromManualStart(): Boolean {
|
||||
return dataStoreManager.getFromStore(DataStoreManager.TUNNEL_RUNNING_FROM_MANUAL_START)
|
||||
?: GeneralState.TUNNELING_RUNNING_FROM_MANUAL_START_DEFAULT
|
||||
}
|
||||
|
||||
override suspend fun setTunnelRunningFromManualStart(id: Int) {
|
||||
setTunnelRunningFromManualStart(true)
|
||||
setActiveTunnelId(id)
|
||||
}
|
||||
|
||||
override suspend fun setManualStop() {
|
||||
setTunnelRunningFromManualStart(false)
|
||||
}
|
||||
|
||||
private suspend fun setTunnelRunningFromManualStart(running: Boolean) {
|
||||
dataStoreManager.saveToDataStore(DataStoreManager.TUNNEL_RUNNING_FROM_MANUAL_START, running)
|
||||
}
|
||||
|
||||
override suspend fun getActiveTunnelId(): Int? {
|
||||
return dataStoreManager.getFromStore(DataStoreManager.ACTIVE_TUNNEL)
|
||||
}
|
||||
|
||||
private suspend fun setActiveTunnelId(id: Int) {
|
||||
dataStoreManager.saveToDataStore(DataStoreManager.ACTIVE_TUNNEL, id)
|
||||
}
|
||||
|
||||
override suspend fun getCurrentSsid(): String? {
|
||||
return dataStoreManager.getFromStore(DataStoreManager.CURRENT_SSID)
|
||||
}
|
||||
|
||||
override suspend fun setCurrentSsid(ssid: String) {
|
||||
dataStoreManager.saveToDataStore(DataStoreManager.CURRENT_SSID, ssid)
|
||||
}
|
||||
|
||||
override val generalStateFlow: Flow<GeneralState> =
|
||||
dataStoreManager.preferencesFlow.map { prefs ->
|
||||
prefs?.let { pref ->
|
||||
try {
|
||||
GeneralState(
|
||||
isLocationDisclosureShown =
|
||||
pref[DataStoreManager.LOCATION_DISCLOSURE_SHOWN]
|
||||
?: GeneralState.LOCATION_DISCLOSURE_SHOWN_DEFAULT,
|
||||
isBatteryOptimizationDisableShown =
|
||||
pref[DataStoreManager.BATTERY_OPTIMIZE_DISABLE_SHOWN]
|
||||
?: GeneralState.BATTERY_OPTIMIZATION_DISABLE_SHOWN_DEFAULT,
|
||||
isTunnelRunningFromManualStart =
|
||||
pref[DataStoreManager.TUNNEL_RUNNING_FROM_MANUAL_START]
|
||||
?: GeneralState.TUNNELING_RUNNING_FROM_MANUAL_START_DEFAULT,
|
||||
isPinLockEnabled =
|
||||
pref[DataStoreManager.IS_PIN_LOCK_ENABLED]
|
||||
?: GeneralState.TUNNELING_RUNNING_FROM_MANUAL_START_DEFAULT,
|
||||
)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
Timber.e(e)
|
||||
GeneralState()
|
||||
}
|
||||
} ?: GeneralState()
|
||||
}
|
||||
}
|
||||
-23
@@ -1,23 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.data.repository
|
||||
|
||||
import com.zaneschepke.wireguardautotunnel.data.SettingsDao
|
||||
import com.zaneschepke.wireguardautotunnel.data.domain.Settings
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
class RoomSettingsRepository(private val settingsDoa: SettingsDao) : SettingsRepository {
|
||||
override suspend fun save(settings: Settings) {
|
||||
settingsDoa.save(settings)
|
||||
}
|
||||
|
||||
override fun getSettingsFlow(): Flow<Settings> {
|
||||
return settingsDoa.getSettingsFlow()
|
||||
}
|
||||
|
||||
override suspend fun getSettings(): Settings {
|
||||
return settingsDoa.getAll().firstOrNull() ?: Settings()
|
||||
}
|
||||
|
||||
override suspend fun getAll(): List<Settings> {
|
||||
return settingsDoa.getAll()
|
||||
}
|
||||
}
|
||||
-71
@@ -1,71 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.data.repository
|
||||
|
||||
import com.zaneschepke.wireguardautotunnel.data.TunnelConfigDao
|
||||
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
||||
import com.zaneschepke.wireguardautotunnel.util.TunnelConfigs
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
class RoomTunnelConfigRepository(private val tunnelConfigDao: TunnelConfigDao) :
|
||||
TunnelConfigRepository {
|
||||
override fun getTunnelConfigsFlow(): Flow<TunnelConfigs> {
|
||||
return tunnelConfigDao.getAllFlow()
|
||||
}
|
||||
|
||||
override suspend fun getAll(): TunnelConfigs {
|
||||
return tunnelConfigDao.getAll()
|
||||
}
|
||||
|
||||
override suspend fun save(tunnelConfig: TunnelConfig) {
|
||||
tunnelConfigDao.save(tunnelConfig)
|
||||
}
|
||||
|
||||
override suspend fun updatePrimaryTunnel(tunnelConfig: TunnelConfig?) {
|
||||
tunnelConfigDao.resetPrimaryTunnel()
|
||||
tunnelConfig?.let {
|
||||
save(
|
||||
it.copy(
|
||||
isPrimaryTunnel = true,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun updateMobileDataTunnel(tunnelConfig: TunnelConfig?) {
|
||||
tunnelConfigDao.resetMobileDataTunnel()
|
||||
tunnelConfig?.let {
|
||||
save(
|
||||
it.copy(
|
||||
isMobileDataTunnel = true,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun delete(tunnelConfig: TunnelConfig) {
|
||||
tunnelConfigDao.delete(tunnelConfig)
|
||||
}
|
||||
|
||||
override suspend fun getById(id: Int): TunnelConfig? {
|
||||
return tunnelConfigDao.getById(id.toLong())
|
||||
}
|
||||
|
||||
override suspend fun count(): Int {
|
||||
return tunnelConfigDao.count().toInt()
|
||||
}
|
||||
|
||||
override suspend fun findByTunnelName(name: String): TunnelConfig? {
|
||||
return tunnelConfigDao.getByName(name)
|
||||
}
|
||||
|
||||
override suspend fun findByTunnelNetworksName(name: String): TunnelConfigs {
|
||||
return tunnelConfigDao.findByTunnelNetworkName(name)
|
||||
}
|
||||
|
||||
override suspend fun findByMobileDataTunnel(): TunnelConfigs {
|
||||
return tunnelConfigDao.findByMobileDataTunnel()
|
||||
}
|
||||
|
||||
override suspend fun findPrimary(): TunnelConfigs {
|
||||
return tunnelConfigDao.findByPrimary()
|
||||
}
|
||||
}
|
||||
-14
@@ -1,14 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.data.repository
|
||||
|
||||
import com.zaneschepke.wireguardautotunnel.data.domain.Settings
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface SettingsRepository {
|
||||
suspend fun save(settings: Settings)
|
||||
|
||||
fun getSettingsFlow(): Flow<Settings>
|
||||
|
||||
suspend fun getSettings(): Settings
|
||||
|
||||
suspend fun getAll(): List<Settings>
|
||||
}
|
||||
-31
@@ -1,31 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.data.repository
|
||||
|
||||
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
||||
import com.zaneschepke.wireguardautotunnel.util.TunnelConfigs
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface TunnelConfigRepository {
|
||||
fun getTunnelConfigsFlow(): Flow<TunnelConfigs>
|
||||
|
||||
suspend fun getAll(): TunnelConfigs
|
||||
|
||||
suspend fun save(tunnelConfig: TunnelConfig)
|
||||
|
||||
suspend fun updatePrimaryTunnel(tunnelConfig: TunnelConfig?)
|
||||
|
||||
suspend fun updateMobileDataTunnel(tunnelConfig: TunnelConfig?)
|
||||
|
||||
suspend fun delete(tunnelConfig: TunnelConfig)
|
||||
|
||||
suspend fun getById(id: Int): TunnelConfig?
|
||||
|
||||
suspend fun count(): Int
|
||||
|
||||
suspend fun findByTunnelName(name: String): TunnelConfig?
|
||||
|
||||
suspend fun findByTunnelNetworksName(name: String): TunnelConfigs
|
||||
|
||||
suspend fun findByMobileDataTunnel(): TunnelConfigs
|
||||
|
||||
suspend fun findPrimary(): TunnelConfigs
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.module
|
||||
|
||||
import android.content.Context
|
||||
import com.zaneschepke.logcatter.LocalLogCollector
|
||||
import com.zaneschepke.logcatter.LogcatUtil
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
class AppModule {
|
||||
@Singleton
|
||||
@ApplicationScope
|
||||
@Provides
|
||||
fun providesApplicationScope(@DefaultDispatcher defaultDispatcher: CoroutineDispatcher): CoroutineScope =
|
||||
CoroutineScope(SupervisorJob() + defaultDispatcher)
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideLogCollect(@ApplicationContext context: Context): LocalLogCollector {
|
||||
return LogcatUtil.init(context = context)
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.module
|
||||
|
||||
import javax.inject.Qualifier
|
||||
|
||||
@Qualifier
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
annotation class Kernel
|
||||
|
||||
@Qualifier
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
annotation class Userspace
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.zaneschepke.wireguardautotunnel.module
|
||||
|
||||
import android.content.Context
|
||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.model.MyObjectBox
|
||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.model.Settings
|
||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.model.TunnelConfig
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import io.objectbox.Box
|
||||
import io.objectbox.BoxStore
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
class BoxModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideBoxStore(@ApplicationContext context : Context) : BoxStore {
|
||||
return MyObjectBox.builder()
|
||||
.androidContext(context.applicationContext)
|
||||
.build()
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideBoxForSettings(store : BoxStore) : Box<Settings> {
|
||||
return store.boxFor(Settings::class.java)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideBoxForTunnels(store : BoxStore) : Box<TunnelConfig> {
|
||||
return store.boxFor(TunnelConfig::class.java)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.module
|
||||
|
||||
import javax.inject.Qualifier
|
||||
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@Qualifier
|
||||
annotation class DefaultDispatcher
|
||||
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@Qualifier
|
||||
annotation class IoDispatcher
|
||||
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@Qualifier
|
||||
annotation class MainDispatcher
|
||||
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
@Qualifier
|
||||
annotation class MainImmediateDispatcher
|
||||
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@Qualifier
|
||||
annotation class ApplicationScope
|
||||
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@Qualifier
|
||||
annotation class ServiceScope
|
||||
-28
@@ -1,28 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.module
|
||||
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
object CoroutinesDispatchersModule {
|
||||
@DefaultDispatcher
|
||||
@Provides
|
||||
fun providesDefaultDispatcher(): CoroutineDispatcher = Dispatchers.Default
|
||||
|
||||
@IoDispatcher
|
||||
@Provides
|
||||
fun providesIoDispatcher(): CoroutineDispatcher = Dispatchers.IO
|
||||
|
||||
@MainDispatcher
|
||||
@Provides
|
||||
fun providesMainDispatcher(): CoroutineDispatcher = Dispatchers.Main
|
||||
|
||||
@MainImmediateDispatcher
|
||||
@Provides
|
||||
fun providesMainImmediateDispatcher(): CoroutineDispatcher = Dispatchers.Main.immediate
|
||||
}
|
||||
@@ -1,88 +1,25 @@
|
||||
package com.zaneschepke.wireguardautotunnel.module
|
||||
|
||||
import android.content.Context
|
||||
import androidx.room.Room
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.data.AppDatabase
|
||||
import com.zaneschepke.wireguardautotunnel.data.DatabaseCallback
|
||||
import com.zaneschepke.wireguardautotunnel.data.SettingsDao
|
||||
import com.zaneschepke.wireguardautotunnel.data.TunnelConfigDao
|
||||
import com.zaneschepke.wireguardautotunnel.data.datastore.DataStoreManager
|
||||
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
||||
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRoomRepository
|
||||
import com.zaneschepke.wireguardautotunnel.data.repository.AppStateRepository
|
||||
import com.zaneschepke.wireguardautotunnel.data.repository.DataStoreAppStateRepository
|
||||
import com.zaneschepke.wireguardautotunnel.data.repository.RoomSettingsRepository
|
||||
import com.zaneschepke.wireguardautotunnel.data.repository.RoomTunnelConfigRepository
|
||||
import com.zaneschepke.wireguardautotunnel.data.repository.SettingsRepository
|
||||
import com.zaneschepke.wireguardautotunnel.data.repository.TunnelConfigRepository
|
||||
import com.zaneschepke.wireguardautotunnel.repository.Repository
|
||||
import com.zaneschepke.wireguardautotunnel.repository.SettingsBox
|
||||
import com.zaneschepke.wireguardautotunnel.repository.TunnelBox
|
||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.model.Settings
|
||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.model.TunnelConfig
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
class RepositoryModule {
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideDatabase(@ApplicationContext context: Context): AppDatabase {
|
||||
return Room.databaseBuilder(
|
||||
context,
|
||||
AppDatabase::class.java,
|
||||
context.getString(R.string.db_name),
|
||||
)
|
||||
.fallbackToDestructiveMigration()
|
||||
.addCallback(DatabaseCallback())
|
||||
.build()
|
||||
}
|
||||
abstract class RepositoryModule {
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideSettingsDoa(appDatabase: AppDatabase): SettingsDao {
|
||||
return appDatabase.settingDao()
|
||||
}
|
||||
@Binds
|
||||
@Singleton
|
||||
abstract fun provideSettingsRepository(settingsBox: SettingsBox) : Repository<Settings>
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideTunnelConfigDoa(appDatabase: AppDatabase): TunnelConfigDao {
|
||||
return appDatabase.tunnelConfigDoa()
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideTunnelConfigRepository(tunnelConfigDao: TunnelConfigDao): TunnelConfigRepository {
|
||||
return RoomTunnelConfigRepository(tunnelConfigDao)
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideSettingsRepository(settingsDao: SettingsDao): SettingsRepository {
|
||||
return RoomSettingsRepository(settingsDao)
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun providePreferencesDataStore(@ApplicationContext context: Context, @IoDispatcher ioDispatcher: CoroutineDispatcher): DataStoreManager {
|
||||
return DataStoreManager(context, ioDispatcher)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGeneralStateRepository(dataStoreManager: DataStoreManager): AppStateRepository {
|
||||
return DataStoreAppStateRepository(dataStoreManager)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideAppDataRepository(
|
||||
settingsRepository: SettingsRepository,
|
||||
tunnelConfigRepository: TunnelConfigRepository,
|
||||
appStateRepository: AppStateRepository,
|
||||
): AppDataRepository {
|
||||
return AppDataRoomRepository(settingsRepository, tunnelConfigRepository, appStateRepository)
|
||||
}
|
||||
}
|
||||
@Binds
|
||||
@Singleton
|
||||
abstract fun provideTunnelRepository(tunnelBox: TunnelBox) : Repository<TunnelConfig>
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.zaneschepke.wireguardautotunnel.module
|
||||
|
||||
import android.content.Context
|
||||
import com.google.mlkit.vision.barcode.common.Barcode
|
||||
import com.google.mlkit.vision.codescanner.GmsBarcodeScanner
|
||||
import com.google.mlkit.vision.codescanner.GmsBarcodeScannerOptions
|
||||
import com.google.mlkit.vision.codescanner.GmsBarcodeScanning
|
||||
import com.zaneschepke.wireguardautotunnel.service.barcode.CodeScanner
|
||||
import com.zaneschepke.wireguardautotunnel.service.barcode.QRScanner
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.components.ViewModelComponent
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.android.scopes.ViewModelScoped
|
||||
|
||||
@Module
|
||||
@InstallIn(ViewModelComponent::class)
|
||||
class ScannerModule {
|
||||
|
||||
@ViewModelScoped
|
||||
@Provides
|
||||
fun provideBarCodeOptions() : GmsBarcodeScannerOptions {
|
||||
return GmsBarcodeScannerOptions.Builder()
|
||||
.setBarcodeFormats(Barcode.FORMAT_QR_CODE)
|
||||
.build()
|
||||
}
|
||||
|
||||
@ViewModelScoped
|
||||
@Provides
|
||||
fun provideBarCodeScanner(@ApplicationContext context: Context, options: GmsBarcodeScannerOptions) : GmsBarcodeScanner {
|
||||
return GmsBarcodeScanning.getClient(context, options)
|
||||
}
|
||||
|
||||
@ViewModelScoped
|
||||
@Provides
|
||||
fun provideQRScanner(gmsBarcodeScanner: GmsBarcodeScanner) : CodeScanner {
|
||||
return QRScanner(gmsBarcodeScanner)
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.zaneschepke.wireguardautotunnel.module
|
||||
|
||||
import com.zaneschepke.wireguardautotunnel.service.network.EthernetService
|
||||
import com.zaneschepke.wireguardautotunnel.service.barcode.CodeScanner
|
||||
import com.zaneschepke.wireguardautotunnel.service.barcode.QRScanner
|
||||
import com.zaneschepke.wireguardautotunnel.service.network.MobileDataService
|
||||
import com.zaneschepke.wireguardautotunnel.service.network.NetworkService
|
||||
import com.zaneschepke.wireguardautotunnel.service.network.WifiService
|
||||
@@ -11,23 +12,21 @@ import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.components.ServiceComponent
|
||||
import dagger.hilt.android.scopes.ServiceScoped
|
||||
import dagger.hilt.android.scopes.ViewModelScoped
|
||||
|
||||
@Module
|
||||
@InstallIn(ServiceComponent::class)
|
||||
abstract class ServiceModule {
|
||||
@Binds
|
||||
@ServiceScoped
|
||||
abstract fun provideNotificationService(wireGuardNotification: WireGuardNotification): NotificationService
|
||||
|
||||
@Binds
|
||||
@ServiceScoped
|
||||
abstract fun provideWifiService(wifiService: WifiService): NetworkService<WifiService>
|
||||
@Binds
|
||||
@ServiceScoped
|
||||
abstract fun provideNotificationService(wireGuardNotification: WireGuardNotification) : NotificationService
|
||||
|
||||
@Binds
|
||||
@ServiceScoped
|
||||
abstract fun provideMobileDataService(mobileDataService: MobileDataService): NetworkService<MobileDataService>
|
||||
@Binds
|
||||
@ServiceScoped
|
||||
abstract fun provideWifiService(wifiService: WifiService) : NetworkService<WifiService>
|
||||
|
||||
@Binds
|
||||
@ServiceScoped
|
||||
abstract fun provideEthernetService(ethernetService: EthernetService): NetworkService<EthernetService>
|
||||
}
|
||||
@Binds
|
||||
@ServiceScoped
|
||||
abstract fun provideMobileDataService(mobileDataService : MobileDataService) : NetworkService<MobileDataService>
|
||||
}
|
||||
@@ -3,11 +3,6 @@ package com.zaneschepke.wireguardautotunnel.module
|
||||
import android.content.Context
|
||||
import com.wireguard.android.backend.Backend
|
||||
import com.wireguard.android.backend.GoBackend
|
||||
import com.wireguard.android.backend.WgQuickBackend
|
||||
import com.wireguard.android.util.RootShell
|
||||
import com.wireguard.android.util.ToolsInstaller
|
||||
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
||||
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
|
||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.VpnService
|
||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.WireGuardTunnel
|
||||
import dagger.Module
|
||||
@@ -15,63 +10,22 @@ import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import javax.inject.Provider
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
class TunnelModule {
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideRootShell(@ApplicationContext context: Context): RootShell {
|
||||
return RootShell(context)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@Userspace
|
||||
fun provideUserspaceBackend(@ApplicationContext context: Context): Backend {
|
||||
return GoBackend(context)
|
||||
}
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideBackend(@ApplicationContext context : Context) : Backend {
|
||||
return GoBackend(context)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@Kernel
|
||||
fun provideKernelBackend(@ApplicationContext context: Context, rootShell: RootShell): Backend {
|
||||
return WgQuickBackend(context, rootShell, ToolsInstaller(context, rootShell))
|
||||
}
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideVpnService(backend: Backend) : VpnService {
|
||||
return WireGuardTunnel(backend)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideAmneziaBackend(@ApplicationContext context: Context): org.amnezia.awg.backend.Backend {
|
||||
return org.amnezia.awg.backend.GoBackend(context)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideVpnService(
|
||||
amneziaBackend: Provider<org.amnezia.awg.backend.Backend>,
|
||||
@Userspace userspaceBackend: Provider<Backend>,
|
||||
@Kernel kernelBackend: Provider<Backend>,
|
||||
appDataRepository: AppDataRepository,
|
||||
@ApplicationScope applicationScope: CoroutineScope,
|
||||
@IoDispatcher ioDispatcher: CoroutineDispatcher,
|
||||
): VpnService {
|
||||
return WireGuardTunnel(
|
||||
amneziaBackend,
|
||||
userspaceBackend,
|
||||
kernelBackend,
|
||||
appDataRepository,
|
||||
applicationScope,
|
||||
ioDispatcher,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideServiceManager(appDataRepository: AppDataRepository, @IoDispatcher ioDispatcher: CoroutineDispatcher): ServiceManager {
|
||||
return ServiceManager(appDataRepository, ioDispatcher)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.module
|
||||
|
||||
import android.content.Context
|
||||
import com.zaneschepke.wireguardautotunnel.util.FileUtils
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.components.ViewModelComponent
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.android.scopes.ViewModelScoped
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
|
||||
@Module
|
||||
@InstallIn(ViewModelComponent::class)
|
||||
class ViewModelModule {
|
||||
@ViewModelScoped
|
||||
@Provides
|
||||
fun provideFileUtils(@ApplicationContext context: Context, @IoDispatcher ioDispatcher: CoroutineDispatcher): FileUtils {
|
||||
return FileUtils(context, ioDispatcher)
|
||||
}
|
||||
}
|
||||
@@ -3,53 +3,46 @@ package com.zaneschepke.wireguardautotunnel.receiver
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
||||
import com.zaneschepke.wireguardautotunnel.module.ApplicationScope
|
||||
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.repository.Repository
|
||||
import com.zaneschepke.wireguardautotunnel.service.foreground.Action
|
||||
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceTracker
|
||||
import com.zaneschepke.wireguardautotunnel.service.foreground.WireGuardConnectivityWatcherService
|
||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.model.Settings
|
||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.model.TunnelConfig
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class BootReceiver : BroadcastReceiver() {
|
||||
@Inject
|
||||
lateinit var appDataRepository: AppDataRepository
|
||||
|
||||
@Inject
|
||||
lateinit var serviceManager: ServiceManager
|
||||
@Inject
|
||||
lateinit var settingsRepo : Repository<Settings>
|
||||
|
||||
@Inject
|
||||
@ApplicationScope
|
||||
lateinit var applicationScope: CoroutineScope
|
||||
|
||||
override fun onReceive(context: Context?, intent: Intent?) {
|
||||
if (Intent.ACTION_BOOT_COMPLETED != intent?.action) return
|
||||
context?.run {
|
||||
applicationScope.launch {
|
||||
val settings = appDataRepository.settings.getSettings()
|
||||
if (settings.isRestoreOnBootEnabled) {
|
||||
if (settings.isAutoTunnelEnabled) {
|
||||
Timber.i("Starting watcher service from boot")
|
||||
serviceManager.startWatcherServiceForeground(context)
|
||||
}
|
||||
if (appDataRepository.appState.isTunnelRunningFromManualStart()) {
|
||||
appDataRepository.appState.getActiveTunnelId()?.let {
|
||||
Timber.i("Starting tunnel that was active before reboot")
|
||||
serviceManager.startVpnServiceForeground(
|
||||
context,
|
||||
appDataRepository.tunnels.getById(it)?.id,
|
||||
)
|
||||
return@launch
|
||||
}
|
||||
}
|
||||
if (settings.isAlwaysOnVpnEnabled) {
|
||||
Timber.i("Starting vpn service from boot AOVPN")
|
||||
serviceManager.startVpnServiceForeground(context)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
if (intent.action == Intent.ACTION_BOOT_COMPLETED) {
|
||||
CoroutineScope(SupervisorJob()).launch {
|
||||
try {
|
||||
val settings = settingsRepo.getAll()
|
||||
if (!settings.isNullOrEmpty()) {
|
||||
val setting = settings.first()
|
||||
if (setting.isAutoTunnelEnabled && setting.defaultTunnel != null) {
|
||||
ServiceTracker.actionOnService(
|
||||
Action.START, context,
|
||||
WireGuardConnectivityWatcherService::class.java,
|
||||
mapOf(context.resources.getString(R.string.tunnel_extras_key) to
|
||||
setting.defaultTunnel!!)
|
||||
)
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
cancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+41
-29
@@ -3,42 +3,54 @@ package com.zaneschepke.wireguardautotunnel.receiver
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import com.zaneschepke.wireguardautotunnel.data.repository.SettingsRepository
|
||||
import com.zaneschepke.wireguardautotunnel.module.ApplicationScope
|
||||
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
|
||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.repository.Repository
|
||||
import com.zaneschepke.wireguardautotunnel.service.foreground.Action
|
||||
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceTracker
|
||||
import com.zaneschepke.wireguardautotunnel.service.foreground.WireGuardTunnelService
|
||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.model.Settings
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class NotificationActionReceiver : BroadcastReceiver() {
|
||||
@Inject
|
||||
lateinit var settingsRepository: SettingsRepository
|
||||
|
||||
@Inject
|
||||
lateinit var serviceManager: ServiceManager
|
||||
|
||||
@Inject
|
||||
@ApplicationScope
|
||||
lateinit var applicationScope: CoroutineScope
|
||||
|
||||
override fun onReceive(context: Context, intent: Intent?) {
|
||||
applicationScope.launch {
|
||||
try {
|
||||
// TODO fix for manual start changes when enabled
|
||||
serviceManager.stopVpnServiceForeground(context)
|
||||
delay(Constants.TOGGLE_TUNNEL_DELAY)
|
||||
serviceManager.startVpnServiceForeground(context)
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e)
|
||||
} finally {
|
||||
cancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@Inject
|
||||
lateinit var settingsRepo : Repository<Settings>
|
||||
override fun onReceive(context: Context, intent: Intent?) {
|
||||
CoroutineScope(SupervisorJob()).launch {
|
||||
try {
|
||||
val settings = settingsRepo.getAll()
|
||||
if (!settings.isNullOrEmpty()) {
|
||||
val setting = settings.first()
|
||||
if (setting.defaultTunnel != null) {
|
||||
ServiceTracker.actionOnService(
|
||||
Action.STOP, context,
|
||||
WireGuardTunnelService::class.java,
|
||||
mapOf(
|
||||
context.resources.getString(R.string.tunnel_extras_key) to
|
||||
setting.defaultTunnel!!
|
||||
)
|
||||
)
|
||||
delay(1000)
|
||||
ServiceTracker.actionOnService(
|
||||
Action.START, context,
|
||||
WireGuardTunnelService::class.java,
|
||||
mapOf(
|
||||
context.resources.getString(R.string.tunnel_extras_key) to
|
||||
setting.defaultTunnel!!
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
cancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.zaneschepke.wireguardautotunnel.repository
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface Repository<T> {
|
||||
suspend fun save(t : T)
|
||||
suspend fun saveAll(t : List<T>)
|
||||
suspend fun getById(id : Long) : T?
|
||||
suspend fun getAll() : List<T>?
|
||||
suspend fun delete(t : T) : Boolean?
|
||||
suspend fun count() : Long?
|
||||
|
||||
val itemFlow : Flow<MutableList<T>>
|
||||
|
||||
fun init()
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package com.zaneschepke.wireguardautotunnel.repository
|
||||
|
||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.model.Settings
|
||||
import io.objectbox.Box
|
||||
import io.objectbox.BoxStore
|
||||
import io.objectbox.kotlin.awaitCallInTx
|
||||
import io.objectbox.kotlin.toFlow
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
|
||||
class SettingsBox @Inject constructor(private val box : Box<Settings>, private val boxStore : BoxStore) : Repository<Settings> {
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
override val itemFlow = box.query().build().subscribe().toFlow()
|
||||
|
||||
override fun init() {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
if(getAll().isNullOrEmpty()) {
|
||||
save(Settings())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun save(t : Settings) {
|
||||
boxStore.awaitCallInTx {
|
||||
box.put(t)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun saveAll(t : List<Settings>) {
|
||||
boxStore.awaitCallInTx {
|
||||
box.put(t)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getById(id: Long): Settings? {
|
||||
return boxStore.awaitCallInTx {
|
||||
box[id]
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getAll(): List<Settings>? {
|
||||
return boxStore.awaitCallInTx {
|
||||
box.all
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun delete(t : Settings): Boolean? {
|
||||
return boxStore.awaitCallInTx {
|
||||
box.remove(t)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun count() : Long? {
|
||||
return boxStore.awaitCallInTx {
|
||||
box.count()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package com.zaneschepke.wireguardautotunnel.repository
|
||||
|
||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.model.TunnelConfig
|
||||
import io.objectbox.Box
|
||||
import io.objectbox.BoxStore
|
||||
import io.objectbox.kotlin.awaitCallInTx
|
||||
import io.objectbox.kotlin.toFlow
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
class TunnelBox @Inject constructor(private val box : Box<TunnelConfig>,private val boxStore : BoxStore) : Repository<TunnelConfig> {
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
override val itemFlow = box.query().build().subscribe().toFlow()
|
||||
override fun init() {
|
||||
|
||||
}
|
||||
|
||||
override suspend fun save(t : TunnelConfig) {
|
||||
Timber.d("Saving tunnel config")
|
||||
boxStore.awaitCallInTx {
|
||||
box.put(t)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override suspend fun saveAll(t : List<TunnelConfig>) {
|
||||
boxStore.awaitCallInTx {
|
||||
box.put(t)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getById(id: Long): TunnelConfig? {
|
||||
return boxStore.awaitCallInTx {
|
||||
box[id]
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getAll(): List<TunnelConfig>? {
|
||||
return boxStore.awaitCallInTx {
|
||||
box.all
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun delete(t : TunnelConfig): Boolean? {
|
||||
return boxStore.awaitCallInTx {
|
||||
box.remove(t)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun count() : Long? {
|
||||
return boxStore.awaitCallInTx {
|
||||
box.count()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.zaneschepke.wireguardautotunnel.service.barcode
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface CodeScanner {
|
||||
fun scan() : Flow<String?>
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.zaneschepke.wireguardautotunnel.service.barcode
|
||||
|
||||
import com.google.mlkit.vision.codescanner.GmsBarcodeScanner
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.callbackFlow
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
class QRScanner @Inject constructor(private val gmsBarcodeScanner: GmsBarcodeScanner) : CodeScanner {
|
||||
override fun scan(): Flow<String?> {
|
||||
return callbackFlow {
|
||||
gmsBarcodeScanner.startScan().addOnSuccessListener {
|
||||
trySend(it.rawValue)
|
||||
}.addOnFailureListener {
|
||||
trySend(it.message)
|
||||
Timber.e(it.message)
|
||||
}
|
||||
awaitClose {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,6 @@
|
||||
package com.zaneschepke.wireguardautotunnel.service.foreground
|
||||
|
||||
enum class Action {
|
||||
START,
|
||||
START_FOREGROUND,
|
||||
STOP,
|
||||
STOP_FOREGROUND,
|
||||
}
|
||||
START,
|
||||
STOP
|
||||
}
|
||||
+54
-44
@@ -1,57 +1,67 @@
|
||||
package com.zaneschepke.wireguardautotunnel.service.foreground
|
||||
|
||||
import android.app.Service
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.os.IBinder
|
||||
import androidx.lifecycle.LifecycleService
|
||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||
import com.zaneschepke.wireguardautotunnel.repository.Repository
|
||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.model.Settings
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
open class ForegroundService : LifecycleService() {
|
||||
private var isServiceStarted = false
|
||||
|
||||
override fun onBind(intent: Intent): IBinder? {
|
||||
super.onBind(intent)
|
||||
// We don't provide binding, so return null
|
||||
return null
|
||||
}
|
||||
open class ForegroundService : Service() {
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
super.onStartCommand(intent, flags, startId)
|
||||
Timber.d("onStartCommand executed with startId: $startId")
|
||||
if (intent != null) {
|
||||
val action = intent.action
|
||||
when (action) {
|
||||
Action.START.name,
|
||||
Action.START_FOREGROUND.name,
|
||||
-> startService(intent.extras)
|
||||
private var isServiceStarted = false
|
||||
|
||||
Action.STOP.name, Action.STOP_FOREGROUND.name -> stopService()
|
||||
Constants.ALWAYS_ON_VPN_ACTION -> {
|
||||
Timber.i("Always-on VPN starting service")
|
||||
startService(intent.extras)
|
||||
}
|
||||
override fun onBind(intent: Intent): IBinder? {
|
||||
// We don't provide binding, so return null
|
||||
return null
|
||||
}
|
||||
|
||||
else -> Timber.d("This should never happen. No action in the received intent")
|
||||
}
|
||||
} else {
|
||||
Timber.d(
|
||||
"with a null intent. It has been probably restarted by the system.",
|
||||
)
|
||||
}
|
||||
return START_STICKY
|
||||
}
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
Timber.d("onStartCommand executed with startId: $startId")
|
||||
if (intent != null) {
|
||||
val action = intent.action
|
||||
Timber.d("using an intent with action $action")
|
||||
when (action) {
|
||||
Action.START.name -> startService(intent.extras)
|
||||
Action.STOP.name -> stopService(intent.extras)
|
||||
"android.net.VpnService" -> {
|
||||
Timber.d("Always-on VPN starting service")
|
||||
startService(intent.extras)
|
||||
}
|
||||
else -> Timber.d("This should never happen. No action in the received intent")
|
||||
}
|
||||
} else {
|
||||
Timber.d(
|
||||
"with a null intent. It has been probably restarted by the system."
|
||||
)
|
||||
}
|
||||
// by returning this we make sure the service is restarted if the system kills the service
|
||||
return START_STICKY
|
||||
}
|
||||
|
||||
protected open fun startService(extras: Bundle?) {
|
||||
if (isServiceStarted) return
|
||||
Timber.d("Starting ${this.javaClass.simpleName}")
|
||||
isServiceStarted = true
|
||||
}
|
||||
|
||||
protected open fun stopService() {
|
||||
Timber.d("Stopping ${this.javaClass.simpleName}")
|
||||
stopForeground(STOP_FOREGROUND_REMOVE)
|
||||
stopSelf()
|
||||
isServiceStarted = false
|
||||
}
|
||||
}
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
Timber.d("The service has been destroyed")
|
||||
}
|
||||
|
||||
protected open fun startService(extras : Bundle?) {
|
||||
if (isServiceStarted) return
|
||||
Timber.d("Starting ${this.javaClass.simpleName}")
|
||||
isServiceStarted = true
|
||||
}
|
||||
|
||||
protected open fun stopService(extras : Bundle?) {
|
||||
Timber.d("Stopping ${this.javaClass.simpleName}")
|
||||
try {
|
||||
stopForeground(STOP_FOREGROUND_REMOVE)
|
||||
stopSelf()
|
||||
} catch (e: Exception) {
|
||||
Timber.d("Service stopped without being started: ${e.message}")
|
||||
}
|
||||
isServiceStarted = false
|
||||
}
|
||||
}
|
||||
-117
@@ -1,117 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.service.foreground
|
||||
|
||||
import android.app.Service
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
||||
import com.zaneschepke.wireguardautotunnel.module.IoDispatcher
|
||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
|
||||
class ServiceManager(
|
||||
private val appDataRepository: AppDataRepository,
|
||||
@IoDispatcher private val ioDispatcher: CoroutineDispatcher,
|
||||
) {
|
||||
private fun <T : Service> actionOnService(action: Action, context: Context, cls: Class<T>, extras: Map<String, Int>? = null) {
|
||||
val intent =
|
||||
Intent(context, cls).also {
|
||||
it.action = action.name
|
||||
extras?.forEach { (k, v) -> it.putExtra(k, v) }
|
||||
}
|
||||
intent.component?.javaClass
|
||||
try {
|
||||
when (action) {
|
||||
Action.START_FOREGROUND, Action.STOP_FOREGROUND ->
|
||||
context.startForegroundService(
|
||||
intent,
|
||||
)
|
||||
|
||||
Action.START, Action.STOP -> context.startService(intent)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e.message)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun startVpnService(context: Context, tunnelId: Int? = null, isManualStart: Boolean = false) {
|
||||
if (isManualStart) onManualStart(tunnelId)
|
||||
actionOnService(
|
||||
Action.START,
|
||||
context,
|
||||
WireGuardTunnelService::class.java,
|
||||
tunnelId?.let { mapOf(Constants.TUNNEL_EXTRA_KEY to it) },
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun stopVpnServiceForeground(context: Context, isManualStop: Boolean = false) {
|
||||
withContext(ioDispatcher) {
|
||||
if (isManualStop) onManualStop()
|
||||
Timber.i("Stopping vpn service")
|
||||
actionOnService(
|
||||
Action.STOP_FOREGROUND,
|
||||
context,
|
||||
WireGuardTunnelService::class.java,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun stopVpnService(context: Context, isManualStop: Boolean = false) {
|
||||
withContext(ioDispatcher) {
|
||||
if (isManualStop) onManualStop()
|
||||
Timber.i("Stopping vpn service")
|
||||
actionOnService(
|
||||
Action.STOP,
|
||||
context,
|
||||
WireGuardTunnelService::class.java,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun onManualStop() {
|
||||
appDataRepository.appState.setManualStop()
|
||||
}
|
||||
|
||||
private suspend fun onManualStart(tunnelId: Int?) {
|
||||
tunnelId?.let {
|
||||
appDataRepository.appState.setTunnelRunningFromManualStart(it)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun startVpnServiceForeground(context: Context, tunnelId: Int? = null, isManualStart: Boolean = false) {
|
||||
withContext(ioDispatcher) {
|
||||
if (isManualStart) onManualStart(tunnelId)
|
||||
actionOnService(
|
||||
Action.START_FOREGROUND,
|
||||
context,
|
||||
WireGuardTunnelService::class.java,
|
||||
tunnelId?.let { mapOf(Constants.TUNNEL_EXTRA_KEY to it) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun startWatcherServiceForeground(context: Context) {
|
||||
actionOnService(
|
||||
Action.START_FOREGROUND,
|
||||
context,
|
||||
WireGuardConnectivityWatcherService::class.java,
|
||||
)
|
||||
}
|
||||
|
||||
fun startWatcherService(context: Context) {
|
||||
actionOnService(
|
||||
Action.START,
|
||||
context,
|
||||
WireGuardConnectivityWatcherService::class.java,
|
||||
)
|
||||
}
|
||||
|
||||
fun stopWatcherService(context: Context) {
|
||||
actionOnService(
|
||||
Action.STOP,
|
||||
context,
|
||||
WireGuardConnectivityWatcherService::class.java,
|
||||
)
|
||||
}
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
package com.zaneschepke.wireguardautotunnel.service.foreground
|
||||
|
||||
enum class ServiceState {
|
||||
STARTED,
|
||||
STOPPED,
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
package com.zaneschepke.wireguardautotunnel.service.foreground
|
||||
|
||||
import android.app.ActivityManager
|
||||
import android.app.Application
|
||||
import android.app.Service
|
||||
import android.content.Context
|
||||
import android.content.Context.ACTIVITY_SERVICE
|
||||
import android.content.Intent
|
||||
import com.google.firebase.crashlytics.ktx.crashlytics
|
||||
import com.google.firebase.ktx.Firebase
|
||||
|
||||
object ServiceTracker {
|
||||
@Suppress("DEPRECATION")
|
||||
private // Deprecated for third party Services.
|
||||
fun <T> Context.isServiceRunning(service: Class<T>) =
|
||||
(getSystemService(ACTIVITY_SERVICE) as ActivityManager)
|
||||
.getRunningServices(Integer.MAX_VALUE)
|
||||
.any { it.service.className == service.name }
|
||||
|
||||
fun <T : Service> getServiceState(context: Context, cls : Class<T>): ServiceState {
|
||||
val isServiceRunning = context.isServiceRunning(cls)
|
||||
return if(isServiceRunning) ServiceState.STARTED else ServiceState.STOPPED
|
||||
}
|
||||
|
||||
fun <T : Service> actionOnService(action: Action, application: Application, cls : Class<T>, extras : Map<String,String>? = null) {
|
||||
if (getServiceState(application, cls) == ServiceState.STOPPED && action == Action.STOP) return
|
||||
val intent = Intent(application, cls).also {
|
||||
it.action = action.name
|
||||
extras?.forEach {(k, v) ->
|
||||
it.putExtra(k, v)
|
||||
}
|
||||
}
|
||||
intent.component?.javaClass
|
||||
try {
|
||||
application.startService(intent)
|
||||
} catch (e : Exception) {
|
||||
e.message?.let { Firebase.crashlytics.log(it) }
|
||||
}
|
||||
}
|
||||
|
||||
fun <T : Service> actionOnService(action: Action, context: Context, cls : Class<T>, extras : Map<String,String>? = null) {
|
||||
if (getServiceState(context, cls) == ServiceState.STOPPED && action == Action.STOP) return
|
||||
val intent = Intent(context, cls).also {
|
||||
it.action = action.name
|
||||
extras?.forEach {(k, v) ->
|
||||
it.putExtra(k, v)
|
||||
}
|
||||
}
|
||||
intent.component?.javaClass
|
||||
try {
|
||||
context.startService(intent)
|
||||
} catch (e : Exception) {
|
||||
e.message?.let { Firebase.crashlytics.log(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
-73
@@ -1,73 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.service.foreground
|
||||
|
||||
import com.zaneschepke.wireguardautotunnel.data.domain.Settings
|
||||
|
||||
data class WatcherState(
|
||||
val isWifiConnected: Boolean = false,
|
||||
val isEthernetConnected: Boolean = false,
|
||||
val isMobileDataConnected: Boolean = false,
|
||||
val currentNetworkSSID: String = "",
|
||||
val settings: Settings = Settings(),
|
||||
) {
|
||||
fun isEthernetConditionMet(): Boolean {
|
||||
return (
|
||||
isEthernetConnected &&
|
||||
settings.isTunnelOnEthernetEnabled
|
||||
)
|
||||
}
|
||||
|
||||
fun isMobileDataConditionMet(): Boolean {
|
||||
return (
|
||||
!isEthernetConnected &&
|
||||
settings.isTunnelOnMobileDataEnabled &&
|
||||
!isWifiConnected &&
|
||||
isMobileDataConnected
|
||||
)
|
||||
}
|
||||
|
||||
fun isTunnelOffOnMobileDataConditionMet(): Boolean {
|
||||
return (
|
||||
!isEthernetConnected &&
|
||||
!settings.isTunnelOnMobileDataEnabled &&
|
||||
isMobileDataConnected &&
|
||||
!isWifiConnected
|
||||
)
|
||||
}
|
||||
|
||||
fun isUntrustedWifiConditionMet(): Boolean {
|
||||
return (
|
||||
!isEthernetConnected &&
|
||||
isWifiConnected &&
|
||||
!settings.trustedNetworkSSIDs.contains(currentNetworkSSID) &&
|
||||
settings.isTunnelOnWifiEnabled
|
||||
)
|
||||
}
|
||||
|
||||
fun isTrustedWifiConditionMet(): Boolean {
|
||||
return (
|
||||
!isEthernetConnected &&
|
||||
(
|
||||
isWifiConnected &&
|
||||
settings.trustedNetworkSSIDs.contains(currentNetworkSSID)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun isTunnelOffOnWifiConditionMet(): Boolean {
|
||||
return (
|
||||
!isEthernetConnected &&
|
||||
(
|
||||
isWifiConnected &&
|
||||
!settings.isTunnelOnWifiEnabled
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun isTunnelOffOnNoConnectivityMet(): Boolean {
|
||||
return (
|
||||
!isEthernetConnected &&
|
||||
!isWifiConnected &&
|
||||
!isMobileDataConnected
|
||||
)
|
||||
}
|
||||
}
|
||||
+196
-434
@@ -1,474 +1,236 @@
|
||||
package com.zaneschepke.wireguardautotunnel.service.foreground
|
||||
|
||||
import android.app.AlarmManager
|
||||
import android.app.Application
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.os.PowerManager
|
||||
import androidx.core.app.ServiceCompat
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import android.os.SystemClock
|
||||
import com.wireguard.android.backend.Tunnel
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
||||
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
||||
import com.zaneschepke.wireguardautotunnel.module.IoDispatcher
|
||||
import com.zaneschepke.wireguardautotunnel.module.MainImmediateDispatcher
|
||||
import com.zaneschepke.wireguardautotunnel.service.network.EthernetService
|
||||
import com.zaneschepke.wireguardautotunnel.repository.Repository
|
||||
import com.zaneschepke.wireguardautotunnel.service.network.MobileDataService
|
||||
import com.zaneschepke.wireguardautotunnel.service.network.NetworkService
|
||||
import com.zaneschepke.wireguardautotunnel.service.network.NetworkStatus
|
||||
import com.zaneschepke.wireguardautotunnel.service.network.WifiService
|
||||
import com.zaneschepke.wireguardautotunnel.service.notification.NotificationService
|
||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelState
|
||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.VpnService
|
||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.model.Settings
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
import java.net.InetAddress
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class WireGuardConnectivityWatcherService : ForegroundService() {
|
||||
private val foregroundId = 122
|
||||
|
||||
@Inject
|
||||
lateinit var wifiService: NetworkService<WifiService>
|
||||
private val foregroundId = 122;
|
||||
|
||||
@Inject
|
||||
lateinit var mobileDataService: NetworkService<MobileDataService>
|
||||
@Inject
|
||||
lateinit var wifiService : NetworkService<WifiService>
|
||||
|
||||
@Inject
|
||||
lateinit var ethernetService: NetworkService<EthernetService>
|
||||
@Inject
|
||||
lateinit var mobileDataService : NetworkService<MobileDataService>
|
||||
|
||||
@Inject
|
||||
lateinit var appDataRepository: AppDataRepository
|
||||
@Inject
|
||||
lateinit var settingsRepo: Repository<Settings>
|
||||
|
||||
@Inject
|
||||
lateinit var notificationService: NotificationService
|
||||
@Inject
|
||||
lateinit var notificationService : NotificationService
|
||||
|
||||
@Inject
|
||||
lateinit var vpnService: VpnService
|
||||
@Inject
|
||||
lateinit var vpnService : VpnService
|
||||
|
||||
@Inject
|
||||
lateinit var serviceManager: ServiceManager
|
||||
private lateinit var watcherJob : Job;
|
||||
private lateinit var setting : Settings
|
||||
private lateinit var tunnelId: String
|
||||
|
||||
@Inject
|
||||
@IoDispatcher
|
||||
lateinit var ioDispatcher: CoroutineDispatcher
|
||||
private var connecting = false
|
||||
private var disconnecting = false
|
||||
private var isWifiConnected = false
|
||||
private var isMobileDataConnected = false
|
||||
|
||||
@Inject
|
||||
@MainImmediateDispatcher
|
||||
lateinit var mainImmediateDispatcher: CoroutineDispatcher
|
||||
private var wakeLock: PowerManager.WakeLock? = null
|
||||
private val tag = this.javaClass.name;
|
||||
|
||||
private val networkEventsFlow = MutableStateFlow(WatcherState())
|
||||
|
||||
private var watcherJob: Job? = null
|
||||
override fun startService(extras: Bundle?) {
|
||||
super.startService(extras)
|
||||
val tunnelId = extras?.getString(getString(R.string.tunnel_extras_key))
|
||||
if (tunnelId != null) {
|
||||
this.tunnelId = tunnelId
|
||||
}
|
||||
// we need this lock so our service gets not affected by Doze Mode
|
||||
initWakeLock()
|
||||
cancelWatcherJob()
|
||||
launchWatcherNotification()
|
||||
if(this::tunnelId.isInitialized) {
|
||||
startWatcherJob()
|
||||
} else {
|
||||
stopService(extras)
|
||||
}
|
||||
}
|
||||
|
||||
private var wakeLock: PowerManager.WakeLock? = null
|
||||
private val tag = this.javaClass.name
|
||||
override fun stopService(extras: Bundle?) {
|
||||
super.stopService(extras)
|
||||
wakeLock?.let {
|
||||
if (it.isHeld) {
|
||||
it.release()
|
||||
}
|
||||
}
|
||||
cancelWatcherJob()
|
||||
stopVPN()
|
||||
stopSelf()
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
lifecycleScope.launch(mainImmediateDispatcher) {
|
||||
try {
|
||||
if (appDataRepository.settings.getSettings().isAutoTunnelPaused) {
|
||||
launchWatcherPausedNotification()
|
||||
} else {
|
||||
launchWatcherNotification()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.e("Failed to start watcher service, not enough permissions")
|
||||
}
|
||||
}
|
||||
}
|
||||
private fun launchWatcherNotification() {
|
||||
val notification = notificationService.createNotification(
|
||||
channelId = getString(R.string.watcher_channel_id),
|
||||
channelName = getString(R.string.watcher_channel_name),
|
||||
description = getString(R.string.watcher_notification_text))
|
||||
super.startForeground(foregroundId, notification)
|
||||
}
|
||||
|
||||
override fun startService(extras: Bundle?) {
|
||||
super.startService(extras)
|
||||
try {
|
||||
// we need this lock so our service gets not affected by Doze Mode
|
||||
lifecycleScope.launch { initWakeLock() }
|
||||
cancelWatcherJob()
|
||||
startWatcherJob()
|
||||
} catch (e: Exception) {
|
||||
Timber.e("Failed to launch watcher service, no permissions")
|
||||
}
|
||||
}
|
||||
//try to start task again if killed
|
||||
override fun onTaskRemoved(rootIntent: Intent) {
|
||||
Timber.d("Task Removed called")
|
||||
val restartServiceIntent = Intent(rootIntent)
|
||||
val restartServicePendingIntent: PendingIntent = PendingIntent.getService(this, 1, restartServiceIntent,
|
||||
PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE);
|
||||
applicationContext.getSystemService(Context.ALARM_SERVICE);
|
||||
val alarmService: AlarmManager = applicationContext.getSystemService(Context.ALARM_SERVICE) as AlarmManager;
|
||||
alarmService.set(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime() + 1000, restartServicePendingIntent);
|
||||
}
|
||||
|
||||
override fun stopService() {
|
||||
super.stopService()
|
||||
wakeLock?.let {
|
||||
if (it.isHeld) {
|
||||
it.release()
|
||||
}
|
||||
}
|
||||
cancelWatcherJob()
|
||||
stopSelf()
|
||||
}
|
||||
private fun initWakeLock() {
|
||||
wakeLock =
|
||||
(getSystemService(Context.POWER_SERVICE) as PowerManager).run {
|
||||
newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "$tag::lock").apply {
|
||||
acquire()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun launchWatcherNotification(description: String = getString(R.string.watcher_notification_text_active)) {
|
||||
val notification =
|
||||
notificationService.createNotification(
|
||||
channelId = getString(R.string.watcher_channel_id),
|
||||
channelName = getString(R.string.watcher_channel_name),
|
||||
title = getString(R.string.auto_tunnel_title),
|
||||
description = description,
|
||||
)
|
||||
ServiceCompat.startForeground(
|
||||
this,
|
||||
foregroundId,
|
||||
notification,
|
||||
Constants.SYSTEM_EXEMPT_SERVICE_TYPE_ID,
|
||||
)
|
||||
}
|
||||
private fun cancelWatcherJob() {
|
||||
if(this::watcherJob.isInitialized) {
|
||||
watcherJob.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
private fun launchWatcherPausedNotification() {
|
||||
launchWatcherNotification(getString(R.string.watcher_notification_text_paused))
|
||||
}
|
||||
private fun startWatcherJob() {
|
||||
watcherJob = CoroutineScope(SupervisorJob()).launch {
|
||||
val settings = settingsRepo.getAll();
|
||||
if(!settings.isNullOrEmpty()) {
|
||||
setting = settings[0]
|
||||
}
|
||||
CoroutineScope(watcherJob).launch {
|
||||
watchForWifiConnectivityChanges()
|
||||
}
|
||||
if(setting.isTunnelOnMobileDataEnabled) {
|
||||
CoroutineScope(watcherJob).launch {
|
||||
watchForMobileDataConnectivityChanges()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun initWakeLock() {
|
||||
wakeLock =
|
||||
(getSystemService(Context.POWER_SERVICE) as PowerManager).run {
|
||||
newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "$tag::lock").apply {
|
||||
try {
|
||||
Timber.i("Initiating wakelock with 10 min timeout")
|
||||
acquire(Constants.BATTERY_SAVER_WATCHER_WAKE_LOCK_TIMEOUT)
|
||||
} finally {
|
||||
release()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
private suspend fun watchForMobileDataConnectivityChanges() {
|
||||
mobileDataService.networkStatus.collect {
|
||||
when(it) {
|
||||
is NetworkStatus.Available -> {
|
||||
Timber.d("Gained Mobile data connection")
|
||||
isMobileDataConnected = true
|
||||
}
|
||||
is NetworkStatus.CapabilitiesChanged -> {
|
||||
isMobileDataConnected = true
|
||||
Timber.d("Mobile data capabilities changed")
|
||||
if(!disconnecting && !connecting) {
|
||||
if(!isWifiConnected && setting.isTunnelOnMobileDataEnabled
|
||||
&& vpnService.getState() == Tunnel.State.DOWN)
|
||||
startVPN()
|
||||
}
|
||||
}
|
||||
is NetworkStatus.Unavailable -> {
|
||||
isMobileDataConnected = false
|
||||
if(!disconnecting && !connecting) {
|
||||
if(!isWifiConnected && vpnService.getState() == Tunnel.State.UP) stopVPN()
|
||||
}
|
||||
Timber.d("Lost mobile data connection")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun cancelWatcherJob() {
|
||||
try {
|
||||
watcherJob?.cancel()
|
||||
} catch (e: CancellationException) {
|
||||
Timber.i("Watcher job cancelled")
|
||||
}
|
||||
}
|
||||
private suspend fun watchForWifiConnectivityChanges() {
|
||||
wifiService.networkStatus.collect {
|
||||
when (it) {
|
||||
is NetworkStatus.Available -> {
|
||||
Timber.d("Gained Wi-Fi connection")
|
||||
isWifiConnected = true
|
||||
}
|
||||
is NetworkStatus.CapabilitiesChanged -> {
|
||||
Timber.d("Wifi capabilities changed")
|
||||
isWifiConnected = true
|
||||
if (!connecting && !disconnecting) {
|
||||
Timber.d("Not connect and not disconnecting")
|
||||
val ssid = wifiService.getNetworkName(it.networkCapabilities);
|
||||
Timber.d("SSID: $ssid")
|
||||
if (!setting.trustedNetworkSSIDs.contains(ssid) && vpnService.getState() == Tunnel.State.DOWN) {
|
||||
Timber.d("Starting VPN Tunnel for untrusted network: $ssid")
|
||||
startVPN()
|
||||
} else if (!disconnecting && vpnService.getState() == Tunnel.State.UP && setting.trustedNetworkSSIDs.contains(
|
||||
ssid
|
||||
)
|
||||
) {
|
||||
Timber.d("Stopping VPN Tunnel for trusted network with ssid: $ssid")
|
||||
stopVPN()
|
||||
}
|
||||
}
|
||||
}
|
||||
is NetworkStatus.Unavailable -> {
|
||||
isWifiConnected = false
|
||||
Timber.d("Lost Wi-Fi connection")
|
||||
if(!connecting || !disconnecting) {
|
||||
if(setting.isTunnelOnMobileDataEnabled && vpnService.getState() == Tunnel.State.DOWN
|
||||
&& isMobileDataConnected){
|
||||
Timber.d("Wifi not available so starting vpn for mobile data")
|
||||
startVPN()
|
||||
}
|
||||
if(!setting.isTunnelOnMobileDataEnabled && vpnService.getState() == Tunnel.State.UP) {
|
||||
Timber.d("Lost WiFi connection, disabling vpn")
|
||||
stopVPN()
|
||||
}
|
||||
}
|
||||
|
||||
private fun startWatcherJob() {
|
||||
watcherJob =
|
||||
lifecycleScope.launch {
|
||||
val setting = appDataRepository.settings.getSettings()
|
||||
launch {
|
||||
Timber.i("Starting wifi watcher")
|
||||
watchForWifiConnectivityChanges()
|
||||
}
|
||||
if (setting.isTunnelOnMobileDataEnabled) {
|
||||
launch {
|
||||
Timber.i("Starting mobile data watcher")
|
||||
watchForMobileDataConnectivityChanges()
|
||||
}
|
||||
}
|
||||
if (setting.isTunnelOnEthernetEnabled) {
|
||||
launch {
|
||||
Timber.i("Starting ethernet data watcher")
|
||||
watchForEthernetConnectivityChanges()
|
||||
}
|
||||
}
|
||||
launch {
|
||||
Timber.i("Starting settings watcher")
|
||||
watchForSettingsChanges()
|
||||
}
|
||||
if (setting.isPingEnabled) {
|
||||
launch {
|
||||
Timber.i("Starting ping watcher")
|
||||
watchForPingFailure()
|
||||
}
|
||||
}
|
||||
launch {
|
||||
Timber.i("Starting management watcher")
|
||||
manageVpn()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun watchForMobileDataConnectivityChanges() {
|
||||
withContext(ioDispatcher) {
|
||||
mobileDataService.networkStatus.collect { status ->
|
||||
when (status) {
|
||||
is NetworkStatus.Available -> {
|
||||
Timber.i("Gained Mobile data connection")
|
||||
networkEventsFlow.update {
|
||||
it.copy(
|
||||
isMobileDataConnected = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
is NetworkStatus.CapabilitiesChanged -> {
|
||||
networkEventsFlow.update {
|
||||
it.copy(
|
||||
isMobileDataConnected = true,
|
||||
)
|
||||
}
|
||||
Timber.i("Mobile data capabilities changed")
|
||||
}
|
||||
|
||||
is NetworkStatus.Unavailable -> {
|
||||
networkEventsFlow.update {
|
||||
it.copy(
|
||||
isMobileDataConnected = false,
|
||||
)
|
||||
}
|
||||
Timber.i("Lost mobile data connection")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun watchForPingFailure() {
|
||||
val context = this
|
||||
withContext(ioDispatcher) {
|
||||
try {
|
||||
do {
|
||||
if (vpnService.vpnState.value.status == TunnelState.UP) {
|
||||
val tunnelConfig = vpnService.vpnState.value.tunnelConfig
|
||||
tunnelConfig?.let {
|
||||
val config = TunnelConfig.configFromWgQuick(it.wgQuick)
|
||||
val results =
|
||||
config.peers.map { peer ->
|
||||
val host =
|
||||
if (peer.endpoint.isPresent &&
|
||||
peer.endpoint.get().resolved.isPresent
|
||||
) {
|
||||
peer.endpoint.get().resolved.get().host
|
||||
} else {
|
||||
Constants.DEFAULT_PING_IP
|
||||
}
|
||||
Timber.i("Checking reachability of: $host")
|
||||
val reachable =
|
||||
InetAddress.getByName(host)
|
||||
.isReachable(Constants.PING_TIMEOUT.toInt())
|
||||
Timber.i("Result: reachable - $reachable")
|
||||
reachable
|
||||
}
|
||||
if (results.contains(false)) {
|
||||
Timber.i("Restarting VPN for ping failure")
|
||||
serviceManager.stopVpnServiceForeground(context)
|
||||
delay(Constants.VPN_RESTART_DELAY)
|
||||
serviceManager.startVpnServiceForeground(context, it.id)
|
||||
delay(Constants.PING_COOLDOWN)
|
||||
}
|
||||
}
|
||||
}
|
||||
delay(Constants.PING_INTERVAL)
|
||||
} while (true)
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun watchForSettingsChanges() {
|
||||
appDataRepository.settings.getSettingsFlow().collect { settings ->
|
||||
if (networkEventsFlow.value.settings.isAutoTunnelPaused
|
||||
!= settings.isAutoTunnelPaused
|
||||
) {
|
||||
when (settings.isAutoTunnelPaused) {
|
||||
true -> launchWatcherPausedNotification()
|
||||
false -> launchWatcherNotification()
|
||||
}
|
||||
}
|
||||
networkEventsFlow.update {
|
||||
it.copy(
|
||||
settings = settings,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun watchForEthernetConnectivityChanges() {
|
||||
withContext(ioDispatcher) {
|
||||
ethernetService.networkStatus.collect { status ->
|
||||
when (status) {
|
||||
is NetworkStatus.Available -> {
|
||||
Timber.i("Gained Ethernet connection")
|
||||
networkEventsFlow.update {
|
||||
it.copy(
|
||||
isEthernetConnected = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
is NetworkStatus.CapabilitiesChanged -> {
|
||||
Timber.i("Ethernet capabilities changed")
|
||||
networkEventsFlow.update {
|
||||
it.copy(
|
||||
isEthernetConnected = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
is NetworkStatus.Unavailable -> {
|
||||
networkEventsFlow.update {
|
||||
it.copy(
|
||||
isEthernetConnected = false,
|
||||
)
|
||||
}
|
||||
Timber.i("Lost Ethernet connection")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun watchForWifiConnectivityChanges() {
|
||||
withContext(ioDispatcher) {
|
||||
wifiService.networkStatus.collect { status ->
|
||||
when (status) {
|
||||
is NetworkStatus.Available -> {
|
||||
Timber.i("Gained Wi-Fi connection")
|
||||
networkEventsFlow.update {
|
||||
it.copy(
|
||||
isWifiConnected = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
is NetworkStatus.CapabilitiesChanged -> {
|
||||
Timber.i("Wifi capabilities changed")
|
||||
networkEventsFlow.update {
|
||||
it.copy(
|
||||
isWifiConnected = true,
|
||||
)
|
||||
}
|
||||
val ssid = wifiService.getNetworkName(status.networkCapabilities)
|
||||
ssid?.let { name ->
|
||||
if (name.contains(Constants.UNREADABLE_SSID)) {
|
||||
Timber.w("SSID unreadable: missing permissions")
|
||||
} else {
|
||||
Timber.i("Detected valid SSID")
|
||||
}
|
||||
appDataRepository.appState.setCurrentSsid(name)
|
||||
networkEventsFlow.update {
|
||||
it.copy(
|
||||
currentNetworkSSID = name,
|
||||
)
|
||||
}
|
||||
} ?: Timber.w("Failed to read ssid")
|
||||
}
|
||||
|
||||
is NetworkStatus.Unavailable -> {
|
||||
networkEventsFlow.update {
|
||||
it.copy(
|
||||
isWifiConnected = false,
|
||||
)
|
||||
}
|
||||
Timber.i("Lost Wi-Fi connection")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getMobileDataTunnel(): TunnelConfig? {
|
||||
return appDataRepository.tunnels.findByMobileDataTunnel().firstOrNull()
|
||||
}
|
||||
|
||||
private suspend fun getSsidTunnel(ssid: String): TunnelConfig? {
|
||||
return appDataRepository.tunnels.findByTunnelNetworksName(ssid).firstOrNull()
|
||||
}
|
||||
|
||||
private fun isTunnelDown(): Boolean {
|
||||
return vpnService.vpnState.value.status == TunnelState.DOWN
|
||||
}
|
||||
|
||||
private suspend fun manageVpn() {
|
||||
val context = this
|
||||
withContext(ioDispatcher) {
|
||||
networkEventsFlow.collectLatest { watcherState ->
|
||||
val autoTunnel = "Auto-tunnel watcher"
|
||||
if (!watcherState.settings.isAutoTunnelPaused) {
|
||||
// delay for rapid network state changes and then collect latest
|
||||
delay(Constants.WATCHER_COLLECTION_DELAY)
|
||||
val tunnelConfig = vpnService.vpnState.value.tunnelConfig
|
||||
when {
|
||||
watcherState.isEthernetConditionMet() -> {
|
||||
Timber.i("$autoTunnel - tunnel on on ethernet condition met")
|
||||
if (isTunnelDown()) serviceManager.startVpnServiceForeground(context)
|
||||
}
|
||||
|
||||
watcherState.isMobileDataConditionMet() -> {
|
||||
Timber.i("$autoTunnel - tunnel on mobile data condition met")
|
||||
val mobileDataTunnel = getMobileDataTunnel()
|
||||
val tunnel =
|
||||
mobileDataTunnel ?: appDataRepository.getPrimaryOrFirstTunnel()
|
||||
if (isTunnelDown() || tunnelConfig?.isMobileDataTunnel == false) {
|
||||
serviceManager.startVpnServiceForeground(
|
||||
context,
|
||||
tunnel?.id,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
watcherState.isTunnelOffOnMobileDataConditionMet() -> {
|
||||
Timber.i("$autoTunnel - tunnel off on mobile data met, turning vpn off")
|
||||
if (!isTunnelDown()) serviceManager.stopVpnServiceForeground(context)
|
||||
}
|
||||
|
||||
watcherState.isUntrustedWifiConditionMet() -> {
|
||||
if (tunnelConfig?.tunnelNetworks?.contains(watcherState.currentNetworkSSID) == false ||
|
||||
tunnelConfig == null
|
||||
) {
|
||||
Timber.i(
|
||||
"$autoTunnel - tunnel on ssid not associated with current tunnel condition met",
|
||||
)
|
||||
getSsidTunnel(watcherState.currentNetworkSSID)?.let {
|
||||
Timber.i("Found tunnel associated with this SSID, bringing tunnel up: ${it.name}")
|
||||
if (isTunnelDown() || tunnelConfig?.id != it.id) {
|
||||
serviceManager.startVpnServiceForeground(
|
||||
context,
|
||||
it.id,
|
||||
)
|
||||
}
|
||||
} ?: suspend {
|
||||
Timber.i("No tunnel associated with this SSID, using defaults")
|
||||
val default = appDataRepository.getPrimaryOrFirstTunnel()
|
||||
if (default?.name != vpnService.name) {
|
||||
default?.let {
|
||||
serviceManager.startVpnServiceForeground(context, it.id)
|
||||
}
|
||||
}
|
||||
}.invoke()
|
||||
}
|
||||
}
|
||||
|
||||
watcherState.isTrustedWifiConditionMet() -> {
|
||||
Timber.i(
|
||||
"$autoTunnel - tunnel off on trusted wifi condition met, turning vpn off",
|
||||
)
|
||||
if (!isTunnelDown()) serviceManager.stopVpnServiceForeground(context)
|
||||
}
|
||||
|
||||
watcherState.isTunnelOffOnWifiConditionMet() -> {
|
||||
Timber.i(
|
||||
"$autoTunnel - tunnel off on wifi condition met, turning vpn off",
|
||||
)
|
||||
if (!isTunnelDown()) serviceManager.stopVpnServiceForeground(context)
|
||||
}
|
||||
|
||||
watcherState.isTunnelOffOnNoConnectivityMet() -> {
|
||||
Timber.i(
|
||||
"$autoTunnel - tunnel off on no connectivity met, turning vpn off",
|
||||
)
|
||||
if (!isTunnelDown()) serviceManager.stopVpnServiceForeground(context)
|
||||
}
|
||||
|
||||
else -> {
|
||||
Timber.i("$autoTunnel - no condition met")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
private fun startVPN() {
|
||||
if(!connecting) {
|
||||
connecting = true
|
||||
ServiceTracker.actionOnService(
|
||||
Action.START,
|
||||
this.applicationContext as Application,
|
||||
WireGuardTunnelService::class.java,
|
||||
mapOf(getString(R.string.tunnel_extras_key) to tunnelId))
|
||||
connecting = false
|
||||
}
|
||||
}
|
||||
private fun stopVPN() {
|
||||
if(!disconnecting) {
|
||||
disconnecting = true
|
||||
ServiceTracker.actionOnService(
|
||||
Action.STOP,
|
||||
this.applicationContext as Application,
|
||||
WireGuardTunnelService::class.java
|
||||
)
|
||||
disconnecting = false
|
||||
}
|
||||
}
|
||||
}
|
||||
+111
-168
@@ -3,196 +3,139 @@ package com.zaneschepke.wireguardautotunnel.service.foreground
|
||||
import android.app.PendingIntent
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import androidx.core.app.ServiceCompat
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
||||
import com.zaneschepke.wireguardautotunnel.module.IoDispatcher
|
||||
import com.zaneschepke.wireguardautotunnel.module.MainImmediateDispatcher
|
||||
import com.zaneschepke.wireguardautotunnel.receiver.NotificationActionReceiver
|
||||
import com.zaneschepke.wireguardautotunnel.repository.Repository
|
||||
import com.zaneschepke.wireguardautotunnel.service.notification.NotificationService
|
||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.HandshakeStatus
|
||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelState
|
||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.VpnService
|
||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||
import com.zaneschepke.wireguardautotunnel.util.handshakeStatus
|
||||
import com.zaneschepke.wireguardautotunnel.util.mapPeerStats
|
||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.model.Settings
|
||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.model.TunnelConfig
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class WireGuardTunnelService : ForegroundService() {
|
||||
private val foregroundId = 123
|
||||
|
||||
@Inject
|
||||
lateinit var vpnService: VpnService
|
||||
private val foregroundId = 123;
|
||||
|
||||
@Inject
|
||||
lateinit var appDataRepository: AppDataRepository
|
||||
@Inject
|
||||
lateinit var vpnService : VpnService
|
||||
|
||||
@Inject
|
||||
lateinit var notificationService: NotificationService
|
||||
@Inject
|
||||
lateinit var settingsRepo: Repository<Settings>
|
||||
|
||||
@Inject
|
||||
@MainImmediateDispatcher
|
||||
lateinit var mainImmediateDispatcher: CoroutineDispatcher
|
||||
@Inject
|
||||
lateinit var notificationService : NotificationService
|
||||
|
||||
@Inject
|
||||
@IoDispatcher
|
||||
lateinit var ioDispatcher: CoroutineDispatcher
|
||||
private lateinit var job : Job
|
||||
|
||||
private var job: Job? = null
|
||||
private var tunnelName : String = ""
|
||||
|
||||
private var didShowConnected = false
|
||||
override fun startService(extras : Bundle?) {
|
||||
super.startService(extras)
|
||||
val tunnelConfigString = extras?.getString(getString(R.string.tunnel_extras_key))
|
||||
cancelJob()
|
||||
job = CoroutineScope(SupervisorJob()).launch {
|
||||
if(tunnelConfigString != null) {
|
||||
try {
|
||||
val tunnelConfig = TunnelConfig.from(tunnelConfigString)
|
||||
tunnelName = tunnelConfig.name
|
||||
vpnService.startTunnel(tunnelConfig)
|
||||
} catch (e : Exception) {
|
||||
Timber.e("Problem starting tunnel: ${e.message}")
|
||||
stopService(extras)
|
||||
}
|
||||
} else {
|
||||
Timber.d("Tunnel config null, starting default tunnel")
|
||||
val settings = settingsRepo.getAll();
|
||||
if(!settings.isNullOrEmpty()) {
|
||||
val setting = settings[0]
|
||||
if(setting.defaultTunnel != null && setting.isAlwaysOnVpnEnabled) {
|
||||
val tunnelConfig = TunnelConfig.from(setting.defaultTunnel!!)
|
||||
tunnelName = tunnelConfig.name
|
||||
vpnService.startTunnel(tunnelConfig)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
CoroutineScope(job).launch {
|
||||
var didShowConnected = false
|
||||
var didShowFailedHandshakeNotification = false
|
||||
vpnService.handshakeStatus.collect {
|
||||
when(it) {
|
||||
HandshakeStatus.NOT_STARTED -> {
|
||||
}
|
||||
HandshakeStatus.NEVER_CONNECTED -> {
|
||||
if(!didShowFailedHandshakeNotification) {
|
||||
launchVpnConnectionFailedNotification(getString(R.string.initial_connection_failure_message))
|
||||
didShowFailedHandshakeNotification = true
|
||||
didShowConnected = false
|
||||
}
|
||||
}
|
||||
HandshakeStatus.HEALTHY -> {
|
||||
if(!didShowConnected) {
|
||||
launchVpnConnectedNotification()
|
||||
didShowConnected = true
|
||||
}
|
||||
}
|
||||
HandshakeStatus.UNHEALTHY -> {
|
||||
if(!didShowFailedHandshakeNotification) {
|
||||
launchVpnConnectionFailedNotification(getString(R.string.lost_connection_failure_message))
|
||||
didShowFailedHandshakeNotification = true
|
||||
didShowConnected = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
lifecycleScope.launch(mainImmediateDispatcher) {
|
||||
// TODO fix this to not launch if AOVPN
|
||||
if (appDataRepository.tunnels.count() != 0) {
|
||||
launchVpnNotification()
|
||||
}
|
||||
}
|
||||
}
|
||||
override fun stopService(extras : Bundle?) {
|
||||
super.stopService(extras)
|
||||
CoroutineScope(Dispatchers.IO).launch() {
|
||||
vpnService.stopTunnel()
|
||||
}
|
||||
cancelJob()
|
||||
stopSelf()
|
||||
}
|
||||
|
||||
override fun startService(extras: Bundle?) {
|
||||
super.startService(extras)
|
||||
cancelJob()
|
||||
job =
|
||||
lifecycleScope.launch {
|
||||
launch {
|
||||
val tunnelId = extras?.getInt(Constants.TUNNEL_EXTRA_KEY)
|
||||
if (vpnService.getState() == TunnelState.UP) {
|
||||
vpnService.stopTunnel()
|
||||
}
|
||||
vpnService.startTunnel(
|
||||
tunnelId?.let {
|
||||
appDataRepository.tunnels.getById(it)
|
||||
},
|
||||
)
|
||||
}
|
||||
launch {
|
||||
handshakeNotifications()
|
||||
}
|
||||
}
|
||||
}
|
||||
private fun launchVpnConnectedNotification() {
|
||||
val notification = notificationService.createNotification(
|
||||
channelId = getString(R.string.vpn_channel_id),
|
||||
channelName = getString(R.string.vpn_channel_name),
|
||||
title = getString(R.string.tunnel_start_title),
|
||||
onGoing = false,
|
||||
showTimestamp = true,
|
||||
description = "${getString(R.string.tunnel_start_text)} $tunnelName"
|
||||
)
|
||||
super.startForeground(foregroundId, notification)
|
||||
}
|
||||
|
||||
// TODO improve tunnel notifications
|
||||
private suspend fun handshakeNotifications() {
|
||||
withContext(ioDispatcher) {
|
||||
var tunnelName: String? = null
|
||||
vpnService.vpnState.collect { state ->
|
||||
state.statistics
|
||||
?.mapPeerStats()
|
||||
?.map { it.value?.handshakeStatus() }
|
||||
.let { statuses ->
|
||||
when {
|
||||
statuses?.all { it == HandshakeStatus.HEALTHY } == true -> {
|
||||
if (!didShowConnected) {
|
||||
delay(Constants.VPN_CONNECTED_NOTIFICATION_DELAY)
|
||||
tunnelName = state.tunnelConfig?.name
|
||||
launchVpnNotification(
|
||||
getString(R.string.tunnel_start_title),
|
||||
"${getString(R.string.tunnel_start_text)} - $tunnelName",
|
||||
)
|
||||
didShowConnected = true
|
||||
}
|
||||
}
|
||||
private fun launchVpnConnectionFailedNotification(message : String) {
|
||||
val notification = notificationService.createNotification(
|
||||
channelId = getString(R.string.vpn_channel_id),
|
||||
channelName = getString(R.string.vpn_channel_name),
|
||||
action = PendingIntent.getBroadcast(this,0,Intent(this, NotificationActionReceiver::class.java),PendingIntent.FLAG_IMMUTABLE),
|
||||
actionText = getString(R.string.restart),
|
||||
title = getString(R.string.vpn_connection_failed),
|
||||
onGoing = false,
|
||||
showTimestamp = true,
|
||||
description = message
|
||||
)
|
||||
super.startForeground(foregroundId, notification)
|
||||
}
|
||||
|
||||
statuses?.any { it == HandshakeStatus.STALE } == true -> {}
|
||||
statuses?.all { it == HandshakeStatus.NOT_STARTED } ==
|
||||
true -> {
|
||||
}
|
||||
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
if (state.status == TunnelState.UP && state.tunnelConfig?.name != tunnelName) {
|
||||
tunnelName = state.tunnelConfig?.name
|
||||
launchVpnNotification(
|
||||
getString(R.string.tunnel_start_title),
|
||||
"${getString(R.string.tunnel_start_text)} - $tunnelName",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun launchAlwaysOnDisabledNotification() {
|
||||
launchVpnNotification(
|
||||
title = this.getString(R.string.vpn_connection_failed),
|
||||
description = this.getString(R.string.always_on_disabled),
|
||||
)
|
||||
}
|
||||
|
||||
override fun stopService() {
|
||||
super.stopService()
|
||||
lifecycleScope.launch {
|
||||
vpnService.stopTunnel()
|
||||
didShowConnected = false
|
||||
}
|
||||
cancelJob()
|
||||
stopSelf()
|
||||
}
|
||||
|
||||
private fun launchVpnNotification(title: String = getString(R.string.vpn_starting), description: String = getString(R.string.attempt_connection)) {
|
||||
val notification =
|
||||
notificationService.createNotification(
|
||||
channelId = getString(R.string.vpn_channel_id),
|
||||
channelName = getString(R.string.vpn_channel_name),
|
||||
title = title,
|
||||
onGoing = false,
|
||||
vibration = false,
|
||||
showTimestamp = true,
|
||||
description = description,
|
||||
)
|
||||
ServiceCompat.startForeground(
|
||||
this,
|
||||
foregroundId,
|
||||
notification,
|
||||
Constants.SYSTEM_EXEMPT_SERVICE_TYPE_ID,
|
||||
)
|
||||
}
|
||||
|
||||
private fun launchVpnConnectionFailedNotification(message: String) {
|
||||
val notification =
|
||||
notificationService.createNotification(
|
||||
channelId = getString(R.string.vpn_channel_id),
|
||||
channelName = getString(R.string.vpn_channel_name),
|
||||
action =
|
||||
PendingIntent.getBroadcast(
|
||||
this,
|
||||
0,
|
||||
Intent(this, NotificationActionReceiver::class.java),
|
||||
PendingIntent.FLAG_IMMUTABLE,
|
||||
),
|
||||
actionText = getString(R.string.restart),
|
||||
title = getString(R.string.vpn_connection_failed),
|
||||
onGoing = false,
|
||||
vibration = true,
|
||||
showTimestamp = true,
|
||||
description = message,
|
||||
)
|
||||
ServiceCompat.startForeground(
|
||||
this,
|
||||
foregroundId,
|
||||
notification,
|
||||
Constants.SYSTEM_EXEMPT_SERVICE_TYPE_ID,
|
||||
)
|
||||
}
|
||||
|
||||
private fun cancelJob() {
|
||||
try {
|
||||
job?.cancel()
|
||||
} catch (e: CancellationException) {
|
||||
Timber.i("Tunnel job cancelled")
|
||||
}
|
||||
}
|
||||
}
|
||||
private fun cancelJob() {
|
||||
if(this::job.isInitialized) {
|
||||
job.cancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
+84
-97
@@ -14,114 +14,101 @@ import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.callbackFlow
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
abstract class BaseNetworkService<T : BaseNetworkService<T>>(
|
||||
val context: Context,
|
||||
networkCapability: Int,
|
||||
) : NetworkService<T> {
|
||||
private val connectivityManager =
|
||||
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||
|
||||
private val wifiManager =
|
||||
context.applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager
|
||||
abstract class BaseNetworkService<T : BaseNetworkService<T>>(val context: Context, networkCapability : Int) : NetworkService<T> {
|
||||
private val connectivityManager =
|
||||
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||
|
||||
override val networkStatus =
|
||||
callbackFlow {
|
||||
val networkStatusCallback =
|
||||
when (Build.VERSION.SDK_INT) {
|
||||
in Build.VERSION_CODES.S..Int.MAX_VALUE -> {
|
||||
object :
|
||||
ConnectivityManager.NetworkCallback(
|
||||
FLAG_INCLUDE_LOCATION_INFO,
|
||||
) {
|
||||
override fun onAvailable(network: Network) {
|
||||
trySend(NetworkStatus.Available(network))
|
||||
}
|
||||
private val wifiManager =
|
||||
context.applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager
|
||||
|
||||
override fun onLost(network: Network) {
|
||||
trySend(NetworkStatus.Unavailable(network))
|
||||
}
|
||||
override val networkStatus = callbackFlow {
|
||||
val networkStatusCallback = when (Build.VERSION.SDK_INT) {
|
||||
in Build.VERSION_CODES.S..Int.MAX_VALUE -> {
|
||||
object : ConnectivityManager.NetworkCallback(
|
||||
FLAG_INCLUDE_LOCATION_INFO
|
||||
) {
|
||||
|
||||
override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
|
||||
trySend(
|
||||
NetworkStatus.CapabilitiesChanged(
|
||||
network,
|
||||
networkCapabilities,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
override fun onAvailable(network: Network) {
|
||||
trySend(NetworkStatus.Available(network))
|
||||
}
|
||||
|
||||
else -> {
|
||||
object : ConnectivityManager.NetworkCallback() {
|
||||
override fun onAvailable(network: Network) {
|
||||
trySend(NetworkStatus.Available(network))
|
||||
}
|
||||
override fun onLost(network: Network) {
|
||||
trySend(NetworkStatus.Unavailable(network))
|
||||
}
|
||||
|
||||
override fun onLost(network: Network) {
|
||||
trySend(NetworkStatus.Unavailable(network))
|
||||
}
|
||||
override fun onCapabilitiesChanged(
|
||||
network: Network,
|
||||
networkCapabilities: NetworkCapabilities
|
||||
) {
|
||||
trySend(NetworkStatus.CapabilitiesChanged(network, networkCapabilities))
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
object : ConnectivityManager.NetworkCallback() {
|
||||
|
||||
override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
|
||||
trySend(
|
||||
NetworkStatus.CapabilitiesChanged(
|
||||
network,
|
||||
networkCapabilities,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
val request =
|
||||
NetworkRequest.Builder()
|
||||
.addTransportType(networkCapability)
|
||||
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
|
||||
.addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
|
||||
.build()
|
||||
connectivityManager.registerNetworkCallback(request, networkStatusCallback)
|
||||
override fun onAvailable(network: Network) {
|
||||
trySend(NetworkStatus.Available(network))
|
||||
}
|
||||
|
||||
awaitClose { connectivityManager.unregisterNetworkCallback(networkStatusCallback) }
|
||||
}
|
||||
override fun onLost(network: Network) {
|
||||
trySend(NetworkStatus.Unavailable(network))
|
||||
}
|
||||
|
||||
override fun getNetworkName(networkCapabilities: NetworkCapabilities): String? {
|
||||
var ssid: String? = getWifiNameFromCapabilities(networkCapabilities)
|
||||
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) {
|
||||
val info = wifiManager.connectionInfo
|
||||
if (info.supplicantState === SupplicantState.COMPLETED) {
|
||||
ssid = info.ssid
|
||||
}
|
||||
}
|
||||
return ssid?.trim('"')
|
||||
}
|
||||
override fun onCapabilitiesChanged(
|
||||
network: Network,
|
||||
networkCapabilities: NetworkCapabilities
|
||||
) {
|
||||
trySend(NetworkStatus.CapabilitiesChanged(network, networkCapabilities))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
val request = NetworkRequest.Builder()
|
||||
.addTransportType(networkCapability)
|
||||
.build()
|
||||
connectivityManager.registerNetworkCallback(request, networkStatusCallback)
|
||||
|
||||
companion object {
|
||||
private fun getWifiNameFromCapabilities(networkCapabilities: NetworkCapabilities): String? {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
val info: WifiInfo
|
||||
if (networkCapabilities.transportInfo is WifiInfo) {
|
||||
info = networkCapabilities.transportInfo as WifiInfo
|
||||
return info.ssid
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
awaitClose {
|
||||
connectivityManager.unregisterNetworkCallback(networkStatusCallback)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun getNetworkName(networkCapabilities: NetworkCapabilities): String? {
|
||||
var ssid : String? = getWifiNameFromCapabilities(networkCapabilities)
|
||||
if((Build.VERSION.SDK_INT == Build.VERSION_CODES.Q) || (Build.VERSION.SDK_INT == Build.VERSION_CODES.R)) {
|
||||
val info = wifiManager.connectionInfo
|
||||
if (info.supplicantState === SupplicantState.COMPLETED) {
|
||||
ssid = info.ssid
|
||||
}
|
||||
}
|
||||
return ssid?.trim('"')
|
||||
}
|
||||
|
||||
|
||||
companion object {
|
||||
private fun getWifiNameFromCapabilities(networkCapabilities: NetworkCapabilities) : String? {
|
||||
val info : WifiInfo
|
||||
if(networkCapabilities.transportInfo is WifiInfo) {
|
||||
info = networkCapabilities.transportInfo as WifiInfo
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
return info.ssid
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <Result> Flow<NetworkStatus>.map(
|
||||
crossinline onUnavailable: suspend (network: Network) -> Result,
|
||||
crossinline onAvailable: suspend (network: Network) -> Result,
|
||||
crossinline onCapabilitiesChanged:
|
||||
suspend (network: Network, networkCapabilities: NetworkCapabilities) -> Result,
|
||||
crossinline onUnavailable: suspend (network : Network) -> Result,
|
||||
crossinline onAvailable: suspend (network : Network) -> Result,
|
||||
crossinline onCapabilitiesChanged: suspend (network : Network, networkCapabilities : NetworkCapabilities) -> Result,
|
||||
): Flow<Result> = map { status ->
|
||||
when (status) {
|
||||
is NetworkStatus.Unavailable -> onUnavailable(status.network)
|
||||
is NetworkStatus.Available -> onAvailable(status.network)
|
||||
is NetworkStatus.CapabilitiesChanged ->
|
||||
onCapabilitiesChanged(
|
||||
status.network,
|
||||
status.networkCapabilities,
|
||||
)
|
||||
}
|
||||
}
|
||||
when (status) {
|
||||
is NetworkStatus.Unavailable -> onUnavailable(status.network)
|
||||
is NetworkStatus.Available -> onAvailable(status.network)
|
||||
is NetworkStatus.CapabilitiesChanged -> onCapabilitiesChanged(status.network, status.networkCapabilities)
|
||||
}
|
||||
}
|
||||
-13
@@ -1,13 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.service.network
|
||||
|
||||
import android.content.Context
|
||||
import android.net.NetworkCapabilities
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import javax.inject.Inject
|
||||
|
||||
class EthernetService
|
||||
@Inject
|
||||
constructor(
|
||||
@ApplicationContext context: Context,
|
||||
) :
|
||||
BaseNetworkService<EthernetService>(context, NetworkCapabilities.TRANSPORT_ETHERNET)
|
||||
+3
-6
@@ -5,9 +5,6 @@ import android.net.NetworkCapabilities
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import javax.inject.Inject
|
||||
|
||||
class MobileDataService
|
||||
@Inject
|
||||
constructor(
|
||||
@ApplicationContext context: Context,
|
||||
) :
|
||||
BaseNetworkService<MobileDataService>(context, NetworkCapabilities.TRANSPORT_CELLULAR)
|
||||
class MobileDataService @Inject constructor(@ApplicationContext context: Context) :
|
||||
BaseNetworkService<MobileDataService>(context, NetworkCapabilities.TRANSPORT_CELLULAR) {
|
||||
}
|
||||
+3
-3
@@ -4,7 +4,7 @@ import android.net.NetworkCapabilities
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface NetworkService<T> {
|
||||
fun getNetworkName(networkCapabilities: NetworkCapabilities): String?
|
||||
fun getNetworkName(networkCapabilities: NetworkCapabilities) : String?
|
||||
val networkStatus : Flow<NetworkStatus>
|
||||
|
||||
val networkStatus: Flow<NetworkStatus>
|
||||
}
|
||||
}
|
||||
+3
-6
@@ -4,10 +4,7 @@ import android.net.Network
|
||||
import android.net.NetworkCapabilities
|
||||
|
||||
sealed class NetworkStatus {
|
||||
class Available(val network: Network) : NetworkStatus()
|
||||
|
||||
class Unavailable(val network: Network) : NetworkStatus()
|
||||
|
||||
class CapabilitiesChanged(val network: Network, val networkCapabilities: NetworkCapabilities) :
|
||||
NetworkStatus()
|
||||
class Available(val network : Network) : NetworkStatus()
|
||||
class Unavailable(val network : Network) : NetworkStatus()
|
||||
class CapabilitiesChanged(val network : Network, val networkCapabilities : NetworkCapabilities) : NetworkStatus()
|
||||
}
|
||||
|
||||
+3
-6
@@ -5,9 +5,6 @@ import android.net.NetworkCapabilities
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import javax.inject.Inject
|
||||
|
||||
class WifiService
|
||||
@Inject
|
||||
constructor(
|
||||
@ApplicationContext context: Context,
|
||||
) :
|
||||
BaseNetworkService<WifiService>(context, NetworkCapabilities.TRANSPORT_WIFI)
|
||||
class WifiService @Inject constructor(@ApplicationContext context: Context) :
|
||||
BaseNetworkService<WifiService>(context, NetworkCapabilities.TRANSPORT_WIFI) {
|
||||
}
|
||||
+14
-15
@@ -5,18 +5,17 @@ import android.app.NotificationManager
|
||||
import android.app.PendingIntent
|
||||
|
||||
interface NotificationService {
|
||||
fun createNotification(
|
||||
channelId: String,
|
||||
channelName: String,
|
||||
title: String = "",
|
||||
action: PendingIntent? = null,
|
||||
actionText: String? = null,
|
||||
description: String,
|
||||
showTimestamp: Boolean = false,
|
||||
importance: Int = NotificationManager.IMPORTANCE_HIGH,
|
||||
vibration: Boolean = false,
|
||||
onGoing: Boolean = true,
|
||||
lights: Boolean = true,
|
||||
onlyAlertOnce: Boolean = true,
|
||||
): Notification
|
||||
}
|
||||
fun createNotification(
|
||||
channelId: String,
|
||||
channelName: String,
|
||||
title: String = "",
|
||||
action: PendingIntent? = null,
|
||||
actionText: String? = null,
|
||||
description: String,
|
||||
showTimestamp : Boolean = false,
|
||||
importance: Int = NotificationManager.IMPORTANCE_HIGH,
|
||||
vibration: Boolean = true,
|
||||
onGoing: Boolean = true,
|
||||
lights: Boolean = true
|
||||
): Notification
|
||||
}
|
||||
+61
-89
@@ -7,99 +7,71 @@ import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.Color
|
||||
import androidx.core.app.NotificationCompat
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.ui.SplashActivity
|
||||
import com.zaneschepke.wireguardautotunnel.ui.MainActivity
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import javax.inject.Inject
|
||||
|
||||
class WireGuardNotification
|
||||
@Inject
|
||||
constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
) :
|
||||
NotificationService {
|
||||
private val notificationManager =
|
||||
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
class WireGuardNotification @Inject constructor(@ApplicationContext private val context: Context) : NotificationService {
|
||||
|
||||
private val watcherBuilder: NotificationCompat.Builder =
|
||||
NotificationCompat.Builder(
|
||||
context,
|
||||
context.getString(R.string.watcher_channel_id),
|
||||
)
|
||||
private val tunnelBuilder: NotificationCompat.Builder =
|
||||
NotificationCompat.Builder(
|
||||
context,
|
||||
context.getString(R.string.vpn_channel_id),
|
||||
)
|
||||
private val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager;
|
||||
|
||||
override fun createNotification(
|
||||
channelId: String,
|
||||
channelName: String,
|
||||
title: String,
|
||||
action: PendingIntent?,
|
||||
actionText: String?,
|
||||
description: String,
|
||||
showTimestamp: Boolean,
|
||||
importance: Int,
|
||||
vibration: Boolean,
|
||||
onGoing: Boolean,
|
||||
lights: Boolean,
|
||||
onlyAlertOnce: Boolean,
|
||||
): Notification {
|
||||
val channel =
|
||||
NotificationChannel(
|
||||
channelId,
|
||||
channelName,
|
||||
importance,
|
||||
)
|
||||
.let {
|
||||
it.description = title
|
||||
it.enableLights(lights)
|
||||
it.lightColor = Color.RED
|
||||
it.enableVibration(vibration)
|
||||
it.vibrationPattern = longArrayOf(100, 200, 300)
|
||||
it
|
||||
}
|
||||
notificationManager.createNotificationChannel(channel)
|
||||
val pendingIntent: PendingIntent =
|
||||
Intent(context, SplashActivity::class.java).let { notificationIntent ->
|
||||
PendingIntent.getActivity(
|
||||
context,
|
||||
0,
|
||||
notificationIntent,
|
||||
PendingIntent.FLAG_IMMUTABLE,
|
||||
)
|
||||
}
|
||||
override fun createNotification(
|
||||
channelId: String,
|
||||
channelName: String,
|
||||
title: String,
|
||||
action: PendingIntent?,
|
||||
actionText: String?,
|
||||
description: String,
|
||||
showTimestamp: Boolean,
|
||||
importance: Int,
|
||||
vibration: Boolean,
|
||||
onGoing: Boolean,
|
||||
lights: Boolean
|
||||
): Notification {
|
||||
val channel = NotificationChannel(
|
||||
channelId,
|
||||
channelName,
|
||||
importance
|
||||
).let {
|
||||
it.description = title
|
||||
it.enableLights(lights)
|
||||
it.lightColor = Color.RED
|
||||
it.enableVibration(vibration)
|
||||
it.vibrationPattern = longArrayOf(100, 200, 300, 400, 500, 400, 300, 200, 400)
|
||||
it
|
||||
}
|
||||
notificationManager.createNotificationChannel(channel)
|
||||
val pendingIntent: PendingIntent =
|
||||
Intent(context, MainActivity::class.java).let { notificationIntent ->
|
||||
PendingIntent.getActivity(
|
||||
context,
|
||||
0,
|
||||
notificationIntent,
|
||||
PendingIntent.FLAG_IMMUTABLE
|
||||
)
|
||||
}
|
||||
|
||||
val builder =
|
||||
when (channelId) {
|
||||
context.getString(R.string.watcher_channel_id) -> watcherBuilder
|
||||
context.getString(R.string.vpn_channel_id) -> tunnelBuilder
|
||||
else -> {
|
||||
NotificationCompat.Builder(
|
||||
context,
|
||||
channelId,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return builder.let {
|
||||
if (action != null && actionText != null) {
|
||||
it.addAction(
|
||||
NotificationCompat.Action.Builder(0, actionText, action).build(),
|
||||
)
|
||||
it.setAutoCancel(true)
|
||||
}
|
||||
it.setContentTitle(title)
|
||||
.setContentText(description)
|
||||
.setOnlyAlertOnce(onlyAlertOnce)
|
||||
.setContentIntent(pendingIntent)
|
||||
.setOngoing(onGoing)
|
||||
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||
.setShowWhen(showTimestamp)
|
||||
.setSmallIcon(R.drawable.ic_launcher)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
}
|
||||
val builder: Notification.Builder =
|
||||
Notification.Builder(
|
||||
context,
|
||||
channelId
|
||||
)
|
||||
return builder.let {
|
||||
if(action != null && actionText != null) {
|
||||
//TODO find a not deprecated way to do this
|
||||
it.addAction(
|
||||
Notification.Action.Builder(0, actionText, action)
|
||||
.build())
|
||||
it.setAutoCancel(true)
|
||||
}
|
||||
it.setContentTitle(title)
|
||||
.setContentText(description)
|
||||
.setContentIntent(pendingIntent)
|
||||
.setOngoing(onGoing)
|
||||
.setShowWhen(showTimestamp)
|
||||
.setSmallIcon(R.mipmap.ic_launcher_foreground)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
}
|
||||
-85
@@ -1,85 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.service.shortcut
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
||||
import com.zaneschepke.wireguardautotunnel.module.ApplicationScope
|
||||
import com.zaneschepke.wireguardautotunnel.service.foreground.Action
|
||||
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
|
||||
import com.zaneschepke.wireguardautotunnel.service.foreground.WireGuardConnectivityWatcherService
|
||||
import com.zaneschepke.wireguardautotunnel.service.foreground.WireGuardTunnelService
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class ShortcutsActivity : ComponentActivity() {
|
||||
@Inject
|
||||
lateinit var appDataRepository: AppDataRepository
|
||||
|
||||
@Inject
|
||||
lateinit var serviceManager: ServiceManager
|
||||
|
||||
@Inject
|
||||
@ApplicationScope
|
||||
lateinit var applicationScope: CoroutineScope
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
applicationScope.launch {
|
||||
val settings = appDataRepository.settings.getSettings()
|
||||
if (settings.isShortcutsEnabled) {
|
||||
when (intent.getStringExtra(CLASS_NAME_EXTRA_KEY)) {
|
||||
WireGuardTunnelService::class.java.simpleName -> {
|
||||
val tunnelName = intent.getStringExtra(TUNNEL_NAME_EXTRA_KEY)
|
||||
val tunnelConfig =
|
||||
tunnelName?.let {
|
||||
appDataRepository.tunnels.getAll().firstOrNull {
|
||||
it.name == tunnelName
|
||||
}
|
||||
}
|
||||
when (intent.action) {
|
||||
Action.START.name ->
|
||||
serviceManager.startVpnServiceForeground(
|
||||
this@ShortcutsActivity,
|
||||
tunnelConfig?.id,
|
||||
isManualStart = true,
|
||||
)
|
||||
|
||||
Action.STOP.name ->
|
||||
serviceManager.stopVpnServiceForeground(
|
||||
this@ShortcutsActivity,
|
||||
isManualStop = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
WireGuardConnectivityWatcherService::class.java.simpleName -> {
|
||||
when (intent.action) {
|
||||
Action.START.name ->
|
||||
appDataRepository.settings.save(
|
||||
settings.copy(
|
||||
isAutoTunnelPaused = false,
|
||||
),
|
||||
)
|
||||
|
||||
Action.STOP.name ->
|
||||
appDataRepository.settings.save(
|
||||
settings.copy(
|
||||
isAutoTunnelPaused = true,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
finish()
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TUNNEL_NAME_EXTRA_KEY = "tunnelName"
|
||||
const val CLASS_NAME_EXTRA_KEY = "className"
|
||||
}
|
||||
}
|
||||
-104
@@ -1,104 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.service.tile
|
||||
|
||||
import android.os.Build
|
||||
import android.service.quicksettings.Tile
|
||||
import android.service.quicksettings.TileService
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.ServiceLifecycleDispatcher
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
||||
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
||||
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class AutoTunnelControlTile : TileService(), LifecycleOwner {
|
||||
@Inject
|
||||
lateinit var appDataRepository: AppDataRepository
|
||||
|
||||
@Inject
|
||||
lateinit var serviceManager: ServiceManager
|
||||
|
||||
private val dispatcher = ServiceLifecycleDispatcher(this)
|
||||
|
||||
private var manualStartConfig: TunnelConfig? = null
|
||||
|
||||
override fun onStartListening() {
|
||||
super.onStartListening()
|
||||
lifecycleScope.launch {
|
||||
val settings = appDataRepository.settings.getSettings()
|
||||
when (settings.isAutoTunnelEnabled) {
|
||||
true -> {
|
||||
if (settings.isAutoTunnelPaused) {
|
||||
setInactive()
|
||||
setTileDescription(this@AutoTunnelControlTile.getString(R.string.paused))
|
||||
} else {
|
||||
setActive()
|
||||
setTileDescription(this@AutoTunnelControlTile.getString(R.string.active))
|
||||
}
|
||||
}
|
||||
|
||||
false -> {
|
||||
setTileDescription(this@AutoTunnelControlTile.getString(R.string.disabled))
|
||||
setUnavailable()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onTileAdded() {
|
||||
super.onTileAdded()
|
||||
onStartListening()
|
||||
}
|
||||
|
||||
override fun onClick() {
|
||||
super.onClick()
|
||||
unlockAndRun {
|
||||
lifecycleScope.launch {
|
||||
try {
|
||||
appDataRepository.toggleWatcherServicePause()
|
||||
onStartListening()
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e.message)
|
||||
} finally {
|
||||
cancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setActive() {
|
||||
qsTile.state = Tile.STATE_ACTIVE
|
||||
qsTile.updateTile()
|
||||
}
|
||||
|
||||
private fun setInactive() {
|
||||
qsTile.state = Tile.STATE_INACTIVE
|
||||
qsTile.updateTile()
|
||||
}
|
||||
|
||||
private fun setUnavailable() {
|
||||
manualStartConfig = null
|
||||
qsTile.state = Tile.STATE_UNAVAILABLE
|
||||
qsTile.updateTile()
|
||||
}
|
||||
|
||||
private fun setTileDescription(description: String) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
qsTile.subtitle = description
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
qsTile.stateDescription = description
|
||||
}
|
||||
qsTile.updateTile()
|
||||
}
|
||||
|
||||
override val lifecycle: Lifecycle
|
||||
get() = dispatcher.lifecycle
|
||||
}
|
||||
-121
@@ -1,121 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.service.tile
|
||||
|
||||
import android.os.Build
|
||||
import android.service.quicksettings.Tile
|
||||
import android.service.quicksettings.TileService
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.ServiceLifecycleDispatcher
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
||||
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
||||
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
|
||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelState
|
||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.VpnService
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class TunnelControlTile : TileService(), LifecycleOwner {
|
||||
@Inject
|
||||
lateinit var appDataRepository: AppDataRepository
|
||||
|
||||
@Inject
|
||||
lateinit var vpnService: VpnService
|
||||
|
||||
@Inject
|
||||
lateinit var serviceManager: ServiceManager
|
||||
|
||||
private val dispatcher = ServiceLifecycleDispatcher(this)
|
||||
|
||||
private var manualStartConfig: TunnelConfig? = null
|
||||
|
||||
override fun onStartListening() {
|
||||
super.onStartListening()
|
||||
Timber.d("On start listening called")
|
||||
lifecycleScope.launch {
|
||||
when (vpnService.getState()) {
|
||||
TunnelState.UP -> {
|
||||
setActive()
|
||||
setTileDescription(vpnService.name)
|
||||
}
|
||||
|
||||
TunnelState.DOWN -> {
|
||||
setInactive()
|
||||
val config =
|
||||
appDataRepository.getStartTunnelConfig()?.also { config ->
|
||||
manualStartConfig = config
|
||||
} ?: appDataRepository.getPrimaryOrFirstTunnel()
|
||||
config?.let {
|
||||
setTileDescription(it.name)
|
||||
} ?: setUnavailable()
|
||||
}
|
||||
|
||||
else -> setInactive()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onTileAdded() {
|
||||
super.onTileAdded()
|
||||
onStartListening()
|
||||
}
|
||||
|
||||
override fun onClick() {
|
||||
super.onClick()
|
||||
unlockAndRun {
|
||||
lifecycleScope.launch {
|
||||
try {
|
||||
if (vpnService.getState() == TunnelState.UP) {
|
||||
serviceManager.stopVpnServiceForeground(
|
||||
this@TunnelControlTile,
|
||||
isManualStop = true,
|
||||
)
|
||||
} else {
|
||||
serviceManager.startVpnServiceForeground(
|
||||
this@TunnelControlTile,
|
||||
manualStartConfig?.id,
|
||||
isManualStart = true,
|
||||
)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e.message)
|
||||
} finally {
|
||||
cancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setActive() {
|
||||
qsTile.state = Tile.STATE_ACTIVE
|
||||
qsTile.updateTile()
|
||||
}
|
||||
|
||||
private fun setInactive() {
|
||||
qsTile.state = Tile.STATE_INACTIVE
|
||||
qsTile.updateTile()
|
||||
}
|
||||
|
||||
private fun setUnavailable() {
|
||||
manualStartConfig = null
|
||||
qsTile.state = Tile.STATE_UNAVAILABLE
|
||||
qsTile.updateTile()
|
||||
}
|
||||
|
||||
private fun setTileDescription(description: String) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
qsTile.subtitle = description
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
qsTile.stateDescription = description
|
||||
}
|
||||
qsTile.updateTile()
|
||||
}
|
||||
|
||||
override val lifecycle: Lifecycle
|
||||
get() = dispatcher.lifecycle
|
||||
}
|
||||
+10
-13
@@ -1,17 +1,14 @@
|
||||
package com.zaneschepke.wireguardautotunnel.service.tunnel
|
||||
|
||||
enum class HandshakeStatus {
|
||||
HEALTHY,
|
||||
STALE,
|
||||
UNKNOWN,
|
||||
NOT_STARTED,
|
||||
;
|
||||
HEALTHY,
|
||||
UNHEALTHY,
|
||||
NEVER_CONNECTED,
|
||||
NOT_STARTED;
|
||||
|
||||
companion object {
|
||||
private const val WG_TYPICAL_HANDSHAKE_INTERVAL_WHEN_HEALTHY_SEC = 180
|
||||
const val STATUS_CHANGE_TIME_BUFFER = 30
|
||||
const val STALE_TIME_LIMIT_SEC =
|
||||
WG_TYPICAL_HANDSHAKE_INTERVAL_WHEN_HEALTHY_SEC + STATUS_CHANGE_TIME_BUFFER
|
||||
const val NEVER_CONNECTED_TO_UNHEALTHY_TIME_LIMIT_SEC = 30
|
||||
}
|
||||
}
|
||||
companion object {
|
||||
private const val WG_TYPICAL_HANDSHAKE_INTERVAL_WHEN_HEALTHY_SEC = 120
|
||||
const val UNHEALTHY_TIME_LIMIT_SEC = WG_TYPICAL_HANDSHAKE_INTERVAL_WHEN_HEALTHY_SEC + 60
|
||||
const val NEVER_CONNECTED_TO_UNHEALTHY_TIME_LIMIT_SEC = 30
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.service.tunnel
|
||||
|
||||
import com.wireguard.android.backend.Tunnel
|
||||
|
||||
enum class TunnelState {
|
||||
UP,
|
||||
DOWN,
|
||||
TOGGLE,
|
||||
;
|
||||
|
||||
fun toWgState(): Tunnel.State {
|
||||
return when (this) {
|
||||
UP -> Tunnel.State.UP
|
||||
DOWN -> Tunnel.State.DOWN
|
||||
TOGGLE -> Tunnel.State.TOGGLE
|
||||
}
|
||||
}
|
||||
|
||||
fun toAmState(): org.amnezia.awg.backend.Tunnel.State {
|
||||
return when (this) {
|
||||
UP -> org.amnezia.awg.backend.Tunnel.State.UP
|
||||
DOWN -> org.amnezia.awg.backend.Tunnel.State.DOWN
|
||||
TOGGLE -> org.amnezia.awg.backend.Tunnel.State.TOGGLE
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun from(state: Tunnel.State): TunnelState {
|
||||
return when (state) {
|
||||
Tunnel.State.DOWN -> DOWN
|
||||
Tunnel.State.TOGGLE -> TOGGLE
|
||||
Tunnel.State.UP -> UP
|
||||
}
|
||||
}
|
||||
|
||||
fun from(state: org.amnezia.awg.backend.Tunnel.State): TunnelState {
|
||||
return when (state) {
|
||||
org.amnezia.awg.backend.Tunnel.State.DOWN -> DOWN
|
||||
org.amnezia.awg.backend.Tunnel.State.TOGGLE -> TOGGLE
|
||||
org.amnezia.awg.backend.Tunnel.State.UP -> UP
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+14
-11
@@ -1,15 +1,18 @@
|
||||
package com.zaneschepke.wireguardautotunnel.service.tunnel
|
||||
|
||||
import com.wireguard.android.backend.Statistics
|
||||
import com.wireguard.android.backend.Tunnel
|
||||
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import com.wireguard.crypto.Key
|
||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.model.TunnelConfig
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
|
||||
interface VpnService : Tunnel, org.amnezia.awg.backend.Tunnel {
|
||||
suspend fun startTunnel(tunnelConfig: TunnelConfig? = null): TunnelState
|
||||
|
||||
suspend fun stopTunnel()
|
||||
|
||||
val vpnState: StateFlow<VpnState>
|
||||
|
||||
fun getState(): TunnelState
|
||||
}
|
||||
interface VpnService : Tunnel {
|
||||
suspend fun startTunnel(tunnelConfig : TunnelConfig) : Tunnel.State
|
||||
suspend fun stopTunnel()
|
||||
val state : SharedFlow<Tunnel.State>
|
||||
val tunnelName : SharedFlow<String>
|
||||
val statistics : SharedFlow<Statistics>
|
||||
val lastHandshake : SharedFlow<Map<Key,Long>>
|
||||
val handshakeStatus : SharedFlow<HandshakeStatus>
|
||||
fun getState() : Tunnel.State
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.service.tunnel
|
||||
|
||||
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.TunnelStatistics
|
||||
|
||||
data class VpnState(
|
||||
val status: TunnelState = TunnelState.DOWN,
|
||||
val tunnelConfig: TunnelConfig? = null,
|
||||
val statistics: TunnelStatistics? = null,
|
||||
)
|
||||
+102
-206
@@ -2,232 +2,128 @@ package com.zaneschepke.wireguardautotunnel.service.tunnel
|
||||
|
||||
import com.wireguard.android.backend.Backend
|
||||
import com.wireguard.android.backend.BackendException
|
||||
import com.wireguard.android.backend.Tunnel.State
|
||||
import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
|
||||
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
||||
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
||||
import com.zaneschepke.wireguardautotunnel.module.ApplicationScope
|
||||
import com.zaneschepke.wireguardautotunnel.module.IoDispatcher
|
||||
import com.zaneschepke.wireguardautotunnel.module.Kernel
|
||||
import com.zaneschepke.wireguardautotunnel.module.Userspace
|
||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.AmneziaStatistics
|
||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.TunnelStatistics
|
||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.WireGuardStatistics
|
||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import com.wireguard.android.backend.Statistics
|
||||
import com.wireguard.android.backend.Tunnel
|
||||
import com.wireguard.crypto.Key
|
||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.model.TunnelConfig
|
||||
import com.zaneschepke.wireguardautotunnel.util.NumberUtils
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.channels.BufferOverflow
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.amnezia.awg.backend.Tunnel
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Provider
|
||||
|
||||
class WireGuardTunnel
|
||||
@Inject
|
||||
constructor(
|
||||
private val userspaceAmneziaBackend: Provider<org.amnezia.awg.backend.Backend>,
|
||||
@Userspace private val userspaceBackend: Provider<Backend>,
|
||||
@Kernel private val kernelBackend: Provider<Backend>,
|
||||
private val appDataRepository: AppDataRepository,
|
||||
@ApplicationScope private val applicationScope: CoroutineScope,
|
||||
@IoDispatcher private val ioDispatcher: CoroutineDispatcher,
|
||||
|
||||
class WireGuardTunnel @Inject constructor(private val backend : Backend,
|
||||
) : VpnService {
|
||||
private val _vpnState = MutableStateFlow(VpnState())
|
||||
override val vpnState: StateFlow<VpnState> = _vpnState.asStateFlow()
|
||||
|
||||
private var statsJob: Job? = null
|
||||
private val _tunnelName = MutableStateFlow("")
|
||||
override val tunnelName get() = _tunnelName.asStateFlow()
|
||||
|
||||
private var backendIsWgUserspace = true
|
||||
private val _state = MutableSharedFlow<Tunnel.State>(
|
||||
replay = 1)
|
||||
|
||||
private var backendIsAmneziaUserspace = false
|
||||
private val _handshakeStatus = MutableSharedFlow<HandshakeStatus>(replay = 1,
|
||||
onBufferOverflow = BufferOverflow.DROP_OLDEST)
|
||||
override val state get() = _state.asSharedFlow()
|
||||
|
||||
init {
|
||||
applicationScope.launch(ioDispatcher) {
|
||||
appDataRepository.settings.getSettingsFlow().collect {
|
||||
if (it.isKernelEnabled && (backendIsWgUserspace || backendIsAmneziaUserspace)) {
|
||||
Timber.i("Setting kernel backend")
|
||||
backendIsWgUserspace = false
|
||||
backendIsAmneziaUserspace = false
|
||||
} else if (!it.isKernelEnabled && !it.isAmneziaEnabled && !backendIsWgUserspace) {
|
||||
Timber.i("Setting WireGuard userspace backend")
|
||||
backendIsWgUserspace = true
|
||||
backendIsAmneziaUserspace = false
|
||||
} else if (it.isAmneziaEnabled && !backendIsAmneziaUserspace) {
|
||||
Timber.i("Setting Amnezia userspace backend")
|
||||
backendIsAmneziaUserspace = true
|
||||
backendIsWgUserspace = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
private val _statistics = MutableSharedFlow<Statistics>(replay = 1)
|
||||
override val statistics get() = _statistics.asSharedFlow()
|
||||
|
||||
private fun setState(tunnelConfig: TunnelConfig?, tunnelState: TunnelState): TunnelState {
|
||||
return if (backendIsAmneziaUserspace) {
|
||||
Timber.i("Using Amnezia backend")
|
||||
val config =
|
||||
tunnelConfig?.let {
|
||||
if (it.amQuick != "") {
|
||||
TunnelConfig.configFromAmQuick(it.amQuick)
|
||||
} else {
|
||||
Timber.w(
|
||||
"Using backwards compatible wg config, amnezia specific config not found.",
|
||||
)
|
||||
TunnelConfig.configFromAmQuick(it.wgQuick)
|
||||
}
|
||||
}
|
||||
val state =
|
||||
userspaceAmneziaBackend.get().setState(this, tunnelState.toAmState(), config)
|
||||
TunnelState.from(state)
|
||||
} else {
|
||||
Timber.i("Using Wg backend")
|
||||
val wgConfig = tunnelConfig?.let { TunnelConfig.configFromWgQuick(it.wgQuick) }
|
||||
val state =
|
||||
backend().setState(
|
||||
this,
|
||||
tunnelState.toWgState(),
|
||||
wgConfig,
|
||||
)
|
||||
TunnelState.from(state)
|
||||
}
|
||||
}
|
||||
private val _lastHandshake = MutableSharedFlow<Map<Key, Long>>(replay = 1)
|
||||
override val lastHandshake get() = _lastHandshake.asSharedFlow()
|
||||
|
||||
override suspend fun startTunnel(tunnelConfig: TunnelConfig?): TunnelState {
|
||||
return withContext(ioDispatcher) {
|
||||
try {
|
||||
// TODO we need better error handling here
|
||||
// need to bubble up these errors to the UI
|
||||
val config = tunnelConfig ?: appDataRepository.getPrimaryOrFirstTunnel()
|
||||
if (config != null) {
|
||||
emitTunnelConfig(config)
|
||||
setState(config, TunnelState.UP)
|
||||
} else {
|
||||
throw Exception("No tunnels")
|
||||
}
|
||||
} catch (e: BackendException) {
|
||||
Timber.e("Failed to start tunnel with error: ${e.message}")
|
||||
TunnelState.from(State.DOWN)
|
||||
}
|
||||
}
|
||||
}
|
||||
override val handshakeStatus: SharedFlow<HandshakeStatus>
|
||||
get() = _handshakeStatus.asSharedFlow()
|
||||
|
||||
private fun backend(): Backend {
|
||||
return when {
|
||||
backendIsWgUserspace -> {
|
||||
userspaceBackend.get()
|
||||
}
|
||||
private lateinit var statsJob : Job
|
||||
|
||||
!backendIsWgUserspace && !backendIsAmneziaUserspace -> {
|
||||
kernelBackend.get()
|
||||
}
|
||||
|
||||
else -> {
|
||||
userspaceBackend.get()
|
||||
}
|
||||
}
|
||||
}
|
||||
override suspend fun startTunnel(tunnelConfig: TunnelConfig) : Tunnel.State{
|
||||
return try {
|
||||
if(getState() == Tunnel.State.UP && _tunnelName.value != tunnelConfig.name) {
|
||||
stopTunnel()
|
||||
}
|
||||
_tunnelName.emit(tunnelConfig.name)
|
||||
val config = TunnelConfig.configFromQuick(tunnelConfig.wgQuick)
|
||||
val state = backend.setState(
|
||||
this, Tunnel.State.UP, config)
|
||||
_state.emit(state)
|
||||
state;
|
||||
} catch (e : Exception) {
|
||||
Timber.e("Failed to start tunnel with error: ${e.message}")
|
||||
Tunnel.State.DOWN
|
||||
}
|
||||
}
|
||||
|
||||
private fun emitTunnelState(state: TunnelState) {
|
||||
_vpnState.tryEmit(
|
||||
_vpnState.value.copy(
|
||||
status = state,
|
||||
),
|
||||
)
|
||||
}
|
||||
override fun getName(): String {
|
||||
return _tunnelName.value
|
||||
}
|
||||
|
||||
private fun emitBackendStatistics(statistics: TunnelStatistics) {
|
||||
_vpnState.tryEmit(
|
||||
_vpnState.value.copy(
|
||||
statistics = statistics,
|
||||
),
|
||||
)
|
||||
}
|
||||
override suspend fun stopTunnel() {
|
||||
try {
|
||||
if(getState() == Tunnel.State.UP) {
|
||||
val state = backend.setState(this, Tunnel.State.DOWN, null)
|
||||
_state.emit(state)
|
||||
}
|
||||
} catch (e : BackendException) {
|
||||
Timber.e("Failed to stop tunnel with error: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun emitTunnelConfig(tunnelConfig: TunnelConfig?) {
|
||||
_vpnState.emit(
|
||||
_vpnState.value.copy(
|
||||
tunnelConfig = tunnelConfig,
|
||||
),
|
||||
)
|
||||
}
|
||||
override fun getState(): Tunnel.State {
|
||||
return backend.getState(this)
|
||||
}
|
||||
|
||||
private fun resetVpnState() {
|
||||
_vpnState.tryEmit(VpnState())
|
||||
}
|
||||
|
||||
override suspend fun stopTunnel() {
|
||||
withContext(ioDispatcher) {
|
||||
try {
|
||||
if (getState() == TunnelState.UP) {
|
||||
val state = setState(null, TunnelState.DOWN)
|
||||
resetVpnState()
|
||||
emitTunnelState(state)
|
||||
}
|
||||
} catch (e: BackendException) {
|
||||
Timber.e("Failed to stop wireguard tunnel with error: ${e.message}")
|
||||
} catch (e: org.amnezia.awg.backend.BackendException) {
|
||||
Timber.e("Failed to stop amnezia tunnel with error: ${e.message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getState(): TunnelState {
|
||||
return if (backendIsAmneziaUserspace) {
|
||||
TunnelState.from(
|
||||
userspaceAmneziaBackend.get().getState(this),
|
||||
)
|
||||
} else {
|
||||
TunnelState.from(backend().getState(this))
|
||||
}
|
||||
}
|
||||
|
||||
override fun getName(): String {
|
||||
return _vpnState.value.tunnelConfig?.name ?: ""
|
||||
}
|
||||
|
||||
override fun onStateChange(newState: Tunnel.State) {
|
||||
handleStateChange(TunnelState.from(newState))
|
||||
}
|
||||
|
||||
private fun handleStateChange(state: TunnelState) {
|
||||
emitTunnelState(state)
|
||||
WireGuardAutoTunnel.requestTunnelTileServiceStateUpdate()
|
||||
if (state == TunnelState.UP) {
|
||||
statsJob = startTunnelStatisticsJob()
|
||||
}
|
||||
if (state == TunnelState.DOWN) {
|
||||
try {
|
||||
statsJob?.cancel()
|
||||
} catch (e: CancellationException) {
|
||||
Timber.i("Stats job cancelled")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun startTunnelStatisticsJob() = applicationScope.launch(ioDispatcher) {
|
||||
while (true) {
|
||||
if (backendIsAmneziaUserspace) {
|
||||
emitBackendStatistics(
|
||||
AmneziaStatistics(
|
||||
userspaceAmneziaBackend.get().getStatistics(this@WireGuardTunnel),
|
||||
),
|
||||
)
|
||||
} else {
|
||||
emitBackendStatistics(
|
||||
WireGuardStatistics(backend().getStatistics(this@WireGuardTunnel)),
|
||||
)
|
||||
}
|
||||
delay(Constants.VPN_STATISTIC_CHECK_INTERVAL)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStateChange(state: State) {
|
||||
handleStateChange(TunnelState.from(state))
|
||||
}
|
||||
}
|
||||
override fun onStateChange(state : Tunnel.State) {
|
||||
val tunnel = this;
|
||||
_state.tryEmit(state)
|
||||
if(state == Tunnel.State.UP) {
|
||||
statsJob = CoroutineScope(Dispatchers.IO).launch {
|
||||
val handshakeMap = HashMap<Key, Long>()
|
||||
var neverHadHandshakeCounter = 0
|
||||
while (true) {
|
||||
val statistics = backend.getStatistics(tunnel)
|
||||
_statistics.emit(statistics)
|
||||
statistics.peers().forEach {
|
||||
val handshakeEpoch = statistics.peer(it)?.latestHandshakeEpochMillis ?: 0L
|
||||
handshakeMap[it] = handshakeEpoch
|
||||
if(handshakeEpoch == 0L) {
|
||||
if(neverHadHandshakeCounter >= HandshakeStatus.NEVER_CONNECTED_TO_UNHEALTHY_TIME_LIMIT_SEC) {
|
||||
_handshakeStatus.emit(HandshakeStatus.NEVER_CONNECTED)
|
||||
} else {
|
||||
_handshakeStatus.emit(HandshakeStatus.NOT_STARTED)
|
||||
}
|
||||
if(neverHadHandshakeCounter <= HandshakeStatus.NEVER_CONNECTED_TO_UNHEALTHY_TIME_LIMIT_SEC) {
|
||||
neverHadHandshakeCounter++
|
||||
}
|
||||
return@forEach
|
||||
}
|
||||
if(NumberUtils.getSecondsBetweenTimestampAndNow(handshakeEpoch) >= HandshakeStatus.UNHEALTHY_TIME_LIMIT_SEC) {
|
||||
_handshakeStatus.emit(HandshakeStatus.UNHEALTHY)
|
||||
} else {
|
||||
_handshakeStatus.emit(HandshakeStatus.HEALTHY)
|
||||
}
|
||||
}
|
||||
_lastHandshake.emit(handshakeMap)
|
||||
delay(1000)
|
||||
}
|
||||
}
|
||||
}
|
||||
if(state == Tunnel.State.DOWN) {
|
||||
if(this::statsJob.isInitialized) {
|
||||
statsJob.cancel()
|
||||
}
|
||||
_handshakeStatus.tryEmit(HandshakeStatus.NOT_STARTED)
|
||||
_lastHandshake.tryEmit(emptyMap())
|
||||
}
|
||||
}
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
package com.zaneschepke.wireguardautotunnel.service.tunnel.model
|
||||
|
||||
import io.objectbox.annotation.Entity
|
||||
import io.objectbox.annotation.Id
|
||||
|
||||
@Entity
|
||||
data class Settings(
|
||||
@Id
|
||||
var id : Long = 0,
|
||||
var isAutoTunnelEnabled : Boolean = false,
|
||||
var isTunnelOnMobileDataEnabled : Boolean = false,
|
||||
var trustedNetworkSSIDs : MutableList<String> = mutableListOf(),
|
||||
var defaultTunnel : String? = null,
|
||||
var isAlwaysOnVpnEnabled : Boolean = false
|
||||
)
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
package com.zaneschepke.wireguardautotunnel.service.tunnel.model
|
||||
|
||||
import com.wireguard.config.Config
|
||||
import io.objectbox.annotation.ConflictStrategy
|
||||
import io.objectbox.annotation.Entity
|
||||
import io.objectbox.annotation.Id
|
||||
import io.objectbox.annotation.Unique
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.Json
|
||||
import java.io.InputStream
|
||||
|
||||
|
||||
@Entity
|
||||
@Serializable
|
||||
data class TunnelConfig(
|
||||
@Id
|
||||
var id : Long = 0,
|
||||
@Unique(onConflict = ConflictStrategy.REPLACE)
|
||||
var name : String,
|
||||
var wgQuick : String
|
||||
) {
|
||||
|
||||
|
||||
override fun toString(): String {
|
||||
return Json.encodeToString(serializer(), this)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val INCLUDED_APPLICATIONS = "IncludedApplications = "
|
||||
private const val EXCLUDED_APPLICATIONS = "ExcludedApplications = "
|
||||
private const val INTERFACE = "[Interface]"
|
||||
private const val NEWLINE_CHAR = "\n"
|
||||
private const val APP_CONFIG_SEPARATOR = ", "
|
||||
|
||||
private fun addApplicationsToConfig(appConfig : String, wgQuick : String) : String {
|
||||
val configList = wgQuick.split(NEWLINE_CHAR).toMutableList()
|
||||
val interfaceIndex = configList.indexOf(INTERFACE)
|
||||
configList.add(interfaceIndex + 1, appConfig)
|
||||
return configList.joinToString(NEWLINE_CHAR)
|
||||
}
|
||||
|
||||
fun clearAllApplicationsFromConfig(wgQuick : String) : String {
|
||||
val configList = wgQuick.split(NEWLINE_CHAR).toMutableList()
|
||||
val itr = configList.iterator()
|
||||
while (itr.hasNext()) {
|
||||
val next = itr.next()
|
||||
if(next.contains(INCLUDED_APPLICATIONS) || next.contains(EXCLUDED_APPLICATIONS)) {
|
||||
itr.remove()
|
||||
}
|
||||
}
|
||||
return configList.joinToString(NEWLINE_CHAR)
|
||||
}
|
||||
|
||||
|
||||
fun setExcludedApplicationsOnQuick(packages : List<String>, wgQuick: String) : String {
|
||||
if(packages.isEmpty()) {
|
||||
return wgQuick
|
||||
}
|
||||
val clearedWgQuick = clearAllApplicationsFromConfig(wgQuick)
|
||||
val excludeConfig = buildExcludedApplicationsString(packages)
|
||||
return addApplicationsToConfig(excludeConfig, clearedWgQuick)
|
||||
}
|
||||
|
||||
fun setIncludedApplicationsOnQuick(packages : List<String>, wgQuick: String) : String {
|
||||
if(packages.isEmpty()) {
|
||||
return wgQuick
|
||||
}
|
||||
val clearedWgQuick = clearAllApplicationsFromConfig(wgQuick)
|
||||
val includeConfig = buildIncludedApplicationsString(packages)
|
||||
return addApplicationsToConfig(includeConfig, clearedWgQuick)
|
||||
}
|
||||
|
||||
private fun buildExcludedApplicationsString(packages : List<String>) : String {
|
||||
return EXCLUDED_APPLICATIONS + packages.joinToString(APP_CONFIG_SEPARATOR)
|
||||
}
|
||||
|
||||
private fun buildIncludedApplicationsString(packages : List<String>) : String {
|
||||
return INCLUDED_APPLICATIONS + packages.joinToString(APP_CONFIG_SEPARATOR)
|
||||
}
|
||||
fun from(string : String) : TunnelConfig {
|
||||
return Json.decodeFromString<TunnelConfig>(string)
|
||||
}
|
||||
fun configFromQuick(wgQuick: String): Config {
|
||||
val inputStream: InputStream = wgQuick.byteInputStream()
|
||||
val reader = inputStream.bufferedReader(Charsets.UTF_8)
|
||||
return Config.parse(reader)
|
||||
}
|
||||
}
|
||||
}
|
||||
-34
@@ -1,34 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.service.tunnel.statistics
|
||||
|
||||
import org.amnezia.awg.backend.Statistics
|
||||
import org.amnezia.awg.crypto.Key
|
||||
|
||||
class AmneziaStatistics(private val statistics: Statistics) : TunnelStatistics() {
|
||||
override fun peerStats(peer: Key): PeerStats? {
|
||||
val key = Key.fromBase64(peer.toBase64())
|
||||
val stats = statistics.peer(key)
|
||||
return stats?.let {
|
||||
PeerStats(
|
||||
rxBytes = stats.rxBytes,
|
||||
txBytes = stats.txBytes,
|
||||
latestHandshakeEpochMillis = stats.latestHandshakeEpochMillis,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun isTunnelStale(): Boolean {
|
||||
return statistics.isStale
|
||||
}
|
||||
|
||||
override fun getPeers(): Array<Key> {
|
||||
return statistics.peers()
|
||||
}
|
||||
|
||||
override fun rx(): Long {
|
||||
return statistics.totalRx()
|
||||
}
|
||||
|
||||
override fun tx(): Long {
|
||||
return statistics.totalTx()
|
||||
}
|
||||
}
|
||||
-18
@@ -1,18 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.service.tunnel.statistics
|
||||
|
||||
import org.amnezia.awg.crypto.Key
|
||||
|
||||
abstract class TunnelStatistics {
|
||||
@JvmRecord
|
||||
data class PeerStats(val rxBytes: Long, val txBytes: Long, val latestHandshakeEpochMillis: Long)
|
||||
|
||||
abstract fun peerStats(peer: Key): PeerStats?
|
||||
|
||||
abstract fun isTunnelStale(): Boolean
|
||||
|
||||
abstract fun getPeers(): Array<Key>
|
||||
|
||||
abstract fun rx(): Long
|
||||
|
||||
abstract fun tx(): Long
|
||||
}
|
||||
-36
@@ -1,36 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.service.tunnel.statistics
|
||||
|
||||
import com.wireguard.android.backend.Statistics
|
||||
import org.amnezia.awg.crypto.Key
|
||||
|
||||
class WireGuardStatistics(private val statistics: Statistics) : TunnelStatistics() {
|
||||
override fun peerStats(peer: Key): PeerStats? {
|
||||
val key = com.wireguard.crypto.Key.fromBase64(peer.toBase64())
|
||||
val peerStats = statistics.peer(key)
|
||||
return peerStats?.let {
|
||||
PeerStats(
|
||||
txBytes = peerStats.txBytes,
|
||||
rxBytes = peerStats.rxBytes,
|
||||
latestHandshakeEpochMillis = peerStats.latestHandshakeEpochMillis,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun isTunnelStale(): Boolean {
|
||||
return statistics.isStale
|
||||
}
|
||||
|
||||
override fun getPeers(): Array<Key> {
|
||||
return statistics.peers().map {
|
||||
Key.fromBase64(it.toBase64())
|
||||
}.toTypedArray()
|
||||
}
|
||||
|
||||
override fun rx(): Long {
|
||||
return statistics.totalRx()
|
||||
}
|
||||
|
||||
override fun tx(): Long {
|
||||
return statistics.totalTx()
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui
|
||||
|
||||
data class AppUiState(
|
||||
val snackbarMessage: String = "",
|
||||
val snackbarMessageConsumed: Boolean = true,
|
||||
val vpnPermissionAccepted: Boolean = false,
|
||||
val notificationPermissionAccepted: Boolean = false,
|
||||
val requestPermissions: Boolean = false,
|
||||
)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user