mirror of
https://github.com/d4rken-org/capod.git
synced 2026-09-14 18:26:11 -04:00
Compare commits
144
Commits
v1.3.7
..
v2.5.0-rc1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6c678be511 | ||
|
|
2823663f09 | ||
|
|
033c2264f8 | ||
|
|
8eb6ce19f7 | ||
|
|
1cfc4e6f65 | ||
|
|
454d76e305 | ||
|
|
f3609fc3a5 | ||
|
|
16da51d095 | ||
|
|
51fb4c7a29 | ||
|
|
0b27cb55dc | ||
|
|
9e836eef69 | ||
|
|
07be4b8258 | ||
|
|
217087c6d4 | ||
|
|
2315a18c86 | ||
|
|
f59e9d2dc5 | ||
|
|
1da220085c | ||
|
|
962b2a01f1 | ||
|
|
eeda14f424 | ||
|
|
6269e20085 | ||
|
|
3d05296eb2 | ||
|
|
e90ab95cd0 | ||
|
|
23ec2f025e | ||
|
|
624898dd5e | ||
|
|
c6d6604399 | ||
|
|
7473704954 | ||
|
|
7a6b216bf6 | ||
|
|
1ef09124af | ||
|
|
6340f73d61 | ||
|
|
b9f4992a33 | ||
|
|
07a1fee013 | ||
|
|
63f37e51ad | ||
|
|
01ea08a5cb | ||
|
|
bddb20c5bb | ||
|
|
f9a97297da | ||
|
|
5fc2b56635 | ||
|
|
172953ccb3 | ||
|
|
d55300f53d | ||
|
|
c3d4500e3a | ||
|
|
cf72e28727 | ||
|
|
b488a1be16 | ||
|
|
805d0cb4b6 | ||
|
|
fa045c2504 | ||
|
|
23fd373a9b | ||
|
|
ab619cb073 | ||
|
|
2d96822014 | ||
|
|
887adc87de | ||
|
|
4219bd02c5 | ||
|
|
48544464ae | ||
|
|
9b4f2477e4 | ||
|
|
26e8840484 | ||
|
|
b6ef6d3ce9 | ||
|
|
cdfcd24049 | ||
|
|
95a8e0b125 | ||
|
|
84b7509f33 | ||
|
|
a53baec9dc | ||
|
|
4b4bc86cf7 | ||
|
|
4d3860089e | ||
|
|
758f55f13c | ||
|
|
1e191c3adc | ||
|
|
1ba2cf7c9d | ||
|
|
9ed1cc00d5 | ||
|
|
3be1173197 | ||
|
|
f6e69c62b1 | ||
|
|
6d9679a7fc | ||
|
|
6ea260a81f | ||
|
|
26da600761 | ||
|
|
33172a7aee | ||
|
|
7eb9f6ac95 | ||
|
|
fc06c285d5 | ||
|
|
ca2e4fec79 | ||
|
|
87ca90f6cc | ||
|
|
7dfea78c87 | ||
|
|
d7808009dc | ||
|
|
62afb7a130 | ||
|
|
87bef9cb9c | ||
|
|
3860cb21c6 | ||
|
|
bf4ad7886c | ||
|
|
1d51c207f4 | ||
|
|
2eb3f70e0c | ||
|
|
eb4d883194 | ||
|
|
3f5c9ff9de | ||
|
|
47039796de | ||
|
|
fd054abcca | ||
|
|
2861bfbd47 | ||
|
|
6ea3127f9c | ||
|
|
f7acb5e115 | ||
|
|
b1ad1f7699 | ||
|
|
ab713df3c0 | ||
|
|
e0d44f2f1c | ||
|
|
61463bc05f | ||
|
|
a219fc3527 | ||
|
|
1cd4028ce8 | ||
|
|
de9a30eb93 | ||
|
|
2210561af3 | ||
|
|
c5ddc474c4 | ||
|
|
688748da36 | ||
|
|
753d311593 | ||
|
|
5bae3d9931 | ||
|
|
a3495a8218 | ||
|
|
5fa68f77a2 | ||
|
|
dbba2e9791 | ||
|
|
6207f26a68 | ||
|
|
7fcd39822e | ||
|
|
d20f8a604f | ||
|
|
a742646127 | ||
|
|
f5078d5ef2 | ||
|
|
3784084abc | ||
|
|
2f463b7735 | ||
|
|
7be5625d66 | ||
|
|
aef14a9cd2 | ||
|
|
67648a04d3 | ||
|
|
58345e3194 | ||
|
|
4cb2ad9a5a | ||
|
|
27395d8e22 | ||
|
|
230c5ea180 | ||
|
|
4d4370ddce | ||
|
|
7e913e7847 | ||
|
|
ec7871e4e9 | ||
|
|
cc5a45a14f | ||
|
|
5fff3457ba | ||
|
|
6f5d66c1d9 | ||
|
|
d94795bf4f | ||
|
|
009b71c42a | ||
|
|
a10716d74e | ||
|
|
b44bf5f401 | ||
|
|
e0af1838e2 | ||
|
|
7d5502a34f | ||
|
|
78ccddf87c | ||
|
|
df1bc48e84 | ||
|
|
3b48d61571 | ||
|
|
92d5e2a40d | ||
|
|
234857c414 | ||
|
|
b1bbe785ea | ||
|
|
487fb49bff | ||
|
|
b14b7fa926 | ||
|
|
4cc1ab143d | ||
|
|
c23de67cd2 | ||
|
|
e9bb4868c8 | ||
|
|
ae6fd0eabd | ||
|
|
f4f73906bc | ||
|
|
c437decbbf | ||
|
|
1d36677ead | ||
|
|
542b69ca92 | ||
|
|
8547c9c396 |
+2
-1
@@ -1,4 +1,5 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github:
|
||||
- d4rken
|
||||
custom:
|
||||
- "https://www.buymeacoffee.com/tydarken"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
name: Android CI
|
||||
name: Code tests & eval
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -7,13 +7,13 @@ on:
|
||||
branches: [ main ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
build-and-test:
|
||||
name: Build and test
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: set up JDK 11
|
||||
- name: Set up JDK 11
|
||||
uses: actions/setup-java@v2
|
||||
with:
|
||||
java-version: '11'
|
||||
@@ -22,7 +22,13 @@ jobs:
|
||||
|
||||
- name: Grant execute permission for gradlew
|
||||
run: chmod +x gradlew
|
||||
- name: Build with Gradle
|
||||
run: ./gradlew assembleDebug
|
||||
- name: Run tests
|
||||
run: ./gradlew testGplayDebugUnitTest testFossDebugUnitTest
|
||||
|
||||
- name: Build FOSS variant
|
||||
run: ./gradlew assembleFossDebug
|
||||
- name: Test FOSS variant
|
||||
run: ./gradlew testFossDebugUnitTest
|
||||
|
||||
- name: Build Google Play variant
|
||||
run: ./gradlew assembleGplayDebug
|
||||
- name: Test Google Play variant
|
||||
run: ./gradlew testGplayDebugUnitTest
|
||||
@@ -0,0 +1,157 @@
|
||||
name: Tagged releases
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
jobs:
|
||||
release-github:
|
||||
name: Create GitHub release
|
||||
runs-on: ubuntu-latest
|
||||
environment: foss-production
|
||||
steps:
|
||||
- name: Decode Keystore
|
||||
env:
|
||||
ENCODED_KEYSTORE: ${{ secrets.SIGNING_KEYSTORE_BASE64 }}
|
||||
run: |
|
||||
TMP_KEYSTORE_DIR_PATH="${RUNNER_TEMP}"/keystore
|
||||
mkdir -p "${TMP_KEYSTORE_DIR_PATH}"
|
||||
TMP_KEYSTORE_FILE_PATH="${TMP_KEYSTORE_DIR_PATH}"/keystore.jks
|
||||
echo $ENCODED_KEYSTORE | base64 -di > "${TMP_KEYSTORE_FILE_PATH}"
|
||||
echo "STORE_PATH=$(echo $TMP_KEYSTORE_FILE_PATH)" >> $GITHUB_ENV
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Get the version
|
||||
id: tagger
|
||||
uses: jimschubert/query-tag-action@v2
|
||||
with:
|
||||
skip-unshallow: 'true'
|
||||
abbrev: false
|
||||
commit-ish: HEAD
|
||||
|
||||
- name: Install JDK ${{ matrix.java_version }}
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'adopt'
|
||||
java-version: 11
|
||||
|
||||
- name: Assemble beta APK
|
||||
if: contains(steps.tagger.outputs.tag, '-beta')
|
||||
run: ./gradlew assembleFossBeta
|
||||
env:
|
||||
VERSION: ${{ github.ref }}
|
||||
STORE_PASSWORD: ${{ secrets.STORE_PASSWORD }}
|
||||
KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
|
||||
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
|
||||
BUGSNAG_API_KEY: ${{ secrets.BUGSNAG_API_KEY }}
|
||||
|
||||
- name: Assemble production APK
|
||||
if: "!contains(steps.tagger.outputs.tag, '-beta')"
|
||||
run: ./gradlew assembleFossRelease
|
||||
env:
|
||||
VERSION: ${{ github.ref }}
|
||||
STORE_PASSWORD: ${{ secrets.STORE_PASSWORD }}
|
||||
KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
|
||||
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
|
||||
BUGSNAG_API_KEY: ${{ secrets.BUGSNAG_API_KEY }}
|
||||
|
||||
- name: Create pre-release
|
||||
if: contains(steps.tagger.outputs.tag, '-beta')
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
prerelease: true
|
||||
tag_name: ${{ steps.tagger.outputs.tag }}
|
||||
name: ${{ steps.tagger.outputs.tag }}
|
||||
generate_release_notes: true
|
||||
files: |
|
||||
app/build/outputs/apk/foss/beta/*.apk
|
||||
app-wear/build/outputs/apk/foss/beta/*.apk
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Create release
|
||||
if: "!contains(steps.tagger.outputs.tag, '-beta')"
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
prerelease: false
|
||||
tag_name: ${{ steps.tagger.outputs.tag }}
|
||||
name: ${{ steps.tagger.outputs.tag }}
|
||||
generate_release_notes: true
|
||||
files: |
|
||||
app/build/outputs/apk/foss/release/*.apk
|
||||
app-wear/build/outputs/apk/foss/release/*.apk
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
release-gplay:
|
||||
name: Create Google Play release
|
||||
runs-on: ubuntu-latest
|
||||
environment: gplay-production
|
||||
steps:
|
||||
- name: Decode Keystore
|
||||
env:
|
||||
ENCODED_KEYSTORE: ${{ secrets.SIGNING_KEYSTORE_BASE64 }}
|
||||
run: |
|
||||
TMP_KEYSTORE_DIR_PATH="${RUNNER_TEMP}"/keystore
|
||||
mkdir -p "${TMP_KEYSTORE_DIR_PATH}"
|
||||
TMP_KEYSTORE_FILE_PATH="${TMP_KEYSTORE_DIR_PATH}"/keystore.jks
|
||||
echo $ENCODED_KEYSTORE | base64 -di > "${TMP_KEYSTORE_FILE_PATH}"
|
||||
echo "STORE_PATH=$(echo $TMP_KEYSTORE_FILE_PATH)" >> $GITHUB_ENV
|
||||
|
||||
- name: Decode Google Play service account key
|
||||
env:
|
||||
ENCODED_SERVICE_KEY: ${{ secrets.GPLAY_SERVICE_ACCOUNT_KEY_JSON_BASE64 }}
|
||||
run: |
|
||||
TMP_SERVICEKEY_DIR="${RUNNER_TEMP}"/gplaykey
|
||||
mkdir -p "${TMP_SERVICEKEY_DIR}"
|
||||
TMP_SERVICEKEY_FILE_PATH="${TMP_SERVICEKEY_DIR}"/service_account.json
|
||||
echo $ENCODED_SERVICE_KEY | base64 -di > "${TMP_SERVICEKEY_FILE_PATH}"
|
||||
echo "SUPPLY_JSON_KEY=$(echo $TMP_SERVICEKEY_FILE_PATH)" >> $GITHUB_ENV
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Get the version
|
||||
id: tagger
|
||||
uses: jimschubert/query-tag-action@v2
|
||||
with:
|
||||
skip-unshallow: 'true'
|
||||
abbrev: false
|
||||
commit-ish: HEAD
|
||||
|
||||
- name: Install JDK ${{ matrix.java_version }}
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'adopt'
|
||||
java-version: 11
|
||||
|
||||
- name: Set up ruby env
|
||||
uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: 2.7.6
|
||||
bundler-cache: true
|
||||
|
||||
- name: Assemble beta and upload to Google Play
|
||||
if: contains(steps.tagger.outputs.tag, '-beta')
|
||||
run: bundle exec fastlane beta
|
||||
env:
|
||||
STORE_PASSWORD: ${{ secrets.STORE_PASSWORD }}
|
||||
KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
|
||||
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
|
||||
BUGSNAG_API_KEY: ${{ secrets.BUGSNAG_API_KEY }}
|
||||
|
||||
- name: Assemble production and upload to Google Play
|
||||
if: "!contains(steps.tagger.outputs.tag, '-beta')"
|
||||
run: bundle exec fastlane production
|
||||
env:
|
||||
STORE_PASSWORD: ${{ secrets.STORE_PASSWORD }}
|
||||
KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
|
||||
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
|
||||
BUGSNAG_API_KEY: ${{ secrets.BUGSNAG_API_KEY }}
|
||||
+12
-2
@@ -1,4 +1,14 @@
|
||||
.idea
|
||||
*.iml
|
||||
.gradle
|
||||
build/
|
||||
/local.properties
|
||||
.DS_Store
|
||||
/build
|
||||
/captures
|
||||
.externalNativeBuild
|
||||
.cxx
|
||||
/.idea/**/*
|
||||
!/.idea/codeStyles/
|
||||
!/.idea/codeStyles/**/*
|
||||
*.jks
|
||||
.local/*
|
||||
/fastlane/report.xml
|
||||
|
||||
Generated
+167
@@ -0,0 +1,167 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<code_scheme name="Project" version="173">
|
||||
<option name="RIGHT_MARGIN" value="120" />
|
||||
<JSON>
|
||||
<option name="OBJECT_WRAPPING" value="1" />
|
||||
<option name="ARRAY_WRAPPING" value="1" />
|
||||
</JSON>
|
||||
<JavaCodeStyleSettings>
|
||||
<option name="DO_NOT_WRAP_AFTER_SINGLE_ANNOTATION" value="true" />
|
||||
<option name="IMPORT_LAYOUT_TABLE">
|
||||
<value>
|
||||
<package name="android" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="com" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="junit" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="net" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="org" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="java" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="javax" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="" withSubpackages="true" static="true" />
|
||||
<emptyLine />
|
||||
</value>
|
||||
</option>
|
||||
</JavaCodeStyleSettings>
|
||||
<JetCodeStyleSettings>
|
||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||
</JetCodeStyleSettings>
|
||||
<codeStyleSettings language="JAVA">
|
||||
<option name="KEEP_SIMPLE_BLOCKS_IN_ONE_LINE" value="true" />
|
||||
<option name="KEEP_SIMPLE_METHODS_IN_ONE_LINE" value="true" />
|
||||
<option name="KEEP_SIMPLE_LAMBDAS_IN_ONE_LINE" value="true" />
|
||||
<option name="KEEP_SIMPLE_CLASSES_IN_ONE_LINE" value="true" />
|
||||
<option name="FIELD_ANNOTATION_WRAP" value="1" />
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="JSON">
|
||||
<indentOptions>
|
||||
<option name="USE_TAB_CHARACTER" value="true" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="XML">
|
||||
<indentOptions>
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||
</indentOptions>
|
||||
<arrangement>
|
||||
<rules>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>xmlns:android</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>xmlns:.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:id</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:name</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>name</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>style</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>ANDROID_ATTRIBUTE_ORDER</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>.*</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
</rules>
|
||||
</arrangement>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="kotlin">
|
||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||
<option name="METHOD_ANNOTATION_WRAP" value="1" />
|
||||
<option name="CLASS_ANNOTATION_WRAP" value="1" />
|
||||
<option name="FIELD_ANNOTATION_WRAP" value="1" />
|
||||
</codeStyleSettings>
|
||||
</code_scheme>
|
||||
</component>
|
||||
Generated
+5
@@ -0,0 +1,5 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<state>
|
||||
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
|
||||
</state>
|
||||
</component>
|
||||
+37
-37
@@ -8,20 +8,20 @@ GEM
|
||||
artifactory (3.0.15)
|
||||
atomos (0.1.3)
|
||||
aws-eventstream (1.2.0)
|
||||
aws-partitions (1.554.0)
|
||||
aws-sdk-core (3.126.0)
|
||||
aws-partitions (1.610.0)
|
||||
aws-sdk-core (3.131.3)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
aws-partitions (~> 1, >= 1.525.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
jmespath (~> 1.0)
|
||||
aws-sdk-kms (1.54.0)
|
||||
aws-sdk-core (~> 3, >= 3.126.0)
|
||||
jmespath (~> 1, >= 1.6.1)
|
||||
aws-sdk-kms (1.58.0)
|
||||
aws-sdk-core (~> 3, >= 3.127.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-sdk-s3 (1.112.0)
|
||||
aws-sdk-core (~> 3, >= 3.126.0)
|
||||
aws-sdk-s3 (1.114.0)
|
||||
aws-sdk-core (~> 3, >= 3.127.0)
|
||||
aws-sdk-kms (~> 1)
|
||||
aws-sigv4 (~> 1.4)
|
||||
aws-sigv4 (1.4.0)
|
||||
aws-sigv4 (1.5.1)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
babosa (1.0.4)
|
||||
claide (1.1.0)
|
||||
@@ -36,8 +36,8 @@ GEM
|
||||
unf (>= 0.0.5, < 1.0.0)
|
||||
dotenv (2.7.6)
|
||||
emoji_regex (3.2.3)
|
||||
excon (0.91.0)
|
||||
faraday (1.9.3)
|
||||
excon (0.92.4)
|
||||
faraday (1.10.0)
|
||||
faraday-em_http (~> 1.0)
|
||||
faraday-em_synchrony (~> 1.0)
|
||||
faraday-excon (~> 1.1)
|
||||
@@ -56,8 +56,8 @@ GEM
|
||||
faraday-em_synchrony (1.0.0)
|
||||
faraday-excon (1.1.0)
|
||||
faraday-httpclient (1.0.1)
|
||||
faraday-multipart (1.0.3)
|
||||
multipart-post (>= 1.2, < 3)
|
||||
faraday-multipart (1.0.4)
|
||||
multipart-post (~> 2)
|
||||
faraday-net_http (1.0.1)
|
||||
faraday-net_http_persistent (1.2.0)
|
||||
faraday-patron (1.0.0)
|
||||
@@ -66,7 +66,7 @@ GEM
|
||||
faraday_middleware (1.2.0)
|
||||
faraday (~> 1.0)
|
||||
fastimage (2.2.6)
|
||||
fastlane (2.204.3)
|
||||
fastlane (2.208.0)
|
||||
CFPropertyList (>= 2.3, < 4.0.0)
|
||||
addressable (>= 2.8, < 3.0.0)
|
||||
artifactory (~> 3.0)
|
||||
@@ -106,9 +106,9 @@ GEM
|
||||
xcpretty (~> 0.3.0)
|
||||
xcpretty-travis-formatter (>= 0.0.3)
|
||||
gh_inspector (1.1.3)
|
||||
google-apis-androidpublisher_v3 (0.16.0)
|
||||
google-apis-core (>= 0.4, < 2.a)
|
||||
google-apis-core (0.4.2)
|
||||
google-apis-androidpublisher_v3 (0.25.0)
|
||||
google-apis-core (>= 0.7, < 2.a)
|
||||
google-apis-core (0.7.0)
|
||||
addressable (~> 2.5, >= 2.5.1)
|
||||
googleauth (>= 0.16.2, < 2.a)
|
||||
httpclient (>= 2.8.1, < 3.a)
|
||||
@@ -117,19 +117,19 @@ GEM
|
||||
retriable (>= 2.0, < 4.a)
|
||||
rexml
|
||||
webrick
|
||||
google-apis-iamcredentials_v1 (0.10.0)
|
||||
google-apis-core (>= 0.4, < 2.a)
|
||||
google-apis-playcustomapp_v1 (0.7.0)
|
||||
google-apis-core (>= 0.4, < 2.a)
|
||||
google-apis-storage_v1 (0.11.0)
|
||||
google-apis-core (>= 0.4, < 2.a)
|
||||
google-apis-iamcredentials_v1 (0.13.0)
|
||||
google-apis-core (>= 0.7, < 2.a)
|
||||
google-apis-playcustomapp_v1 (0.10.0)
|
||||
google-apis-core (>= 0.7, < 2.a)
|
||||
google-apis-storage_v1 (0.18.0)
|
||||
google-apis-core (>= 0.7, < 2.a)
|
||||
google-cloud-core (1.6.0)
|
||||
google-cloud-env (~> 1.0)
|
||||
google-cloud-errors (~> 1.0)
|
||||
google-cloud-env (1.5.0)
|
||||
faraday (>= 0.17.3, < 2.0)
|
||||
google-cloud-env (1.6.0)
|
||||
faraday (>= 0.17.3, < 3.0)
|
||||
google-cloud-errors (1.2.0)
|
||||
google-cloud-storage (1.36.1)
|
||||
google-cloud-storage (1.37.0)
|
||||
addressable (~> 2.8)
|
||||
digest-crc (~> 0.4)
|
||||
google-apis-iamcredentials_v1 (~> 0.1)
|
||||
@@ -137,20 +137,20 @@ GEM
|
||||
google-cloud-core (~> 1.6)
|
||||
googleauth (>= 0.16.2, < 2.a)
|
||||
mini_mime (~> 1.0)
|
||||
googleauth (1.1.0)
|
||||
faraday (>= 0.17.3, < 2.0)
|
||||
googleauth (1.2.0)
|
||||
faraday (>= 0.17.3, < 3.a)
|
||||
jwt (>= 1.4, < 3.0)
|
||||
memoist (~> 0.16)
|
||||
multi_json (~> 1.11)
|
||||
os (>= 0.9, < 2.0)
|
||||
signet (>= 0.16, < 2.a)
|
||||
highline (2.0.3)
|
||||
http-cookie (1.0.4)
|
||||
http-cookie (1.0.5)
|
||||
domain_name (~> 0.5)
|
||||
httpclient (2.8.3)
|
||||
jmespath (1.5.0)
|
||||
json (2.6.1)
|
||||
jwt (2.3.0)
|
||||
jmespath (1.6.1)
|
||||
json (2.6.2)
|
||||
jwt (2.4.1)
|
||||
memoist (0.16.2)
|
||||
mini_magick (4.11.0)
|
||||
mini_mime (1.1.2)
|
||||
@@ -161,9 +161,9 @@ GEM
|
||||
optparse (0.1.1)
|
||||
os (1.1.4)
|
||||
plist (3.6.0)
|
||||
public_suffix (4.0.6)
|
||||
public_suffix (4.0.7)
|
||||
rake (13.0.6)
|
||||
representable (3.1.1)
|
||||
representable (3.2.0)
|
||||
declarative (< 0.1.0)
|
||||
trailblazer-option (>= 0.1.1, < 0.2.0)
|
||||
uber (< 0.2.0)
|
||||
@@ -173,9 +173,9 @@ GEM
|
||||
ruby2_keywords (0.0.5)
|
||||
rubyzip (2.3.2)
|
||||
security (0.1.3)
|
||||
signet (0.16.0)
|
||||
signet (0.17.0)
|
||||
addressable (~> 2.8)
|
||||
faraday (>= 0.17.3, < 2.0)
|
||||
faraday (>= 0.17.5, < 3.a)
|
||||
jwt (>= 1.5, < 3.0)
|
||||
multi_json (~> 1.10)
|
||||
simctl (1.6.8)
|
||||
@@ -192,11 +192,11 @@ GEM
|
||||
uber (0.1.0)
|
||||
unf (0.1.4)
|
||||
unf_ext
|
||||
unf_ext (0.0.8)
|
||||
unf_ext (0.0.8.2)
|
||||
unicode-display_width (1.8.0)
|
||||
webrick (1.7.0)
|
||||
word_wrap (1.0.0)
|
||||
xcodeproj (1.21.0)
|
||||
xcodeproj (1.22.0)
|
||||
CFPropertyList (>= 2.3.3, < 4.0)
|
||||
atomos (~> 0.1.3)
|
||||
claide (>= 1.0.2, < 2.0)
|
||||
|
||||
+28
-10
@@ -1,24 +1,42 @@
|
||||
# Privacy policy for the app `CAPod`
|
||||
# Privacy policy
|
||||
This is the privacy policy for the Android app "CAPod - Companion for AirPods".
|
||||
|
||||
* I do not collect personal information
|
||||
* I do not sell, monetize or otherwise misappropriate any collected data.
|
||||
## Preamble
|
||||
CAPod respects your privacy.
|
||||
|
||||
Anonymous device information may be collected in the event of a crash (see [Automatic crash reports](#automatic-crash-reports)).
|
||||
I do not collect, share or sell personal information.
|
||||
|
||||
Send a [quick mail](mailto:support@darken.eu) if you have questions.
|
||||
|
||||
My underlying privacy principle is the [Golden Rule](https://en.wikipedia.org/wiki/Golden_Rule).
|
||||
|
||||
Send a [quick mail](mailto:support@darken.eu) if you have further questions.
|
||||
## Location data
|
||||
|
||||
CAPod does not collect, share or sell location data.
|
||||
|
||||
Location permissions are required to receive Bluetooth Low Energy (BLE) data and ebale its core functionality.
|
||||
The permission "access fine location" (`ACCESS_FINE_LOCATION`) and "access coarse location" (`ACCESS_COARSE_LOCATION`) are required to receive Bluetooth Low Energy data on Android 11 and lower.
|
||||
On Android 12+ the newer and more fine grained `BLUETOOTH_SCAN` permission is used instead.
|
||||
|
||||
Bluetooth Low Energy is a technology that devices like AirPods use to communicate their status to nearby devices.
|
||||
CAPod requests location permissions because these permissions are required to work with Bluetooth Low Energy data.
|
||||
This is a privacy measure on Android's side because you could determine someones location by scanning for Bluetooth devices:
|
||||
If you know the physical location of a Bluetooth device (e.g. AirTags) you could use Bluetooth data to calculate your position.
|
||||
|
||||
### Location access in the background
|
||||
|
||||
CAPod uses the "location access in the background" permission (`ACCESS_BACKGROUND_LOCATION`) on Android 11 and older to receive Bluetooth Low Energy data while the app is in the background. This permission enables the "Show popup" and "Autoconnect" features and allows CAPod to react to nearby devices whil the app is closed.
|
||||
|
||||
## Automatic crash reports
|
||||
|
||||
The app uses "Bugsnag" for automatic crash reports:
|
||||
Anonymous device information may be collected in the event of an app crash or error.
|
||||
|
||||
To do this the app uses the service "Bugsnag":
|
||||
https://www.bugsnag.com/
|
||||
|
||||
Bugsnags privacy policy can be found here:
|
||||
|
||||
Bugsnag's privacy policy can be found here:
|
||||
https://docs.bugsnag.com/legal/privacy-policy/
|
||||
|
||||
Crash reports may contain device and app related information.
|
||||
Crash reports may contain device and app related information, e.g. your phone model, Android version and app version.
|
||||
|
||||
You can disable automatic crash reports in the app's settings.
|
||||
You can disable automatic reports in the app's settings.
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
<img src="https://github.com/d4rken/capod/raw/main/.assets/banner.png" width="400">
|
||||
<img src="https://github.com/d4rken-org/capod/raw/main/.assets/banner.png" width="400">
|
||||
|
||||
# Companion App for AirPods (CAPod)
|
||||
|
||||

|
||||
[](https://github.com/d4rken/capod/releases/latest)
|
||||
[](https://github.com/d4rken-org/capod/releases/latest)
|
||||
[](https://github.com/d4rken/capod/actions/workflows/code-checks.yml)
|
||||
[](https://crowdin.com/project/capod)
|
||||
[](https://github.com/d4rken-org/capod/releases/latest)
|
||||
|
||||
A companion app that adds support for AirPod specific features to Android:
|
||||
|
||||
@@ -14,14 +15,18 @@ A companion app that adds support for AirPod specific features to Android:
|
||||
* Ear detection with automatic play/pause.
|
||||
* Automatically connect phone & AirPods.
|
||||
* Show popup when case is opened.
|
||||
* Support for Wear OS
|
||||
* Widgets
|
||||
|
||||
CAPod is ad-free. Some additional features require an in-app purchase.
|
||||
|
||||
Currently supported models:
|
||||
|
||||
* AirPods Gen1
|
||||
* AirPods Gen2
|
||||
* AirPods Pro
|
||||
* AirPods 1. Generation
|
||||
* AirPods 2. Generation
|
||||
* AirPods 3. Generation
|
||||
* AirPods Pro 1. Generation
|
||||
* AirPods Pro 2. Generation
|
||||
* AirPods Max
|
||||
* Power Beats Pro
|
||||
* Power Beats 3
|
||||
@@ -32,24 +37,29 @@ Currently supported models:
|
||||
|
||||
## Download
|
||||
|
||||
* [Google Play](https://play.google.com/store/apps/details?id=eu.darken.capod)
|
||||
* [GitHub](https://github.com/d4rken/capod/releases/latest)
|
||||
| Source | Status |
|
||||
|-----------------------|--------|
|
||||
| [Google Play](https://play.google.com/store/apps/details?id=eu.darken.capod) | [](https://play.google.com/store/apps/details?id=eu.darken.capod) |
|
||||
| [Google Play Beta](https://play.google.com/apps/testing/eu.darken.capod) | ? |
|
||||
| [Github Releases](https://github.com/d4rken-org/capod/releases) | [](https://github.com/d4rken-org/capod/releases/latest) |
|
||||
| [F-Droid (IzzyOnDroid)](https://apt.izzysoft.de/packages/eu.darken.capod/) | [](https://apt.izzysoft.de/packages/eu.darken.capod/) |
|
||||
|
||||
|
||||
## Support the project
|
||||
|
||||
* Buy the CAPod Pro In-App purchase on [Google Play](https://play.google.com/store/apps/details?id=eu.darken.capod)
|
||||
* [Sponsor development](https://github.com/sponsors/d4rken) on GitHub
|
||||
* Help translate CAPod [on Crowdin](https://crowdin.com/project/capod)
|
||||
* [Buy me a coffee](https://www.buymeacoffee.com/tydarken)
|
||||
|
||||
## Get help
|
||||
|
||||
* [Github Issues](https://github.com/d4rken/capod/issues)
|
||||
* [Github Issues](https://github.com/d4rken-org/capod/issues)
|
||||
* [Discord](https://discord.gg/vHubYPp)
|
||||
|
||||
## Screenshots
|
||||
|
||||
<img src="https://github.com/d4rken/capod/raw/main/.assets/screenshots/1.png" width="200"><img src="https://github.com/d4rken/capod/raw/main/.assets/screenshots/2.png" width="200"><img src="https://github.com/d4rken/capod/raw/main/.assets/screenshots/3.png" width="200"><img src="https://github.com/d4rken/capod/raw/main/.assets/screenshots/4.png" width="200">
|
||||
|
||||
<img src="https://github.com/d4rken-org/capod/raw/main/.assets/screenshots/1.png" width="200"><img src="https://github.com/d4rken-org/capod/raw/main/.assets/screenshots/2.png" width="200"><img src="https://github.com/d4rken-org/capod/raw/main/.assets/screenshots/3.png" width="200"><img src="https://github.com/d4rken-org/capod/raw/main/.assets/screenshots/4.png" width="200">
|
||||
<img src="https://raw.githubusercontent.com/d4rken-org/capod/main/fastlane/metadata/android/en-US/images/phoneScreenshots/5.png" width="200">
|
||||
## Thanks to
|
||||
|
||||
* The [OpenPods](https://github.com/adolfintel/OpenPods) project,
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
/build
|
||||
@@ -0,0 +1,100 @@
|
||||
plugins {
|
||||
id("com.android.library")
|
||||
id("kotlin-android")
|
||||
id("kotlin-kapt")
|
||||
id("kotlin-parcelize")
|
||||
}
|
||||
apply(plugin = "dagger.hilt.android.plugin")
|
||||
|
||||
android {
|
||||
compileSdk = ProjectConfig.compileSdk
|
||||
|
||||
defaultConfig {
|
||||
minSdk = ProjectConfig.minSdk
|
||||
targetSdk = ProjectConfig.targetSdk
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
consumerProguardFiles("consumer-rules.pro")
|
||||
|
||||
buildConfigField("Long", "VERSION_CODE", "${ProjectConfig.Version.code}L")
|
||||
buildConfigField("String", "VERSION_NAME", "\"${ProjectConfig.Version.name}\"")
|
||||
buildConfigField("String", "APPLICATION_ID", "\"${ProjectConfig.packageName}\"")
|
||||
buildConfigField("String", "GITSHA", "\"${lastCommitHash()}\"")
|
||||
buildConfigField("String", "BUILDTIME", "\"${buildTime()}\"")
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
viewBinding = true
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
isCoreLibraryDesugaringEnabled = true
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
freeCompilerArgs = freeCompilerArgs + listOf(
|
||||
"-opt-in=kotlin.ExperimentalStdlibApi",
|
||||
"-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
|
||||
"-opt-in=kotlin.time.ExperimentalTime",
|
||||
"-opt-in=kotlin.ExperimentalUnsignedTypes",
|
||||
)
|
||||
}
|
||||
|
||||
flavorDimensions.add("version")
|
||||
productFlavors {
|
||||
create("foss") {
|
||||
dimension = "version"
|
||||
}
|
||||
create("gplay") {
|
||||
dimension = "version"
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
val customProguardRules = fileTree(File("../proguard")) {
|
||||
include("*.pro")
|
||||
}
|
||||
debug {
|
||||
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"))
|
||||
proguardFiles(*customProguardRules.toList().toTypedArray())
|
||||
proguardFiles("proguard-rules-debug.pro")
|
||||
}
|
||||
create("beta") {
|
||||
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"))
|
||||
proguardFiles(*customProguardRules.toList().toTypedArray())
|
||||
}
|
||||
release {
|
||||
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"))
|
||||
proguardFiles(*customProguardRules.toList().toTypedArray())
|
||||
}
|
||||
}
|
||||
|
||||
testOptions {
|
||||
unitTests {
|
||||
isIncludeAndroidResources = true
|
||||
}
|
||||
tasks.withType<Test> {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:1.1.5")
|
||||
|
||||
addBaseAndroid()
|
||||
addBaseAndroidUi()
|
||||
addBaseKotlin()
|
||||
addDagger()
|
||||
addMoshi()
|
||||
addBaseWorkManager()
|
||||
addNavigation()
|
||||
|
||||
addTesting()
|
||||
|
||||
implementation("com.bugsnag:bugsnag-android:5.9.2")
|
||||
|
||||
}
|
||||
Vendored
+21
@@ -0,0 +1,21 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest package="eu.darken.capod.common">
|
||||
|
||||
</manifest>
|
||||
@@ -0,0 +1,45 @@
|
||||
package eu.darken.capod.common
|
||||
|
||||
|
||||
// Can't be const because that prevents them from being mocked in tests
|
||||
@Suppress("MayBeConstant")
|
||||
object BuildConfigWrap {
|
||||
val DEBUG: Boolean = BuildConfig.DEBUG
|
||||
|
||||
val BUILD_TYPE: BuildType = when (val typ = BuildConfig.BUILD_TYPE) {
|
||||
"debug" -> BuildType.DEV
|
||||
"beta" -> BuildType.BETA
|
||||
"release" -> BuildType.RELEASE
|
||||
else -> throw IllegalArgumentException("Unknown buildtype: $typ")
|
||||
}
|
||||
|
||||
enum class BuildType {
|
||||
DEV,
|
||||
BETA,
|
||||
RELEASE,
|
||||
;
|
||||
}
|
||||
|
||||
val FLAVOR: Flavor = when (val flav = BuildConfig.FLAVOR) {
|
||||
"gplay" -> Flavor.GPLAY
|
||||
"foss" -> Flavor.FOSS
|
||||
else -> throw IllegalStateException("Unknown flavor: $flav")
|
||||
}
|
||||
|
||||
enum class Flavor {
|
||||
GPLAY,
|
||||
FOSS,
|
||||
;
|
||||
}
|
||||
|
||||
val APPLICATION_ID: String = BuildConfig.APPLICATION_ID
|
||||
|
||||
val VERSION_CODE: Long = BuildConfig.VERSION_CODE.toLong()
|
||||
val VERSION_NAME: String = BuildConfig.VERSION_NAME
|
||||
val GIT_SHA: String = BuildConfig.GITSHA
|
||||
val BUILDTIME: String = BuildConfig.BUILDTIME
|
||||
|
||||
val VERSION_DESCRIPTION_LONG: String = "v$VERSION_NAME ($VERSION_CODE) [$GIT_SHA] ${FLAVOR}_$BUILD_TYPE"
|
||||
val VERSION_DESCRIPTION_SHORT: String = "v$VERSION_NAME [$GIT_SHA] $FLAVOR"
|
||||
val VERSION_DESCRIPTION_TINY: String = "v$VERSION_NAME"
|
||||
}
|
||||
+2
-2
@@ -13,6 +13,6 @@ object BuildWrap {
|
||||
}
|
||||
}
|
||||
|
||||
fun hasApiLevel(level: Int): Boolean = BuildWrap.VERSION.SDK_INT >= level
|
||||
fun hasApiLevel(level: Int): Boolean = BuildWrap.VersionWrap.SDK_INT >= level
|
||||
|
||||
fun withinApiLevel(start: Int, end: Int): Boolean = BuildWrap.VERSION.SDK_INT in start..end
|
||||
fun withinApiLevel(start: Int, end: Int): Boolean = BuildWrap.VersionWrap.SDK_INT in start..end
|
||||
@@ -0,0 +1,5 @@
|
||||
package eu.darken.capod.common
|
||||
|
||||
object PrivacyPolicy {
|
||||
const val URL = "https://github.com/d4rken-org/capod/blob/main/PRIVACY_POLICY.md"
|
||||
}
|
||||
+10
-4
@@ -3,14 +3,19 @@ package eu.darken.capod.common.bluetooth
|
||||
import android.bluetooth.le.ScanResult
|
||||
import android.os.Parcelable
|
||||
import androidx.core.util.forEach
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import java.time.Instant
|
||||
|
||||
@Parcelize
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class BleScanResult(
|
||||
val address: String,
|
||||
val rssi: Int,
|
||||
val generatedAtNanos: Long,
|
||||
val manufacturerSpecificData: Map<Int, ByteArray>
|
||||
@Json(name = "receivedAt") val receivedAt: Instant,
|
||||
@Json(name = "address") val address: String,
|
||||
@Json(name = "rssi") val rssi: Int,
|
||||
@Json(name = "generatedAtNanos") val generatedAtNanos: Long,
|
||||
@Json(name = "manufacturerSpecificData") val manufacturerSpecificData: Map<Int, ByteArray>
|
||||
) : Parcelable {
|
||||
|
||||
fun getManufacturerSpecificData(id: Int): ByteArray? = manufacturerSpecificData[id]
|
||||
@@ -25,6 +30,7 @@ data class BleScanResult(
|
||||
|
||||
companion object {
|
||||
fun fromScanResult(scanResult: ScanResult) = BleScanResult(
|
||||
receivedAt = Instant.now(),
|
||||
address = scanResult.device.address,
|
||||
rssi = scanResult.rssi,
|
||||
generatedAtNanos = scanResult.timestampNanos,
|
||||
+7
-4
@@ -10,7 +10,6 @@ import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import eu.darken.capod.common.debug.logging.Logging.Priority.*
|
||||
import eu.darken.capod.common.debug.logging.log
|
||||
import eu.darken.capod.common.debug.logging.logTag
|
||||
import eu.darken.capod.main.core.ScannerMode
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
@@ -36,7 +35,7 @@ class BleScanner @Inject constructor(
|
||||
log(TAG, VERBOSE) { "scan(filters=$filters, scannerMode=$scannerMode, compatMode=$compatMode)" }
|
||||
if (compatMode) log(TAG, WARN) { "Using compatibilityMode!" }
|
||||
|
||||
val adapter = bluetoothManager.adapter
|
||||
val adapter = bluetoothManager.adapter ?: throw IllegalStateException("Bluetooth adapter unavailable")
|
||||
|
||||
val supportsOffloadFiltering = adapter.isOffloadedFilteringSupported.also {
|
||||
log(TAG, if (it) DEBUG else WARN) { "isOffloadedFilteringSupported=$it" }
|
||||
@@ -46,7 +45,7 @@ class BleScanner @Inject constructor(
|
||||
log(TAG, if (it) DEBUG else WARN) { "isOffloadedScanBatchingSupported=$it" }
|
||||
} && !compatMode
|
||||
|
||||
val scanner = bluetoothManager.scanner
|
||||
val scanner = bluetoothManager.scanner ?: throw IllegalStateException("BLE scanner unavailable")
|
||||
|
||||
val callback = object : ScanCallback() {
|
||||
var lastScanAt = System.currentTimeMillis()
|
||||
@@ -56,7 +55,11 @@ class BleScanner @Inject constructor(
|
||||
lastScanAt = System.currentTimeMillis()
|
||||
"onScanResult(delay=${delay}ms, callbackType=$callbackType, result=$result)"
|
||||
}
|
||||
val toSend = if (supportsOffloadFiltering || filters.isEmpty() || filters.any { it.matches(result) }) {
|
||||
val toSend = if (
|
||||
supportsOffloadFiltering
|
||||
|| filters.isEmpty()
|
||||
|| filters.any { it.matchesSafe(result) }
|
||||
) {
|
||||
listOf(BleScanResult.fromScanResult(result))
|
||||
} else {
|
||||
log(TAG, VERBOSE) { "Manual filtering: No match for $result" }
|
||||
@@ -0,0 +1,26 @@
|
||||
package eu.darken.capod.common.bluetooth
|
||||
|
||||
import android.bluetooth.BluetoothDevice
|
||||
import android.bluetooth.le.ScanFilter
|
||||
import android.bluetooth.le.ScanResult
|
||||
import android.os.ParcelUuid
|
||||
import eu.darken.capod.common.debug.logging.asLog
|
||||
import eu.darken.capod.common.debug.logging.log
|
||||
|
||||
fun BluetoothDevice.hasFeature(uuid: ParcelUuid): Boolean {
|
||||
return uuids?.contains(uuid) ?: false
|
||||
}
|
||||
|
||||
/**
|
||||
* java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.Object android.util.SparseArray.get(int)' on a null object reference
|
||||
* at android.bluetooth.le.ScanRecord.getManufacturerSpecificData(ScanRecord.java:118)
|
||||
* at android.bluetooth.le.ScanFilter.matches(ScanFilter.java:369)
|
||||
* ZenFone Max Pro M1 (ZB602KL) (WW) / Max Pro M1 (ZB601KL) (IN) (ZB602KL), Android 9, PKQ1.WW_Phone-16.2017.2009.087-20200826
|
||||
* Intel Gemini Lake Chromebook (octopus), Android 9, R99-14469.59.0 release-keys
|
||||
*/
|
||||
fun ScanFilter.matchesSafe(scanResult: ScanResult): Boolean = try {
|
||||
matches(scanResult)
|
||||
} catch (e: NullPointerException) {
|
||||
log { "AOSP error: ${e.asLog()}" }
|
||||
false
|
||||
}
|
||||
+6
-6
@@ -20,7 +20,6 @@ import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.IOException
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@@ -31,12 +30,11 @@ class BluetoothManager2 @Inject constructor(
|
||||
private val dispatcherProvider: DispatcherProvider,
|
||||
) {
|
||||
|
||||
val adapter: BluetoothAdapter
|
||||
val adapter: BluetoothAdapter?
|
||||
get() = manager.adapter
|
||||
|
||||
val scanner: BluetoothLeScanner
|
||||
get() = adapter.bluetoothLeScanner
|
||||
?: throw IllegalStateException("Bluetooth is disabled or permissiong missing")
|
||||
val scanner: BluetoothLeScanner?
|
||||
get() = adapter?.bluetoothLeScanner
|
||||
|
||||
val isBluetoothEnabled: Flow<Boolean> = callbackFlow {
|
||||
send(manager.adapter?.isEnabled ?: false)
|
||||
@@ -159,7 +157,9 @@ class BluetoothManager2 @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
fun bondedDevices(): Set<BluetoothDevice> = adapter.bondedDevices
|
||||
fun bondedDevices(): Flow<Set<BluetoothDevice>> = flow {
|
||||
emit(adapter?.bondedDevices ?: throw IllegalStateException("Bluetooth adapter unavailable"))
|
||||
}
|
||||
|
||||
suspend fun nudgeConnection(device: BluetoothDevice): Boolean = getBluetoothProfile().map { bluetoothProfile ->
|
||||
try {
|
||||
+8
@@ -3,6 +3,7 @@ package eu.darken.capod.common.bluetooth
|
||||
import dagger.Reusable
|
||||
import eu.darken.capod.common.SystemClockWrap
|
||||
import eu.darken.capod.common.debug.autoreport.DebugSettings
|
||||
import java.time.Instant
|
||||
import javax.inject.Inject
|
||||
import kotlin.random.Random
|
||||
|
||||
@@ -20,6 +21,7 @@ class FakeBleData @Inject constructor(
|
||||
val fakeDevices = mutableListOf<BleScanResult>()
|
||||
// AirPods Gen1
|
||||
BleScanResult(
|
||||
receivedAt = Instant.now(),
|
||||
address = "78:73:AF:B4:85:22",
|
||||
rssi = Random.nextInt(100) * -1,
|
||||
generatedAtNanos = SystemClockWrap.elapsedRealtimeNanos + 100,
|
||||
@@ -32,6 +34,7 @@ class FakeBleData @Inject constructor(
|
||||
|
||||
// AirPods Gen2
|
||||
BleScanResult(
|
||||
receivedAt = Instant.now(),
|
||||
address = "78:73:FF:B4:85:5E",
|
||||
rssi = Random.nextInt(100) * -1,
|
||||
generatedAtNanos = SystemClockWrap.elapsedRealtimeNanos + 100,
|
||||
@@ -44,6 +47,7 @@ class FakeBleData @Inject constructor(
|
||||
|
||||
// AirPods Gen3
|
||||
BleScanResult(
|
||||
receivedAt = Instant.now(),
|
||||
address = "4E:9E:D1:49:D2:6D",
|
||||
rssi = Random.nextInt(15, 75) * -1,
|
||||
generatedAtNanos = SystemClockWrap.elapsedRealtimeNanos + 200,
|
||||
@@ -55,6 +59,7 @@ class FakeBleData @Inject constructor(
|
||||
}
|
||||
// AirPods Max
|
||||
BleScanResult(
|
||||
receivedAt = Instant.now(),
|
||||
address = "7E:E5:C7:65:D2:B5",
|
||||
rssi = Random.nextInt(15, 75) * -1,
|
||||
generatedAtNanos = SystemClockWrap.elapsedRealtimeNanos + 300,
|
||||
@@ -66,6 +71,7 @@ class FakeBleData @Inject constructor(
|
||||
}
|
||||
// BeatsFlex
|
||||
BleScanResult(
|
||||
receivedAt = Instant.now(),
|
||||
address = "5E:9E:D1:49:D2:6D",
|
||||
rssi = Random.nextInt(15, 75) * -1,
|
||||
generatedAtNanos = SystemClockWrap.elapsedRealtimeNanos + 400,
|
||||
@@ -78,6 +84,7 @@ class FakeBleData @Inject constructor(
|
||||
|
||||
// Tws i99999
|
||||
BleScanResult(
|
||||
receivedAt = Instant.now(),
|
||||
address = "5E:9E:D1:29:D2:6D",
|
||||
rssi = Random.nextInt(15, 75) * -1,
|
||||
generatedAtNanos = SystemClockWrap.elapsedRealtimeNanos + 400,
|
||||
@@ -90,6 +97,7 @@ class FakeBleData @Inject constructor(
|
||||
|
||||
// Unknown Device
|
||||
BleScanResult(
|
||||
receivedAt = Instant.now(),
|
||||
address = "6E:9E:D1:49:D2:6D",
|
||||
rssi = Random.nextInt(15, 75) * -1,
|
||||
generatedAtNanos = SystemClockWrap.elapsedRealtimeNanos + 500,
|
||||
+2
-2
@@ -1,9 +1,9 @@
|
||||
package eu.darken.capod.main.core
|
||||
package eu.darken.capod.common.bluetooth
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import eu.darken.capod.R
|
||||
import eu.darken.capod.common.R
|
||||
|
||||
@JsonClass(generateAdapter = false)
|
||||
enum class ScannerMode(
|
||||
+2
-2
@@ -28,12 +28,12 @@ class AutoReporting @Inject constructor(
|
||||
) {
|
||||
|
||||
fun setup() {
|
||||
val isEnabled = debugSettings.isAutoReportEnabled.value
|
||||
val isEnabled = debugSettings.isAutoReportingEnabled.value
|
||||
log(TAG) { "setup(): isEnabled=$isEnabled" }
|
||||
|
||||
try {
|
||||
val bugsnagConfig = Configuration.load(context).apply {
|
||||
if (debugSettings.isAutoReportEnabled.value) {
|
||||
if (debugSettings.isAutoReportingEnabled.value) {
|
||||
Logging.install(bugsnagLogger.get())
|
||||
setUser(installId.id, null, null)
|
||||
autoTrackSessions = true
|
||||
+6
-2
@@ -4,6 +4,7 @@ import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import androidx.preference.PreferenceDataStore
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import eu.darken.capod.common.BuildConfigWrap
|
||||
import eu.darken.capod.common.preferences.PreferenceStoreMapper
|
||||
import eu.darken.capod.common.preferences.Settings
|
||||
import eu.darken.capod.common.preferences.createFlowPreference
|
||||
@@ -17,8 +18,11 @@ class DebugSettings @Inject constructor(
|
||||
|
||||
override val preferences: SharedPreferences = context.getSharedPreferences("settings_debug", Context.MODE_PRIVATE)
|
||||
|
||||
val isAutoReportEnabled = preferences.createFlowPreference("debug.bugreport.automatic.enabled", true)
|
||||
|
||||
val isAutoReportingEnabled = preferences.createFlowPreference(
|
||||
key = "debug.bugreport.automatic.enabled",
|
||||
// Reporting is opt-out for gplay, and opt-in for github builds
|
||||
defaultValue = BuildConfigWrap.FLAVOR == BuildConfigWrap.Flavor.GPLAY
|
||||
)
|
||||
val isDebugModeEnabled = preferences.createFlowPreference("debug.mode.enabled", false)
|
||||
|
||||
val showFakeData = preferences.createFlowPreference("debug.fakedata.enabled", false)
|
||||
+3
-4
@@ -6,7 +6,6 @@ import android.content.pm.PackageManager
|
||||
import com.bugsnag.android.Event
|
||||
import com.bugsnag.android.OnErrorCallback
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import eu.darken.capod.BuildConfig
|
||||
import eu.darken.capod.common.BuildConfigWrap
|
||||
import eu.darken.capod.common.debug.autoreport.DebugSettings
|
||||
import eu.darken.capod.common.debug.logging.Logging.Priority.WARN
|
||||
@@ -26,13 +25,13 @@ class BugsnagErrorHandler @Inject constructor(
|
||||
bugsnagLogger.injectLog(event)
|
||||
|
||||
TAB_APP.also { tab ->
|
||||
event.addMetadata(tab, "gitSha", BuildConfig.GITSHA)
|
||||
event.addMetadata(tab, "buildTime", BuildConfig.BUILDTIME)
|
||||
event.addMetadata(tab, "gitSha", BuildConfigWrap.GIT_SHA)
|
||||
event.addMetadata(tab, "buildTime", BuildConfigWrap.BUILDTIME)
|
||||
|
||||
context.tryFormattedSignature()?.let { event.addMetadata(tab, "signatures", it) }
|
||||
}
|
||||
|
||||
return debugSettings.isAutoReportEnabled.value && !BuildConfigWrap.DEBUG
|
||||
return debugSettings.isAutoReportingEnabled.value && !eu.darken.capod.common.BuildConfigWrap.DEBUG
|
||||
}
|
||||
|
||||
companion object {
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
package eu.darken.capod.common.error
|
||||
|
||||
import android.content.Context
|
||||
import eu.darken.capod.R
|
||||
import eu.darken.capod.common.R
|
||||
|
||||
interface HasLocalizedError {
|
||||
fun getLocalizedError(context: Context): LocalizedError
|
||||
+9
-3
@@ -25,7 +25,8 @@ class DynamicStateFlow<T>(
|
||||
loggingTag: String? = null,
|
||||
parentScope: CoroutineScope,
|
||||
coroutineContext: CoroutineContext = parentScope.coroutineContext,
|
||||
private val startValueProvider: suspend CoroutineScope.() -> T
|
||||
private val onRelease: CoroutineScope.(T) -> Unit = {},
|
||||
private val startValueProvider: suspend CoroutineScope.() -> T,
|
||||
) {
|
||||
private val lTag = loggingTag?.let { "$it:DSFlow" }
|
||||
|
||||
@@ -47,6 +48,12 @@ class DynamicStateFlow<T>(
|
||||
}
|
||||
}
|
||||
|
||||
invokeOnClose {
|
||||
lTag?.let { log(it, VERBOSE) { "invokeOnClose executing..." } }
|
||||
onRelease(currentValue)
|
||||
lTag?.let { log(it, VERBOSE) { "internal channelFlow finished." } }
|
||||
}
|
||||
|
||||
updateActions.collect { update ->
|
||||
currentValue = valueGuard.withLock {
|
||||
try {
|
||||
@@ -68,12 +75,11 @@ class DynamicStateFlow<T>(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lTag?.let { log(it, VERBOSE) { "internal channelFlow finished." } }
|
||||
}
|
||||
|
||||
private val internalFlow = producer
|
||||
.onStart { lTag?.let { log(it, VERBOSE) { "Internal onStart" } } }
|
||||
// .onEach { value -> lTag?.let { log(it, VERBOSE) { "New value: $value" } } }
|
||||
.onCompletion { err ->
|
||||
when {
|
||||
err is CancellationException -> {
|
||||
+9
-1
@@ -7,6 +7,7 @@ import eu.darken.capod.common.debug.logging.log
|
||||
import eu.darken.capod.common.error.hasCause
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlin.time.Duration
|
||||
|
||||
@@ -40,7 +41,7 @@ fun <T : Any?> Flow<T>.replayingShare(scope: CoroutineScope) = this.shareIn(
|
||||
started = SharingStarted.WhileSubscribed(replayExpiration = Duration.ZERO)
|
||||
)
|
||||
|
||||
internal fun <T> Flow<T>.withPrevious(): Flow<Pair<T?, T>> = this
|
||||
fun <T> Flow<T>.withPrevious(): Flow<Pair<T?, T>> = this
|
||||
.scan(Pair<T?, T?>(null, null)) { previous, current -> Pair(previous.second, current) }
|
||||
.drop(1)
|
||||
.map {
|
||||
@@ -71,4 +72,11 @@ fun <T> Flow<T>.setupCommonEventHandlers(tag: String, identifier: () -> String)
|
||||
log(tag, ERROR) { "${identifier()} failed: ${it.asLog()}" }
|
||||
throw it
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> Flow<T>.throttleLatest(delayMillis: Long): Flow<T> = this
|
||||
.conflate()
|
||||
.transform {
|
||||
emit(it)
|
||||
delay(delayMillis)
|
||||
}
|
||||
+8
-2
@@ -6,8 +6,8 @@ import android.os.Build
|
||||
import android.os.PowerManager
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.core.content.ContextCompat
|
||||
import eu.darken.capod.R
|
||||
import eu.darken.capod.common.BuildConfigWrap
|
||||
import eu.darken.capod.common.R
|
||||
import eu.darken.capod.common.withinApiLevel
|
||||
|
||||
enum class Permission(
|
||||
@@ -71,7 +71,13 @@ enum class Permission(
|
||||
isGranted = {
|
||||
android.provider.Settings.canDrawOverlays(it)
|
||||
},
|
||||
)
|
||||
),
|
||||
POST_NOTIFICATIONS(
|
||||
minApiLevel = Build.VERSION_CODES.S,
|
||||
labelRes = R.string.permission_post_notifications_label,
|
||||
descriptionRes = R.string.permission_post_notifications_description,
|
||||
permissionId = "android.permission.POST_NOTIFICATIONS",
|
||||
),
|
||||
}
|
||||
|
||||
fun Permission.isRequired(context: Context): Boolean = when {
|
||||
+1
-1
@@ -2,7 +2,7 @@ package eu.darken.capod.common.preferences
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import androidx.core.content.edit
|
||||
import eu.darken.capod.common.debug.logging.Logging.Priority.*
|
||||
import eu.darken.capod.common.debug.logging.Logging.Priority.VERBOSE
|
||||
import eu.darken.capod.common.debug.logging.log
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
+1
-1
@@ -9,5 +9,5 @@ class JavaInstantAdapter {
|
||||
fun toJson(obj: Instant): Long = obj.toEpochMilli()
|
||||
|
||||
@FromJson
|
||||
fun fromJson(epochMillis: Long): Instant = Instant.ofEpochSecond(epochMillis)
|
||||
fun fromJson(epochMillis: Long): Instant = Instant.ofEpochMilli(epochMillis)
|
||||
}
|
||||
+2
@@ -9,6 +9,8 @@ interface UpgradeRepo {
|
||||
|
||||
fun launchBillingFlow(activity: Activity)
|
||||
|
||||
fun getSponsorUrl(): String? = null
|
||||
|
||||
interface Info {
|
||||
val type: Type
|
||||
|
||||
+2
-1
@@ -8,7 +8,8 @@ import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.DefaultLifecycleObserver
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.viewbinding.ViewBinding
|
||||
import eu.darken.capod.common.debug.logging.Logging.Priority.*
|
||||
import eu.darken.capod.common.debug.logging.Logging.Priority.VERBOSE
|
||||
import eu.darken.capod.common.debug.logging.Logging.Priority.WARN
|
||||
import eu.darken.capod.common.debug.logging.log
|
||||
import kotlin.properties.ReadOnlyProperty
|
||||
import kotlin.reflect.KProperty
|
||||
+2
-1
@@ -5,6 +5,7 @@ import android.content.SharedPreferences
|
||||
import androidx.preference.PreferenceDataStore
|
||||
import com.squareup.moshi.Moshi
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import eu.darken.capod.common.bluetooth.ScannerMode
|
||||
import eu.darken.capod.common.debug.autoreport.DebugSettings
|
||||
import eu.darken.capod.common.preferences.PreferenceStoreMapper
|
||||
import eu.darken.capod.common.preferences.Settings
|
||||
@@ -67,6 +68,6 @@ class GeneralSettings @Inject constructor(
|
||||
showAll,
|
||||
minimumSignalQuality,
|
||||
mainDeviceAddress,
|
||||
debugSettings.isAutoReportEnabled,
|
||||
debugSettings.isAutoReportingEnabled,
|
||||
)
|
||||
}
|
||||
+1
-1
@@ -3,7 +3,7 @@ package eu.darken.capod.main.core
|
||||
import androidx.annotation.StringRes
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import eu.darken.capod.R
|
||||
import eu.darken.capod.common.R
|
||||
|
||||
@JsonClass(generateAdapter = false)
|
||||
enum class MonitorMode(
|
||||
+1
-1
@@ -6,7 +6,7 @@ import eu.darken.capod.common.debug.logging.log
|
||||
import eu.darken.capod.common.debug.logging.logTag
|
||||
import eu.darken.capod.common.permissions.Permission
|
||||
import eu.darken.capod.common.permissions.isRequired
|
||||
import eu.darken.capod.reaction.settings.ReactionSettings
|
||||
import eu.darken.capod.reaction.core.ReactionSettings
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user